+++ /dev/null
-/*
- * 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.nettyutil.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.
- */
-abstract class Invoker {
- private boolean invoked = false;
-
- private Invoker() {
- }
-
- protected boolean isInvoked() {
- return invoked;
- }
-
- public void setInvoked() {
- this.invoked = true;
- }
-
- abstract void invoke(SshSession session) throws IOException;
-
- public static Invoker netconfSubsystem(){
- return subsystem("netconf");
- }
-
- public static Invoker subsystem(final String subsystem) {
- return new Invoker() {
- @Override
- synchronized void invoke(SshSession session) throws IOException {
- if (isInvoked()) {
- throw new IllegalStateException("Already invoked.");
- }
- try {
- session.startSubSystem(subsystem);
- } finally {
- setInvoked();
- }
- }
- };
- }
-}
+++ /dev/null
-/*
- * 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.nettyutil.handler.ssh.client;
-
-import ch.ethz.ssh2.Connection;
-import ch.ethz.ssh2.Session;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
-import org.opendaylight.controller.netconf.nettyutil.handler.ssh.virtualsocket.VirtualSocket;
-
-/**
- * Wrapper class around GANYMED SSH java library.
- */
-class SshClient {
- private final VirtualSocket socket;
- private final Map<Integer, SshSession> 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 close() {
- for (SshSession session : openSessions.values()){
- session.close();
- }
-
- openSessions.clear();
-
- if (connection != null) {
- connection.close();
- }
- }
-
- @Override
- public String toString() {
- return "SshClient{" +
- "socket=" + socket +
- '}';
- }
-}
+++ /dev/null
-/*
- * 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.nettyutil.handler.ssh.client;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
-import io.netty.channel.ChannelFuture;
-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.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * Worker thread class. Handles all downstream and upstream events in SSH Netty
- * pipeline.
- */
-class SshClientAdapter implements Runnable {
- private static final Logger logger = LoggerFactory.getLogger(SshClientAdapter.class);
-
- private static final int BUFFER_SIZE = 1024;
-
- private final SshClient sshClient;
- private final Invoker invoker;
-
- private OutputStream stdIn;
-
- private final Queue<ByteBuf> postponed = new LinkedList<>();
-
- private ChannelHandlerContext ctx;
- private ChannelPromise disconnectPromise;
-
- private final AtomicBoolean stopRequested = new AtomicBoolean(false);
-
- private final Object lock = new Object();
-
- public SshClientAdapter(final SshClient sshClient, final Invoker invoker) {
- this.sshClient = sshClient;
- this.invoker = invoker;
- }
-
- // TODO ganymed spawns a Thread that receives the data from remote inside TransportManager
- // Get rid of this thread and reuse Ganymed internal thread (not sure if its possible without modifications in ganymed)
- public void run() {
- try {
- final SshSession session = sshClient.openSession();
- invoker.invoke(session);
- final InputStream stdOut = session.getStdout();
-
- synchronized (lock) {
- stdIn = session.getStdin();
- while (postponed.peek() != null) {
- writeImpl(postponed.poll());
- }
- }
-
- while (!stopRequested.get()) {
- final byte[] readBuff = new byte[BUFFER_SIZE];
- final int c = stdOut.read(readBuff);
- if (c == -1) {
- continue;
- }
-
- ctx.fireChannelRead(Unpooled.copiedBuffer(readBuff, 0, c));
- }
- } catch (final Exception e) {
- logger.error("Unexpected exception", e);
- } finally {
- sshClient.close();
-
- synchronized (lock) {
- if (disconnectPromise != null) {
- ctx.disconnect(disconnectPromise);
- }
- }
- }
- }
-
- // TODO: needs rework to match netconf framer API.
- public void write(final ByteBuf message) throws IOException {
- synchronized (lock) {
- if (stdIn == null) {
- postponed.add(message);
- return;
- }
- writeImpl(message);
- }
- }
-
- private void writeImpl(final ByteBuf message) throws IOException {
- message.getBytes(0, stdIn, message.readableBytes());
- message.release();
- stdIn.flush();
- }
-
- public void stop(final ChannelPromise promise) {
- synchronized (lock) {
- stopRequested.set(true);
- disconnectPromise = promise;
- }
- }
-
- public Thread start(final ChannelHandlerContext ctx, final ChannelFuture channelFuture) {
- checkArgument(channelFuture.isSuccess());
- checkNotNull(ctx.channel().remoteAddress());
- synchronized (this) {
- checkState(this.ctx == null);
- this.ctx = ctx;
- }
- final String threadName = toString();
- final Thread thread = new Thread(this, threadName);
- thread.start();
- return thread;
- }
-
- @Override
- public String toString() {
- return "SshClientAdapter{" +
- "sshClient=" + sshClient +
- '}';
- }
-}
+++ /dev/null
-/*
- * 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.nettyutil.handler.ssh.client;
-
-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.nettyutil.handler.ssh.authentication.AuthenticationHandler;
-import org.opendaylight.controller.netconf.nettyutil.handler.ssh.virtualsocket.VirtualSocket;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * 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.nettyutil.handler.ssh.client.SshClientAdapter};
- */
-public class SshHandler extends ChannelOutboundHandlerAdapter {
- private static final Logger logger = LoggerFactory.getLogger(SshHandler.class);
- private static final String SOCKET = "socket";
-
- private final VirtualSocket virtualSocket = new VirtualSocket();
- private final SshClientAdapter sshClientAdapter;
-
-
- public static SshHandler createForNetconfSubsystem(AuthenticationHandler authenticationHandler) throws IOException {
- return new SshHandler(authenticationHandler, Invoker.netconfSubsystem());
- }
-
-
- 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) {
- if (ctx.channel().pipeline().get(SOCKET) != null) {
- ctx.channel().pipeline().remove(SOCKET);
- }
- }
-
- @Override
- public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws IOException {
- this.sshClientAdapter.write((ByteBuf) msg);
- }
-
- @Override
- public void connect(final ChannelHandlerContext ctx,
- SocketAddress remoteAddress,
- SocketAddress localAddress,
- ChannelPromise promise) {
- ctx.connect(remoteAddress, localAddress, promise);
-
- promise.addListener(new ChannelFutureListener() {
- public void operationComplete(ChannelFuture channelFuture) {
- if (channelFuture.isSuccess()) {
- sshClientAdapter.start(ctx, channelFuture);
- } else {
- logger.debug("Failed to connect to remote host");
- }
- }}
- );
- }
-
- @Override
- public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) {
- sshClientAdapter.stop(promise);
- }
-}
+++ /dev/null
-/*
- * 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.nettyutil.handler.ssh.client;
-
-import ch.ethz.ssh2.Session;
-import ch.ethz.ssh2.channel.Channel;
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Wrapper class for proprietary SSH sessions implementations
- */
-class SshSession implements Closeable {
- private final Session session;
-
- public SshSession(final Session session) {
- this.session = session;
- }
-
- public void startSubSystem(final String name) throws IOException {
- session.startSubSystem(name);
- }
-
- public InputStream getStdout() {
- return session.getStdout();
- }
-
- // FIXME according to http://www.ganymed.ethz.ch/ssh2/FAQ.html#blocking you should read data from both stdout and stderr to prevent window filling up (since stdout and stderr share a window)
- // FIXME stdErr is not used anywhere
- public InputStream getStderr() {
- return session.getStderr();
- }
-
- public OutputStream getStdin() {
- return session.getStdin();
- }
-
- @Override
- public void close() {
- if (session.getState() == Channel.STATE_OPEN || session.getState() == Channel.STATE_OPENING) {
- session.close();
- }
- }
-}
import org.junit.Test;
import org.opendaylight.controller.netconf.netty.EchoClientHandler.State;
import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
-import org.opendaylight.controller.netconf.nettyutil.handler.ssh.client.SshHandler;
+import org.opendaylight.controller.netconf.nettyutil.handler.ssh.client.AsyncSshHandler;
import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
import org.opendaylight.controller.netconf.ssh.authentication.AuthProviderImpl;
ChannelInitializer<NioSocketChannel> channelInitializer = new ChannelInitializer<NioSocketChannel>() {
@Override
public void initChannel(NioSocketChannel ch) throws Exception {
- ch.pipeline().addFirst(SshHandler.createForNetconfSubsystem(new LoginPassword("a", "a")));
+ ch.pipeline().addFirst(AsyncSshHandler.createForNetconfSubsystem(new LoginPassword("a", "a")));
ch.pipeline().addLast(echoClientHandler);
}
};