Shade mina-sshd
[netconf.git] / netconf / mdsal-netconf-ssh / src / main / java / org / opendaylight / netconf / ssh / RemoteNetconfCommand.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 package org.opendaylight.netconf.ssh;
9
10 import static java.util.Objects.requireNonNull;
11
12 import io.netty.bootstrap.Bootstrap;
13 import io.netty.channel.Channel;
14 import io.netty.channel.ChannelFuture;
15 import io.netty.channel.ChannelInitializer;
16 import io.netty.channel.EventLoopGroup;
17 import io.netty.channel.local.LocalAddress;
18 import io.netty.channel.local.LocalChannel;
19 import java.io.InputStream;
20 import java.io.OutputStream;
21 import java.net.InetSocketAddress;
22 import java.net.SocketAddress;
23 import org.opendaylight.netconf.api.messages.NetconfHelloMessageAdditionalHeader;
24 import org.opendaylight.netconf.shaded.sshd.common.NamedFactory;
25 import org.opendaylight.netconf.shaded.sshd.common.io.IoInputStream;
26 import org.opendaylight.netconf.shaded.sshd.common.io.IoOutputStream;
27 import org.opendaylight.netconf.shaded.sshd.server.Environment;
28 import org.opendaylight.netconf.shaded.sshd.server.ExitCallback;
29 import org.opendaylight.netconf.shaded.sshd.server.channel.ChannelSession;
30 import org.opendaylight.netconf.shaded.sshd.server.command.AsyncCommand;
31 import org.opendaylight.netconf.shaded.sshd.server.command.Command;
32 import org.opendaylight.netconf.shaded.sshd.server.session.ServerSession;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 /**
37  * This command handles all netconf related rpc and forwards to delegate server.
38  * Uses netty to make a local connection to delegate server.
39  *
40  * <p>
41  * Command is Apache Mina SSH terminology for objects handling ssh data.
42  */
43 public class RemoteNetconfCommand implements AsyncCommand {
44
45     private static final Logger LOG = LoggerFactory.getLogger(RemoteNetconfCommand.class);
46
47     private final EventLoopGroup clientEventGroup;
48     private final LocalAddress localAddress;
49
50     private IoInputStream in;
51     private IoOutputStream out;
52     private ExitCallback callback;
53     private NetconfHelloMessageAdditionalHeader netconfHelloMessageAdditionalHeader;
54
55     private Channel clientChannel;
56     private ChannelFuture clientChannelFuture;
57
58     public RemoteNetconfCommand(final EventLoopGroup clientEventGroup, final LocalAddress localAddress) {
59         this.clientEventGroup = clientEventGroup;
60         this.localAddress = localAddress;
61     }
62
63     @Override
64     @SuppressWarnings("checkstyle:hiddenField")
65     public void setIoInputStream(final IoInputStream in) {
66         this.in = in;
67     }
68
69     @Override
70     @SuppressWarnings("checkstyle:hiddenField")
71     public void setIoOutputStream(final IoOutputStream out) {
72         this.out = out;
73     }
74
75     @Override
76     public void setIoErrorStream(final IoOutputStream err) {
77         // TODO do we want to use error stream in some way ?
78     }
79
80     @Override
81     @SuppressWarnings("checkstyle:hiddenField")
82     public void setInputStream(final InputStream in) {
83         throw new UnsupportedOperationException("Synchronous IO is unsupported");
84     }
85
86     @Override
87     @SuppressWarnings("checkstyle:hiddenField")
88     public void setOutputStream(final OutputStream out) {
89         throw new UnsupportedOperationException("Synchronous IO is unsupported");
90
91     }
92
93     @Override
94     public void setErrorStream(final OutputStream err) {
95         throw new UnsupportedOperationException("Synchronous IO is unsupported");
96
97     }
98
99     @Override
100     @SuppressWarnings("checkstyle:hiddenField")
101     public void setExitCallback(final ExitCallback callback) {
102         this.callback = callback;
103     }
104
105     @Override
106     public void start(final ChannelSession channel, final Environment env) {
107         final ServerSession session = channel.getServerSession();
108         final SocketAddress remoteAddress = session.getIoSession().getRemoteAddress();
109         final String hostName;
110         final String port;
111         if (remoteAddress instanceof InetSocketAddress) {
112             hostName = ((InetSocketAddress) remoteAddress).getAddress().getHostAddress();
113             port = Integer.toString(((InetSocketAddress) remoteAddress).getPort());
114         } else {
115             hostName = "";
116             port = "";
117         }
118         netconfHelloMessageAdditionalHeader = new NetconfHelloMessageAdditionalHeader(session.getUsername(), hostName,
119             port, "ssh", "client");
120
121         LOG.trace("Establishing internal connection to netconf server for client: {}", getClientAddress());
122
123         final Bootstrap clientBootstrap = new Bootstrap();
124         clientBootstrap.group(clientEventGroup).channel(LocalChannel.class);
125
126         clientBootstrap.handler(new ChannelInitializer<LocalChannel>() {
127             @Override
128             public void initChannel(final LocalChannel ch) {
129                 ch.pipeline()
130                         .addLast(new SshProxyClientHandler(in, out, netconfHelloMessageAdditionalHeader, callback));
131             }
132         });
133         clientChannelFuture = clientBootstrap.connect(localAddress);
134         clientChannelFuture.addListener(future -> {
135             if (future.isSuccess()) {
136                 clientChannel = clientChannelFuture.channel();
137             } else {
138                 LOG.warn("Unable to establish internal connection to netconf server for client: {}",
139                         getClientAddress());
140                 requireNonNull(callback, "Exit callback must be set").onExit(1,
141                     "Unable to establish internal connection to netconf server for client: " + getClientAddress());
142             }
143         });
144     }
145
146     @Override
147     public void destroy(final ChannelSession channel) {
148         LOG.trace("Releasing internal connection to netconf server for client: {} on channel: {}",
149                 getClientAddress(), clientChannel);
150
151         clientChannelFuture.cancel(true);
152         if (clientChannel != null) {
153             clientChannel.close().addListener(future -> {
154                 if (!future.isSuccess()) {
155                     LOG.warn("Unable to release internal connection to netconf server on channel: {}",
156                             clientChannel);
157                 }
158             });
159         }
160     }
161
162     private String getClientAddress() {
163         return netconfHelloMessageAdditionalHeader.getAddress();
164     }
165
166     public static class NetconfCommandFactory implements NamedFactory<Command> {
167
168         public static final String NETCONF = "netconf";
169
170         private final EventLoopGroup clientBootstrap;
171         private final LocalAddress localAddress;
172
173         public NetconfCommandFactory(final EventLoopGroup clientBootstrap, final LocalAddress localAddress) {
174
175             this.clientBootstrap = clientBootstrap;
176             this.localAddress = localAddress;
177         }
178
179         @Override
180         public String getName() {
181             return NETCONF;
182         }
183
184         @Override
185         public RemoteNetconfCommand create() {
186             return new RemoteNetconfCommand(clientBootstrap, localAddress);
187         }
188     }
189 }