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