From dfedfab4f26c8a1c1d77b242c375d73260df57b7 Mon Sep 17 00:00:00 2001 From: Tony Tkacik Date: Thu, 28 Nov 2013 15:23:25 +0100 Subject: [PATCH] Fixed handshake issues with SSH Netconf client Change-Id: I979d64e529337a3bf1cd84f82684c5ce822a5514 Signed-off-by: Tony Tkacik --- .../netconf/NetconfConnectorModule.java | 12 +- .../yang/odl-sal-netconf-connector-cfg.yang | 11 ++ opendaylight/netconf/netconf-client/pom.xml | 5 +- .../client/NetconfSshClientDispatcher.java | 116 +++++++++++++++++- opendaylight/netconf/netconf-util/pom.xml | 1 + .../netconf/util/handler/ssh/SshHandler.java | 5 +- .../ssh/authentication/LoginPassword.java | 6 +- .../util/handler/ssh/client/SshClient.java | 19 +-- .../handler/ssh/client/SshClientAdapter.java | 45 +++++-- 9 files changed, 190 insertions(+), 30 deletions(-) diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java index 2a556c9be4..1275924614 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java @@ -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 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); diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang index 45f10162ca..9238514110 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang @@ -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 { diff --git a/opendaylight/netconf/netconf-client/pom.xml b/opendaylight/netconf/netconf-client/pom.xml index ffd46e882c..de4fb101f9 100644 --- a/opendaylight/netconf/netconf-client/pom.xml +++ b/opendaylight/netconf/netconf-client/pom.xml @@ -60,11 +60,12 @@ 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 diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java index ce0f427475..b19c09263b 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java @@ -8,11 +8,123 @@ 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. absent(), bossGroup, workerGroup); + this.authHandler = authHandler; + this.timer = new HashedWheelTimer(); + this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(timer); + } + + @Override + public Future createClient(InetSocketAddress address, + final NetconfClientSessionListener sessionListener, ReconnectStrategy strat) { + return super.createClient(address, strat, new PipelineInitializer() { + + @Override + public void initializeChannel(SocketChannel arg0, Promise 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 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 promise) { + ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator(new SessionListenerFactory() { + @Override + public SessionListener getSessionListener() { + return sessionListener; + } + }, ch, promise)); + + } + } + + private static final class NetconfHandlerFactory extends ProtocolHandlerFactory { + + 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) }; + } } } diff --git a/opendaylight/netconf/netconf-util/pom.xml b/opendaylight/netconf/netconf-util/pom.xml index c19506b236..353dd1aae7 100644 --- a/opendaylight/netconf/netconf-util/pom.xml +++ b/opendaylight/netconf/netconf-util/pom.xml @@ -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.*, com.google.common.base, diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java index b911989c64..0d9096c02a 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java @@ -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 diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java index bb0d37899d..2f1b260bd0 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java @@ -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."); } } diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java index c43aa6f3e5..3cb608db6a 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java @@ -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(); } } diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java index a50462e40d..4213fe3e06 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java @@ -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 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(); } -- 2.36.6