Bump upstreams to SNAPSHOTs
[netconf.git] / netconf / callhome-protocol / src / main / java / org / opendaylight / netconf / callhome / protocol / tls / NetconfCallHomeTlsServer.java
1 /*
2  * Copyright (c) 2020 Pantheon Technologies, s.r.o. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.callhome.protocol.tls;
9
10 import static java.util.Objects.requireNonNull;
11
12 import io.netty.bootstrap.ServerBootstrap;
13 import io.netty.channel.Channel;
14 import io.netty.channel.ChannelFuture;
15 import io.netty.channel.ChannelOption;
16 import io.netty.channel.EventLoopGroup;
17 import io.netty.channel.socket.nio.NioServerSocketChannel;
18 import io.netty.handler.ssl.SslHandler;
19 import io.netty.util.concurrent.Future;
20 import io.netty.util.concurrent.GenericFutureListener;
21 import java.net.InetSocketAddress;
22 import java.security.PublicKey;
23 import java.security.cert.Certificate;
24 import java.util.Optional;
25 import org.opendaylight.netconf.callhome.protocol.CallHomeNetconfSubsystemListener;
26 import org.opendaylight.netconf.client.SslHandlerFactory;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 public final class NetconfCallHomeTlsServer {
31     private static final Logger LOG = LoggerFactory.getLogger(NetconfCallHomeTlsServer.class);
32
33     private final String host;
34     private final Integer port;
35     private final Integer timeout;
36     private final Integer maxConnections;
37     private final SslHandlerFactory sslHandlerFactory;
38     private final CallHomeNetconfSubsystemListener subsystemListener;
39     private final EventLoopGroup bossGroup;
40     private final EventLoopGroup workerGroup;
41     private final TlsAllowedDevicesMonitor allowedDevicesMonitor;
42
43     private ChannelFuture cf;
44
45     NetconfCallHomeTlsServer(final String host, final Integer port, final Integer timeout, final Integer maxConnections,
46                              final SslHandlerFactory sslHandlerFactory,
47                              final CallHomeNetconfSubsystemListener subsystemListener,
48                              final EventLoopGroup bossGroup, final EventLoopGroup workerGroup,
49                              final TlsAllowedDevicesMonitor allowedDevicesMonitor) {
50         this.host = requireNonNull(host);
51         this.port = requireNonNull(port);
52         this.timeout = requireNonNull(timeout);
53         this.maxConnections = requireNonNull(maxConnections);
54         this.sslHandlerFactory = requireNonNull(sslHandlerFactory);
55         this.subsystemListener = requireNonNull(subsystemListener);
56         this.bossGroup = requireNonNull(bossGroup);
57         this.workerGroup = requireNonNull(workerGroup);
58         this.allowedDevicesMonitor = requireNonNull(allowedDevicesMonitor);
59     }
60
61     public void start() {
62         final ChannelFuture bindFuture = new ServerBootstrap()
63             .group(bossGroup, workerGroup)
64             // FIXME: a case against using globals: we really would like to use epoll() here
65             .channel(NioServerSocketChannel.class)
66             .localAddress(new InetSocketAddress(host, port))
67             .childOption(ChannelOption.SO_KEEPALIVE, true)
68             .childOption(ChannelOption.SO_BACKLOG, maxConnections)
69             .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout)
70             .childHandler(new TlsAuthChannelInitializer(sslHandlerFactory, handshakeListener))
71             .bind();
72         bindFuture.addListener(bindListener);
73     }
74
75     GenericFutureListener<Future<Channel>> handshakeListener = new GenericFutureListener<>() {
76         @Override
77         public void operationComplete(final Future<Channel> future) throws Exception {
78             if (future.isSuccess()) {
79                 LOG.debug("SSL handshake completed successfully, accepting connection...");
80                 final Channel channel = future.get();
81                 // If the ssl handshake was successful it is expected that session contains peer certificate(s)
82                 final Certificate cert = channel.pipeline().get(SslHandler.class).engine().getSession()
83                     .getPeerCertificates()[0];
84                 final PublicKey publicKey = cert.getPublicKey();
85                 final Optional<String> deviceId = allowedDevicesMonitor.findDeviceIdByPublicKey(publicKey);
86                 if (deviceId.isEmpty()) {
87                     LOG.error("Unable to identify connected device by provided certificate");
88                     channel.close();
89                 } else {
90                     final CallHomeTlsSessionContext tlsSessionContext = new CallHomeTlsSessionContext(deviceId.get(),
91                         channel, sslHandlerFactory, subsystemListener);
92                     tlsSessionContext.openNetconfChannel(channel);
93                 }
94             } else {
95                 LOG.debug("SSL handshake failed, rejecting connection...");
96                 future.get().close();
97             }
98         }
99     };
100
101     GenericFutureListener<ChannelFuture> bindListener = future -> {
102         if (future.isSuccess()) {
103             LOG.debug("Call-Home TLS server bind completed");
104         } else {
105             LOG.error("Call-Home TLS server bind failed", future.cause());
106         }
107         cf = future.channel().closeFuture().addListener(f -> stop());
108     };
109
110     public void stop() {
111         LOG.debug("Stopping the Call-Home TLS server...");
112         try {
113             if (cf != null && cf.channel().isOpen()) {
114                 cf.channel().close().sync();
115             }
116         } catch (final InterruptedException e) {
117             LOG.error("Error during shutdown of the Call-Home TLS server", e);
118         }
119     }
120 }