Enable server heartbeats
[netconf.git] / netconf / mdsal-netconf-ssh / src / main / java / org / opendaylight / netconf / ssh / SshProxyServer.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. 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
9 package org.opendaylight.netconf.ssh;
10
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.collect.ImmutableList;
15 import io.netty.channel.EventLoopGroup;
16 import java.io.IOException;
17 import java.nio.channels.AsynchronousChannelGroup;
18 import java.util.List;
19 import java.util.concurrent.ExecutorService;
20 import java.util.concurrent.ScheduledExecutorService;
21 import java.util.concurrent.TimeUnit;
22 import org.opendaylight.netconf.shaded.sshd.common.FactoryManager;
23 import org.opendaylight.netconf.shaded.sshd.common.NamedFactory;
24 import org.opendaylight.netconf.shaded.sshd.common.RuntimeSshException;
25 import org.opendaylight.netconf.shaded.sshd.common.cipher.BuiltinCiphers;
26 import org.opendaylight.netconf.shaded.sshd.common.cipher.Cipher;
27 import org.opendaylight.netconf.shaded.sshd.common.io.IoAcceptor;
28 import org.opendaylight.netconf.shaded.sshd.common.io.IoConnector;
29 import org.opendaylight.netconf.shaded.sshd.common.io.IoHandler;
30 import org.opendaylight.netconf.shaded.sshd.common.io.IoServiceEventListener;
31 import org.opendaylight.netconf.shaded.sshd.common.io.IoServiceFactory;
32 import org.opendaylight.netconf.shaded.sshd.common.io.IoServiceFactoryFactory;
33 import org.opendaylight.netconf.shaded.sshd.common.io.nio2.Nio2Acceptor;
34 import org.opendaylight.netconf.shaded.sshd.common.io.nio2.Nio2Connector;
35 import org.opendaylight.netconf.shaded.sshd.common.io.nio2.Nio2ServiceFactoryFactory;
36 import org.opendaylight.netconf.shaded.sshd.common.session.SessionHeartbeatController.HeartbeatType;
37 import org.opendaylight.netconf.shaded.sshd.common.util.closeable.AbstractCloseable;
38 import org.opendaylight.netconf.shaded.sshd.server.SshServer;
39
40 /**
41  * Proxy SSH server that just delegates decrypted content to a delegate server within same VM.
42  * Implemented using Apache Mina SSH lib.
43  */
44 public class SshProxyServer implements AutoCloseable {
45     private final SshServer sshServer;
46     private final ScheduledExecutorService minaTimerExecutor;
47     private final EventLoopGroup clientGroup;
48     private final IoServiceFactoryFactory nioServiceWithPoolFactoryFactory;
49
50     private SshProxyServer(final ScheduledExecutorService minaTimerExecutor, final EventLoopGroup clientGroup,
51             final IoServiceFactoryFactory serviceFactory) {
52         this.minaTimerExecutor = minaTimerExecutor;
53         this.clientGroup = clientGroup;
54         this.nioServiceWithPoolFactoryFactory = serviceFactory;
55         this.sshServer = SshServer.setUpDefaultServer();
56     }
57
58     public SshProxyServer(final ScheduledExecutorService minaTimerExecutor,
59                           final EventLoopGroup clientGroup, final ExecutorService nioExecutor) {
60         this(minaTimerExecutor, clientGroup, new NioServiceWithPoolFactoryFactory(nioExecutor));
61     }
62
63     /**
64      * Create a server with a shared {@link AsynchronousChannelGroup}. Unlike the other constructor, this does
65      * not create a dedicated thread group, which is useful when you need to start a large number of servers and do
66      * not want to have a thread group (and hence an anonyous thread) for each of them.
67      */
68     @VisibleForTesting
69     public SshProxyServer(final ScheduledExecutorService minaTimerExecutor, final EventLoopGroup clientGroup,
70             final AsynchronousChannelGroup group) {
71         this(minaTimerExecutor, clientGroup, new SharedNioServiceFactoryFactory(group));
72     }
73
74     public void bind(final SshProxyServerConfiguration sshProxyServerConfiguration) throws IOException {
75         sshServer.setHost(sshProxyServerConfiguration.getBindingAddress().getHostString());
76         sshServer.setPort(sshProxyServerConfiguration.getBindingAddress().getPort());
77
78         //remove rc4 ciphers
79         final List<NamedFactory<Cipher>> cipherFactories = sshServer.getCipherFactories();
80         cipherFactories.removeIf(factory -> factory.getName().contains(BuiltinCiphers.arcfour128.getName())
81                 || factory.getName().contains(BuiltinCiphers.arcfour256.getName()));
82         sshServer.setPasswordAuthenticator(
83             (username, password, session)
84                 -> sshProxyServerConfiguration.getAuthenticator().authenticated(username, password));
85
86         sshProxyServerConfiguration.getPublickeyAuthenticator().ifPresent(sshServer::setPublickeyAuthenticator);
87
88         sshServer.setKeyPairProvider(sshProxyServerConfiguration.getKeyPairProvider());
89
90         sshServer.setIoServiceFactoryFactory(nioServiceWithPoolFactoryFactory);
91         sshServer.setScheduledExecutorService(minaTimerExecutor);
92
93         final int idleTimeout = sshProxyServerConfiguration.getIdleTimeout();
94         sshServer.getProperties().put(FactoryManager.IDLE_TIMEOUT, String.valueOf(idleTimeout));
95         final String nioReadTimeout;
96         if (idleTimeout > 0) {
97             final long heartBeat = idleTimeout * 333333L;
98             sshServer.setSessionHeartbeat(HeartbeatType.IGNORE, TimeUnit.NANOSECONDS, heartBeat);
99             nioReadTimeout = String.valueOf(idleTimeout + TimeUnit.SECONDS.toMillis(15L));
100         } else {
101             nioReadTimeout = "0";
102         }
103         sshServer.getProperties().put(FactoryManager.NIO2_READ_TIMEOUT, nioReadTimeout);
104         sshServer.getProperties().put(FactoryManager.AUTH_TIMEOUT, String.valueOf(idleTimeout));
105
106         final RemoteNetconfCommand.NetconfCommandFactory netconfCommandFactory =
107                 new RemoteNetconfCommand.NetconfCommandFactory(clientGroup,
108                         sshProxyServerConfiguration.getLocalAddress());
109         sshServer.setSubsystemFactories(ImmutableList.of(netconfCommandFactory));
110         sshServer.start();
111     }
112
113     @Override
114     public void close() throws IOException {
115         try {
116             sshServer.stop(true);
117         } finally {
118             sshServer.close(true);
119         }
120     }
121
122     private abstract static class AbstractNioServiceFactory extends AbstractCloseable implements IoServiceFactory {
123         private final FactoryManager manager;
124         private final AsynchronousChannelGroup group;
125
126         private IoServiceEventListener eventListener;
127
128         AbstractNioServiceFactory(final FactoryManager manager, final AsynchronousChannelGroup group) {
129             this.manager = requireNonNull(manager);
130             this.group = requireNonNull(group);
131         }
132
133         final AsynchronousChannelGroup group() {
134             return group;
135         }
136
137         @Override
138         public final IoConnector createConnector(final IoHandler handler) {
139             return new Nio2Connector(manager, handler, group);
140         }
141
142         @Override
143         public final IoAcceptor createAcceptor(final IoHandler handler) {
144             return new Nio2Acceptor(manager, handler, group);
145         }
146
147         @Override
148         public final IoServiceEventListener getIoServiceEventListener() {
149             return eventListener;
150         }
151
152         @Override
153         public final void setIoServiceEventListener(final IoServiceEventListener listener) {
154             eventListener = listener;
155         }
156     }
157
158     /**
159      * Based on Nio2ServiceFactory with one addition: injectable executor.
160      */
161     private static final class NioServiceWithPoolFactory extends AbstractNioServiceFactory {
162         NioServiceWithPoolFactory(final FactoryManager manager, final AsynchronousChannelGroup group) {
163             super(manager, group);
164         }
165
166         @Override
167         protected void doCloseImmediately() {
168             try {
169                 group().shutdownNow();
170                 group().awaitTermination(5, TimeUnit.SECONDS);
171             } catch (final IOException | InterruptedException e) {
172                 log.debug("Exception caught while closing channel group", e);
173             } finally {
174                 super.doCloseImmediately();
175             }
176         }
177     }
178
179     private static final class NioServiceWithPoolFactoryFactory extends Nio2ServiceFactoryFactory {
180         private final ExecutorService nioExecutor;
181
182         NioServiceWithPoolFactoryFactory(final ExecutorService nioExecutor) {
183             this.nioExecutor = nioExecutor;
184         }
185
186         @Override
187         public IoServiceFactory create(final FactoryManager manager) {
188             try {
189                 return new NioServiceWithPoolFactory(manager, AsynchronousChannelGroup.withThreadPool(nioExecutor));
190             } catch (final IOException e) {
191                 throw new RuntimeSshException("Failed to create channel group", e);
192             }
193         }
194     }
195
196     private static final class SharedNioServiceFactory extends AbstractNioServiceFactory {
197         SharedNioServiceFactory(final FactoryManager manager, final AsynchronousChannelGroup group) {
198             super(manager, group);
199         }
200     }
201
202     private static final class SharedNioServiceFactoryFactory extends Nio2ServiceFactoryFactory {
203         private final AsynchronousChannelGroup group;
204
205         SharedNioServiceFactoryFactory(final AsynchronousChannelGroup group) {
206             this.group = requireNonNull(group);
207         }
208
209         @Override
210         public IoServiceFactory create(final FactoryManager manager) {
211             return new SharedNioServiceFactory(manager, group);
212         }
213     }
214 }