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