Merge "MD-SAL StatisticsManager- Stopping statistics thread until i fix all the relev...
authorEd Warnicke <eaw@cisco.com>
Thu, 28 Nov 2013 11:50:52 +0000 (11:50 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Thu, 28 Nov 2013 11:50:52 +0000 (11:50 +0000)
19 files changed:
opendaylight/distribution/opendaylight/pom.xml
opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini
opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSession.java
opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClient.java
opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSession.java
opendaylight/netconf/netconf-it/pom.xml
opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java
opendaylight/netconf/netconf-it/src/test/resources/logback.xml [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/pom.xml [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/osgi/NetconfSSHActivator.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/KeyStoreHandler.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/RSAKey.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/IOThread.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/SSHServerTest.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java
opendaylight/netconf/pom.xml
third-party/ganymed/pom.xml

index 5b08af7..5f26516 100644 (file)
           <artifactId>netconf-mapping-api</artifactId>
           <version>${netconf.version}</version>
         </dependency>
+        <dependency>
+          <groupId>org.opendaylight.controller</groupId>
+          <artifactId>netconf-ssh</artifactId>
+          <version>${netconf.version}</version>
+        </dependency>
         <dependency>
           <groupId>org.opendaylight.controller</groupId>
           <artifactId>config-netconf-connector</artifactId>
           <groupId>org.opendaylight.yangtools</groupId>
           <artifactId>yang-model-api</artifactId>
          </dependency>
-
          <dependency>
           <groupId>org.opendaylight.yangtools.model</groupId>
           <artifactId>yang-ext</artifactId>
          </dependency>
-
         <dependency>
          <groupId>org.opendaylight.controller.thirdparty</groupId>
          <artifactId>ganymed</artifactId>
index bfc3962..00c46d9 100644 (file)
@@ -17,10 +17,9 @@ osgi.bundles=\
 netconf.tcp.address=0.0.0.0
 netconf.tcp.port=8383
 
-#netconf.tls.address=127.0.0.1
-#netconf.tls.port=8384
-#netconf.tls.keystore=
-#netconf.tls.keystore.password=
+
+netconf.ssh.address=0.0.0.0
+netconf.ssh.port=1830
 
 netconf.config.persister.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter
 fileStorage=configuration/controller.config
index 8b761a8..17330b7 100644 (file)
@@ -9,10 +9,6 @@ package org.opendaylight.controller.netconf.api;
 
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelHandler;
-
-import java.io.IOException;
-import java.util.Map;
-
 import org.opendaylight.protocol.framework.AbstractProtocolSession;
 import org.opendaylight.protocol.framework.ProtocolMessageDecoder;
 import org.opendaylight.protocol.framework.ProtocolMessageEncoder;
@@ -20,18 +16,20 @@ import org.opendaylight.protocol.framework.SessionListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
+import java.util.Map;
+
 public abstract class NetconfSession extends AbstractProtocolSession<NetconfMessage> {
 
     private ChannelHandler exiEncoder;
     private String exiEncoderName;
     private String removeAfterMessageSentname;
     private String pmeName,pmdName;
-    private final  Channel channel;
+    protected final  Channel channel;
     private final  SessionListener sessionListener;
     private final long sessionId;
     private boolean up = false;
     private static final Logger logger = LoggerFactory.getLogger(NetconfSession.class);
-    private static final int T = 0;
 
     protected NetconfSession(SessionListener sessionListener, Channel channel, long sessionId) {
         this.sessionListener = sessionListener;
index 61a9a9b..d959774 100644 (file)
@@ -50,7 +50,6 @@ public class NetconfClient implements Closeable {
     private NetconfClient(String clientLabelForLogging, InetSocketAddress address, ReconnectStrategy strat, NetconfClientDispatcher netconfClientDispatcher) throws InterruptedException {
         this.label = clientLabelForLogging;
         dispatch = netconfClientDispatcher;
-
         sessionListener = new NetconfClientSessionListener();
         Future<NetconfClientSession> clientFuture = dispatch.createClient(address, sessionListener, strat);
         this.address = address;
index 11c7f30..c2c8d38 100644 (file)
@@ -9,14 +9,13 @@
 package org.opendaylight.controller.netconf.client;
 
 import io.netty.channel.Channel;
-
-import java.util.Collection;
-
 import org.opendaylight.controller.netconf.api.NetconfSession;
 import org.opendaylight.protocol.framework.SessionListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Collection;
+
 public class NetconfClientSession extends NetconfSession {
 
     private static final Logger logger = LoggerFactory.getLogger(NetconfClientSession.class);
@@ -32,5 +31,4 @@ public class NetconfClientSession extends NetconfSession {
     public Collection<String> getServerCapabilities() {
         return capabilities;
     }
-
 }
index 13b0a1e..410d9a9 100644 (file)
             <artifactId>yang-store-api</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>yang-test</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
             <artifactId>netconf-api</artifactId>
             <artifactId>config-netconf-connector</artifactId>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>${project.groupId}</groupId>
-            <artifactId>yang-test</artifactId>
-            <scope>test</scope>
-        </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
             <artifactId>config-manager</artifactId>
             <artifactId>netconf-mapping-api</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>netconf-ssh</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
             <artifactId>netconf-util</artifactId>
                             <goal>test</goal>
                         </goals>
                         <configuration>
-                            <includes>
-                                <include>**/org/opendaylight/controller/netconf/it/*.java</include>
-                            </includes>
                             <skip>false</skip>
+                            <argLine>-Dlogback.configurationFile=${maven.test.dest}/logback.xml</argLine>
                         </configuration>
                     </execution>
                 </executions>
index c03254d..e5b9fa3 100644 (file)
@@ -8,6 +8,8 @@
 
 package org.opendaylight.controller.netconf.it;
 
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
 import com.google.common.base.Optional;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -15,6 +17,20 @@ import io.netty.channel.ChannelFuture;
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.util.HashedWheelTimer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.management.ManagementFactory;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import javax.management.ObjectName;
+import javax.net.ssl.SSLContext;
+import javax.xml.parsers.ParserConfigurationException;
+import junit.framework.Assert;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
@@ -49,43 +65,34 @@ import org.opendaylight.controller.netconf.impl.mapping.ExiDecoderHandler;
 import org.opendaylight.controller.netconf.impl.mapping.ExiEncoderHandler;
 import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListenerImpl;
 import org.opendaylight.controller.netconf.persist.impl.ConfigPersisterNotificationHandler;
+import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
 import org.opendaylight.controller.netconf.util.test.XmlFileLoader;
 import org.opendaylight.controller.netconf.util.xml.ExiParameters;
 import org.opendaylight.controller.netconf.util.xml.XmlElement;
 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
+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 javax.management.ObjectName;
-import javax.net.ssl.SSLContext;
-import javax.xml.parsers.ParserConfigurationException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.management.ManagementFactory;
-import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
+import static java.util.Collections.emptyList;
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.internal.util.Checks.checkNotNull;
 
 public class NetconfITTest extends AbstractConfigTest {
 
-    // private static final Logger logger =
-    // LoggerFactory.getLogger(NetconfITTest.class);
+     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 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;
@@ -95,6 +102,7 @@ public class NetconfITTest extends AbstractConfigTest {
 
     private NetconfClientDispatcher clientDispatcher;
 
+
     @Before
     public void setUp() throws Exception {
         super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(getModuleFactories().toArray(
@@ -158,10 +166,16 @@ public class NetconfITTest extends AbstractConfigTest {
                 "/META-INF/yang/config-test.yang", "/META-INF/yang/config-test-impl.yang",
                 "/META-INF/yang/ietf-inet-types.yang");
         final Collection<InputStream> yangDependencies = new ArrayList<>();
+        List<String> failedToFind = new ArrayList<>();
         for (String path : paths) {
-            final InputStream is = checkNotNull(NetconfITTest.class.getResourceAsStream(path), path + " not found");
-            yangDependencies.add(is);
+            InputStream resourceAsStream = NetconfITTest.class.getResourceAsStream(path);
+            if (resourceAsStream == null) {
+                failedToFind.add(path);
+            } else {
+                yangDependencies.add(resourceAsStream);
+            }
         }
+        assertEquals("Some yang files were not found",emptyList(), failedToFind);
         return yangDependencies;
     }
 
@@ -445,4 +459,46 @@ public class NetconfITTest extends AbstractConfigTest {
         return netconfClient;
     }
 
+    private void startSSHServer() throws Exception{
+        logger.info("Creating SSH server");
+        Thread sshServerThread = new Thread(NetconfSSHServer.start(10830,tcpAddress));
+        sshServerThread.setDaemon(true);
+        sshServerThread.start();
+        logger.info("SSH server on");
+    }
+
+    @Test
+    public void sshTest() throws Exception {
+        startSSHServer();
+        logger.info("creating connection");
+        Connection conn = new Connection(sshAddress.getHostName(),sshAddress.getPort());
+        Assert.assertNotNull(conn);
+        logger.info("connection created");
+        conn.connect();
+        boolean isAuthenticated = conn.authenticateWithPassword(USERNAME,PASSWORD);
+        assertTrue(isAuthenticated);
+        logger.info("user authenticated");
+        final Session sess = conn.openSession();
+        sess.startSubSystem("netconf");
+        logger.info("user authenticated");
+        sess.getStdin().write(XmlUtil.toString(this.getConfig.getDocument()).getBytes());
+
+        new Thread(){
+           public void run(){
+               while (true){
+                 byte[] bytes = new byte[1024];
+                   int c = 0;
+                   try {
+                       c = sess.getStdout().read(bytes);
+                   } catch (IOException e) {
+                       e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
+                   }
+                   logger.info("got data:"+bytes);
+                 if (c == 0) break;
+               }
+           }
+        }.join();
+    }
+
+
 }
diff --git a/opendaylight/netconf/netconf-it/src/test/resources/logback.xml b/opendaylight/netconf/netconf-it/src/test/resources/logback.xml
new file mode 100644 (file)
index 0000000..fa467a1
--- /dev/null
@@ -0,0 +1,15 @@
+<configuration scan="true">
+
+    <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>
+
+  <logger name="org.opendaylight.controller.netconf" level="DEBUG"/>
+
+  <root level="error">
+    <appender-ref ref="STDOUT" />
+  </root>
+
+</configuration>
diff --git a/opendaylight/netconf/netconf-ssh/pom.xml b/opendaylight/netconf/netconf-ssh/pom.xml
new file mode 100644 (file)
index 0000000..794bb16
--- /dev/null
@@ -0,0 +1,84 @@
+<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">
+    <parent>
+        <artifactId>netconf-subsystem</artifactId>
+        <groupId>org.opendaylight.controller</groupId>
+        <version>0.2.3-SNAPSHOT</version>
+        <relativePath>../</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>netconf-ssh</artifactId>
+    <name>${project.artifactId}</name>
+    <packaging>bundle</packaging>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>netconf-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>netconf-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller.thirdparty</groupId>
+            <artifactId>ganymed</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>org.opendaylight.controller.netconf.osgi.NetconfSSHActivator</Bundle-Activator>
+                        <Export-Package>
+                            org.opendaylight.controller.netconf.ssh,
+                        </Export-Package>
+                        <Import-Package>
+                            com.google.common.base,
+                            com.google.common.collect,
+                            ch.ethz.ssh2,
+                            ch.ethz.ssh2.signature,
+                            io.netty.buffer,
+                            io.netty.channel,
+                            io.netty.channel.nio,
+                            io.netty.channel.socket,
+                            io.netty.util,
+                            io.netty.util.concurrent,
+                            javax.annotation,
+                            java.net,
+                            javax.net.ssl,
+                            javax.xml.namespace,
+                            javax.xml.parsers,
+                            javax.xml.xpath,
+                            org.apache.commons.io,
+                            org.opendaylight.controller.netconf.api,
+                            org.opendaylight.controller.netconf.client,
+                            org.opendaylight.controller.netconf.util,
+                            org.opendaylight.controller.netconf.util.osgi,
+                            org.opendaylight.controller.netconf.util.xml,
+                            org.opendaylight.protocol.framework,
+                            org.osgi.framework,
+                            org.slf4j,
+                            org.w3c.dom,
+                            org.xml.sax
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/osgi/NetconfSSHActivator.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/osgi/NetconfSSHActivator.java
new file mode 100644 (file)
index 0000000..6626f47
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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.osgi;
+
+import com.google.common.base.Optional;
+import java.net.InetSocketAddress;
+import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+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 listen for client connections. Each client connection creation is handled in separate
+ * {@link org.opendaylight.controller.netconf.ssh.threads.SocketThread} 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.
+ **/
+public class NetconfSSHActivator implements BundleActivator{
+
+    private NetconfSSHServer server;
+    private static final Logger logger =  LoggerFactory.getLogger(NetconfSSHActivator.class);
+
+    @Override
+    public void start(BundleContext context) throws Exception {
+
+        logger.trace("Starting netconf SSH  bridge.");
+
+        Optional<InetSocketAddress> sshSocketAddressOptional = NetconfConfigUtil.extractSSHNetconfAddress(context);
+        Optional<InetSocketAddress> tcpSocketAddressOptional = NetconfConfigUtil.extractTCPNetconfAddress(context);
+
+        if (sshSocketAddressOptional.isPresent() && tcpSocketAddressOptional.isPresent()){
+            server = NetconfSSHServer.start(sshSocketAddressOptional.get().getPort(),tcpSocketAddressOptional.get());
+            Thread serverThread = new  Thread(server,"netconf SSH server thread");
+            serverThread.setDaemon(true);
+            serverThread.start();
+            logger.trace("Netconf SSH  bridge up and running.");
+        } else {
+            logger.trace("No valid connection configuration for SSH bridge found.");
+            throw new Exception("No valid connection configuration for SSH bridge found.");
+        }
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        if (server != null){
+            logger.trace("Netconf SSH bridge going down ...");
+            server.stop();
+            logger.trace("Netconf SSH bridge is down ...");
+        }
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java
new file mode 100644 (file)
index 0000000..72135cc
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.controller.netconf.ssh.threads.SocketThread;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@ThreadSafe
+public class NetconfSSHServer implements Runnable {
+
+    private static boolean acceptMore = true;
+    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 NetconfSSHServer(int serverPort,InetSocketAddress clientAddress) throws Exception{
+
+        logger.trace("Creating SSH server socket on port {}",serverPort);
+        this.ss = new ServerSocket(serverPort);
+        if (!ss.isBound()){
+            throw new Exception("Socket can't be bound to requested port :"+serverPort);
+        }
+        logger.trace("Server socket created.");
+        this.clientAddress = clientAddress;
+
+    }
+
+
+    public static NetconfSSHServer start(int serverPort, InetSocketAddress clientAddress) throws Exception {
+        return new NetconfSSHServer(serverPort, clientAddress);
+    }
+
+    public void stop() throws Exception {
+        acceptMore = false;
+        logger.trace("Closing SSH server socket.");
+        ss.close();
+        logger.trace("SSH server socket closed.");
+    }
+
+    @Override
+    public void run() {
+        while (acceptMore) {
+            logger.trace("Starting new socket thread.");
+            try {
+               SocketThread.start(ss.accept(), clientAddress, sesssionId.incrementAndGet());
+            } catch (IOException e) {
+                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
+            }
+        }
+    }
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/KeyStoreHandler.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/KeyStoreHandler.java
new file mode 100644 (file)
index 0000000..59a911b
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * 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 ch.ethz.ssh2.signature.RSAPrivateKey;
+
+public interface KeyStoreHandler {
+    public RSAPrivateKey getPrivateKey();
+}
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/RSAKey.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/RSAKey.java
new file mode 100644 (file)
index 0000000..b420b33
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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 ch.ethz.ssh2.signature.RSAPrivateKey;
+
+import java.math.BigInteger;
+
+public class RSAKey implements KeyStoreHandler {
+
+    private static RSAPrivateKey hostkey = null;
+    private static String user = "netconf";
+    private static String password = "netconf";
+    static {
+
+        BigInteger p = new BigInteger("2967886344240998436887630478678331145236162666668503940430852241825039192450179076148979094256007292741704260675085192441025058193581327559331546948442042987131728039318861235625879376246169858586459472691398815098207618446039");    //.BigInteger.probablePrime(N / 2, rnd);
+        BigInteger q = new BigInteger("4311534819291430017572425052029278681302539382618633848168923130451247487970187151403375389974616614405320169278870943605377518341666894603659873284783174749122655429409273983428000534304828056597676444751611433784228298909767"); //BigInteger.probablePrime(N / 2, rnd);
+        BigInteger phi = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));
+
+        BigInteger n = p.multiply(q);
+        BigInteger e = new BigInteger("65537");
+        BigInteger d = e.modInverse(phi);
+
+        hostkey = new RSAPrivateKey(d, e, n);
+    }
+
+    @Override
+    public RSAPrivateKey getPrivateKey() {
+        return hostkey;
+    }
+}
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
new file mode 100644 (file)
index 0000000..33ed88e
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.ServerConnection;
+import ch.ethz.ssh2.ServerSession;
+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;
+
+@ThreadSafe
+public class IOThread extends Thread {
+
+    private static final Logger logger =  LoggerFactory.getLogger(IOThread.class);
+
+    private InputStream inputStream;
+    private OutputStream outputStream;
+    private String id;
+    private ServerSession servSession;
+    private ServerConnection servconnection;
+
+
+    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());
+    }
+
+    @Override
+    public void run() {
+        logger.trace("thread {} started", super.getName());
+        try {
+            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
new file mode 100644 (file)
index 0000000..95fdd48
--- /dev/null
@@ -0,0 +1,176 @@
+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 java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.controller.netconf.ssh.authentication.RSAKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@ThreadSafe
+public class SocketThread implements Runnable, ServerAuthenticationCallback, ServerConnectionCallback
+{
+
+    private Socket socket;
+    private static final String USER = "netconf";
+    private static final String PASSWORD = "netconf";
+    private InetSocketAddress clientAddress;
+    private static final Logger logger =  LoggerFactory.getLogger(SocketThread.class);
+    private ServerConnection conn = null;
+    private long sessionId;
+
+
+    public static void start(Socket socket, InetSocketAddress clientAddress, long sessionId) throws IOException{
+        Thread netconf_ssh_socket_thread = new Thread(new SocketThread(socket,clientAddress,sessionId));
+        netconf_ssh_socket_thread.setDaemon(true);
+        netconf_ssh_socket_thread.start();
+    }
+    private SocketThread(Socket socket, InetSocketAddress clientAddress, long sessionId) throws IOException {
+
+        this.socket = socket;
+        this.clientAddress = clientAddress;
+        this.sessionId = sessionId;
+
+    }
+
+    @Override
+    public void run() {
+        conn = new ServerConnection(socket);
+        RSAKey keyStore = new RSAKey();
+        conn.setRsaHostKey(keyStore.getPrivateKey());
+        conn.setAuthenticationCallback(this);
+        conn.setServerConnectionCallback(this);
+        try {
+            conn.connect();
+        } catch (IOException e) {
+            logger.error("SocketThread error ",e);
+        }
+    }
+    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(){
+                    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");
+                                netconf_ssh_output = new IOThread(ss.getStdout(),echoSocket.getOutputStream(),"output_thread_"+sessionId,ss,conn);
+                                netconf_ssh_output.setDaemon(false);
+                                netconf_ssh_output.start();
+
+                            } catch (Throwable t){
+                                logger.error(t.getMessage(),t);
+
+                                try {
+                                    if (netconf_ssh_input!=null){
+                                        netconf_ssh_input.join();
+                                    }
+                                } catch (InterruptedException e) {
+                                   logger.error("netconf_ssh_input join error ",e);
+                                }
+
+                                try {
+                                    if (netconf_ssh_output!=null){
+                                        netconf_ssh_output.join();
+                                    }
+                                } catch (InterruptedException e) {
+                                    logger.error("netconf_ssh_output join error ",e);
+                                }
+
+                            }
+                        } else {
+                            try {
+                                ss.getStdin().write("wrong subsystem requested - closing connection".getBytes());
+                                ss.close();
+                            } catch (IOException e) {
+                                logger.debug("excpetion while sending bad subsystem response",e);
+                            }
+                        }
+                    }
+                };
+            }
+            @Override
+            public Runnable requestPtyReq(final ServerSession ss, final PtySettings pty) throws IOException
+            {
+                return new Runnable()
+                {
+                    public void run()
+                    {
+                        //noop
+                    }
+                };
+            }
+
+            @Override
+            public Runnable requestShell(final ServerSession ss) throws IOException
+            {
+                return new Runnable()
+                {
+                    public void run()
+                    {
+                        //noop
+                    }
+                };
+            }
+        };
+
+        return cb;
+    }
+
+    public String initAuthentication(ServerConnection sc)
+    {
+        return "";
+    }
+
+    public String[] getRemainingAuthMethods(ServerConnection sc)
+    {
+        return new String[] { ServerAuthenticationCallback.METHOD_PASSWORD };
+    }
+
+    public AuthenticationResult authenticateWithNone(ServerConnection sc, String username)
+    {
+        return AuthenticationResult.FAILURE;
+    }
+
+    public AuthenticationResult authenticateWithPassword(ServerConnection sc, String username, String password)
+    {
+        if (USER.equals(username) && PASSWORD.equals(password))
+            return AuthenticationResult.SUCCESS;
+
+        return AuthenticationResult.FAILURE;
+    }
+
+    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/ssh/SSHServerTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/SSHServerTest.java
new file mode 100644 (file)
index 0000000..54bc7bc
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import junit.framework.Assert;
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+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);
+
+//    @Before
+    public void startSSHServer() throws Exception{
+            logger.info("Creating SSH server");
+            NetconfSSHServer server = NetconfSSHServer.start(PORT,tcpAddress);
+            Thread sshServerThread = new Thread(server);
+            sshServerThread.setDaemon(true);
+            sshServerThread.start();
+            logger.info("SSH server on");
+    }
+
+    @Test
+    public void connect(){
+        Connection conn = new Connection(HOST,PORT);
+        Assert.assertNotNull(conn);
+        try {
+            logger.info("connecting to SSH server");
+            conn.connect();
+            logger.info("authenticating ...");
+            boolean isAuthenticated = conn.authenticateWithPassword(USER,PASSWORD);
+            Assert.assertTrue(isAuthenticated);
+            logger.info("opening session");
+            Session sess = conn.openSession();
+            logger.info("subsystem netconf");
+            sess.startSubSystem("netconf");
+            sess.getStdin().write("<?xml version=\"1.0\" encoding=\"UTF-8\"?><hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><capabilities><capability>urn:ietf:params:netconf:base:1.1</capability></capabilities></hello>]]>]]>".getBytes());
+            IOUtils.copy(sess.getStdout(), System.out);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+}
index 7606839..8a0a9cd 100644 (file)
@@ -26,7 +26,7 @@ public class NetconfConfigUtil {
     private static final String PREFIX_PROP = "netconf.";
 
     private enum InfixProp {
-        tcp, tls
+        tcp, tls, ssh
     }
 
     private static final String PORT_SUFFIX_PROP = ".port";
@@ -39,6 +39,11 @@ public class NetconfConfigUtil {
         return extractSomeNetconfAddress(context, InfixProp.tcp);
     }
 
+    public static Optional<InetSocketAddress> extractSSHNetconfAddress(BundleContext context) {
+        return extractSomeNetconfAddress(context, InfixProp.ssh);
+    }
+
+
     public static Optional<TLSConfiguration> extractTLSConfiguration(BundleContext context) {
         Optional<InetSocketAddress> address = extractSomeNetconfAddress(context, InfixProp.tls);
         if (address.isPresent()) {
index b22732e..ad83564 100644 (file)
@@ -26,6 +26,7 @@
         <module>config-persister-impl</module>
         <module>netconf-mapping-api</module>
         <module>netconf-client</module>
+        <module>netconf-ssh</module>
         <module>../../third-party/ganymed</module>
         <module>../../third-party/com.siemens.ct.exi</module>
     </modules>
                 <version>${netconf.version}</version>
                 <type>test-jar</type>
             </dependency>
+            <dependency>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>netconf-ssh</artifactId>
+                <version>${netconf.version}</version>
+            </dependency>
             <dependency>
                 <groupId>${project.groupId}</groupId>
                 <artifactId>netconf-mapping-api</artifactId>
index 98a6596..266b5a5 100644 (file)
@@ -41,7 +41,7 @@
                 <extensions>true</extensions>
                 <configuration>
                     <instructions>
-                        <Export-Package>ch.ethz.ssh2</Export-Package>
+                        <Export-Package>ch.ethz.ssh2.*</Export-Package>
                         <Embed-Dependency>ganymed-ssh2;scope=compile</Embed-Dependency>
                         <Embed-Transitive>true</Embed-Transitive>
                     </instructions>