From 9f027b8cc547e4fbd1f65e69153cac66286ab952 Mon Sep 17 00:00:00 2001 From: Robert Gallas Date: Thu, 7 Nov 2013 15:04:53 +0100 Subject: [PATCH] SSH netty handler Added Netty SSH handler and underlying virtual socket infrasctructure Change-Id: I119494835e61820d23789125b70bb70c1299ae31 Signed-off-by: Robert Gallas --- opendaylight/commons/opendaylight/pom.xml | 5 + .../distribution/opendaylight/pom.xml | 14 +- opendaylight/netconf/netconf-util/pom.xml | 5 + .../netconf/util/handler/ssh/SshHandler.java | 74 +++++++ .../authentication/AuthenticationHandler.java | 20 ++ .../ssh/authentication/LoginPassword.java | 33 +++ .../util/handler/ssh/client/Invoker.java | 36 ++++ .../util/handler/ssh/client/SshClient.java | 67 ++++++ .../handler/ssh/client/SshClientAdapter.java | 106 +++++++++ .../util/handler/ssh/client/SshSession.java | 75 +++++++ .../ssh/virtualsocket/ChannelInputStream.java | 131 +++++++++++ .../virtualsocket/ChannelOutputStream.java | 98 +++++++++ .../ssh/virtualsocket/VirtualSocket.java | 204 ++++++++++++++++++ .../virtualsocket/VirtualSocketException.java | 15 ++ opendaylight/netconf/pom.xml | 1 + 15 files changed, 879 insertions(+), 5 deletions(-) create mode 100644 opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java create mode 100644 opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/AuthenticationHandler.java create mode 100644 opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java create mode 100644 opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/Invoker.java create mode 100644 opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java create mode 100644 opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java create mode 100644 opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshSession.java create mode 100644 opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelInputStream.java create mode 100644 opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelOutputStream.java create mode 100644 opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocket.java create mode 100644 opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocketException.java diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index f7b2a01eb8..8ff25c35de 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -617,6 +617,11 @@ org.apache.catalina.filters.CorsFilter 7.0.42 + + org.opendaylight.controller.thirdparty + ganymed + 1.0-SNAPSHOT + org.opendaylight.yangtools.model diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index 9b07f75c6d..a0d7162b3f 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -287,7 +287,7 @@ 2.4 - + org.opendaylight.yangtools.thirdparty antlr4-runtime-osgi-nohead 4.0 @@ -334,11 +334,15 @@ yang-model-api - - org.opendaylight.yangtools.model - yang-ext - + + org.opendaylight.yangtools.model + yang-ext + + + org.opendaylight.controller.thirdparty + ganymed + diff --git a/opendaylight/netconf/netconf-util/pom.xml b/opendaylight/netconf/netconf-util/pom.xml index 20603c4774..d6bf62413a 100644 --- a/opendaylight/netconf/netconf-util/pom.xml +++ b/opendaylight/netconf/netconf-util/pom.xml @@ -52,6 +52,10 @@ netty-handler ${netconf.netty.version} + + org.opendaylight.controller.thirdparty + ganymed + @@ -72,6 +76,7 @@ org.opendaylight.controller.config.stat, com.google.common.base, com.google.common.collect, + ch.ethz.ssh2, io.netty.buffer, io.netty.channel, io.netty.channel.socket, 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 new file mode 100644 index 0000000000..b911989c64 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java @@ -0,0 +1,74 @@ +/* + * 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.util.handler.ssh; + +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; +import org.opendaylight.controller.netconf.util.handler.ssh.client.SshClientAdapter; +import org.opendaylight.controller.netconf.util.handler.ssh.virtualsocket.VirtualSocket; + +/** + * Netty SSH handler class. Acts as interface between Netty and SSH library. All standard Netty message handling + * stops at instance of this class. All downstream events are handed of to wrapped {@link org.opendaylight.controller.netconf.util.handler.ssh.client.SshClientAdapter}; + */ +public class SshHandler extends ChannelOutboundHandlerAdapter { + private final VirtualSocket virtualSocket = new VirtualSocket(); + private final SshClientAdapter sshClientAdapter; + + public SshHandler(AuthenticationHandler authenticationHandler, Invoker invoker) throws IOException { + SshClient sshClient = new SshClient(virtualSocket, authenticationHandler); + this.sshClientAdapter = new SshClientAdapter(sshClient, invoker); + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx){ + if (ctx.channel().pipeline().get("socket") == null) { + ctx.channel().pipeline().addFirst("socket", virtualSocket); + } + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + if (ctx.channel().pipeline().get("socket") != null) { + ctx.channel().pipeline().remove("socket"); + } + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + this.sshClientAdapter.write((String) msg); + } + + @Override + public void connect(final ChannelHandlerContext ctx, + SocketAddress remoteAddress, + SocketAddress localAddress, + ChannelPromise promise) throws Exception { + ctx.connect(remoteAddress, localAddress, promise); + + promise.addListener(new ChannelFutureListener() { + public void operationComplete(ChannelFuture channelFuture) throws Exception { + sshClientAdapter.start(ctx); + }} + ); + } + + @Override + public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + sshClientAdapter.stop(promise); + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/AuthenticationHandler.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/AuthenticationHandler.java new file mode 100644 index 0000000000..a0e82f8bac --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/AuthenticationHandler.java @@ -0,0 +1,20 @@ +/* + * 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.util.handler.ssh.authentication; + +import ch.ethz.ssh2.Connection; + +import java.io.IOException; + +/** + * Class providing authentication facility to SSH handler. + */ +public abstract class AuthenticationHandler { + public abstract void authenticate(Connection connection) throws IOException; +} 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 new file mode 100644 index 0000000000..bb0d37899d --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java @@ -0,0 +1,33 @@ +/* + * 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.util.handler.ssh.authentication; + +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} + */ +public class LoginPassword extends AuthenticationHandler { + private final String username; + private final String password; + + public LoginPassword(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public void authenticate(Connection connection) throws IOException { + boolean isAuthenticated = connection.authenticateWithPassword(username, password); + + 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/Invoker.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/Invoker.java new file mode 100644 index 0000000000..12d1129daf --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/Invoker.java @@ -0,0 +1,36 @@ +package org.opendaylight.controller.netconf.util.handler.ssh.client; + +import java.io.IOException; + +/** + * Abstract class providing mechanism of invoking various SSH level services. + * Class is not allowed to be extended, as it provides its own implementations via instance initiators. + */ +public abstract class Invoker { + private boolean invoked = false; + + private Invoker(){} + + protected boolean isInvoked() { + return invoked; + } + + abstract void invoke(SshSession session) throws IOException; + + /** + * Invoker implementation to invokes subsystem SSH service. + * + * @param subsystem + * @return + */ + public static Invoker subsystem(final String subsystem) { + return new Invoker() { + @Override + void invoke(SshSession session) throws IOException { + if (isInvoked() == true) throw new IllegalStateException("Already invoked."); + + session.startSubSystem(subsystem); + } + }; + } +} 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 new file mode 100644 index 0000000000..c43aa6f3e5 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java @@ -0,0 +1,67 @@ +/* + * 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.util.handler.ssh.client; + +import ch.ethz.ssh2.Connection; +import ch.ethz.ssh2.Session; +import ch.ethz.ssh2.channel.Channel; +import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler; +import org.opendaylight.controller.netconf.util.handler.ssh.virtualsocket.VirtualSocket; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + + +/** + * Wrapper class around GANYMED SSH java library. + */ +public class SshClient { + private final VirtualSocket socket; + private final Map openSessions = new HashMap(); + private final AuthenticationHandler authenticationHandler; + private Connection connection; + + public SshClient(VirtualSocket socket, + AuthenticationHandler authenticationHandler) throws IOException { + this.socket = socket; + this.authenticationHandler = authenticationHandler; + } + + public SshSession openSession() throws IOException { + if(connection == null) connect(); + + Session session = connection.openSession(); + SshSession sshSession = new SshSession(session); + openSessions.put(openSessions.size(), sshSession); + + return sshSession; + } + + 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) { + session.session.close(); + } + } + + public void close() { + for(SshSession session : openSessions.values()) closeSession(session); + + openSessions.clear(); + + 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 new file mode 100644 index 0000000000..a50462e40d --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java @@ -0,0 +1,106 @@ +/* + * 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.util.handler.ssh.client; + +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.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. + */ +public class SshClientAdapter implements Runnable { + private final SshClient sshClient; + private final Invoker invoker; + + private SshSession session; + private InputStream stdOut; + private InputStream stdErr; + private OutputStream stdIn; + + private ChannelHandlerContext ctx; + private ChannelPromise disconnectPromise; + + private final AtomicBoolean stopRequested = new AtomicBoolean(false); + + private final Object lock = new Object(); + + public SshClientAdapter(SshClient sshClient, + Invoker invoker) { + this.sshClient = sshClient; + this.invoker = invoker; + } + + public void run() { + try { + session = sshClient.openSession(); + invoker.invoke(session); + + stdOut = session.getStdout(); + stdErr = session.getStderr(); + + synchronized(lock) { + stdIn = session.getStdin(); + } + + while (stopRequested.get() == false) { + byte[] readBuff = new byte[1024]; + int c = stdOut.read(readBuff); + + byte[] tranBuff = new byte[c]; + System.arraycopy(readBuff, 0, tranBuff, 0, c); + + ByteBuf byteBuf = Unpooled.buffer(c); + byteBuf.writeBytes(tranBuff); + ctx.fireChannelRead(byteBuf); + } + + } catch (VirtualSocketException e) { + // Netty closed connection prematurely. + // Just pass and move on. + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + sshClient.close(); + + synchronized (lock) { + if(disconnectPromise != null) ctx.disconnect(disconnectPromise); + } + } + } + + // TODO: needs rework to match netconf framer API. + public void write(String message) throws IOException { + synchronized (lock) { + if (stdIn == null) throw new IllegalStateException("StdIn not available"); + } + stdIn.write(message.getBytes()); + stdIn.flush(); + } + + public void stop(ChannelPromise promise) { + synchronized (lock) { + stopRequested.set(true); + disconnectPromise = promise; + } + } + + public void start(ChannelHandlerContext ctx) { + if(this.ctx != null) return; // context is already associated. + + this.ctx = ctx; + new Thread(this).start(); + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshSession.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshSession.java new file mode 100644 index 0000000000..df400aa141 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshSession.java @@ -0,0 +1,75 @@ +/* + * 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.util.handler.ssh.client; + +import ch.ethz.ssh2.Session; +import ch.ethz.ssh2.StreamGobbler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Wrapper class for proprietary SSH sessions implementations + */ +public class SshSession { + final Session session; + + public SshSession(Session session) { + this.session = session; + } + + public void execCommand(String cmd) throws IOException { + session.execCommand(cmd); + } + + public void execCommand(String cmd, String charsetName) throws IOException { + session.execCommand(cmd, charsetName); + } + + public void startShell() throws IOException { + session.startShell(); + } + + public void startSubSystem(String name) throws IOException { + session.startSubSystem(name); + } + + public int getState() { + return session.getState(); + } + + public InputStream getStdout() { + return new StreamGobbler(session.getStdout()); + } + + public InputStream getStderr() { + return session.getStderr(); + } + + public OutputStream getStdin() { + return session.getStdin(); + } + + public int waitUntilDataAvailable(long timeout) throws IOException { + return session.waitUntilDataAvailable(timeout); + } + + public int waitForCondition(int condition_set, long timeout) { + return session.waitForCondition(condition_set, timeout); + } + + public Integer getExitStatus() { + return session.getExitStatus(); + } + + public String getExitSignal() { + return session.getExitSignal(); + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelInputStream.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelInputStream.java new file mode 100644 index 0000000000..07c81b0ccb --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelInputStream.java @@ -0,0 +1,131 @@ +/* + * 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.util.handler.ssh.virtualsocket; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandler; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Class provides {@link InputStream} functionality to users of virtual socket. + */ +public class ChannelInputStream extends InputStream implements ChannelInboundHandler { + private final Object lock = new Object(); + private final ByteBuf bb = Unpooled.buffer(); + + @Override + public int read(byte b[], int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + int bytesRead = 1; + synchronized (lock) { + int c = read(); + + b[off] = (byte)c; + + if(this.bb.readableBytes() == 0) return bytesRead; + + int ltr = len-1; + ltr = (ltr <= bb.readableBytes()) ? ltr : bb.readableBytes(); + + bb.readBytes(b, 1, ltr); + bytesRead += ltr; + } + return bytesRead; + } + + @Override + public int read() throws IOException { + synchronized (lock) { + while (this.bb.readableBytes() == 0) { + try { + lock.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + return this.bb.readByte() & 0xFF; + } + } + + @Override + public int available() throws IOException { + synchronized (lock) { + return this.bb.readableBytes(); + } + } + + public void channelRegistered(ChannelHandlerContext ctx) + throws Exception { + ctx.fireChannelRegistered(); + } + + public void channelUnregistered(ChannelHandlerContext ctx) + throws Exception { + ctx.fireChannelUnregistered(); + } + + public void channelActive(ChannelHandlerContext ctx) + throws Exception { + ctx.fireChannelActive(); + } + + public void channelInactive(ChannelHandlerContext ctx) + throws Exception { + ctx.fireChannelInactive(); + } + + public void channelRead(ChannelHandlerContext ctx, Object o) + throws Exception { + synchronized(lock) { + this.bb.discardReadBytes(); + this.bb.writeBytes((ByteBuf) o); + lock.notifyAll(); + } + } + + public void channelReadComplete(ChannelHandlerContext ctx) + throws Exception { + ctx.fireChannelReadComplete(); + } + + public void userEventTriggered(ChannelHandlerContext ctx, Object o) + throws Exception { + ctx.fireUserEventTriggered(o); + } + + public void channelWritabilityChanged(ChannelHandlerContext ctx) + throws Exception { + ctx.fireChannelWritabilityChanged(); + } + + public void handlerAdded(ChannelHandlerContext ctx) + throws Exception { + } + + public void handlerRemoved(ChannelHandlerContext ctx) + throws Exception { + } + + public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) + throws Exception { + ctx.fireExceptionCaught(throwable); + } +} + diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelOutputStream.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelOutputStream.java new file mode 100644 index 0000000000..b1314a6eed --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelOutputStream.java @@ -0,0 +1,98 @@ +/* + * 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.util.handler.ssh.virtualsocket; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandler; +import io.netty.channel.ChannelPromise; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.SocketAddress; + +/** + * Class provides {@link OutputStream) functionality to users of virtual socket. + */ +public class ChannelOutputStream extends OutputStream implements ChannelOutboundHandler { + private final Object lock = new Object(); + private ByteBuf buff = Unpooled.buffer(); + private ChannelHandlerContext ctx; + + @Override + public void flush() throws IOException { + synchronized(lock) { + ctx.writeAndFlush(buff).awaitUninterruptibly(); + buff = Unpooled.buffer(); + } + } + + @Override + public void write(int b) throws IOException { + synchronized(lock) { + buff.writeByte(b); + } + } + + public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, + ChannelPromise promise) throws Exception { + ctx.bind(localAddress, promise); + } + + public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, + SocketAddress localAddress, ChannelPromise promise) + throws Exception { + this.ctx = ctx; + ctx.connect(remoteAddress, localAddress, promise); + } + + public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) + throws Exception { + ctx.disconnect(promise); + } + + public void close(ChannelHandlerContext ctx, ChannelPromise promise) + throws Exception { + ctx.close(promise); + } + + public void deregister(ChannelHandlerContext ctx, ChannelPromise channelPromise) + throws Exception { + ctx.deregister(channelPromise); + } + + public void read(ChannelHandlerContext ctx) + throws Exception { + ctx.read(); + } + + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) + throws Exception { + // pass + } + + public void flush(ChannelHandlerContext ctx) + throws Exception { + // pass + } + + public void handlerAdded(ChannelHandlerContext ctx) + throws Exception { + } + + public void handlerRemoved(ChannelHandlerContext ctx) + throws Exception { + } + + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) + throws Exception { + ctx.fireExceptionCaught(cause); + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocket.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocket.java new file mode 100644 index 0000000000..1011ca16be --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocket.java @@ -0,0 +1,204 @@ +/* + * 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.util.handler.ssh.virtualsocket; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.SocketChannel; + +/** + * Handler class providing Socket functionality to OIO client application. By using VirtualSocket user can + * use OIO application in asynchronous environment and NIO EventLoop. Using VirtualSocket OIO applications + * are able to use full potential of NIO environment. + */ +public class VirtualSocket extends Socket implements ChannelHandler { + private final ChannelInputStream chis = new ChannelInputStream(); + private final ChannelOutputStream chos = new ChannelOutputStream(); + private ChannelHandlerContext ctx; + + + public InputStream getInputStream() { + return this.chis; + } + + public OutputStream getOutputStream() { + return this.chos; + } + + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + this.ctx = ctx; + + if (ctx.channel().pipeline().get("outputStream") == null) { + ctx.channel().pipeline().addFirst("outputStream", chos); + } + + if (ctx.channel().pipeline().get("inputStream") == null) { + ctx.channel().pipeline().addFirst("inputStream", chis); + } + } + + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + if (ctx.channel().pipeline().get("outputStream") != null) { + ctx.channel().pipeline().remove("outputStream"); + } + + if (ctx.channel().pipeline().get("inputStream") != null) { + ctx.channel().pipeline().remove("inputStream"); + } + } + + public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) throws Exception { + ctx.fireExceptionCaught(throwable); + } + + public VirtualSocket() {super();} + + @Override + public void connect(SocketAddress endpoint) throws IOException {} + + @Override + public void connect(SocketAddress endpoint, int timeout) throws IOException {} + + @Override + public void bind(SocketAddress bindpoint) throws IOException {} + + @Override + public InetAddress getInetAddress() { + InetSocketAddress isa = getInetSocketAddress(); + + if (isa == null) throw new VirtualSocketException(); + + return getInetSocketAddress().getAddress(); + } + + @Override + public InetAddress getLocalAddress() {return null;} + + @Override + public int getPort() { + return getInetSocketAddress().getPort(); + } + + private InetSocketAddress getInetSocketAddress() { + return (InetSocketAddress)getRemoteSocketAddress(); + } + + @Override + public int getLocalPort() {return -1;} + + @Override + public SocketAddress getRemoteSocketAddress() { + return this.ctx.channel().remoteAddress(); + } + + @Override + public SocketAddress getLocalSocketAddress() { + return this.ctx.channel().localAddress(); + } + + @Override + public SocketChannel getChannel() {return null;} + + @Override + public void setTcpNoDelay(boolean on) throws SocketException {} + + @Override + public boolean getTcpNoDelay() throws SocketException {return false;} + + @Override + public void setSoLinger(boolean on, int linger) throws SocketException {} + + @Override + public int getSoLinger() throws SocketException {return -1;} + + @Override + public void sendUrgentData(int data) throws IOException {} + + @Override + public void setOOBInline(boolean on) throws SocketException {} + + @Override + public boolean getOOBInline() throws SocketException {return false;} + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException {} + + @Override + public synchronized int getSoTimeout() throws SocketException {return -1;} + + @Override + public synchronized void setSendBufferSize(int size) throws SocketException {} + + @Override + public synchronized int getSendBufferSize() throws SocketException {return -1;} + + @Override + public synchronized void setReceiveBufferSize(int size) throws SocketException {} + + @Override + public synchronized int getReceiveBufferSize() throws SocketException {return -1;} + + @Override + public void setKeepAlive(boolean on) throws SocketException {} + + @Override + public boolean getKeepAlive() throws SocketException {return false;} + + @Override + public void setTrafficClass(int tc) throws SocketException {} + + @Override + public int getTrafficClass() throws SocketException {return -1;} + + @Override + public void setReuseAddress(boolean on) throws SocketException {} + + @Override + public boolean getReuseAddress() throws SocketException {return false;} + + @Override + public synchronized void close() throws IOException {} + + @Override + public void shutdownInput() throws IOException {} + + @Override + public void shutdownOutput() throws IOException {} + + @Override + public String toString() { + return "Virtual socket InetAdress["+getInetAddress()+"], Port["+getPort()+"]"; + } + + @Override + public boolean isConnected() {return false;} + + @Override + public boolean isBound() {return false;} + + @Override + public boolean isClosed() {return false;} + + @Override + public boolean isInputShutdown() {return false;} + + @Override + public boolean isOutputShutdown() {return false;} + + @Override + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {} +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocketException.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocketException.java new file mode 100644 index 0000000000..46fdbb83e7 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocketException.java @@ -0,0 +1,15 @@ +/* + * 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.util.handler.ssh.virtualsocket; + +/** + * Exception class which provides notification about exceptional situations at the virtual socket layer. + */ +public class VirtualSocketException extends RuntimeException { +} diff --git a/opendaylight/netconf/pom.xml b/opendaylight/netconf/pom.xml index 8f69f8dca0..5447f7f5d0 100644 --- a/opendaylight/netconf/pom.xml +++ b/opendaylight/netconf/pom.xml @@ -26,6 +26,7 @@ config-persister-impl netconf-mapping-api netconf-client + ../../third-party/ganymed -- 2.36.6