From: Ed Warnicke Date: Thu, 28 Nov 2013 11:50:52 +0000 (+0000) Subject: Merge "MD-SAL StatisticsManager- Stopping statistics thread until i fix all the relev... X-Git-Tag: jenkins-controller-bulk-release-prepare-only-2-1~316 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=549b3260a884bf1801d6ea1a0f4ffb7bb5ed2bf5;hp=8582d8c0a0bde5aaaefc870ba77a33834d78a660 Merge "MD-SAL StatisticsManager- Stopping statistics thread until i fix all the relevant issue from openflowplugin" --- diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index 5b08af79f8..5f2651659d 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -244,6 +244,11 @@ netconf-mapping-api ${netconf.version} + + org.opendaylight.controller + netconf-ssh + ${netconf.version} + org.opendaylight.controller config-netconf-connector @@ -407,12 +412,10 @@ org.opendaylight.yangtools yang-model-api - org.opendaylight.yangtools.model yang-ext - org.opendaylight.controller.thirdparty ganymed diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini b/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini index bfc3962040..00c46d9529 100644 --- a/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini +++ b/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini @@ -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 diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSession.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSession.java index 8b761a85b2..17330b7bab 100644 --- a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSession.java +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSession.java @@ -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 { 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; diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClient.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClient.java index 61a9a9b954..d95977492a 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClient.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClient.java @@ -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 clientFuture = dispatch.createClient(address, sessionListener, strat); this.address = address; diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSession.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSession.java index 11c7f3061f..c2c8d38b9a 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSession.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSession.java @@ -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 getServerCapabilities() { return capabilities; } - } diff --git a/opendaylight/netconf/netconf-it/pom.xml b/opendaylight/netconf/netconf-it/pom.xml index 13b0a1e570..410d9a96aa 100644 --- a/opendaylight/netconf/netconf-it/pom.xml +++ b/opendaylight/netconf/netconf-it/pom.xml @@ -30,6 +30,11 @@ yang-store-api test + + ${project.groupId} + yang-test + test + ${project.groupId} netconf-api @@ -50,11 +55,6 @@ config-netconf-connector test - - ${project.groupId} - yang-test - test - ${project.groupId} config-manager @@ -81,6 +81,11 @@ netconf-mapping-api test + + ${project.groupId} + netconf-ssh + test + ${project.groupId} netconf-util @@ -141,10 +146,8 @@ test - - **/org/opendaylight/controller/netconf/it/*.java - false + -Dlogback.configurationFile=${maven.test.dest}/logback.xml diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java index c03254dba2..e5b9fa3ffc 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java @@ -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 yangDependencies = new ArrayList<>(); + List 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 index 0000000000..fa467a1080 --- /dev/null +++ b/opendaylight/netconf/netconf-it/src/test/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + %date{"yyyy-MM-dd HH:mm:ss.SSS z"} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/opendaylight/netconf/netconf-ssh/pom.xml b/opendaylight/netconf/netconf-ssh/pom.xml new file mode 100644 index 0000000000..794bb16605 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/pom.xml @@ -0,0 +1,84 @@ + + + netconf-subsystem + org.opendaylight.controller + 0.2.3-SNAPSHOT + ../ + + 4.0.0 + netconf-ssh + ${project.artifactId} + bundle + + + + + ${project.groupId} + netconf-util + + + ${project.groupId} + netconf-api + + + org.slf4j + slf4j-api + + + org.opendaylight.controller.thirdparty + ganymed + + + commons-io + commons-io + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.netconf.osgi.NetconfSSHActivator + + org.opendaylight.controller.netconf.ssh, + + + 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 + + + + + + + + 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 index 0000000000..6626f47b03 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/osgi/NetconfSSHActivator.java @@ -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 sshSocketAddressOptional = NetconfConfigUtil.extractSSHNetconfAddress(context); + Optional 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 index 0000000000..72135cc7dc --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java @@ -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 index 0000000000..59a911b207 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/KeyStoreHandler.java @@ -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 index 0000000000..b420b33a7b --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/RSAKey.java @@ -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 index 0000000000..33ed88edf8 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/IOThread.java @@ -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 index 0000000000..95fdd48bfe --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java @@ -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 index 0000000000..54bc7bc4b6 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/SSHServerTest.java @@ -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("urn:ietf:params:netconf:base:1.1]]>]]>".getBytes()); + IOUtils.copy(sess.getStdout(), System.out); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java index 76068399c1..8a0a9cd80e 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java @@ -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 extractSSHNetconfAddress(BundleContext context) { + return extractSomeNetconfAddress(context, InfixProp.ssh); + } + + public static Optional extractTLSConfiguration(BundleContext context) { Optional address = extractSomeNetconfAddress(context, InfixProp.tls); if (address.isPresent()) { diff --git a/opendaylight/netconf/pom.xml b/opendaylight/netconf/pom.xml index b22732e630..ad8356431e 100644 --- a/opendaylight/netconf/pom.xml +++ b/opendaylight/netconf/pom.xml @@ -26,6 +26,7 @@ config-persister-impl netconf-mapping-api netconf-client + netconf-ssh ../../third-party/ganymed ../../third-party/com.siemens.ct.exi @@ -139,6 +140,11 @@ ${netconf.version} test-jar + + ${project.groupId} + netconf-ssh + ${netconf.version} + ${project.groupId} netconf-mapping-api diff --git a/third-party/ganymed/pom.xml b/third-party/ganymed/pom.xml index 98a6596e0e..266b5a560a 100644 --- a/third-party/ganymed/pom.xml +++ b/third-party/ganymed/pom.xml @@ -41,7 +41,7 @@ true - ch.ethz.ssh2 + ch.ethz.ssh2.* ganymed-ssh2;scope=compile true