2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
9 package org.opendaylight.netconf.ssh;
11 import static java.util.Objects.requireNonNull;
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;
46 * Proxy SSH server that just delegates decrypted content to a delegate server within same VM.
47 * Implemented using Apache Mina SSH lib.
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;
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();
63 public SshProxyServer(final ScheduledExecutorService minaTimerExecutor,
64 final EventLoopGroup clientGroup, final ExecutorService nioExecutor) {
65 this(minaTimerExecutor, clientGroup, new NioServiceWithPoolFactoryFactory(nioExecutor));
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.
74 public SshProxyServer(final ScheduledExecutorService minaTimerExecutor, final EventLoopGroup clientGroup,
75 final AsynchronousChannelGroup group) {
76 this(minaTimerExecutor, clientGroup, new SharedNioServiceFactoryFactory(group));
79 public void bind(final SshProxyServerConfiguration sshProxyServerConfiguration) throws IOException {
80 sshServer.setHost(sshProxyServerConfiguration.getBindingAddress().getHostString());
81 sshServer.setPort(sshProxyServerConfiguration.getBindingAddress().getPort());
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));
91 sshProxyServerConfiguration.getPublickeyAuthenticator().ifPresent(sshServer::setPublickeyAuthenticator);
93 sshServer.setKeyPairProvider(sshProxyServerConfiguration.getKeyPairProvider());
95 sshServer.setIoServiceFactoryFactory(nioServiceWithPoolFactoryFactory);
96 sshServer.setScheduledExecutorService(minaTimerExecutor);
98 final int idleTimeoutMillis = sshProxyServerConfiguration.getIdleTimeout();
99 final Duration idleTimeout = Duration.ofMillis(idleTimeoutMillis);
100 CoreModuleProperties.IDLE_TIMEOUT.set(sshServer, idleTimeout);
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));
108 nioReadTimeout = Duration.ZERO;
110 CoreModuleProperties.NIO2_READ_TIMEOUT.set(sshServer, nioReadTimeout);
111 CoreModuleProperties.AUTH_TIMEOUT.set(sshServer, idleTimeout);
112 CoreModuleProperties.TCP_NODELAY.set(sshServer, Boolean.TRUE);
114 final RemoteNetconfCommand.NetconfCommandFactory netconfCommandFactory =
115 new RemoteNetconfCommand.NetconfCommandFactory(clientGroup,
116 sshProxyServerConfiguration.getLocalAddress());
117 sshServer.setSubsystemFactories(ImmutableList.of(netconfCommandFactory));
122 public void close() throws IOException {
124 sshServer.stop(true);
126 sshServer.close(true);
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;
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);
143 final AsynchronousChannelGroup group() {
147 final ExecutorService resumeTasks() {
152 public final IoConnector createConnector(final IoHandler handler) {
153 return new Nio2Connector(manager, handler, group, resumeTasks);
157 public final IoAcceptor createAcceptor(final IoHandler handler) {
158 return new Nio2Acceptor(manager, handler, group, resumeTasks);
162 public final IoServiceEventListener getIoServiceEventListener() {
163 return eventListener;
167 public final void setIoServiceEventListener(final IoServiceEventListener listener) {
168 eventListener = listener;
173 * Based on Nio2ServiceFactory with one addition: injectable executor.
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);
182 protected void doCloseImmediately() {
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);
191 super.doCloseImmediately();
196 private static final class NioServiceWithPoolFactoryFactory extends Nio2ServiceFactoryFactory {
197 private static final AtomicLong COUNTER = new AtomicLong();
199 private final ExecutorServiceFacade nioExecutor;
201 NioServiceWithPoolFactoryFactory(final ExecutorService nioExecutor) {
202 this.nioExecutor = new ExecutorServiceFacade(nioExecutor);
206 public IoServiceFactory create(final FactoryManager manager) {
208 return new NioServiceWithPoolFactory(manager, AsynchronousChannelGroup.withThreadPool(nioExecutor),
209 Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
210 .setNameFormat("sshd-resume-read-" + COUNTER.getAndIncrement() + "-%d")
212 } catch (final IOException e) {
213 throw new RuntimeSshException("Failed to create channel group", e);
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);
225 private static final class SharedNioServiceFactoryFactory extends Nio2ServiceFactoryFactory {
226 private final AsynchronousChannelGroup group;
228 SharedNioServiceFactoryFactory(final AsynchronousChannelGroup group) {
229 this.group = requireNonNull(group);
233 public IoServiceFactory create(final FactoryManager manager) {
234 return new SharedNioServiceFactory(manager, group, Executors.newSingleThreadExecutor());