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