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