BUG 624 - Make netconf TCP port optional. 85/7585/1
authorTomas Olvecky <tolvecky@cisco.com>
Tue, 27 May 2014 10:41:16 +0000 (12:41 +0200)
committerTomas Olvecky <tolvecky@cisco.com>
Mon, 2 Jun 2014 12:39:27 +0000 (14:39 +0200)
Previously netconf-impl opened a TCP port with no authentication on
localhost, and netconf-ssh used it as a bridge to forward trafic
after processing authentication and encryption.

This patch creates new project netconf-tcp and modifies netconf-impl
to open the netconf server on LocalAddress. Both tcp and ssh modules
now communicate with this local server.
Config ini is modified so that the TCP port (8383) is not enabled
by default.

Change-Id: I74bded660f10b20d09535d32308aff5b2ae611d9
Signed-off-by: Tomas Olvecky <tolvecky@cisco.com>
40 files changed:
opendaylight/commons/opendaylight/pom.xml
opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java
opendaylight/distribution/opendaylight/pom.xml
opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini
opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/SshClientChannelInitializer.java
opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/TcpClientChannelInitializer.java
opendaylight/netconf/netconf-client/src/test/java/org/opendaylight/controller/netconf/client/test/TestingNetconfClient.java
opendaylight/netconf/netconf-impl/pom.xml
opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java
opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java
opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java
opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java
opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/AbstractChannelInitializer.java
opendaylight/netconf/netconf-ssh/pom.xml
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProviderInterface.java [deleted file]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/PEMGenerator.java
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/IOThread.java [deleted file]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java [deleted file]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/KeyGeneratorTest.java [deleted file]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/SSHServerTest.java [deleted file]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClient.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClientHandler.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServer.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServerHandler.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServer.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServerHandler.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/authentication/SSHServerTest.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/resources/logback-test.xml [new file with mode: 0644]
opendaylight/netconf/netconf-tcp/pom.xml [new file with mode: 0644]
opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServer.java [new file with mode: 0644]
opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServerHandler.java [new file with mode: 0644]
opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/osgi/NetconfTCPActivator.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/pom.xml
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java
opendaylight/netconf/pom.xml

index c166f668ccd48e9419e79536c7b39523b7f04238..e88e39a61e5dd7784d8d336cf84f0a3086c87112 100644 (file)
         <version>${netconf.version}</version>
         <type>test-jar</type>
       </dependency>
         <version>${netconf.version}</version>
         <type>test-jar</type>
       </dependency>
+      <dependency>
+        <groupId>org.opendaylight.controller</groupId>
+        <artifactId>netconf-tcp</artifactId>
+        <version>${netconf.version}</version>
+      </dependency>
       <dependency>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>netconf-util</artifactId>
       <dependency>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>netconf-util</artifactId>
index 916ef9a88befa87ac8b5c17902ec6d970f23807d..fef2c7196948c007705b4444a5c9445065618648 100644 (file)
@@ -7,12 +7,16 @@
  */
 package org.opendaylight.protocol.framework;
 
  */
 package org.opendaylight.protocol.framework;
 
+import com.google.common.base.Preconditions;
 import io.netty.bootstrap.Bootstrap;
 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.bootstrap.Bootstrap;
 import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelOption;
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelOption;
 import io.netty.channel.EventLoopGroup;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.local.LocalServerChannel;
 import io.netty.channel.socket.SocketChannel;
 import io.netty.channel.socket.nio.NioServerSocketChannel;
 import io.netty.channel.socket.nio.NioSocketChannel;
 import io.netty.channel.socket.SocketChannel;
 import io.netty.channel.socket.nio.NioServerSocketChannel;
 import io.netty.channel.socket.nio.NioSocketChannel;
@@ -21,22 +25,20 @@ import io.netty.util.concurrent.EventExecutor;
 import io.netty.util.concurrent.Future;
 import io.netty.util.concurrent.GlobalEventExecutor;
 import io.netty.util.concurrent.Promise;
 import io.netty.util.concurrent.Future;
 import io.netty.util.concurrent.GlobalEventExecutor;
 import io.netty.util.concurrent.Promise;
-
 import java.io.Closeable;
 import java.net.InetSocketAddress;
 import java.io.Closeable;
 import java.net.InetSocketAddress;
-
+import java.net.SocketAddress;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Preconditions;
-
 /**
  * Dispatcher class for creating servers and clients. The idea is to first create servers and clients and the run the
  * start method that will handle sockets in different thread.
  */
 public abstract class AbstractDispatcher<S extends ProtocolSession<?>, L extends SessionListener<?, ?, ?>> implements Closeable {
 
 /**
  * Dispatcher class for creating servers and clients. The idea is to first create servers and clients and the run the
  * start method that will handle sockets in different thread.
  */
 public abstract class AbstractDispatcher<S extends ProtocolSession<?>, L extends SessionListener<?, ?, ?>> implements Closeable {
 
-    protected interface PipelineInitializer<S extends ProtocolSession<?>> {
+
+    protected interface ChannelPipelineInitializer<CH extends Channel, S extends ProtocolSession<?>> {
         /**
          * Initializes channel by specifying the handlers in its pipeline. Handlers are protocol specific, therefore this
          * method needs to be implemented in protocol specific Dispatchers.
         /**
          * Initializes channel by specifying the handlers in its pipeline. Handlers are protocol specific, therefore this
          * method needs to be implemented in protocol specific Dispatchers.
@@ -44,7 +46,11 @@ public abstract class AbstractDispatcher<S extends ProtocolSession<?>, L extends
          * @param channel whose pipeline should be defined, also to be passed to {@link SessionNegotiatorFactory}
          * @param promise to be passed to {@link SessionNegotiatorFactory}
          */
          * @param channel whose pipeline should be defined, also to be passed to {@link SessionNegotiatorFactory}
          * @param promise to be passed to {@link SessionNegotiatorFactory}
          */
-        void initializeChannel(SocketChannel channel, Promise<S> promise);
+        void initializeChannel(CH channel, Promise<S> promise);
+    }
+
+    protected interface PipelineInitializer<S extends ProtocolSession<?>> extends ChannelPipelineInitializer<SocketChannel, S> {
+
     }
 
 
     }
 
 
@@ -76,25 +82,43 @@ public abstract class AbstractDispatcher<S extends ProtocolSession<?>, L extends
      * @return ChannelFuture representing the binding process
      */
     protected ChannelFuture createServer(final InetSocketAddress address, final PipelineInitializer<S> initializer) {
      * @return ChannelFuture representing the binding process
      */
     protected ChannelFuture createServer(final InetSocketAddress address, final PipelineInitializer<S> initializer) {
+        return createServer(address, NioServerSocketChannel.class, initializer);
+    }
+
+    /**
+     * Creates server. Each server needs factories to pass their instances to client sessions.
+     *
+     * @param address address to which the server should be bound
+     * @param channelClass The {@link Class} which is used to create {@link Channel} instances from.
+     * @param initializer instance of PipelineInitializer used to initialize the channel pipeline
+     *
+     * @return ChannelFuture representing the binding process
+     */
+    protected <CH extends Channel> ChannelFuture createServer(SocketAddress address, Class<? extends ServerChannel> channelClass,
+                                                              final ChannelPipelineInitializer<CH, S> initializer) {
         final ServerBootstrap b = new ServerBootstrap();
         final ServerBootstrap b = new ServerBootstrap();
-        b.childHandler(new ChannelInitializer<SocketChannel>() {
+        b.childHandler(new ChannelInitializer<CH>() {
 
             @Override
 
             @Override
-            protected void initChannel(final SocketChannel ch) {
+            protected void initChannel(final CH ch) {
                 initializer.initializeChannel(ch, new DefaultPromise<S>(executor));
             }
         });
 
         b.option(ChannelOption.SO_BACKLOG, 128);
                 initializer.initializeChannel(ch, new DefaultPromise<S>(executor));
             }
         });
 
         b.option(ChannelOption.SO_BACKLOG, 128);
-        b.childOption(ChannelOption.SO_KEEPALIVE, true);
+        if (LocalServerChannel.class.equals(channelClass) == false) {
+            // makes no sense for LocalServer and produces warning
+            b.childOption(ChannelOption.SO_KEEPALIVE, true);
+        }
         customizeBootstrap(b);
 
         if (b.group() == null) {
             b.group(bossGroup, workerGroup);
         }
         try {
         customizeBootstrap(b);
 
         if (b.group() == null) {
             b.group(bossGroup, workerGroup);
         }
         try {
-            b.channel(NioServerSocketChannel.class);
+            b.channel(channelClass);
         } catch (IllegalStateException e) {
         } catch (IllegalStateException e) {
+            // FIXME: if this is ok, document why
             LOG.trace("Not overriding channelFactory on bootstrap {}", b, e);
         }
 
             LOG.trace("Not overriding channelFactory on bootstrap {}", b, e);
         }
 
index 5b44bb7569b9c3df2bce4c6ef76c4741e7541061..3802370aca3e8a7726d27d7fab1ca67d6eeb1944 100644 (file)
           <groupId>org.opendaylight.controller</groupId>
           <artifactId>netconf-ssh</artifactId>
         </dependency>
           <groupId>org.opendaylight.controller</groupId>
           <artifactId>netconf-ssh</artifactId>
         </dependency>
+        <dependency>
+          <groupId>org.opendaylight.controller</groupId>
+          <artifactId>netconf-tcp</artifactId>
+        </dependency>
         <dependency>
           <groupId>org.opendaylight.controller</groupId>
           <artifactId>netconf-util</artifactId>
         <dependency>
           <groupId>org.opendaylight.controller</groupId>
           <artifactId>netconf-util</artifactId>
index f15f8f7404d1420d0efc361551c99b0355d60170..f05afbb346f7f157162b313981874c4d3d6bdd40 100644 (file)
@@ -14,13 +14,11 @@ osgi.bundles=\
 
 # Netconf startup configuration
 
 
 # Netconf startup configuration
 
-# Netconf tcp address:port is optional with default value 127.0.0.1:8383
+# Netconf tcp address:port is optional
 #netconf.tcp.address=127.0.0.1
 #netconf.tcp.address=127.0.0.1
-#netconf.tcp.port=8384
-
-#netconf.tcp.client.address=127.0.0.1
-#netconf.tcp.client.port=8384
+#netconf.tcp.port=8383
 
 
+# Netconf tcp address:port is optional
 netconf.ssh.address=0.0.0.0
 netconf.ssh.port=1830
 netconf.ssh.pk.path = ./configuration/RSA.pk
 netconf.ssh.address=0.0.0.0
 netconf.ssh.port=1830
 netconf.ssh.pk.path = ./configuration/RSA.pk
index 799674487f4de5d936067b51b3f9ae392caee897..829ac304bd667f680d813e32ff58ed9f9b0b6f37 100644 (file)
@@ -7,7 +7,7 @@
  */
 package org.opendaylight.controller.netconf.client;
 
  */
 package org.opendaylight.controller.netconf.client;
 
-import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.Channel;
 import io.netty.util.concurrent.Promise;
 import java.io.IOException;
 import org.opendaylight.controller.netconf.nettyutil.AbstractChannelInitializer;
 import io.netty.util.concurrent.Promise;
 import java.io.IOException;
 import org.opendaylight.controller.netconf.nettyutil.AbstractChannelInitializer;
@@ -31,7 +31,7 @@ final class SshClientChannelInitializer extends AbstractChannelInitializer<Netco
     }
 
     @Override
     }
 
     @Override
-    public void initialize(final SocketChannel ch, final Promise<NetconfClientSession> promise) {
+    public void initialize(final Channel ch, final Promise<NetconfClientSession> promise) {
         try {
             final Invoker invoker = Invoker.subsystem("netconf");
             ch.pipeline().addFirst(new SshHandler(authenticationHandler, invoker));
         try {
             final Invoker invoker = Invoker.subsystem("netconf");
             ch.pipeline().addFirst(new SshHandler(authenticationHandler, invoker));
@@ -42,7 +42,7 @@ final class SshClientChannelInitializer extends AbstractChannelInitializer<Netco
     }
 
     @Override
     }
 
     @Override
-    protected void initializeSessionNegotiator(final SocketChannel ch,
+    protected void initializeSessionNegotiator(final Channel ch,
                                                final Promise<NetconfClientSession> promise) {
         ch.pipeline().addAfter(NETCONF_MESSAGE_DECODER,  AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR,
                 negotiatorFactory.getSessionNegotiator(new SessionListenerFactory<NetconfClientSessionListener>() {
                                                final Promise<NetconfClientSession> promise) {
         ch.pipeline().addAfter(NETCONF_MESSAGE_DECODER,  AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR,
                 negotiatorFactory.getSessionNegotiator(new SessionListenerFactory<NetconfClientSessionListener>() {
index 4a0a089fae97e1ff3d1c8b969f6da077e3584cb7..ee8f8baf0151c4cdeb820878ca9c440e60c9b64b 100644 (file)
@@ -7,7 +7,7 @@
  */
 package org.opendaylight.controller.netconf.client;
 
  */
 package org.opendaylight.controller.netconf.client;
 
-import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.Channel;
 import io.netty.util.concurrent.Promise;
 import org.opendaylight.controller.netconf.nettyutil.AbstractChannelInitializer;
 import org.opendaylight.protocol.framework.SessionListenerFactory;
 import io.netty.util.concurrent.Promise;
 import org.opendaylight.controller.netconf.nettyutil.AbstractChannelInitializer;
 import org.opendaylight.protocol.framework.SessionListenerFactory;
@@ -24,12 +24,7 @@ class TcpClientChannelInitializer extends AbstractChannelInitializer<NetconfClie
     }
 
     @Override
     }
 
     @Override
-    public void initialize(final SocketChannel ch, final Promise<NetconfClientSession> promise) {
-        super.initialize(ch, promise);
-    }
-
-    @Override
-    protected void initializeSessionNegotiator(final SocketChannel ch, final Promise<NetconfClientSession> promise) {
+    protected void initializeSessionNegotiator(final Channel ch, final Promise<NetconfClientSession> promise) {
         ch.pipeline().addAfter(NETCONF_MESSAGE_DECODER, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR,
                 negotiatorFactory.getSessionNegotiator(new SessionListenerFactory<NetconfClientSessionListener>() {
                     @Override
         ch.pipeline().addAfter(NETCONF_MESSAGE_DECODER, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR,
                 negotiatorFactory.getSessionNegotiator(new SessionListenerFactory<NetconfClientSessionListener>() {
                     @Override
index 60d8f3044aee5667ea04858377d31b109ca88ef0..afa17532d55ce6628b1659db90e666acf1e97250 100644 (file)
@@ -8,24 +8,35 @@
 
 package org.opendaylight.controller.netconf.client.test;
 
 
 package org.opendaylight.controller.netconf.client.test;
 
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Sets;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.util.HashedWheelTimer;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GlobalEventExecutor;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.Closeable;
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
 import java.util.Set;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.Set;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-
 import org.opendaylight.controller.netconf.api.NetconfMessage;
 import org.opendaylight.controller.netconf.client.NetconfClientDispatcher;
 import org.opendaylight.controller.netconf.api.NetconfMessage;
 import org.opendaylight.controller.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl;
 import org.opendaylight.controller.netconf.client.NetconfClientSession;
 import org.opendaylight.controller.netconf.client.NetconfClientSessionListener;
 import org.opendaylight.controller.netconf.client.SimpleNetconfClientSessionListener;
 import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration;
 import org.opendaylight.controller.netconf.client.NetconfClientSession;
 import org.opendaylight.controller.netconf.client.NetconfClientSessionListener;
 import org.opendaylight.controller.netconf.client.SimpleNetconfClientSessionListener;
 import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Sets;
-import io.netty.util.concurrent.Future;
+import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol;
+import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder;
+import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
+import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
+import org.opendaylight.protocol.framework.NeverReconnectStrategy;
 
 
 /**
 
 
 /**
@@ -95,4 +106,29 @@ public class TestingNetconfClient implements Closeable {
         Preconditions.checkState(clientSession != null, "Client was not initialized successfully");
         return Sets.newHashSet(clientSession.getServerCapabilities());
     }
         Preconditions.checkState(clientSession != null, "Client was not initialized successfully");
         return Sets.newHashSet(clientSession.getServerCapabilities());
     }
+
+    public static void main(String[] args) throws Exception {
+        HashedWheelTimer hashedWheelTimer = new HashedWheelTimer();
+        NioEventLoopGroup nettyGroup = new NioEventLoopGroup();
+        NetconfClientDispatcherImpl netconfClientDispatcher = new NetconfClientDispatcherImpl(nettyGroup, nettyGroup, hashedWheelTimer);
+        LoginPassword authHandler = new LoginPassword("admin", "admin");
+        TestingNetconfClient client = new TestingNetconfClient("client", netconfClientDispatcher, getClientConfig("127.0.0.1", 1830, true, Optional.of(authHandler)));
+        System.out.println(client.getCapabilities());
+    }
+
+    private static NetconfClientConfiguration getClientConfig(String host ,int port, boolean ssh, Optional<? extends AuthenticationHandler> maybeAuthHandler) throws UnknownHostException {
+        InetSocketAddress netconfAddress = new InetSocketAddress(InetAddress.getByName(host), port);
+        final NetconfClientConfigurationBuilder b = NetconfClientConfigurationBuilder.create();
+        b.withAddress(netconfAddress);
+        b.withSessionListener(new SimpleNetconfClientSessionListener());
+        b.withReconnectStrategy(new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE,
+                NetconfClientConfigurationBuilder.DEFAULT_CONNECTION_TIMEOUT_MILLIS));
+        if (ssh) {
+            b.withProtocol(NetconfClientProtocol.SSH);
+            b.withAuthHandler(maybeAuthHandler.get());
+        } else {
+            b.withProtocol(NetconfClientProtocol.TCP);
+        }
+        return b.build();
+    }
 }
 }
index 1d94517152f45215cc7481a963a290a2870640d2..c60506ef447d821d5458dbb399c30a1ab0e94325 100644 (file)
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.7</version>
         <configuration>
           <instructions>
             <Bundle-Activator>org.opendaylight.controller.netconf.impl.osgi.NetconfImplActivator</Bundle-Activator>
         <configuration>
           <instructions>
             <Bundle-Activator>org.opendaylight.controller.netconf.impl.osgi.NetconfImplActivator</Bundle-Activator>
                             io.netty.buffer,
                             io.netty.handler.codec,
                             io.netty.channel.nio,
                             io.netty.buffer,
                             io.netty.handler.codec,
                             io.netty.channel.nio,
+                            io.netty.channel.local,
                             javax.annotation,
                             javax.management,
                             javax.net.ssl,
                             javax.annotation,
                             javax.management,
                             javax.net.ssl,
index de3dee14437b804fd18819bb20302a962fd213e2..4dfb7498184af4e7a71f7c40744821e258e00966 100644 (file)
@@ -8,8 +8,13 @@
 
 package org.opendaylight.controller.netconf.impl;
 
 
 package org.opendaylight.controller.netconf.impl;
 
+import com.google.common.annotations.VisibleForTesting;
+import io.netty.channel.Channel;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.channel.local.LocalServerChannel;
 import io.netty.channel.socket.SocketChannel;
 import io.netty.util.concurrent.Promise;
 import java.net.InetSocketAddress;
 import io.netty.channel.socket.SocketChannel;
 import io.netty.util.concurrent.Promise;
 import java.net.InetSocketAddress;
@@ -27,6 +32,7 @@ public class NetconfServerDispatcher extends AbstractDispatcher<NetconfServerSes
         this.initializer = serverChannelInitializer;
     }
 
         this.initializer = serverChannelInitializer;
     }
 
+    @VisibleForTesting
     public ChannelFuture createServer(InetSocketAddress address) {
 
         return super.createServer(address, new PipelineInitializer<NetconfServerSession>() {
     public ChannelFuture createServer(InetSocketAddress address) {
 
         return super.createServer(address, new PipelineInitializer<NetconfServerSession>() {
@@ -37,6 +43,15 @@ public class NetconfServerDispatcher extends AbstractDispatcher<NetconfServerSes
         });
     }
 
         });
     }
 
+    public ChannelFuture createLocalServer(LocalAddress address) {
+        return super.createServer(address, LocalServerChannel.class, new ChannelPipelineInitializer<LocalChannel, NetconfServerSession>() {
+            @Override
+            public void initializeChannel(final LocalChannel ch, final Promise<NetconfServerSession> promise) {
+                initializer.initialize(ch, promise);
+            }
+        });
+    }
+
     public static class ServerChannelInitializer extends AbstractChannelInitializer<NetconfServerSession> {
 
         public static final String DESERIALIZER_EX_HANDLER_KEY = "deserializerExHandler";
     public static class ServerChannelInitializer extends AbstractChannelInitializer<NetconfServerSession> {
 
         public static final String DESERIALIZER_EX_HANDLER_KEY = "deserializerExHandler";
@@ -50,16 +65,15 @@ public class NetconfServerDispatcher extends AbstractDispatcher<NetconfServerSes
         }
 
         @Override
         }
 
         @Override
-        protected void initializeMessageDecoder(SocketChannel ch) {
+        protected void initializeMessageDecoder(Channel ch) {
             super.initializeMessageDecoder(ch);
             ch.pipeline().addLast(DESERIALIZER_EX_HANDLER_KEY, new DeserializerExceptionHandler());
         }
 
         @Override
             super.initializeMessageDecoder(ch);
             ch.pipeline().addLast(DESERIALIZER_EX_HANDLER_KEY, new DeserializerExceptionHandler());
         }
 
         @Override
-        protected void initializeSessionNegotiator(SocketChannel ch, Promise<NetconfServerSession> promise) {
+        protected void initializeSessionNegotiator(Channel ch, Promise<NetconfServerSession> promise) {
             ch.pipeline().addAfter(DESERIALIZER_EX_HANDLER_KEY, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR,
                     negotiatorFactory.getSessionNegotiator(null, ch, promise));
         }
     }
             ch.pipeline().addAfter(DESERIALIZER_EX_HANDLER_KEY, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR,
                     negotiatorFactory.getSessionNegotiator(null, ch, promise));
         }
     }
-
 }
 }
index 7130dc350134578372348ce829bda08243a5303c..6ab62ef29a82037cd12e01c5f71d88e34a32a5bd 100644 (file)
@@ -7,12 +7,13 @@
  */
 package org.opendaylight.controller.netconf.impl.osgi;
 
  */
 package org.opendaylight.controller.netconf.impl.osgi;
 
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.util.HashedWheelTimer;
 import java.lang.management.ManagementFactory;
 import java.lang.management.ManagementFactory;
-import java.net.InetSocketAddress;
 import java.util.Dictionary;
 import java.util.Hashtable;
 import java.util.concurrent.TimeUnit;
 import java.util.Dictionary;
 import java.util.Hashtable;
 import java.util.concurrent.TimeUnit;
-
 import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService;
 import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer;
 import org.opendaylight.controller.netconf.impl.NetconfServerDispatcher;
 import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService;
 import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer;
 import org.opendaylight.controller.netconf.impl.NetconfServerDispatcher;
@@ -26,9 +27,6 @@ import org.osgi.framework.ServiceRegistration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.util.HashedWheelTimer;
-
 public class NetconfImplActivator implements BundleActivator {
 
     private static final Logger logger = LoggerFactory.getLogger(NetconfImplActivator.class);
 public class NetconfImplActivator implements BundleActivator {
 
     private static final Logger logger = LoggerFactory.getLogger(NetconfImplActivator.class);
@@ -40,17 +38,16 @@ public class NetconfImplActivator implements BundleActivator {
     private ServiceRegistration<NetconfMonitoringService> regMonitoring;
 
     @Override
     private ServiceRegistration<NetconfMonitoringService> regMonitoring;
 
     @Override
-    public void start(final BundleContext context) {
-        final InetSocketAddress address = NetconfConfigUtil.extractTCPNetconfServerAddress(context,
-                NetconfConfigUtil.DEFAULT_NETCONF_TCP_ADDRESS);
-        final NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl();
+    public void start(final BundleContext context)  {
+
+        NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl();
         startOperationServiceFactoryTracker(context, factoriesListener);
 
         startOperationServiceFactoryTracker(context, factoriesListener);
 
-        final SessionIdProvider idProvider = new SessionIdProvider();
+        SessionIdProvider idProvider = new SessionIdProvider();
         timer = new HashedWheelTimer();
         timer = new HashedWheelTimer();
-
         long connectionTimeoutMillis = NetconfConfigUtil.extractTimeoutMillis(context);
 
         long connectionTimeoutMillis = NetconfConfigUtil.extractTimeoutMillis(context);
 
+
         commitNot = new DefaultCommitNotificationProducer(ManagementFactory.getPlatformMBeanServer());
 
         SessionMonitoringService monitoringService = startMonitoringService(context, factoriesListener);
         commitNot = new DefaultCommitNotificationProducer(ManagementFactory.getPlatformMBeanServer());
 
         SessionMonitoringService monitoringService = startMonitoringService(context, factoriesListener);
@@ -62,24 +59,24 @@ public class NetconfImplActivator implements BundleActivator {
 
         NetconfServerDispatcher.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerChannelInitializer(
                 serverNegotiatorFactory);
 
         NetconfServerDispatcher.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerChannelInitializer(
                 serverNegotiatorFactory);
+        NetconfServerDispatcher dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup, eventLoopGroup);
 
 
-        NetconfServerDispatcher dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup,
-                eventLoopGroup);
-
-        logger.info("Starting TCP netconf server at {}", address);
-        dispatch.createServer(address);
+        LocalAddress address = NetconfConfigUtil.getNetconfLocalAddress();
+        logger.trace("Starting local netconf server at {}", address);
+        dispatch.createLocalServer(address);
 
         context.registerService(NetconfOperationProvider.class, factoriesListener, null);
 
         context.registerService(NetconfOperationProvider.class, factoriesListener, null);
+
     }
 
     }
 
-    private void startOperationServiceFactoryTracker(final BundleContext context, final NetconfOperationServiceFactoryListenerImpl factoriesListener) {
+    private void startOperationServiceFactoryTracker(BundleContext context, NetconfOperationServiceFactoryListenerImpl factoriesListener) {
         factoriesTracker = new NetconfOperationServiceFactoryTracker(context, factoriesListener);
         factoriesTracker.open();
     }
 
         factoriesTracker = new NetconfOperationServiceFactoryTracker(context, factoriesListener);
         factoriesTracker.open();
     }
 
-    private NetconfMonitoringServiceImpl startMonitoringService(final BundleContext context, final NetconfOperationServiceFactoryListenerImpl factoriesListener) {
-        final NetconfMonitoringServiceImpl netconfMonitoringServiceImpl = new NetconfMonitoringServiceImpl(factoriesListener);
-        final Dictionary<String, ?> dic = new Hashtable<>();
+    private NetconfMonitoringServiceImpl startMonitoringService(BundleContext context, NetconfOperationServiceFactoryListenerImpl factoriesListener) {
+        NetconfMonitoringServiceImpl netconfMonitoringServiceImpl = new NetconfMonitoringServiceImpl(factoriesListener);
+        Dictionary<String, ?> dic = new Hashtable<>();
         regMonitoring = context.registerService(NetconfMonitoringService.class, netconfMonitoringServiceImpl, dic);
 
         return netconfMonitoringServiceImpl;
         regMonitoring = context.registerService(NetconfMonitoringService.class, netconfMonitoringServiceImpl, dic);
 
         return netconfMonitoringServiceImpl;
index 140284e4ee1946ab2cc579fba0cf08362440e580..0969bd92a59a7cb6132a2b3b6cb5ad713af5077e 100644 (file)
@@ -16,13 +16,13 @@ import static org.mockito.Mockito.mock;
 
 import ch.ethz.ssh2.Connection;
 import io.netty.channel.ChannelFuture;
 
 import ch.ethz.ssh2.Connection;
 import io.netty.channel.ChannelFuture;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.util.concurrent.GlobalEventExecutor;
 import io.netty.util.concurrent.GlobalEventExecutor;
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.management.ManagementFactory;
 import java.net.InetSocketAddress;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.management.ManagementFactory;
 import java.net.InetSocketAddress;
-import java.nio.file.Files;
 import java.util.Collection;
 import java.util.List;
 import junit.framework.Assert;
 import java.util.Collection;
 import java.util.List;
 import junit.framework.Assert;
@@ -50,16 +50,14 @@ import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
 import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
 import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
 import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil;
 import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
 import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
 import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
 import org.opendaylight.controller.netconf.util.test.XmlFileLoader;
 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
 import org.opendaylight.controller.netconf.util.test.XmlFileLoader;
 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
-import org.opendaylight.controller.sal.authorization.AuthResultEnum;
-import org.opendaylight.controller.usermanager.IUserManager;
 import org.opendaylight.protocol.framework.NeverReconnectStrategy;
 
 public class NetconfITSecureTest extends AbstractNetconfConfigTest {
 
     private static final InetSocketAddress tlsAddress = new InetSocketAddress("127.0.0.1", 12024);
 import org.opendaylight.protocol.framework.NeverReconnectStrategy;
 
 public class NetconfITSecureTest extends AbstractNetconfConfigTest {
 
     private static final InetSocketAddress tlsAddress = new InetSocketAddress("127.0.0.1", 12024);
-    private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 12023);
 
     private DefaultCommitNotificationProducer commitNot;
     private NetconfSSHServer sshServer;
 
     private DefaultCommitNotificationProducer commitNot;
     private NetconfSSHServer sshServer;
@@ -79,13 +77,10 @@ public class NetconfITSecureTest extends AbstractNetconfConfigTest {
 
 
         final NetconfServerDispatcher dispatchS = createDispatcher(factoriesListener);
 
 
         final NetconfServerDispatcher dispatchS = createDispatcher(factoriesListener);
-        ChannelFuture s = dispatchS.createServer(tcpAddress);
+        ChannelFuture s = dispatchS.createLocalServer(NetconfConfigUtil.getNetconfLocalAddress());
         s.await();
         s.await();
-
-        sshServer = NetconfSSHServer.start(tlsAddress.getPort(), tcpAddress, getAuthProvider());
-        Thread thread = new Thread(sshServer);
-        thread.setDaemon(true);
-        thread.start();
+        EventLoopGroup bossGroup  = new NioEventLoopGroup();
+        sshServer = NetconfSSHServer.start(tlsAddress.getPort(), NetconfConfigUtil.getNetconfLocalAddress(), getAuthProvider(), bossGroup);
     }
 
     private NetconfServerDispatcher createDispatcher(NetconfOperationServiceFactoryListenerImpl factoriesListener) {
     }
 
     private NetconfServerDispatcher createDispatcher(NetconfOperationServiceFactoryListenerImpl factoriesListener) {
@@ -140,13 +135,10 @@ public class NetconfITSecureTest extends AbstractNetconfConfigTest {
     }
 
     public AuthProvider getAuthProvider() throws Exception {
     }
 
     public AuthProvider getAuthProvider() throws Exception {
-        final IUserManager userManager = mock(IUserManager.class);
-        doReturn(AuthResultEnum.AUTH_ACCEPT).when(userManager).authenticate(anyString(), anyString());
-
-        final File privateKeyFile = Files.createTempFile("tmp-netconf-test", "pk").toFile();
-        privateKeyFile.deleteOnExit();
-        String privateKeyPEMString = PEMGenerator.generateTo(privateKeyFile);
-        return new AuthProvider(userManager, privateKeyPEMString);
+        AuthProvider mock = mock(AuthProvider.class);
+        doReturn(true).when(mock).authenticated(anyString(), anyString());
+        doReturn(PEMGenerator.generate().toCharArray()).when(mock).getPEMAsCharArray();
+        return mock;
     }
 
     public AuthenticationHandler getAuthHandler() throws IOException {
     }
 
     public AuthenticationHandler getAuthHandler() throws IOException {
index fd43f67c056407c15dc0692d938b4fad7c30586b..60a5207daa2e74b54cbc5a1a2b1ec9dd423a3f2b 100644 (file)
@@ -8,7 +8,6 @@
 
 package org.opendaylight.controller.netconf.it;
 
 
 package org.opendaylight.controller.netconf.it;
 
-import static java.util.Collections.emptyList;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
@@ -17,6 +16,10 @@ import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
+import com.google.common.base.Throwables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import io.netty.channel.ChannelFuture;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.management.ManagementFactory;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.management.ManagementFactory;
@@ -29,10 +32,8 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
-
 import javax.management.ObjectName;
 import javax.xml.parsers.ParserConfigurationException;
 import javax.management.ObjectName;
 import javax.xml.parsers.ParserConfigurationException;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
@@ -66,32 +67,20 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controll
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.test.types.rev131127.TestIdentity2;
 import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry;
 import org.opendaylight.yangtools.yang.data.impl.codec.IdentityCodec;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.test.types.rev131127.TestIdentity2;
 import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry;
 import org.opendaylight.yangtools.yang.data.impl.codec.IdentityCodec;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.xml.sax.SAXException;
 
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.xml.sax.SAXException;
 
-import com.google.common.base.Throwables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import io.netty.channel.ChannelFuture;
-
 public class NetconfITTest extends AbstractNetconfConfigTest {
 
     // TODO refactor, pull common code up to AbstractNetconfITTest
 
 public class NetconfITTest extends AbstractNetconfConfigTest {
 
     // TODO refactor, pull common code up to AbstractNetconfITTest
 
-    private static final Logger logger = LoggerFactory.getLogger(NetconfITTest.class);
-
     private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 12023);
     private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 12023);
-    private static final InetSocketAddress sshAddress = new InetSocketAddress("127.0.0.1", 10830);
-    private static final String USERNAME = "netconf";
-    private static final String PASSWORD = "netconf";
 
 
-    private NetconfMessage getConfig, getConfigCandidate, editConfig,
-            closeSession, startExi, stopExi;
+
+    private NetconfMessage getConfig, getConfigCandidate, editConfig, closeSession;
     private DefaultCommitNotificationProducer commitNot;
     private NetconfServerDispatcher dispatch;
 
     private DefaultCommitNotificationProducer commitNot;
     private NetconfServerDispatcher dispatch;
 
@@ -139,10 +128,6 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
         this.editConfig = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/edit_config.xml");
         this.getConfig = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/getConfig.xml");
         this.getConfigCandidate = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/getConfig_candidate.xml");
         this.editConfig = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/edit_config.xml");
         this.getConfig = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/getConfig.xml");
         this.getConfigCandidate = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/getConfig_candidate.xml");
-        this.startExi = XmlFileLoader
-                .xmlFileToNetconfMessage("netconfMessages/startExi.xml");
-        this.stopExi = XmlFileLoader
-                .xmlFileToNetconfMessage("netconfMessages/stopExi.xml");
         this.closeSession = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/closeSession.xml");
     }
 
         this.closeSession = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/closeSession.xml");
     }
 
@@ -166,7 +151,7 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
                 yangDependencies.add(resourceAsStream);
             }
         }
                 yangDependencies.add(resourceAsStream);
             }
         }
-        assertEquals("Some yang files were not found", emptyList(), failedToFind);
+        assertEquals("Some yang files were not found", Collections.<String>emptyList(), failedToFind);
         return yangDependencies;
     }
 
         return yangDependencies;
     }
 
@@ -198,6 +183,7 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
     public void testTwoSessions() throws Exception {
         try (TestingNetconfClient netconfClient = new TestingNetconfClient("1", clientDispatcher, getClientConfiguration(tcpAddress, 10000)))  {
             try (TestingNetconfClient netconfClient2 = new TestingNetconfClient("2", clientDispatcher, getClientConfiguration(tcpAddress, 10000))) {
     public void testTwoSessions() throws Exception {
         try (TestingNetconfClient netconfClient = new TestingNetconfClient("1", clientDispatcher, getClientConfiguration(tcpAddress, 10000)))  {
             try (TestingNetconfClient netconfClient2 = new TestingNetconfClient("2", clientDispatcher, getClientConfiguration(tcpAddress, 10000))) {
+                assertNotNull(netconfClient2.getCapabilities());
             }
         }
     }
             }
         }
     }
index e88bf53ae0a7ccfc95d091521e0587a55fc1ef71..7897666ddc6763cff64f8e7f6dd225f411c9eb54 100644 (file)
@@ -8,7 +8,7 @@
 
 package org.opendaylight.controller.netconf.nettyutil;
 
 
 package org.opendaylight.controller.netconf.nettyutil;
 
-import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.Channel;
 import io.netty.util.concurrent.Promise;
 import org.opendaylight.controller.netconf.api.NetconfSession;
 import org.opendaylight.controller.netconf.nettyutil.handler.FramingMechanismHandlerFactory;
 import io.netty.util.concurrent.Promise;
 import org.opendaylight.controller.netconf.api.NetconfSession;
 import org.opendaylight.controller.netconf.nettyutil.handler.FramingMechanismHandlerFactory;
@@ -25,7 +25,7 @@ public abstract class AbstractChannelInitializer<S extends NetconfSession> {
     public static final String NETCONF_MESSAGE_FRAME_ENCODER = "frameEncoder";
     public static final String NETCONF_SESSION_NEGOTIATOR = "negotiator";
 
     public static final String NETCONF_MESSAGE_FRAME_ENCODER = "frameEncoder";
     public static final String NETCONF_SESSION_NEGOTIATOR = "negotiator";
 
-    public void initialize(SocketChannel ch, Promise<S> promise) {
+    public void initialize(Channel ch, Promise<S> promise) {
         ch.pipeline().addLast(NETCONF_MESSAGE_AGGREGATOR, new NetconfEOMAggregator());
         initializeMessageDecoder(ch);
         ch.pipeline().addLast(NETCONF_MESSAGE_FRAME_ENCODER, FramingMechanismHandlerFactory.createHandler(FramingMechanism.EOM));
         ch.pipeline().addLast(NETCONF_MESSAGE_AGGREGATOR, new NetconfEOMAggregator());
         initializeMessageDecoder(ch);
         ch.pipeline().addLast(NETCONF_MESSAGE_FRAME_ENCODER, FramingMechanismHandlerFactory.createHandler(FramingMechanism.EOM));
@@ -34,13 +34,13 @@ public abstract class AbstractChannelInitializer<S extends NetconfSession> {
         initializeSessionNegotiator(ch, promise);
     }
 
         initializeSessionNegotiator(ch, promise);
     }
 
-    protected void initializeMessageEncoder(SocketChannel ch) {
+    protected void initializeMessageEncoder(Channel ch) {
         // Special encoding handler for hello message to include additional header if available,
         // it is thrown away after successful negotiation
         ch.pipeline().addLast(NETCONF_MESSAGE_ENCODER, new NetconfHelloMessageToXMLEncoder());
     }
 
         // Special encoding handler for hello message to include additional header if available,
         // it is thrown away after successful negotiation
         ch.pipeline().addLast(NETCONF_MESSAGE_ENCODER, new NetconfHelloMessageToXMLEncoder());
     }
 
-    protected void initializeMessageDecoder(SocketChannel ch) {
+    protected void initializeMessageDecoder(Channel ch) {
         // Special decoding handler for hello message to parse additional header if available,
         // it is thrown away after successful negotiation
         ch.pipeline().addLast(NETCONF_MESSAGE_DECODER, new NetconfXMLToHelloMessageDecoder());
         // Special decoding handler for hello message to parse additional header if available,
         // it is thrown away after successful negotiation
         ch.pipeline().addLast(NETCONF_MESSAGE_DECODER, new NetconfXMLToHelloMessageDecoder());
@@ -50,6 +50,6 @@ public abstract class AbstractChannelInitializer<S extends NetconfSession> {
      * Insert session negotiator into the pipeline. It must be inserted after message decoder
      * identified by {@link AbstractChannelInitializer#NETCONF_MESSAGE_DECODER}, (or any other custom decoder processor)
      */
      * Insert session negotiator into the pipeline. It must be inserted after message decoder
      * identified by {@link AbstractChannelInitializer#NETCONF_MESSAGE_DECODER}, (or any other custom decoder processor)
      */
-    protected abstract void initializeSessionNegotiator(SocketChannel ch, Promise<S> promise);
+    protected abstract void initializeSessionNegotiator(Channel ch, Promise<S> promise);
 
 }
 
 }
index 622881352eef102119eed81a74f32ee0db2ed76a..cbd3efc57f7bc0abfd27e77bec63334087265fab 100644 (file)
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.7</version>
         <configuration>
           <instructions>
             <Bundle-Activator>org.opendaylight.controller.netconf.ssh.osgi.NetconfSSHActivator</Bundle-Activator>
             <Import-Package>com.google.common.base,
         <configuration>
           <instructions>
             <Bundle-Activator>org.opendaylight.controller.netconf.ssh.osgi.NetconfSSHActivator</Bundle-Activator>
             <Import-Package>com.google.common.base,
-                            ch.ethz.ssh2,
-                            ch.ethz.ssh2.signature,
-                            org.apache.commons.io,
-                            org.opendaylight.controller.netconf.util.osgi,
-                            org.opendaylight.controller.usermanager,
-                            org.opendaylight.controller.sal.authorization,
-                            org.opendaylight.controller.sal.utils,
-                            org.osgi.framework,
-                            org.osgi.util.tracker,
-                            org.slf4j,
-                            org.bouncycastle.openssl</Import-Package>
+              ch.ethz.ssh2,
+              ch.ethz.ssh2.signature,
+              org.apache.commons.io,
+              org.opendaylight.controller.netconf.util.osgi,
+              org.opendaylight.controller.usermanager,
+              org.opendaylight.controller.sal.authorization,
+              org.opendaylight.controller.sal.utils,
+              org.osgi.framework,
+              org.osgi.util.tracker,
+              org.slf4j,
+              org.bouncycastle.openssl,
+              io.netty.bootstrap, io.netty.buffer, io.netty.channel, io.netty.channel.local, io.netty.channel.nio,
+              io.netty.handler.stream, io.netty.util.concurrent, org.apache.commons.lang3,
+              org.opendaylight.controller.netconf.util.messages</Import-Package>
           </instructions>
         </configuration>
       </plugin>
           </instructions>
         </configuration>
       </plugin>
index c6974d4982db051f1cb6a66d3f67394ee2d57817..08bf9836b22135a295f2e54d68fa6c73ffeadf57 100644 (file)
@@ -7,79 +7,94 @@
  */
 package org.opendaylight.controller.netconf.ssh;
 
  */
 package org.opendaylight.controller.netconf.ssh;
 
-import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
-import org.opendaylight.controller.netconf.ssh.threads.SocketThread;
-import org.opendaylight.controller.usermanager.IUserManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.concurrent.ThreadSafe;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
 import java.io.IOException;
 import java.io.IOException;
-import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicLong;
+import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
+import org.opendaylight.controller.netconf.ssh.threads.Handshaker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 
+/**
+ * Thread that accepts client connections. Accepted socket is forwarded to {@link org.opendaylight.controller.netconf.ssh.threads.Handshaker},
+ * which is executed in {@link #handshakeExecutor}.
+ */
 @ThreadSafe
 @ThreadSafe
-public final class NetconfSSHServer implements Runnable {
+public final class NetconfSSHServer extends Thread implements AutoCloseable {
 
 
-    private ServerSocket ss = null;
-    private static final Logger logger =  LoggerFactory.getLogger(NetconfSSHServer.class);
-    private static final AtomicLong sesssionId = new AtomicLong();
-    private final InetSocketAddress clientAddress;
-    private final AuthProvider authProvider;
-    private volatile boolean up = false;
+    private static final Logger logger = LoggerFactory.getLogger(NetconfSSHServer.class);
+    private static final AtomicLong sessionIdCounter = new AtomicLong();
 
 
-    private NetconfSSHServer(int serverPort,InetSocketAddress clientAddress, AuthProvider authProvider) throws IllegalStateException, IOException {
+    private final ServerSocket serverSocket;
+    private final LocalAddress localAddress;
+    private final EventLoopGroup bossGroup;
+    private final AuthProvider authProvider;
+    private final ExecutorService handshakeExecutor;
+    private volatile boolean up;
 
 
-        logger.trace("Creating SSH server socket on port {}",serverPort);
-        this.ss = new ServerSocket(serverPort);
-        if (!ss.isBound()){
-            throw new IllegalStateException("Socket can't be bound to requested port :"+serverPort);
+    private NetconfSSHServer(int serverPort, LocalAddress localAddress, AuthProvider authProvider, EventLoopGroup bossGroup) throws IOException {
+        super(NetconfSSHServer.class.getSimpleName());
+        this.bossGroup = bossGroup;
+        logger.trace("Creating SSH server socket on port {}", serverPort);
+        this.serverSocket = new ServerSocket(serverPort);
+        if (serverSocket.isBound() == false) {
+            throw new IllegalStateException("Socket can't be bound to requested port :" + serverPort);
         }
         logger.trace("Server socket created.");
         }
         logger.trace("Server socket created.");
-        this.clientAddress = clientAddress;
+        this.localAddress = localAddress;
         this.authProvider = authProvider;
         this.up = true;
         this.authProvider = authProvider;
         this.up = true;
+        handshakeExecutor = Executors.newFixedThreadPool(10);
     }
 
     }
 
-    public static NetconfSSHServer start(int serverPort, InetSocketAddress clientAddress,AuthProvider authProvider) throws IllegalStateException, IOException {
-        return new NetconfSSHServer(serverPort, clientAddress,authProvider);
+    public static NetconfSSHServer start(int serverPort, LocalAddress localAddress, AuthProvider authProvider, EventLoopGroup bossGroup) throws IOException {
+        NetconfSSHServer netconfSSHServer = new NetconfSSHServer(serverPort, localAddress, authProvider, bossGroup);
+        netconfSSHServer.start();
+        return netconfSSHServer;
     }
 
     }
 
-    public void stop() throws IOException {
+    @Override
+    public void close() throws IOException {
         up = false;
         logger.trace("Closing SSH server socket.");
         up = false;
         logger.trace("Closing SSH server socket.");
-        ss.close();
+        serverSocket.close();
+        bossGroup.shutdownGracefully();
         logger.trace("SSH server socket closed.");
     }
 
         logger.trace("SSH server socket closed.");
     }
 
-    public void removeUserManagerService(){
-        this.authProvider.removeUserManagerService();
-    }
-
-    public void addUserManagerService(IUserManager userManagerService){
-        this.authProvider.addUserManagerService(userManagerService);
-    }
-    public boolean isUp(){
-        return this.up;
-    }
     @Override
     public void run() {
         while (up) {
     @Override
     public void run() {
         while (up) {
-            logger.trace("Starting new socket thread.");
+            Socket acceptedSocket = null;
             try {
             try {
-                SocketThread.start(ss.accept(), clientAddress, sesssionId.incrementAndGet(), authProvider);
-            }
-            catch (IOException e) {
-                if( up ) {
-                    logger.error("Exception occurred during socket thread initialization", e);
+                acceptedSocket = serverSocket.accept();
+            } catch (IOException e) {
+                if (up == false) {
+                    logger.trace("Exiting server thread", e);
+                } else {
+                    logger.warn("Exception occurred during socket.accept", e);
                 }
                 }
-                else {
-                    // We're shutting down so an exception is expected as the socket's been closed.
-                    // Log to debug.
-                    logger.debug("Shutting down - got expected exception: " + e);
+            }
+            if (acceptedSocket != null) {
+                try {
+                    Handshaker task = new Handshaker(acceptedSocket, localAddress, sessionIdCounter.incrementAndGet(), authProvider, bossGroup);
+                    handshakeExecutor.submit(task);
+                } catch (IOException e) {
+                    logger.warn("Cannot set PEMHostKey, closing connection", e);
+                    try {
+                        acceptedSocket.close();
+                    } catch (IOException e1) {
+                        logger.warn("Ignoring exception while closing socket", e);
+                    }
                 }
             }
         }
                 }
             }
         }
+        logger.debug("Server thread is exiting");
     }
 }
     }
 }
index 2e9a0b9d8bbd256154ff82689e85e556b60c9e90..5d39dd1eb8adad115e030c0136cbae702d2bde65 100644 (file)
@@ -7,41 +7,75 @@
  */
 package org.opendaylight.controller.netconf.ssh.authentication;
 
  */
 package org.opendaylight.controller.netconf.ssh.authentication;
 
-import java.io.IOException;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
 import org.opendaylight.controller.sal.authorization.AuthResultEnum;
 import org.opendaylight.controller.usermanager.IUserManager;
 import org.opendaylight.controller.sal.authorization.AuthResultEnum;
 import org.opendaylight.controller.usermanager.IUserManager;
-import static com.google.common.base.Preconditions.checkNotNull;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 
-public class AuthProvider implements AuthProviderInterface {
+public class AuthProvider {
+    private static final Logger logger = LoggerFactory.getLogger(AuthProvider.class);
 
 
-    private IUserManager um;
     private final String pem;
     private final String pem;
+    private IUserManager nullableUserManager;
 
 
-    public AuthProvider(IUserManager ium, String pemCertificate) throws IllegalArgumentException, IOException {
+    public AuthProvider(String pemCertificate, final BundleContext bundleContext) {
         checkNotNull(pemCertificate, "Parameter 'pemCertificate' is null");
         checkNotNull(pemCertificate, "Parameter 'pemCertificate' is null");
-        checkNotNull(ium, "No user manager service available.");
-        this.um = ium;
         pem = pemCertificate;
         pem = pemCertificate;
+
+        ServiceTrackerCustomizer<IUserManager, IUserManager> customizer = new ServiceTrackerCustomizer<IUserManager, IUserManager>() {
+            @Override
+            public IUserManager addingService(final ServiceReference<IUserManager> reference) {
+                logger.trace("Service {} added", reference);
+                nullableUserManager = bundleContext.getService(reference);
+                return nullableUserManager;
+            }
+
+            @Override
+            public void modifiedService(final ServiceReference<IUserManager> reference, final IUserManager service) {
+                logger.trace("Replacing modified service {} in netconf SSH.", reference);
+                nullableUserManager = service;
+            }
+
+            @Override
+            public void removedService(final ServiceReference<IUserManager> reference, final IUserManager service) {
+                logger.trace("Removing service {} from netconf SSH. " +
+                        "SSH won't authenticate users until IUserManager service will be started.", reference);
+                synchronized (AuthProvider.this) {
+                    nullableUserManager = null;
+                }
+            }
+        };
+        ServiceTracker<IUserManager, IUserManager> listenerTracker = new ServiceTracker<>(bundleContext, IUserManager.class, customizer);
+        listenerTracker.open();
     }
 
     }
 
-    @Override
-    public boolean authenticated(String username, String password) {
-        AuthResultEnum authResult = this.um.authenticate(username, password);
+    /**
+     * Authenticate user. This implementation tracks IUserManager and delegates the decision to it. If the service is not
+     * available, IllegalStateException is thrown.
+     */
+    public synchronized boolean authenticated(String username, String password) {
+        if (nullableUserManager == null) {
+            logger.warn("Cannot authenticate user '{}', user manager service is missing", username);
+            throw new IllegalStateException("User manager service is not available");
+        }
+        AuthResultEnum authResult = nullableUserManager.authenticate(username, password);
+        logger.debug("Authentication result for user '{}' : {}", username, authResult);
         return authResult.equals(AuthResultEnum.AUTH_ACCEPT) || authResult.equals(AuthResultEnum.AUTH_ACCEPT_LOC);
     }
 
         return authResult.equals(AuthResultEnum.AUTH_ACCEPT) || authResult.equals(AuthResultEnum.AUTH_ACCEPT_LOC);
     }
 
-    @Override
     public char[] getPEMAsCharArray() {
         return pem.toCharArray();
     }
 
     public char[] getPEMAsCharArray() {
         return pem.toCharArray();
     }
 
-    @Override
-    public void removeUserManagerService() {
-        this.um = null;
-    }
-
-    @Override
-    public void addUserManagerService(IUserManager userManagerService) {
-        this.um = userManagerService;
+    @VisibleForTesting
+    void setNullableUserManager(IUserManager nullableUserManager) {
+        this.nullableUserManager = nullableUserManager;
     }
 }
     }
 }
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProviderInterface.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProviderInterface.java
deleted file mode 100644 (file)
index fad0f79..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-
-/*
- * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.controller.netconf.ssh.authentication;
-
-import org.opendaylight.controller.usermanager.IUserManager;
-
-public interface AuthProviderInterface {
-
-    public boolean authenticated(String username, String password) throws IllegalStateException;
-    public char[] getPEMAsCharArray() throws Exception;
-    public void removeUserManagerService();
-    public void addUserManagerService(IUserManager userManagerService);
-}
index 348fe006f3a7d4cfefcb8c502a9115da422a0a61..53ab8219ee9c2a9f98e144f8c125f45b86edb002 100644 (file)
@@ -8,8 +8,11 @@
 
 package org.opendaylight.controller.netconf.ssh.authentication;
 
 
 package org.opendaylight.controller.netconf.ssh.authentication;
 
+import com.google.common.annotations.VisibleForTesting;
+import java.io.FileInputStream;
 import java.security.NoSuchAlgorithmException;
 import org.apache.commons.io.FileUtils;
 import java.security.NoSuchAlgorithmException;
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
 import org.bouncycastle.openssl.PEMWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.bouncycastle.openssl.PEMWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -26,17 +29,55 @@ public class PEMGenerator {
     private static final Logger logger = LoggerFactory.getLogger(PEMGenerator.class);
     private static final int KEY_SIZE = 4096;
 
     private static final Logger logger = LoggerFactory.getLogger(PEMGenerator.class);
     private static final int KEY_SIZE = 4096;
 
+
+    public static String readOrGeneratePK(File privateKeyFile) throws IOException {
+        if (privateKeyFile.exists() == false) {
+            // generate & save to file
+            try {
+                return generateTo(privateKeyFile);
+            } catch (Exception e) {
+                logger.error("Exception occurred while generating PEM string to {}", privateKeyFile, e);
+                throw new IllegalStateException("Error generating RSA key from file " + privateKeyFile);
+            }
+        } else {
+            // read from file
+            try (FileInputStream fis = new FileInputStream(privateKeyFile)) {
+                return IOUtils.toString(fis);
+            } catch (final IOException e) {
+                logger.error("Error reading RSA key from file {}", privateKeyFile, e);
+                throw new IOException("Error reading RSA key from file " + privateKeyFile, e);
+            }
+        }
+    }
+
+    /**
+     * Generate private key to a file and return its content as string.
+     *
+     * @param privateFile path where private key should be generated
+     * @return String representation of private key
+     * @throws IOException
+     * @throws NoSuchAlgorithmException
+     */
+    @VisibleForTesting
     public static String generateTo(File privateFile) throws IOException, NoSuchAlgorithmException {
     public static String generateTo(File privateFile) throws IOException, NoSuchAlgorithmException {
+        logger.info("Generating private key to {}", privateFile.getAbsolutePath());
+        String privatePEM = generate();
+        FileUtils.write(privateFile, privatePEM);
+        return privatePEM;
+    }
+
+    @VisibleForTesting
+    public static String generate() throws NoSuchAlgorithmException, IOException {
         KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
         SecureRandom sr = new SecureRandom();
         keyGen.initialize(KEY_SIZE, sr);
         KeyPair keypair = keyGen.generateKeyPair();
         KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
         SecureRandom sr = new SecureRandom();
         keyGen.initialize(KEY_SIZE, sr);
         KeyPair keypair = keyGen.generateKeyPair();
-        logger.info("Generating private key to {}", privateFile.getAbsolutePath());
-        String privatePEM = toString(keypair.getPrivate());
-        FileUtils.write(privateFile, privatePEM);
-        return privatePEM;
+        return toString(keypair.getPrivate());
     }
 
     }
 
+    /**
+     * Get string representation of a key.
+     */
     private static String toString(Key key) throws IOException {
         try (StringWriter writer = new StringWriter()) {
             try (PEMWriter pemWriter = new PEMWriter(writer)) {
     private static String toString(Key key) throws IOException {
         try (StringWriter writer = new StringWriter()) {
             try (PEMWriter pemWriter = new PEMWriter(writer)) {
@@ -45,4 +86,5 @@ public class PEMGenerator {
             return writer.toString();
         }
     }
             return writer.toString();
         }
     }
+
 }
 }
index d74308cfadbae8e658e58f9b189d93baaea83c2e..a26843fae17a97621b48221a932eb9b823a6ce83 100644 (file)
@@ -7,24 +7,24 @@
  */
 package org.opendaylight.controller.netconf.ssh.osgi;
 
  */
 package org.opendaylight.controller.netconf.ssh.osgi;
 
-import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
 
 
+import com.google.common.base.Optional;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.nio.NioEventLoopGroup;
 import java.io.File;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import org.apache.commons.io.FilenameUtils;
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
 import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
 import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
 import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
 import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
 import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
 import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
 import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
-import org.opendaylight.controller.usermanager.IUserManager;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil.InfixProp;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
-import org.osgi.util.tracker.ServiceTrackerCustomizer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,112 +32,56 @@ import org.slf4j.LoggerFactory;
  * Activator for netconf SSH bundle which creates SSH bridge between netconf client and netconf server. Activator
  * starts SSH Server in its own thread. This thread is closed when activator calls stop() method. Server opens socket
  * and listens for client connections. Each client connection creation is handled in separate
  * Activator for netconf SSH bundle which creates SSH bridge between netconf client and netconf server. Activator
  * starts SSH Server in its own thread. This thread is closed when activator calls stop() method. Server opens socket
  * and listens for client connections. Each client connection creation is handled in separate
- * {@link org.opendaylight.controller.netconf.ssh.threads.SocketThread} thread.
+ * {@link org.opendaylight.controller.netconf.ssh.threads.Handshaker} thread.
  * This thread creates two additional threads {@link org.opendaylight.controller.netconf.ssh.threads.IOThread}
  * forwarding data from/to client.IOThread closes servers session and server connection when it gets -1 on input stream.
  * {@link org.opendaylight.controller.netconf.ssh.threads.IOThread}'s run method waits for -1 on input stream to finish.
  * All threads are daemons.
  * This thread creates two additional threads {@link org.opendaylight.controller.netconf.ssh.threads.IOThread}
  * forwarding data from/to client.IOThread closes servers session and server connection when it gets -1 on input stream.
  * {@link org.opendaylight.controller.netconf.ssh.threads.IOThread}'s run method waits for -1 on input stream to finish.
  * All threads are daemons.
- **/
-public class NetconfSSHActivator implements BundleActivator{
+ */
+public class NetconfSSHActivator implements BundleActivator {
+    private static final Logger logger = LoggerFactory.getLogger(NetconfSSHActivator.class);
 
     private NetconfSSHServer server;
 
     private NetconfSSHServer server;
-    private static final Logger logger =  LoggerFactory.getLogger(NetconfSSHActivator.class);
-    private IUserManager iUserManager;
-    private BundleContext context = null;
-
-    private ServiceTrackerCustomizer<IUserManager, IUserManager> customizer = new ServiceTrackerCustomizer<IUserManager, IUserManager>(){
-        @Override
-        public IUserManager addingService(final ServiceReference<IUserManager> reference) {
-            logger.trace("Service {} added, let there be SSH bridge.", reference);
-            iUserManager =  context.getService(reference);
-            try {
-                onUserManagerFound(iUserManager);
-            } catch (final Exception e) {
-                logger.trace("Can't start SSH server due to {}",e);
-            }
-            return iUserManager;
-        }
-        @Override
-        public void modifiedService(final ServiceReference<IUserManager> reference, final IUserManager service) {
-            logger.trace("Replacing modified service {} in netconf SSH.", reference);
-            server.addUserManagerService(service);
-        }
-        @Override
-        public void removedService(final ServiceReference<IUserManager> reference, final IUserManager service) {
-            logger.trace("Removing service {} from netconf SSH. " +
-                    "SSH won't authenticate users until IUserManager service will be started.", reference);
-            removeUserManagerService();
-        }
-    };
-
 
     @Override
 
     @Override
-    public void start(final BundleContext context) {
-        this.context = context;
-        listenForManagerService();
+    public void start(final BundleContext bundleContext) throws IOException {
+        server = startSSHServer(bundleContext);
     }
 
     @Override
     public void stop(BundleContext context) throws IOException {
     }
 
     @Override
     public void stop(BundleContext context) throws IOException {
-        if (server != null){
-            server.stop();
-            logger.trace("Netconf SSH bridge is down ...");
+        if (server != null) {
+            server.close();
         }
     }
         }
     }
-    private void startSSHServer() throws IOException {
-        checkNotNull(this.iUserManager, "No user manager service available.");
-        logger.trace("Starting netconf SSH  bridge.");
-        final InetSocketAddress sshSocketAddress = NetconfConfigUtil.extractSSHNetconfAddress(context,
-                NetconfConfigUtil.DEFAULT_NETCONF_SSH_ADDRESS);
-        final InetSocketAddress tcpSocketAddress = NetconfConfigUtil.extractTCPNetconfClientAddress(context,
-               NetconfConfigUtil.DEFAULT_NETCONF_TCP_ADDRESS);
 
 
-        String path =  FilenameUtils.separatorsToSystem(NetconfConfigUtil.getPrivateKeyPath(context));
+    private static NetconfSSHServer startSSHServer(BundleContext bundleContext) throws IOException {
+        Optional<InetSocketAddress> maybeSshSocketAddress = NetconfConfigUtil.extractNetconfServerAddress(bundleContext,
+                InfixProp.ssh);
 
 
-        if (path.isEmpty()) {
-            throw new IllegalStateException("Missing netconf.ssh.pk.path key in configuration file.");
+        if (maybeSshSocketAddress.isPresent() == false) {
+            logger.trace("SSH bridge not configured");
+            return null;
         }
         }
+        InetSocketAddress sshSocketAddress = maybeSshSocketAddress.get();
+        logger.trace("Starting netconf SSH  bridge at {}", sshSocketAddress);
 
 
-        final File privateKeyFile = new File(path);
-        final String privateKeyPEMString;
-        if (privateKeyFile.exists() == false) {
-            // generate & save to file
-            try {
-                privateKeyPEMString = PEMGenerator.generateTo(privateKeyFile);
-            } catch (Exception e) {
-                logger.error("Exception occurred while generating PEM string {}", e);
-                throw new IllegalStateException("Error generating RSA key from file " + path);
-            }
-        } else {
-            // read from file
-            try (FileInputStream fis = new FileInputStream(path)) {
-                privateKeyPEMString = IOUtils.toString(fis);
-            } catch (final IOException e) {
-                logger.error("Error reading RSA key from file '{}'", path);
-                throw new IOException("Error reading RSA key from file " + path, e);
-            }
-        }
-        final AuthProvider authProvider = new AuthProvider(iUserManager, privateKeyPEMString);
-        this.server = NetconfSSHServer.start(sshSocketAddress.getPort(), tcpSocketAddress, authProvider);
+        LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress();
+
+        String path = FilenameUtils.separatorsToSystem(NetconfConfigUtil.getPrivateKeyPath(bundleContext));
+        checkState(StringUtils.isNotBlank(path), "Path to ssh private key is blank. Reconfigure %s", NetconfConfigUtil.getPrivateKeyKey());
+        String privateKeyPEMString = PEMGenerator.readOrGeneratePK(new File(path));
+
+        final AuthProvider authProvider = new AuthProvider(privateKeyPEMString, bundleContext);
+        EventLoopGroup bossGroup  = new NioEventLoopGroup();
+        NetconfSSHServer server = NetconfSSHServer.start(sshSocketAddress.getPort(), localAddress, authProvider, bossGroup);
 
         final Thread serverThread = new Thread(server, "netconf SSH server thread");
         serverThread.setDaemon(true);
         serverThread.start();
         logger.trace("Netconf SSH  bridge up and running.");
 
         final Thread serverThread = new Thread(server, "netconf SSH server thread");
         serverThread.setDaemon(true);
         serverThread.start();
         logger.trace("Netconf SSH  bridge up and running.");
+        return server;
     }
 
     }
 
-    private void onUserManagerFound(final IUserManager userManager) throws Exception{
-        if (server!=null && server.isUp()){
-           server.addUserManagerService(userManager);
-        } else {
-           startSSHServer();
-        }
-    }
-    private void removeUserManagerService(){
-        this.server.removeUserManagerService();
-    }
-    private void listenForManagerService(){
-        final ServiceTracker<IUserManager, IUserManager> listenerTracker = new ServiceTracker<>(context, IUserManager.class,customizer);
-        listenerTracker.open();
-    }
+
 }
 }
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java
new file mode 100644 (file)
index 0000000..d999d37
--- /dev/null
@@ -0,0 +1,406 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.netconf.ssh.threads;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import ch.ethz.ssh2.AuthenticationResult;
+import ch.ethz.ssh2.PtySettings;
+import ch.ethz.ssh2.ServerAuthenticationCallback;
+import ch.ethz.ssh2.ServerConnection;
+import ch.ethz.ssh2.ServerConnectionCallback;
+import ch.ethz.ssh2.ServerSession;
+import ch.ethz.ssh2.ServerSessionCallback;
+import ch.ethz.ssh2.SimpleServerSessionCallback;
+import com.google.common.base.Supplier;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufProcessor;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.handler.stream.ChunkedStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * One instance represents per connection, responsible for ssh handshake.
+ * Once auth succeeds and correct subsystem is chosen, backend connection with
+ * netty netconf server is made. This task finishes right after negotiation is done.
+ */
+@ThreadSafe
+public class Handshaker implements Runnable {
+    private static final Logger logger = LoggerFactory.getLogger(Handshaker.class);
+
+    private final ServerConnection ganymedConnection;
+    private final String session;
+
+
+    public Handshaker(Socket socket, LocalAddress localAddress, long sessionId, AuthProvider authProvider,
+                      EventLoopGroup bossGroup) throws IOException {
+
+        this.session = "Session " + sessionId;
+
+        String remoteAddressWithPort = socket.getRemoteSocketAddress().toString().replace("/", "");
+        logger.debug("{} started with {}", session, remoteAddressWithPort);
+        String remoteAddress, remotePort;
+        if (remoteAddressWithPort.contains(":")) {
+            String[] split = remoteAddressWithPort.split(":");
+            remoteAddress = split[0];
+            remotePort = split[1];
+        } else {
+            remoteAddress = remoteAddressWithPort;
+            remotePort = "";
+        }
+        ServerAuthenticationCallbackImpl serverAuthenticationCallback = new ServerAuthenticationCallbackImpl(
+                authProvider, session);
+
+        ganymedConnection = new ServerConnection(socket);
+
+        ServerConnectionCallbackImpl serverConnectionCallback = new ServerConnectionCallbackImpl(
+                serverAuthenticationCallback, remoteAddress, remotePort, session,
+                getGanymedAutoCloseable(ganymedConnection), localAddress, bossGroup);
+
+        // initialize ganymed
+        ganymedConnection.setPEMHostKey(authProvider.getPEMAsCharArray(), null);
+        ganymedConnection.setAuthenticationCallback(serverAuthenticationCallback);
+        ganymedConnection.setServerConnectionCallback(serverConnectionCallback);
+    }
+
+
+    private static AutoCloseable getGanymedAutoCloseable(final ServerConnection ganymedConnection) {
+        return new AutoCloseable() {
+            @Override
+            public void close() throws Exception {
+                ganymedConnection.close();
+            }
+        };
+    }
+
+    @Override
+    public void run() {
+        // let ganymed process handshake
+        logger.trace("{} SocketThread is started", session);
+        try {
+            // TODO this should be guarded with a timer to prevent resource exhaustion
+            ganymedConnection.connect();
+        } catch (IOException e) {
+            logger.warn("{} SocketThread error ", session, e);
+        }
+        logger.trace("{} SocketThread is exiting", session);
+    }
+}
+
+/**
+ * Netty client handler that forwards bytes from backed server to supplied output stream.
+ * When backend server closes the connection, remoteConnection.close() is called to tear
+ * down ssh connection.
+ */
+class SSHClientHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(SSHClientHandler.class);
+    private final AutoCloseable remoteConnection;
+    private final OutputStream remoteOutputStream;
+    private final String session;
+    private ChannelHandlerContext channelHandlerContext;
+
+    public SSHClientHandler(AutoCloseable remoteConnection, OutputStream remoteOutputStream,
+                            String session) {
+        this.remoteConnection = remoteConnection;
+        this.remoteOutputStream = remoteOutputStream;
+        this.session = session;
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        this.channelHandlerContext = ctx;
+        logger.debug("{} Client active", session);
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) {
+        ByteBuf bb = (ByteBuf) msg;
+        // we can block the server here so that slow client does not cause memory pressure
+        try {
+            bb.forEachByte(new ByteBufProcessor() {
+                @Override
+                public boolean process(byte value) throws Exception {
+                    remoteOutputStream.write(value);
+                    return true;
+                }
+            });
+        } finally {
+            bb.release();
+        }
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) throws IOException {
+        logger.trace("{} Flushing", session);
+        remoteOutputStream.flush();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        logger.warn("{} Unexpected exception from downstream", session, cause);
+        ctx.close();
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        logger.trace("{} channelInactive() called, closing remote client ctx", session);
+        remoteConnection.close();//this should close socket and all threads created for this client
+        this.channelHandlerContext = null;
+    }
+
+    public ChannelHandlerContext getChannelHandlerContext() {
+        return checkNotNull(channelHandlerContext, "Channel is not active");
+    }
+}
+
+/**
+ * Ganymed handler that gets unencrypted input and output streams, connects them to netty.
+ * Checks that 'netconf' subsystem is chosen by user.
+ * Launches new ClientInputStreamPoolingThread thread once session is established.
+ * Writes custom header to netty server, to inform it about IP address and username.
+ */
+class ServerConnectionCallbackImpl implements ServerConnectionCallback {
+    private static final Logger logger = LoggerFactory.getLogger(ServerConnectionCallbackImpl.class);
+    public static final String NETCONF_SUBSYSTEM = "netconf";
+
+    private final Supplier<String> currentUserSupplier;
+    private final String remoteAddress;
+    private final String remotePort;
+    private final String session;
+    private final AutoCloseable ganymedConnection;
+    private final LocalAddress localAddress;
+    private final EventLoopGroup bossGroup;
+
+    ServerConnectionCallbackImpl(Supplier<String> currentUserSupplier, String remoteAddress, String remotePort, String session,
+                                 AutoCloseable ganymedConnection, LocalAddress localAddress, EventLoopGroup bossGroup) {
+        this.currentUserSupplier = currentUserSupplier;
+        this.remoteAddress = remoteAddress;
+        this.remotePort = remotePort;
+        this.session = session;
+        this.ganymedConnection = ganymedConnection;
+        // initialize netty local connection
+        this.localAddress = localAddress;
+        this.bossGroup = bossGroup;
+    }
+
+    private static ChannelFuture initializeNettyConnection(LocalAddress localAddress, EventLoopGroup bossGroup,
+                                                           final SSHClientHandler sshClientHandler) {
+        Bootstrap clientBootstrap = new Bootstrap();
+        clientBootstrap.group(bossGroup).channel(LocalChannel.class);
+
+        clientBootstrap.handler(new ChannelInitializer<LocalChannel>() {
+            @Override
+            public void initChannel(LocalChannel ch) throws Exception {
+                ch.pipeline().addLast(sshClientHandler);
+            }
+        });
+        // asynchronously initialize local connection to netconf server
+        return clientBootstrap.connect(localAddress);
+    }
+
+    @Override
+    public ServerSessionCallback acceptSession(final ServerSession serverSession) {
+        String currentUser = currentUserSupplier.get();
+        final String additionalHeader = new NetconfHelloMessageAdditionalHeader(currentUser, remoteAddress,
+                remotePort, "ssh", "client").toFormattedString();
+
+
+        return new SimpleServerSessionCallback() {
+            @Override
+            public Runnable requestSubsystem(final ServerSession ss, final String subsystem) throws IOException {
+                return new Runnable() {
+                    @Override
+                    public void run() {
+                        if (NETCONF_SUBSYSTEM.equals(subsystem)) {
+                            // connect
+                            final SSHClientHandler sshClientHandler = new SSHClientHandler(ganymedConnection, ss.getStdin(), session);
+                            ChannelFuture clientChannelFuture = initializeNettyConnection(localAddress, bossGroup, sshClientHandler);
+                            // get channel
+                            final Channel channel = clientChannelFuture.awaitUninterruptibly().channel();
+                            new ClientInputStreamPoolingThread(session, ss.getStdout(), channel, new AutoCloseable() {
+                                @Override
+                                public void close() throws Exception {
+                                    logger.trace("Closing both ganymed and local connection");
+                                    try {
+                                        ganymedConnection.close();
+                                    } catch (Exception e) {
+                                        logger.warn("Ignoring exception while closing ganymed", e);
+                                    }
+                                    try {
+                                        channel.close();
+                                    } catch (Exception e) {
+                                        logger.warn("Ignoring exception while closing channel", e);
+                                    }
+                                }
+                            }, sshClientHandler.getChannelHandlerContext()).start();
+
+                            // write additional header
+                            channel.writeAndFlush(Unpooled.copiedBuffer(additionalHeader.getBytes()));
+                        } else {
+                            logger.debug("{} Wrong subsystem requested:'{}', closing ssh session", serverSession, subsystem);
+                            String reason = "Only netconf subsystem is supported, requested:" + subsystem;
+                            closeSession(ss, reason);
+                        }
+                    }
+                };
+            }
+
+            public void closeSession(ServerSession ss, String reason) {
+                logger.trace("{} Closing session - {}", serverSession, reason);
+                try {
+                    ss.getStdin().write(reason.getBytes());
+                } catch (IOException e) {
+                    logger.warn("{} Exception while closing session", serverSession, e);
+                }
+                ss.close();
+            }
+
+            @Override
+            public Runnable requestPtyReq(final ServerSession ss, final PtySettings pty) throws IOException {
+                return new Runnable() {
+                    @Override
+                    public void run() {
+                        closeSession(ss, "PTY request not supported");
+                    }
+                };
+            }
+
+            @Override
+            public Runnable requestShell(final ServerSession ss) throws IOException {
+                return new Runnable() {
+                    @Override
+                    public void run() {
+                        closeSession(ss, "Shell not supported");
+                    }
+                };
+            }
+        };
+    }
+}
+
+/**
+ * Only thread that is required during ssh session, forwards client's input to netty.
+ * When user closes connection, onEndOfInput.close() is called to tear down the local channel.
+ */
+class ClientInputStreamPoolingThread extends Thread {
+    private static final Logger logger = LoggerFactory.getLogger(ClientInputStreamPoolingThread.class);
+
+    private final InputStream fromClientIS;
+    private final Channel serverChannel;
+    private final AutoCloseable onEndOfInput;
+    private final ChannelHandlerContext channelHandlerContext;
+
+    ClientInputStreamPoolingThread(String session, InputStream fromClientIS, Channel serverChannel, AutoCloseable onEndOfInput,
+                                   ChannelHandlerContext channelHandlerContext) {
+        super(ClientInputStreamPoolingThread.class.getSimpleName() + " " + session);
+        this.fromClientIS = fromClientIS;
+        this.serverChannel = serverChannel;
+        this.onEndOfInput = onEndOfInput;
+        this.channelHandlerContext = channelHandlerContext;
+    }
+
+    @Override
+    public void run() {
+        ChunkedStream chunkedStream = new ChunkedStream(fromClientIS);
+        try {
+            ByteBuf byteBuf;
+            while ((byteBuf = chunkedStream.readChunk(channelHandlerContext/*only needed for ByteBuf alloc */)) != null) {
+                serverChannel.writeAndFlush(byteBuf);
+            }
+        } catch (Exception e) {
+            logger.warn("Exception", e);
+        } finally {
+            logger.trace("End of input");
+            // tear down connection
+            try {
+                onEndOfInput.close();
+            } catch (Exception e) {
+                logger.warn("Ignoring exception while closing socket", e);
+            }
+        }
+    }
+}
+
+/**
+ * Authentication handler for ganymed.
+ * Provides current user name after authenticating using supplied AuthProvider.
+ */
+@NotThreadSafe
+class ServerAuthenticationCallbackImpl implements ServerAuthenticationCallback, Supplier<String> {
+    private static final Logger logger = LoggerFactory.getLogger(ServerAuthenticationCallbackImpl.class);
+    private final AuthProvider authProvider;
+    private final String session;
+    private String currentUser;
+
+    ServerAuthenticationCallbackImpl(AuthProvider authProvider, String session) {
+        this.authProvider = authProvider;
+        this.session = session;
+    }
+
+    @Override
+    public String initAuthentication(ServerConnection sc) {
+        logger.trace("{} Established connection", session);
+        return "Established connection" + "\r\n";
+    }
+
+    @Override
+    public String[] getRemainingAuthMethods(ServerConnection sc) {
+        return new String[]{ServerAuthenticationCallback.METHOD_PASSWORD};
+    }
+
+    @Override
+    public AuthenticationResult authenticateWithNone(ServerConnection sc, String username) {
+        return AuthenticationResult.FAILURE;
+    }
+
+    @Override
+    public AuthenticationResult authenticateWithPassword(ServerConnection sc, String username, String password) {
+        checkState(currentUser == null);
+        try {
+            if (authProvider.authenticated(username, password)) {
+                currentUser = username;
+                logger.trace("{} user {} authenticated", session, currentUser);
+                return AuthenticationResult.SUCCESS;
+            }
+        } catch (Exception e) {
+            logger.warn("{} Authentication failed", session, e);
+        }
+        return AuthenticationResult.FAILURE;
+    }
+
+    @Override
+    public AuthenticationResult authenticateWithPublicKey(ServerConnection sc, String username, String algorithm,
+                                                          byte[] publicKey, byte[] signature) {
+        return AuthenticationResult.FAILURE;
+    }
+
+    @Override
+    public String get() {
+        return currentUser;
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/IOThread.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/IOThread.java
deleted file mode 100644 (file)
index c53a625..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.controller.netconf.ssh.threads;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import javax.annotation.concurrent.ThreadSafe;
-
-import org.apache.commons.io.IOUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ch.ethz.ssh2.ServerConnection;
-import ch.ethz.ssh2.ServerSession;
-
-@ThreadSafe
-public class IOThread extends Thread {
-
-    private static final Logger logger =  LoggerFactory.getLogger(IOThread.class);
-
-    private final InputStream inputStream;
-    private final OutputStream outputStream;
-    private final ServerSession servSession;
-    private final ServerConnection servconnection;
-    private String customHeader;
-
-
-    public IOThread (InputStream is, OutputStream os, String id,ServerSession ss, ServerConnection conn){
-        this.inputStream = is;
-        this.outputStream = os;
-        this.servSession = ss;
-        this.servconnection = conn;
-        super.setName(id);
-        logger.trace("IOThread {} created", super.getName());
-    }
-
-    public IOThread (InputStream is, OutputStream os, String id,ServerSession ss, ServerConnection conn,String header){
-        this.inputStream = is;
-        this.outputStream = os;
-        this.servSession = ss;
-        this.servconnection = conn;
-        this.customHeader = header;
-        super.setName(id);
-        logger.trace("IOThread {} created", super.getName());
-    }
-
-    @Override
-    public void run() {
-        logger.trace("thread {} started", super.getName());
-        try {
-            if (this.customHeader!=null && !this.customHeader.equals("")){
-                this.outputStream.write(this.customHeader.getBytes());
-                logger.trace("adding {} header", this.customHeader);
-            }
-            IOUtils.copy(this.inputStream, this.outputStream);
-        } catch (Exception e) {
-            logger.error("inputstream -> outputstream copy error ",e);
-        }
-        logger.trace("closing server session");
-        servSession.close();
-        servconnection.close();
-        logger.trace("thread {} is closing",super.getName());
-    }
-}
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java
deleted file mode 100644 (file)
index 04639cb..0000000
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.controller.netconf.ssh.threads;
-
-
-import ch.ethz.ssh2.AuthenticationResult;
-import ch.ethz.ssh2.PtySettings;
-import ch.ethz.ssh2.ServerAuthenticationCallback;
-import ch.ethz.ssh2.ServerConnection;
-import ch.ethz.ssh2.ServerConnectionCallback;
-import ch.ethz.ssh2.ServerSession;
-import ch.ethz.ssh2.ServerSessionCallback;
-import ch.ethz.ssh2.SimpleServerSessionCallback;
-import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.concurrent.ThreadSafe;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-
-@ThreadSafe
-public class SocketThread implements Runnable, ServerAuthenticationCallback, ServerConnectionCallback {
-    private static final Logger logger = LoggerFactory.getLogger(SocketThread.class);
-
-    private final Socket socket;
-    private final InetSocketAddress clientAddress;
-    private ServerConnection conn = null;
-    private final long sessionId;
-    private String currentUser;
-    private final String remoteAddressWithPort;
-    private final AuthProvider authProvider;
-
-
-    public static void start(Socket socket,
-                             InetSocketAddress clientAddress,
-                             long sessionId,
-                             AuthProvider authProvider) throws IOException {
-        Thread netconf_ssh_socket_thread = new Thread(new SocketThread(socket, clientAddress, sessionId, authProvider));
-        netconf_ssh_socket_thread.setDaemon(true);
-        netconf_ssh_socket_thread.start();
-    }
-
-    private SocketThread(Socket socket,
-                         InetSocketAddress clientAddress,
-                         long sessionId,
-                         AuthProvider authProvider) throws IOException {
-
-        this.socket = socket;
-        this.clientAddress = clientAddress;
-        this.sessionId = sessionId;
-        this.remoteAddressWithPort = socket.getRemoteSocketAddress().toString().replaceFirst("/", "");
-        this.authProvider = authProvider;
-
-    }
-
-    @Override
-    public void run() {
-        conn = new ServerConnection(socket);
-        try {
-            conn.setPEMHostKey(authProvider.getPEMAsCharArray(), "netconf");
-        } catch (Exception e) {
-            logger.warn("Server authentication setup failed.", e);
-        }
-        conn.setAuthenticationCallback(this);
-        conn.setServerConnectionCallback(this);
-        try {
-            conn.connect();
-        } catch (IOException e) {
-            logger.error("SocketThread error ", e);
-        }
-    }
-
-    @Override
-    public ServerSessionCallback acceptSession(final ServerSession session) {
-        SimpleServerSessionCallback cb = new SimpleServerSessionCallback() {
-            @Override
-            public Runnable requestSubsystem(final ServerSession ss, final String subsystem) throws IOException {
-                return new Runnable() {
-                    @Override
-                    public void run() {
-                        if (subsystem.equals("netconf")) {
-                            IOThread netconf_ssh_input = null;
-                            IOThread netconf_ssh_output = null;
-                            try {
-                                String hostName = clientAddress.getHostName();
-                                int portNumber = clientAddress.getPort();
-                                final Socket echoSocket = new Socket(hostName, portNumber);
-                                logger.trace("echo socket created");
-
-                                logger.trace("starting netconf_ssh_input thread");
-                                netconf_ssh_input = new IOThread(echoSocket.getInputStream(), ss.getStdin(), "input_thread_" + sessionId, ss, conn);
-                                netconf_ssh_input.setDaemon(false);
-                                netconf_ssh_input.start();
-
-                                logger.trace("starting netconf_ssh_output thread");
-                                final String customHeader = "[" + currentUser + ";" + remoteAddressWithPort + ";ssh;;;;;;]\n";
-                                netconf_ssh_output = new IOThread(ss.getStdout(), echoSocket.getOutputStream(), "output_thread_" + sessionId, ss, conn, customHeader);
-                                netconf_ssh_output.setDaemon(false);
-                                netconf_ssh_output.start();
-
-                            } catch (Exception t) {
-                                logger.error("SSH bridge could not create echo socket: {}", t.getMessage(), t);
-
-                                try {
-                                    if (netconf_ssh_input != null) {
-                                        netconf_ssh_input.join();
-                                    }
-                                } catch (InterruptedException e1) {
-                                    Thread.currentThread().interrupt();
-                                    logger.error("netconf_ssh_input join error ", e1);
-                                }
-
-                                try {
-                                    if (netconf_ssh_output != null) {
-                                        netconf_ssh_output.join();
-                                    }
-                                } catch (InterruptedException e2) {
-                                    Thread.currentThread().interrupt();
-                                    logger.error("netconf_ssh_output join error ", e2);
-                                }
-                            }
-                        } else {
-                            String reason = "Only netconf subsystem is supported, requested:" + subsystem;
-                            closeSession(ss, reason);
-                        }
-                    }
-                };
-            }
-
-            public void closeSession(ServerSession ss, String reason) {
-                logger.trace("Closing session - {}", reason);
-                try {
-                    ss.getStdin().write(reason.getBytes());
-                } catch (IOException e) {
-                    logger.debug("Exception while closing session", e);
-                }
-                ss.close();
-            }
-
-            @Override
-            public Runnable requestPtyReq(final ServerSession ss, final PtySettings pty) throws IOException {
-                return new Runnable() {
-                    @Override
-                    public void run() {
-                        closeSession(ss, "PTY request not supported");
-                    }
-                };
-            }
-
-            @Override
-            public Runnable requestShell(final ServerSession ss) throws IOException {
-                return new Runnable() {
-                    @Override
-                    public void run() {
-                        closeSession(ss, "Shell not supported");
-                    }
-                };
-            }
-        };
-
-        return cb;
-    }
-
-    @Override
-    public String initAuthentication(ServerConnection sc) {
-        logger.trace("Established connection with host {}", remoteAddressWithPort);
-        return "Established connection with host " + remoteAddressWithPort + "\r\n";
-    }
-
-    @Override
-    public String[] getRemainingAuthMethods(ServerConnection sc) {
-        return new String[]{ServerAuthenticationCallback.METHOD_PASSWORD};
-    }
-
-    @Override
-    public AuthenticationResult authenticateWithNone(ServerConnection sc, String username) {
-        return AuthenticationResult.FAILURE;
-    }
-
-    @Override
-    public AuthenticationResult authenticateWithPassword(ServerConnection sc, String username, String password) {
-
-        try {
-            if (authProvider.authenticated(username, password)) {
-                currentUser = username;
-                logger.trace("user {}@{} authenticated", currentUser, remoteAddressWithPort);
-                return AuthenticationResult.SUCCESS;
-            }
-        } catch (Exception e) {
-            logger.warn("Authentication failed due to :" + e.getLocalizedMessage());
-        }
-        return AuthenticationResult.FAILURE;
-    }
-
-    @Override
-    public AuthenticationResult authenticateWithPublicKey(ServerConnection sc, String username, String algorithm,
-                                                          byte[] publickey, byte[] signature) {
-        return AuthenticationResult.FAILURE;
-    }
-
-}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/KeyGeneratorTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/KeyGeneratorTest.java
deleted file mode 100644 (file)
index 298f91c..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-
-package org.opendaylight.controller.netconf;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
-import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
-import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
-import org.opendaylight.controller.usermanager.IUserManager;
-import org.opendaylight.controller.usermanager.UserConfig;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.InetSocketAddress;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doReturn;
-
-// This test is intended to be verified using ssh
-@Ignore
-public class KeyGeneratorTest {
-
-    @Mock
-    private IUserManager iUserManager;
-    File tempFile;
-
-    @Before
-    public void setUp() throws IOException {
-        MockitoAnnotations.initMocks(this);
-        doReturn(null).when(iUserManager).addLocalUser(any(UserConfig.class));
-        tempFile = File.createTempFile("odltest", ".tmp");
-        tempFile.deleteOnExit();
-    }
-
-    @After
-    public void tearDown() {
-        assertTrue(tempFile.delete());
-    }
-
-    @Test
-    public void test() throws Exception {
-        String pem = PEMGenerator.generateTo(tempFile);
-
-        AuthProvider authProvider = new AuthProvider(iUserManager, pem);
-        InetSocketAddress inetSocketAddress = new InetSocketAddress(Inet4Address.getLoopbackAddress().getHostAddress(), 8383);
-        NetconfSSHServer server = NetconfSSHServer.start(1830, inetSocketAddress, authProvider);
-
-        Thread serverThread = new  Thread(server,"netconf SSH server thread");
-        serverThread.start();
-        serverThread.join();
-    }
-}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/SSHServerTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/SSHServerTest.java
deleted file mode 100644 (file)
index 663a0b4..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.controller.netconf;
-
-import ch.ethz.ssh2.Connection;
-import junit.framework.Assert;
-import org.apache.commons.io.IOUtils;
-import org.junit.Test;
-import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
-import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-
-
-public class SSHServerTest {
-
-    private static final String USER = "netconf";
-    private static final String PASSWORD  = "netconf";
-    private static final String HOST = "127.0.0.1";
-    private static final int PORT = 1830;
-    private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 8383);
-    private static final Logger logger =  LoggerFactory.getLogger(SSHServerTest.class);
-    private Thread sshServerThread;
-
-
-
-
-    public void startSSHServer() throws Exception{
-        logger.info("Creating SSH server");
-        StubUserManager um = new StubUserManager(USER,PASSWORD);
-        String pem;
-        try(InputStream is = getClass().getResourceAsStream("/RSA.pk")) {
-            pem = IOUtils.toString(is);
-        }
-        AuthProvider ap = new AuthProvider(um, pem);
-        NetconfSSHServer server = NetconfSSHServer.start(PORT,tcpAddress,ap);
-        sshServerThread = new Thread(server);
-        sshServerThread.setDaemon(true);
-        sshServerThread.start();
-        logger.info("SSH server on");
-    }
-
-    @Test
-    public void connect(){
-        try {
-            this.startSSHServer();
-            Connection conn = new Connection(HOST,PORT);
-            Assert.assertNotNull(conn);
-            logger.info("connecting to SSH server");
-            conn.connect();
-            logger.info("authenticating ...");
-            boolean isAuthenticated = conn.authenticateWithPassword(USER,PASSWORD);
-            Assert.assertTrue(isAuthenticated);
-        } catch (Exception e) {
-            logger.error("Error while starting SSH server.", e);
-        }
-
-    }
-
-}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClient.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClient.java
new file mode 100644 (file)
index 0000000..5d0c71a
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Sends one message when a connection is open and echoes back any received
+ * data to the server.  Simply put, the echo client initiates the ping-pong
+ * traffic between the echo client and server by sending the first message to
+ * the server.
+ */
+public class EchoClient implements Runnable {
+    private static final Logger logger = LoggerFactory.getLogger(EchoClient.class);
+
+    private final ChannelHandler clientHandler;
+
+
+    public EchoClient(ChannelHandler clientHandler) {
+        this.clientHandler = clientHandler;
+    }
+
+    public void run() {
+        // Configure the client.
+        EventLoopGroup group = new NioEventLoopGroup();
+        try {
+            Bootstrap b = new Bootstrap();
+            b.group(group)
+                    .channel(LocalChannel.class)
+                    .handler(new ChannelInitializer<LocalChannel>() {
+                        @Override
+                        public void initChannel(LocalChannel ch) throws Exception {
+                            ch.pipeline().addLast(clientHandler);
+                        }
+                    });
+
+            // Start the client.
+            LocalAddress localAddress = new LocalAddress("foo");
+            ChannelFuture f = b.connect(localAddress).sync();
+
+            // Wait until the connection is closed.
+            f.channel().closeFuture().sync();
+        } catch (Exception e) {
+            logger.error("Error in client", e);
+            throw new RuntimeException("Error in client", e);
+        } finally {
+            // Shut down the event loop to terminate all threads.
+            logger.info("Client is shutting down");
+            group.shutdownGracefully();
+        }
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClientHandler.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoClientHandler.java
new file mode 100644 (file)
index 0000000..81182a5
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.netty;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.Charsets;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handler implementation for the echo client.  It initiates the ping-pong
+ * traffic between the echo client and server by sending the first message to
+ * the server.
+ */
+public class EchoClientHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(EchoClientHandler.class);
+
+    private ChannelHandlerContext ctx;
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        checkState(this.ctx == null);
+        logger.info("client active");
+        this.ctx = ctx;
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        ByteBuf bb = (ByteBuf) msg;
+        logger.info(">{}", bb.toString(Charsets.UTF_8));
+        bb.release();
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        logger.warn("Unexpected exception from downstream.", cause);
+        checkState(this.ctx.equals(ctx));
+        ctx.close();
+        this.ctx = null;
+    }
+
+    public void write(String message) {
+        ByteBuf byteBuf = Unpooled.copiedBuffer(message.getBytes());
+        ctx.writeAndFlush(byteBuf);
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServer.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServer.java
new file mode 100644 (file)
index 0000000..ec89d75
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.channel.local.LocalServerChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Echoes back any received data from a client.
+ */
+public class EchoServer implements Runnable {
+    private static final Logger logger = LoggerFactory.getLogger(EchoServer.class);
+
+    public void run() {
+        // Configure the server.
+        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+        EventLoopGroup workerGroup = new NioEventLoopGroup();
+        try {
+            ServerBootstrap b = new ServerBootstrap();
+            b.group(bossGroup, workerGroup)
+                    .channel(LocalServerChannel.class)
+                    .option(ChannelOption.SO_BACKLOG, 100)
+                    .handler(new LoggingHandler(LogLevel.INFO))
+                    .childHandler(new ChannelInitializer<LocalChannel>() {
+                        @Override
+                        public void initChannel(LocalChannel ch) throws Exception {
+                            ch.pipeline().addLast(new EchoServerHandler());
+                        }
+                    });
+
+            // Start the server.
+            LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress();
+            ChannelFuture f = b.bind(localAddress).sync();
+
+            // Wait until the server socket is closed.
+            f.channel().closeFuture().sync();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            // Shut down all event loops to terminate all threads.
+            bossGroup.shutdownGracefully();
+            workerGroup.shutdownGracefully();
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        new Thread(new EchoServer()).start();
+        Thread.sleep(1000);
+        EchoClientHandler clientHandler = new EchoClientHandler();
+        EchoClient echoClient = new EchoClient(clientHandler);
+        new Thread(echoClient).start();
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+        do {
+            String message = reader.readLine();
+            if (message == null ||  "exit".equalsIgnoreCase(message)) {
+                break;
+            }
+            logger.debug("Got '{}'", message);
+            clientHandler.write(message);
+        } while (true);
+        System.exit(0);
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServerHandler.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/EchoServerHandler.java
new file mode 100644 (file)
index 0000000..1286ec6
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.netty;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handler implementation for the echo server.
+ */
+@Sharable
+public class EchoServerHandler extends ChannelInboundHandlerAdapter {
+
+    private static final Logger logger = LoggerFactory.getLogger(EchoServerHandler.class.getName());
+    private String fromLastNewLine = "";
+    private final Splitter splitter = Splitter.onPattern("\r?\n");
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        logger.debug("sleep start");
+        Thread.sleep(1000);
+        logger.debug("sleep done");
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        ByteBuf byteBuf = (ByteBuf) msg;
+        String message = byteBuf.toString(Charsets.UTF_8);
+        logger.info("writing back '{}'", message);
+        ctx.write(msg);
+        fromLastNewLine += message;
+        for (String line : splitter.split(fromLastNewLine)) {
+            if ("quit".equals(line)) {
+                logger.info("closing server ctx");
+                ctx.flush();
+                ctx.close();
+                break;
+            }
+            fromLastNewLine = line; // last line should be preserved
+        }
+
+        // do not release byteBuf as it is handled back
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+        logger.debug("flushing");
+        ctx.flush();
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServer.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServer.java
new file mode 100644 (file)
index 0000000..8f2c502
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import java.net.InetSocketAddress;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
+
+public class ProxyServer implements Runnable {
+    private final ProxyHandlerFactory proxyHandlerFactory;
+
+    public ProxyServer(ProxyHandlerFactory proxyHandlerFactory) {
+        this.proxyHandlerFactory = proxyHandlerFactory;
+    }
+
+    public void run() {
+        // Configure the server.
+        final EventLoopGroup bossGroup = new NioEventLoopGroup();
+        EventLoopGroup workerGroup = new NioEventLoopGroup();
+        try {
+            final LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress();
+            ServerBootstrap serverBootstrap = new ServerBootstrap();
+            serverBootstrap.group(bossGroup, workerGroup)
+                    .channel(NioServerSocketChannel.class)
+                    .option(ChannelOption.SO_BACKLOG, 100)
+                    .handler(new LoggingHandler(LogLevel.INFO))
+                    .childHandler(new ChannelInitializer<SocketChannel>() {
+                        @Override
+                        public void initChannel(SocketChannel ch) throws Exception {
+                            ch.pipeline().addLast(proxyHandlerFactory.create(bossGroup, localAddress));
+                        }
+                    });
+
+            // Start the server.
+            InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8080);
+            ChannelFuture f = serverBootstrap.bind(address).sync();
+
+            // Wait until the server socket is closed.
+            f.channel().closeFuture().sync();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            // Shut down all event loops to terminate all threads.
+            bossGroup.shutdownGracefully();
+            workerGroup.shutdownGracefully();
+        }
+    }
+    public static interface ProxyHandlerFactory {
+        ChannelHandler create(EventLoopGroup bossGroup, LocalAddress localAddress);
+    }
+
+    public static void main(String[] args) {
+        ProxyHandlerFactory proxyHandlerFactory = new ProxyHandlerFactory() {
+            @Override
+            public ChannelHandler create(EventLoopGroup bossGroup, LocalAddress localAddress) {
+                return new ProxyServerHandler(bossGroup, localAddress);
+            }
+        };
+        start(proxyHandlerFactory);
+    }
+
+    public static void start(ProxyHandlerFactory proxyHandlerFactory) {
+        new Thread(new EchoServer()).start();
+        new Thread(new ProxyServer(proxyHandlerFactory)).start();
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServerHandler.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/ProxyServerHandler.java
new file mode 100644 (file)
index 0000000..ecab212
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.netty;
+
+import com.google.common.base.Charsets;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ProxyServerHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(ProxyServerHandler.class.getName());
+    private final Bootstrap clientBootstrap;
+    private final LocalAddress localAddress;
+
+
+    private Channel clientChannel;
+
+    public ProxyServerHandler(EventLoopGroup bossGroup, LocalAddress localAddress) {
+        clientBootstrap = new Bootstrap();
+        clientBootstrap.group(bossGroup).channel(LocalChannel.class);
+        this.localAddress = localAddress;
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext remoteCtx) {
+        final ProxyClientHandler clientHandler = new ProxyClientHandler(remoteCtx);
+        clientBootstrap.handler(new ChannelInitializer<LocalChannel>() {
+            @Override
+            public void initChannel(LocalChannel ch) throws Exception {
+                ch.pipeline().addLast(clientHandler);
+            }
+        });
+        ChannelFuture clientChannelFuture = clientBootstrap.connect(localAddress).awaitUninterruptibly();
+        clientChannel = clientChannelFuture.channel();
+        clientChannel.writeAndFlush(Unpooled.copiedBuffer("connected\n".getBytes()));
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) {
+        logger.info("channelInactive - closing client connection");
+        clientChannel.close();
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, final Object msg) {
+        logger.debug("Writing to client {}", msg);
+        clientChannel.write(msg);
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        logger.debug("flushing");
+        clientChannel.flush();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        logger.warn("Unexpected exception from downstream.", cause);
+        ctx.close();
+    }
+}
+
+class ProxyClientHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(ProxyClientHandler.class);
+
+    private final ChannelHandlerContext remoteCtx;
+
+
+    public ProxyClientHandler(ChannelHandlerContext remoteCtx) {
+        this.remoteCtx = remoteCtx;
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        logger.info("client active");
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) {
+        ByteBuf bb = (ByteBuf) msg;
+        logger.info(">{}", bb.toString(Charsets.UTF_8));
+        remoteCtx.write(msg);
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        logger.debug("Flushing server ctx");
+        remoteCtx.flush();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        logger.warn("Unexpected exception from downstream", cause);
+        ctx.close();
+    }
+
+    // called both when local or remote connection dies
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) {
+        logger.debug("channelInactive() called, closing remote client ctx");
+        remoteCtx.close();
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java
new file mode 100644 (file)
index 0000000..4e32e82
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.netty;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import io.netty.channel.nio.NioEventLoopGroup;
+import org.junit.Test;
+import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
+import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
+import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SSHTest {
+    public static final Logger logger = LoggerFactory.getLogger(SSHTest.class);
+
+    @Test
+    public void test() throws Exception {
+        new Thread(new EchoServer(), "EchoServer").start();
+        AuthProvider authProvider = mock(AuthProvider.class);
+        doReturn(PEMGenerator.generate().toCharArray()).when(authProvider).getPEMAsCharArray();
+        doReturn(true).when(authProvider).authenticated(anyString(), anyString());
+        NetconfSSHServer thread = NetconfSSHServer.start(1831, NetconfConfigUtil.getNetconfLocalAddress(), authProvider, new NioEventLoopGroup());
+        Thread.sleep(2000);
+        logger.info("Closing socket");
+        thread.close();
+        thread.join();
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/authentication/SSHServerTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/authentication/SSHServerTest.java
new file mode 100644 (file)
index 0000000..5e368bc
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.netconf.ssh.authentication;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+
+import ch.ethz.ssh2.Connection;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import junit.framework.Assert;
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.netconf.StubUserManager;
+import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class SSHServerTest {
+
+    private static final String USER = "netconf";
+    private static final String PASSWORD = "netconf";
+    private static final String HOST = "127.0.0.1";
+    private static final int PORT = 1830;
+    private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 8383);
+    private static final Logger logger = LoggerFactory.getLogger(SSHServerTest.class);
+    private Thread sshServerThread;
+
+    @Mock
+    private BundleContext mockedContext;
+
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doReturn(null).when(mockedContext).createFilter(anyString());
+        doNothing().when(mockedContext).addServiceListener(any(ServiceListener.class), anyString());
+        doReturn(new ServiceReference[0]).when(mockedContext).getServiceReferences(anyString(), anyString());
+
+        logger.info("Creating SSH server");
+        StubUserManager um = new StubUserManager(USER, PASSWORD);
+        String pem;
+        try (InputStream is = getClass().getResourceAsStream("/RSA.pk")) {
+            pem = IOUtils.toString(is);
+        }
+        AuthProvider ap = new AuthProvider(pem, mockedContext);
+        ap.setNullableUserManager(um);
+        EventLoopGroup bossGroup = new NioEventLoopGroup();
+        NetconfSSHServer server = NetconfSSHServer.start(PORT, NetconfConfigUtil.getNetconfLocalAddress(),
+                ap, bossGroup);
+
+        sshServerThread = new Thread(server);
+        sshServerThread.setDaemon(true);
+        sshServerThread.start();
+        logger.info("SSH server on " + PORT);
+    }
+
+    @Test
+    public void connect() {
+        try {
+            Connection conn = new Connection(HOST, PORT);
+            Assert.assertNotNull(conn);
+            logger.info("connecting to SSH server");
+            conn.connect();
+            logger.info("authenticating ...");
+            boolean isAuthenticated = conn.authenticateWithPassword(USER, PASSWORD);
+            Assert.assertTrue(isAuthenticated);
+        } catch (Exception e) {
+            logger.error("Error while starting SSH server.", e);
+        }
+
+    }
+
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/test/resources/logback-test.xml b/opendaylight/netconf/netconf-ssh/src/test/resources/logback-test.xml
new file mode 100644 (file)
index 0000000..324c234
--- /dev/null
@@ -0,0 +1,13 @@
+<configuration>
+
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%date{"yyyy-MM-dd HH:mm:ss.SSS z"} [%thread] %-5level %logger{36} - %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="error">
+    <appender-ref ref="STDOUT" />
+  </root>
+  <logger name="org.opendaylight.controller.netconf" level="TRACE"/>
+</configuration>
diff --git a/opendaylight/netconf/netconf-tcp/pom.xml b/opendaylight/netconf/netconf-tcp/pom.xml
new file mode 100644 (file)
index 0000000..65da6e9
--- /dev/null
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+  ~
+  ~ This program and the accompanying materials are made available under the
+  ~ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+  ~ and is available at http://www.eclipse.org/legal/epl-v10.html
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>netconf-subsystem</artifactId>
+    <version>0.2.5-SNAPSHOT</version>
+    <relativePath>../</relativePath>
+  </parent>
+  <artifactId>netconf-tcp</artifactId>
+  <packaging>bundle</packaging>
+  <name>${project.artifactId}</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>netconf-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>netconf-util</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>mockito-configuration</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.7</version>
+        <configuration>
+          <instructions>
+            <Bundle-Activator>org.opendaylight.controller.netconf.tcp.osgi.NetconfTCPActivator</Bundle-Activator>
+            <Import-Package>com.google.common.base, io.netty.bootstrap, io.netty.channel, io.netty.channel.local,
+              io.netty.channel.nio, io.netty.channel.socket, io.netty.channel.socket.nio, io.netty.handler.logging,
+              io.netty.util.concurrent, org.opendaylight.controller.netconf.util.osgi, org.osgi.framework, org.slf4j</Import-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServer.java b/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServer.java
new file mode 100644 (file)
index 0000000..2e0022c
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.tcp.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import java.net.InetSocketAddress;
+
+public class ProxyServer implements AutoCloseable {
+    private final EventLoopGroup bossGroup = new NioEventLoopGroup();
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
+    private final ChannelFuture channelFuture;
+
+    public ProxyServer(InetSocketAddress address, final LocalAddress localAddress) {
+        // Configure the server.
+        final Bootstrap clientBootstrap = new Bootstrap();
+        clientBootstrap.group(bossGroup).channel(LocalChannel.class);
+
+        ServerBootstrap serverBootstrap = new ServerBootstrap();
+        serverBootstrap.group(bossGroup, workerGroup)
+                .channel(NioServerSocketChannel.class)
+                .handler(new LoggingHandler(LogLevel.DEBUG))
+                .childHandler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    public void initChannel(SocketChannel ch) throws Exception {
+                        ch.pipeline().addLast(new ProxyServerHandler(clientBootstrap, localAddress));
+                    }
+                });
+
+        // Start the server.
+        channelFuture = serverBootstrap.bind(address).syncUninterruptibly();
+    }
+
+    @Override
+    public void close() {
+        channelFuture.channel().close();
+        bossGroup.shutdownGracefully();
+        workerGroup.shutdownGracefully();
+    }
+}
diff --git a/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServerHandler.java b/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/netty/ProxyServerHandler.java
new file mode 100644 (file)
index 0000000..fa88928
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.tcp.netty;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ProxyServerHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(ProxyServerHandler.class.getName());
+    private final Bootstrap clientBootstrap;
+    private final LocalAddress localAddress;
+
+    private Channel clientChannel;
+
+    public ProxyServerHandler(Bootstrap clientBootstrap, LocalAddress localAddress) {
+        this.clientBootstrap = clientBootstrap;
+        this.localAddress = localAddress;
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext remoteCtx) {
+        final ProxyClientHandler clientHandler = new ProxyClientHandler(remoteCtx);
+        clientBootstrap.handler(new ChannelInitializer<LocalChannel>() {
+            @Override
+            public void initChannel(LocalChannel ch) throws Exception {
+                ch.pipeline().addLast(clientHandler);
+            }
+        });
+        ChannelFuture clientChannelFuture = clientBootstrap.connect(localAddress).awaitUninterruptibly();
+        clientChannel = clientChannelFuture.channel();
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) {
+        logger.trace("channelInactive - closing client channel");
+        clientChannel.close();
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, final Object msg) {
+        logger.trace("Writing to client channel");
+        clientChannel.write(msg);
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        logger.trace("Flushing client channel");
+        clientChannel.flush();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        logger.warn("Unexpected exception from downstream.", cause);
+        ctx.close();
+    }
+}
+
+class ProxyClientHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(ProxyClientHandler.class);
+
+    private final ChannelHandlerContext remoteCtx;
+    private ChannelHandlerContext localCtx;
+
+    public ProxyClientHandler(ChannelHandlerContext remoteCtx) {
+        this.remoteCtx = remoteCtx;
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        checkState(this.localCtx == null);
+        logger.trace("Client channel active");
+        this.localCtx = ctx;
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) {
+        logger.trace("Forwarding message");
+        remoteCtx.write(msg);
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        logger.trace("Flushing remote ctx");
+        remoteCtx.flush();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        logger.warn("Unexpected exception from downstream", cause);
+        checkState(this.localCtx.equals(ctx));
+        ctx.close();
+    }
+
+    // called both when local or remote connection dies
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) {
+        logger.trace("channelInactive() called, closing remote client ctx");
+        remoteCtx.close();
+    }
+
+}
diff --git a/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/osgi/NetconfTCPActivator.java b/opendaylight/netconf/netconf-tcp/src/main/java/org/opendaylight/controller/netconf/tcp/osgi/NetconfTCPActivator.java
new file mode 100644 (file)
index 0000000..bc94e59
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.tcp.osgi;
+
+import com.google.common.base.Optional;
+import java.net.InetSocketAddress;
+import org.opendaylight.controller.netconf.tcp.netty.ProxyServer;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil.InfixProp;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Opens TCP port specified in config.ini, creates bridge between this port and local netconf server.
+ */
+public class NetconfTCPActivator implements BundleActivator {
+    private static final Logger logger = LoggerFactory.getLogger(NetconfTCPActivator.class);
+    private ProxyServer proxyServer;
+
+    @Override
+    public void start(BundleContext context) {
+        final Optional<InetSocketAddress> maybeAddress = NetconfConfigUtil.extractNetconfServerAddress(context, InfixProp.tcp);
+        if (maybeAddress.isPresent() == false) {
+            logger.debug("Netconf tcp server is not configured to start");
+            return;
+        }
+        InetSocketAddress address = maybeAddress.get();
+        if (address.getAddress().isAnyLocalAddress()) {
+            logger.warn("Unprotected netconf TCP address is configured to ANY local address. This is a security risk. " +
+                    "Consider changing {} to 127.0.0.1", NetconfConfigUtil.getNetconfServerAddressKey(InfixProp.tcp));
+        }
+        logger.info("Starting TCP netconf server at {}", address);
+        proxyServer = new ProxyServer(address, NetconfConfigUtil.getNetconfLocalAddress());
+    }
+
+    @Override
+    public void stop(BundleContext context) {
+        if (proxyServer != null) {
+            proxyServer.close();
+        }
+    }
+}
index dcbdcabbba2683b260b003624c189561a134de2a..d9d957c663766ccb847cc57a3cde920b19d6cfd2 100644 (file)
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.7</version>
         <configuration>
           <instructions>
             <Import-Package>com.google.common.base, com.google.common.collect, io.netty.channel,
               io.netty.util.concurrent, javax.annotation, javax.xml.namespace, javax.xml.parsers, javax.xml.transform,
               javax.xml.transform.dom, javax.xml.transform.stream, javax.xml.validation, javax.xml.xpath,
               org.opendaylight.controller.netconf.api, org.opendaylight.controller.netconf.mapping.api,
         <configuration>
           <instructions>
             <Import-Package>com.google.common.base, com.google.common.collect, io.netty.channel,
               io.netty.util.concurrent, javax.annotation, javax.xml.namespace, javax.xml.parsers, javax.xml.transform,
               javax.xml.transform.dom, javax.xml.transform.stream, javax.xml.validation, javax.xml.xpath,
               org.opendaylight.controller.netconf.api, org.opendaylight.controller.netconf.mapping.api,
-              org.osgi.framework, org.slf4j, org.w3c.dom, org.xml.sax</Import-Package>
+              org.osgi.framework, org.slf4j, org.w3c.dom, org.xml.sax,io.netty.channel.local</Import-Package>
             <Export-Package>org.opendaylight.controller.netconf.util.*</Export-Package>
           </instructions>
         </configuration>
             <Export-Package>org.opendaylight.controller.netconf.util.*</Export-Package>
           </instructions>
         </configuration>
index 0993b8ad0c7038e5b86b0fee0e5cb4d30fd701f8..333fea3493172286fdba2c807eff105760741411 100644 (file)
@@ -9,36 +9,35 @@
 package org.opendaylight.controller.netconf.util.osgi;
 
 import com.google.common.base.Optional;
 package org.opendaylight.controller.netconf.util.osgi;
 
 import com.google.common.base.Optional;
+import io.netty.channel.local.LocalAddress;
+import java.net.InetSocketAddress;
 import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.net.InetSocketAddress;
-
 public final class NetconfConfigUtil {
     private static final Logger logger = LoggerFactory.getLogger(NetconfConfigUtil.class);
 
 public final class NetconfConfigUtil {
     private static final Logger logger = LoggerFactory.getLogger(NetconfConfigUtil.class);
 
-    public static final InetSocketAddress DEFAULT_NETCONF_TCP_ADDRESS
-            = new InetSocketAddress("127.0.0.1", 8383);
-    public static final InetSocketAddress DEFAULT_NETCONF_SSH_ADDRESS
-            = new InetSocketAddress("0.0.0.0", 1830);
-
     private static final String PREFIX_PROP = "netconf.";
 
     private NetconfConfigUtil() {
     }
 
     private static final String PREFIX_PROP = "netconf.";
 
     private NetconfConfigUtil() {
     }
 
-    private enum InfixProp {
+    public enum InfixProp {
         tcp, ssh
     }
 
     private static final String PORT_SUFFIX_PROP = ".port";
     private static final String ADDRESS_SUFFIX_PROP = ".address";
         tcp, ssh
     }
 
     private static final String PORT_SUFFIX_PROP = ".port";
     private static final String ADDRESS_SUFFIX_PROP = ".address";
-    private static final String CLIENT_PROP = ".client";
     private static final String PRIVATE_KEY_PATH_PROP = ".pk.path";
 
     private static final String CONNECTION_TIMEOUT_MILLIS_PROP = "connectionTimeoutMillis";
     private static final long DEFAULT_TIMEOUT_MILLIS = 5000;
     private static final String PRIVATE_KEY_PATH_PROP = ".pk.path";
 
     private static final String CONNECTION_TIMEOUT_MILLIS_PROP = "connectionTimeoutMillis";
     private static final long DEFAULT_TIMEOUT_MILLIS = 5000;
+    private static final LocalAddress netconfLocalAddress = new LocalAddress("netconf");
+
+    public static LocalAddress getNetconfLocalAddress() {
+        return netconfLocalAddress;
+    }
 
     public static long extractTimeoutMillis(final BundleContext bundleContext) {
         final String key = PREFIX_PROP + CONNECTION_TIMEOUT_MILLIS_PROP;
 
     public static long extractTimeoutMillis(final BundleContext bundleContext) {
         final String key = PREFIX_PROP + CONNECTION_TIMEOUT_MILLIS_PROP;
@@ -54,22 +53,6 @@ public final class NetconfConfigUtil {
         }
     }
 
         }
     }
 
-    public static InetSocketAddress extractTCPNetconfServerAddress(final BundleContext context, final InetSocketAddress defaultAddress) {
-        final Optional<InetSocketAddress> extracted = extractNetconfServerAddress(context, InfixProp.tcp);
-        final InetSocketAddress netconfTcpAddress = getNetconfAddress(defaultAddress, extracted, InfixProp.tcp);
-        logger.debug("Using {} as netconf tcp address", netconfTcpAddress);
-        if (netconfTcpAddress.getAddress().isAnyLocalAddress()) {
-            logger.warn("Unprotected netconf TCP address is configured to ANY local address. This is a security risk. " +
-                    "Consider changing {} to 127.0.0.1", PREFIX_PROP + InfixProp.tcp + ADDRESS_SUFFIX_PROP);
-        }
-        return netconfTcpAddress;
-    }
-
-    public static InetSocketAddress extractTCPNetconfClientAddress(final BundleContext context, final InetSocketAddress defaultAddress) {
-        final Optional<InetSocketAddress> extracted = extractNetconfClientAddress(context, InfixProp.tcp);
-        return getNetconfAddress(defaultAddress, extracted, InfixProp.tcp);
-    }
-
     /**
      * Get extracted address or default.
      *
     /**
      * Get extracted address or default.
      *
@@ -93,15 +76,12 @@ public final class NetconfConfigUtil {
         return inetSocketAddress;
     }
 
         return inetSocketAddress;
     }
 
-    public static InetSocketAddress extractSSHNetconfAddress(final BundleContext context, final InetSocketAddress defaultAddress) {
-        Optional<InetSocketAddress> extractedAddress = extractNetconfServerAddress(context, InfixProp.ssh);
-        InetSocketAddress netconfSSHAddress = getNetconfAddress(defaultAddress, extractedAddress, InfixProp.ssh);
-        logger.debug("Using {} as netconf SSH address", netconfSSHAddress);
-        return netconfSSHAddress;
+    public static String getPrivateKeyPath(final BundleContext context) {
+        return getPropertyValue(context, getPrivateKeyKey());
     }
 
     }
 
-    public static String getPrivateKeyPath(final BundleContext context) {
-        return getPropertyValue(context, PREFIX_PROP + InfixProp.ssh + PRIVATE_KEY_PATH_PROP);
+    public static String getPrivateKeyKey() {
+        return PREFIX_PROP + InfixProp.ssh + PRIVATE_KEY_PATH_PROP;
     }
 
     private static String getPropertyValue(final BundleContext context, final String propertyName) {
     }
 
     private static String getPropertyValue(final BundleContext context, final String propertyName) {
@@ -112,16 +92,20 @@ public final class NetconfConfigUtil {
         return propertyValue;
     }
 
         return propertyValue;
     }
 
+    public static String getNetconfServerAddressKey(InfixProp infixProp) {
+        return PREFIX_PROP + infixProp + ADDRESS_SUFFIX_PROP;
+    }
+
     /**
      * @param context   from which properties are being read.
      * @param infixProp either tcp or ssh
      * @return value if address and port are present and valid, Optional.absent otherwise.
      * @throws IllegalStateException if address or port are invalid, or configuration is missing
      */
     /**
      * @param context   from which properties are being read.
      * @param infixProp either tcp or ssh
      * @return value if address and port are present and valid, Optional.absent otherwise.
      * @throws IllegalStateException if address or port are invalid, or configuration is missing
      */
-    private static Optional<InetSocketAddress> extractNetconfServerAddress(final BundleContext context,
+    public static Optional<InetSocketAddress> extractNetconfServerAddress(final BundleContext context,
                                                                            final InfixProp infixProp) {
 
                                                                            final InfixProp infixProp) {
 
-        final Optional<String> address = getProperty(context, PREFIX_PROP + infixProp + ADDRESS_SUFFIX_PROP);
+        final Optional<String> address = getProperty(context, getNetconfServerAddressKey(infixProp));
         final Optional<String> port = getProperty(context, PREFIX_PROP + infixProp + PORT_SUFFIX_PROP);
 
         if (address.isPresent() && port.isPresent()) {
         final Optional<String> port = getProperty(context, PREFIX_PROP + infixProp + PORT_SUFFIX_PROP);
 
         if (address.isPresent() && port.isPresent()) {
@@ -140,24 +124,6 @@ public final class NetconfConfigUtil {
         return new InetSocketAddress(address.get(), portNumber);
     }
 
         return new InetSocketAddress(address.get(), portNumber);
     }
 
-    private static Optional<InetSocketAddress> extractNetconfClientAddress(final BundleContext context,
-                                                                           final InfixProp infixProp) {
-        final Optional<String> address = getProperty(context,
-                PREFIX_PROP + infixProp + CLIENT_PROP + ADDRESS_SUFFIX_PROP);
-        final Optional<String> port = getProperty(context,
-                PREFIX_PROP + infixProp + CLIENT_PROP + PORT_SUFFIX_PROP);
-
-        if (address.isPresent() && port.isPresent()) {
-            try {
-                return Optional.of(parseAddress(address, port));
-            } catch (final RuntimeException e) {
-                logger.warn("Unable to parse client {} netconf address from {}:{}, fallback to server address",
-                        infixProp, address, port, e);
-            }
-        }
-        return extractNetconfServerAddress(context, infixProp);
-    }
-
     private static Optional<String> getProperty(final BundleContext context, final String propKey) {
         String value = context.getProperty(propKey);
         if (value != null && value.isEmpty()) {
     private static Optional<String> getProperty(final BundleContext context, final String propKey) {
         String value = context.getProperty(propKey);
         if (value != null && value.isEmpty()) {
index 4f87fd8626b2d1cfd98925c0e3be5015808b41a3..e1a9cada19d4486386cfcb432238ce4d2a057551 100644 (file)
@@ -27,6 +27,7 @@
     <module>netconf-mapping-api</module>
     <module>netconf-client</module>
     <module>netconf-ssh</module>
     <module>netconf-mapping-api</module>
     <module>netconf-client</module>
     <module>netconf-ssh</module>
+    <module>netconf-tcp</module>
     <module>netconf-monitoring</module>
     <module>ietf-netconf-monitoring</module>
     <module>ietf-netconf-monitoring-extension</module>
     <module>netconf-monitoring</module>
     <module>ietf-netconf-monitoring</module>
     <module>ietf-netconf-monitoring-extension</module>