Merge "Fixed handshake issues with SSH Netconf client"
authorEd Warnicke <eaw@cisco.com>
Thu, 28 Nov 2013 14:51:34 +0000 (14:51 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Thu, 28 Nov 2013 14:51:34 +0000 (14:51 +0000)
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java
opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang
opendaylight/netconf/netconf-client/pom.xml
opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java
opendaylight/netconf/netconf-util/pom.xml
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java

index 2a556c9be41c235aee2fb76ff8b712ffe33c6761..12759246140a9d7da0947ff8f73e8d461e6614d6 100644 (file)
@@ -17,6 +17,9 @@ import java.net.InetSocketAddress;
 import javax.net.ssl.SSLContext;
 
 import org.opendaylight.controller.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.controller.netconf.client.NetconfSshClientDispatcher;
+import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler;
+import org.opendaylight.controller.netconf.util.handler.ssh.authentication.LoginPassword;
 import org.opendaylight.controller.sal.connect.netconf.NetconfDevice;
 import org.osgi.framework.BundleContext;
 
@@ -75,8 +78,13 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co
         EventLoopGroup bossGroup = getBossThreadGroupDependency();
         EventLoopGroup workerGroup = getWorkerThreadGroupDependency();
         Optional<SSLContext> maybeContext = Optional.absent();
-        NetconfClientDispatcher dispatcher = new NetconfClientDispatcher(maybeContext , bossGroup, workerGroup);
-        
+        NetconfClientDispatcher dispatcher = null;
+        if(getTcpOnly()) {
+            dispatcher = new NetconfClientDispatcher(maybeContext , bossGroup, workerGroup);
+        } else {
+            AuthenticationHandler authHandler = new LoginPassword(getUsername(),getPassword());
+            dispatcher = new NetconfSshClientDispatcher(authHandler , bossGroup, workerGroup);
+        }
         getDomRegistryDependency().registerProvider(device, bundleContext);
         
         device.start(dispatcher);
index 45f10162ca7edd600ca86bc5a56ac3cfb229fafb..923851411090a9c93f9b0cf1d59e9be1a197a92b 100644 (file)
@@ -45,7 +45,18 @@ module odl-sal-netconf-connector-cfg {
             leaf port {
                 type uint32;
             }
+            
+            leaf tcp-only {
+                type boolean;
+            }
 
+            leaf username {
+                type string;
+            }
+            
+            leaf password {
+                type string;
+            }
             container dom-registry {
                 uses config:service-ref {
                     refine type {
index ffd46e882c60f4a86b47470159d18139863ffff9..de4fb101f9d6fefb065e26f4b03bd46af4979cf3 100644 (file)
                             javax.xml.xpath,
                             org.opendaylight.controller.netconf.api,
                             org.opendaylight.controller.netconf.util,
-                            org.opendaylight.controller.netconf.util.xml,
+                            org.opendaylight.controller.netconf.util.*,
                             org.opendaylight.protocol.framework,
                             org.slf4j,
                             org.w3c.dom,
-                            org.xml.sax
+                            org.xml.sax,
+                            io.netty.handler.codec
                         </Import-Package>
                     </instructions>
                 </configuration>
index ce0f4274757ac37a1791569b29b95906ec26cd6b..b19c09263b9fa481c76307da0bb2dbebc310bb85 100644 (file)
 
 package org.opendaylight.controller.netconf.client;
 
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import javax.net.ssl.SSLContext;
+
+import org.opendaylight.controller.netconf.api.NetconfMessage;
+import org.opendaylight.controller.netconf.api.NetconfSession;
+import org.opendaylight.controller.netconf.api.NetconfTerminationReason;
+import org.opendaylight.controller.netconf.util.AbstractChannelInitializer;
+import org.opendaylight.controller.netconf.util.handler.FramingMechanismHandlerFactory;
+import org.opendaylight.controller.netconf.util.handler.NetconfMessageAggregator;
+import org.opendaylight.controller.netconf.util.handler.ssh.SshHandler;
+import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler;
+import org.opendaylight.controller.netconf.util.handler.ssh.client.Invoker;
+import org.opendaylight.controller.netconf.util.messages.FramingMechanism;
+import org.opendaylight.controller.netconf.util.messages.NetconfMessageFactory;
+import org.opendaylight.protocol.framework.ProtocolHandlerFactory;
+import org.opendaylight.protocol.framework.ProtocolMessageDecoder;
+import org.opendaylight.protocol.framework.ProtocolMessageEncoder;
+import org.opendaylight.protocol.framework.ReconnectStrategy;
+import org.opendaylight.protocol.framework.SessionListener;
+import org.opendaylight.protocol.framework.SessionListenerFactory;
+
+import com.google.common.base.Optional;
+
+import io.netty.channel.ChannelHandler;
 import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.util.HashedWheelTimer;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.Promise;
 
 public class NetconfSshClientDispatcher extends NetconfClientDispatcher {
 
-    public NetconfSshClientDispatcher(EventLoopGroup bossGroup, EventLoopGroup workerGroup) {
-        super(null, bossGroup, workerGroup);
+    private AuthenticationHandler authHandler;
+    private HashedWheelTimer timer;
+    private NetconfClientSessionNegotiatorFactory negotatorFactory;
+
+    public NetconfSshClientDispatcher(AuthenticationHandler authHandler, EventLoopGroup bossGroup,
+            EventLoopGroup workerGroup) {
+        super(Optional.<SSLContext> absent(), bossGroup, workerGroup);
+        this.authHandler = authHandler;
+        this.timer = new HashedWheelTimer();
+        this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(timer);
+    }
+
+    @Override
+    public Future<NetconfClientSession> createClient(InetSocketAddress address,
+            final NetconfClientSessionListener sessionListener, ReconnectStrategy strat) {
+        return super.createClient(address, strat, new PipelineInitializer<NetconfClientSession>() {
+
+            @Override
+            public void initializeChannel(SocketChannel arg0, Promise<NetconfClientSession> arg1) {
+                new NetconfSshClientInitializer(authHandler, negotatorFactory, sessionListener).initialize(arg0, arg1);
+            }
+
+        });
+    }
+
+    private static final class NetconfSshClientInitializer extends AbstractChannelInitializer {
+
+        private final NetconfHandlerFactory handlerFactory;
+        private final AuthenticationHandler authenticationHandler;
+        private final NetconfClientSessionNegotiatorFactory negotiatorFactory;
+        private final NetconfClientSessionListener sessionListener;
+
+        public NetconfSshClientInitializer(AuthenticationHandler authHandler,
+                NetconfClientSessionNegotiatorFactory negotiatorFactory,
+                final NetconfClientSessionListener sessionListener) {
+            this.handlerFactory = new NetconfHandlerFactory(new NetconfMessageFactory());
+            this.authenticationHandler = authHandler;
+            this.negotiatorFactory = negotiatorFactory;
+            this.sessionListener = sessionListener;
+        }
+
+        @Override
+        public void initialize(SocketChannel ch, Promise<? extends NetconfSession> promise) {
+            try {
+                Invoker invoker = Invoker.subsystem("netconf");
+                ch.pipeline().addFirst(new SshHandler(authenticationHandler, invoker));
+                ch.pipeline().addLast("aggregator", new NetconfMessageAggregator(FramingMechanism.EOM));
+                ch.pipeline().addLast(handlerFactory.getDecoders());
+                initializeAfterDecoder(ch, promise);
+                ch.pipeline().addLast("frameEncoder",
+                        FramingMechanismHandlerFactory.createHandler(FramingMechanism.EOM));
+                ch.pipeline().addLast(handlerFactory.getEncoders());
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        protected void initializeAfterDecoder(SocketChannel ch, Promise<? extends NetconfSession> promise) {
+            ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator(new SessionListenerFactory() {
+                @Override
+                public SessionListener<NetconfMessage, NetconfClientSession, NetconfTerminationReason> getSessionListener() {
+                    return sessionListener;
+                }
+            }, ch, promise));
+
+        }
+    }
+
+    private static final class NetconfHandlerFactory extends ProtocolHandlerFactory<NetconfMessage> {
+
+        public NetconfHandlerFactory(final NetconfMessageFactory msgFactory) {
+            super(msgFactory);
+        }
+
+        @Override
+        public ChannelHandler[] getEncoders() {
+            return new ChannelHandler[] { new ProtocolMessageEncoder(this.msgFactory) };
+        }
+
+        @Override
+        public ChannelHandler[] getDecoders() {
+            return new ChannelHandler[] { new ProtocolMessageDecoder(this.msgFactory) };
+        }
     }
 }
index c19506b236a2d8b9cacdc7e03cc263f20a131bfb..353dd1aae70ba8eac0b8cb2fa9ca4ab21da5ba10 100644 (file)
@@ -76,6 +76,7 @@
                             org.opendaylight.controller.netconf.util.mapping,
                             org.opendaylight.controller.netconf.util.messages,
                             org.opendaylight.controller.netconf.util.handler,
+                            org.opendaylight.controller.netconf.util.handler.*,
                         </Export-Package>
                         <Import-Package>
                             com.google.common.base,
index b911989c646b64b139dd4a678cbd0c8adc03596d..0d9096c02a3abd1b33204665cb72dc4cbd10e805 100644 (file)
@@ -8,13 +8,16 @@
 
 package org.opendaylight.controller.netconf.util.handler.ssh;
 
+import io.netty.buffer.ByteBuf;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelFutureListener;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelOutboundHandlerAdapter;
 import io.netty.channel.ChannelPromise;
+
 import java.io.IOException;
 import java.net.SocketAddress;
+
 import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler;
 import org.opendaylight.controller.netconf.util.handler.ssh.client.Invoker;
 import org.opendaylight.controller.netconf.util.handler.ssh.client.SshClient;
@@ -50,7 +53,7 @@ public class SshHandler extends ChannelOutboundHandlerAdapter {
 
     @Override
     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
-        this.sshClientAdapter.write((String) msg);
+        this.sshClientAdapter.write((ByteBuf) msg);
     }
 
     @Override
index bb0d37899d7bfd0d5486d4ad22872f49313d4309..2f1b260bd087fd0f48dccdb0af1911157395d7cf 100644 (file)
@@ -13,7 +13,8 @@ import ch.ethz.ssh2.Connection;
 import java.io.IOException;
 
 /**
- * Class Providing username/password authentication option to {@link org.opendaylight.controller.netconf.util.handler.ssh.SshHandler}
+ * Class Providing username/password authentication option to
+ * {@link org.opendaylight.controller.netconf.util.handler.ssh.SshHandler}
  */
 public class LoginPassword extends AuthenticationHandler {
     private final String username;
@@ -28,6 +29,7 @@ public class LoginPassword extends AuthenticationHandler {
     public void authenticate(Connection connection) throws IOException {
         boolean isAuthenticated = connection.authenticateWithPassword(username, password);
 
-        if (isAuthenticated == false) throw new IOException("Authentication failed.");
+        if (isAuthenticated == false)
+            throw new IOException("Authentication failed.");
     }
 }
index c43aa6f3e59db1a57cf5a4166e61bfb0416a310f..3cb608db6a87c3ef121cc6a4edf0f0676fab9912 100644 (file)
@@ -18,7 +18,6 @@ import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
-
 /**
  * Wrapper class around GANYMED SSH java library.
  */
@@ -28,16 +27,16 @@ public class SshClient {
     private final AuthenticationHandler authenticationHandler;
     private Connection connection;
 
-    public SshClient(VirtualSocket socket,
-                     AuthenticationHandler authenticationHandler) throws IOException {
+    public SshClient(VirtualSocket socket, AuthenticationHandler authenticationHandler) throws IOException {
         this.socket = socket;
         this.authenticationHandler = authenticationHandler;
     }
 
     public SshSession openSession() throws IOException {
-        if(connection == null) connect();
+        if (connection == null)
+            connect();
 
-        Session session =  connection.openSession();
+        Session session = connection.openSession();
         SshSession sshSession = new SshSession(session);
         openSessions.put(openSessions.size(), sshSession);
 
@@ -46,22 +45,24 @@ public class SshClient {
 
     private void connect() throws IOException {
         connection = new Connection(socket);
+
         connection.connect();
         authenticationHandler.authenticate(connection);
     }
 
     public void closeSession(SshSession session) {
-        if(   session.getState() == Channel.STATE_OPEN
-           || session.getState() == Channel.STATE_OPENING) {
+        if (session.getState() == Channel.STATE_OPEN || session.getState() == Channel.STATE_OPENING) {
             session.session.close();
         }
     }
 
     public void close() {
-        for(SshSession session : openSessions.values()) closeSession(session);
+        for (SshSession session : openSessions.values())
+            closeSession(session);
 
         openSessions.clear();
 
-        if(connection != null) connection.close();
+        if (connection != null)
+            connection.close();
     }
 }
index a50462e40dae1e3c5ce40afd451713992ad28e2a..4213fe3e0642db6257b3e655c407a4394785fca3 100644 (file)
@@ -12,14 +12,19 @@ import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelPromise;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.Queue;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.opendaylight.controller.netconf.util.handler.ssh.virtualsocket.VirtualSocketException;
 
+
 /**
- * Worker thread class. Handles all downstream and upstream events in SSH Netty pipeline.
+ * Worker thread class. Handles all downstream and upstream events in SSH Netty
+ * pipeline.
  */
 public class SshClientAdapter implements Runnable {
     private final SshClient sshClient;
@@ -30,6 +35,9 @@ public class SshClientAdapter implements Runnable {
     private InputStream stdErr;
     private OutputStream stdIn;
 
+    private Queue<ByteBuf> postponned = new LinkedList<>();
+
+
     private ChannelHandlerContext ctx;
     private ChannelPromise disconnectPromise;
 
@@ -37,8 +45,7 @@ public class SshClientAdapter implements Runnable {
 
     private final Object lock = new Object();
 
-    public SshClientAdapter(SshClient sshClient,
-                            Invoker invoker) {
+    public SshClientAdapter(SshClient sshClient, Invoker invoker) {
         this.sshClient = sshClient;
         this.invoker = invoker;
     }
@@ -47,18 +54,24 @@ public class SshClientAdapter implements Runnable {
         try {
             session = sshClient.openSession();
             invoker.invoke(session);
-
             stdOut = session.getStdout();
             stdErr = session.getStderr();
 
-            synchronized(lock) {
+            synchronized (lock) {
+
                 stdIn = session.getStdin();
+                ByteBuf message = null;
+                while ((message = postponned.poll()) != null) {
+                    writeImpl(message);
+                }
             }
 
             while (stopRequested.get() == false) {
                 byte[] readBuff = new byte[1024];
                 int c = stdOut.read(readBuff);
-
+                if (c == -1) {
+                    continue;
+                }
                 byte[] tranBuff = new byte[c];
                 System.arraycopy(readBuff, 0, tranBuff, 0, c);
 
@@ -76,17 +89,25 @@ public class SshClientAdapter implements Runnable {
             sshClient.close();
 
             synchronized (lock) {
-                if(disconnectPromise != null) ctx.disconnect(disconnectPromise);
+                if (disconnectPromise != null)
+                    ctx.disconnect(disconnectPromise);
             }
         }
     }
 
     // TODO: needs rework to match netconf framer API.
-    public void write(String message) throws IOException {
+    public void write(ByteBuf message) throws IOException {
         synchronized (lock) {
-            if (stdIn == null) throw new IllegalStateException("StdIn not available");
+            if (stdIn == null) {
+                postponned.add(message);
+                return;
+            }
+            writeImpl(message);
         }
-        stdIn.write(message.getBytes());
+    }
+
+    private void writeImpl(ByteBuf message) throws IOException {
+        message.getBytes(0, stdIn, message.readableBytes());
         stdIn.flush();
     }
 
@@ -98,8 +119,8 @@ public class SshClientAdapter implements Runnable {
     }
 
     public void start(ChannelHandlerContext ctx) {
-        if(this.ctx != null) return; // context is already associated.
-
+        if (this.ctx != null)
+            return; // context is already associated.
         this.ctx = ctx;
         new Thread(this).start();
     }