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;
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 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;
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);
return capabilities;
}
+ public Channel getChannel(){
+ return channel;
+ }
+
}
<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>
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;
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;
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 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", 830);
+ private static final String USERNAME = "netconf";
+ private static final String PASSWORD = "netconf";
private NetconfMessage getConfig, getConfigCandidate, editConfig,
closeSession, startExi, stopExi;
private NetconfClientDispatcher clientDispatcher;
+
@Before
public void setUp() throws Exception {
super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(getModuleFactories().toArray(
return netconfClient;
}
+ private class TestSSHServer implements Runnable {
+ public void run() {
+ try {
+ NetconfSSHServer.start();
+ } catch (Exception e) {
+ logger.info(e.getMessage());
+ }
+ }
+ }
+ private void startSSHServer() throws Exception{
+ logger.info("Creating SSH server");
+ Thread sshServerThread = new Thread(new TestSSHServer());
+ sshServerThread.setDaemon(true);
+ sshServerThread.start();
+ logger.info("SSH server on");
+ }
+
+ @Test
+ public void sshTest() throws Exception {
+ startSSHServer();
+ Connection conn = new Connection(sshAddress.getHostName(),sshAddress.getPort());
+ Assert.assertNotNull(conn);
+ try {
+ conn.connect();
+ boolean isAuthenticated = conn.authenticateWithPassword(USERNAME,PASSWORD);
+ assertTrue(isAuthenticated);
+ Session sess = conn.openSession();
+ sess.startSubSystem("netconf");
+// sess.requestPTY("");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
}
--- /dev/null
+<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>
--- /dev/null
+<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-client</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>
+ </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,
+ javax.net.ssl,
+ javax.xml.namespace,
+ javax.xml.parsers,
+ javax.xml.xpath,
+ org.opendaylight.controller.netconf.api,
+ org.opendaylight.controller.netconf.client,
+ org.opendaylight.controller.netconf.util,
+ 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>
--- /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.osgi;
+
+import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class NetconfSSHActivator implements BundleActivator{
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ NetconfSSHServer.start();
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+
+ }
+}
--- /dev/null
+/*\r
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.\r
+ *\r
+ * This program and the accompanying materials are made available under the\r
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
+ * and is available at http://www.eclipse.org/legal/epl-v10.html\r
+ */
+package org.opendaylight.controller.netconf.ssh;
+
+import java.net.ServerSocket;
+
+public class NetconfSSHServer {
+
+ private static boolean acceptMore = true;
+ private static final int SERVER_PORT = 830;
+ private ServerSocket ss = null;
+
+ private NetconfSSHServer() throws Exception{
+ this.ss = new ServerSocket(SERVER_PORT);
+ while (acceptMore) {
+ SocketThread.start(ss.accept());
+ }
+ }
+ public static NetconfSSHServer start() throws Exception {
+ return new NetconfSSHServer();
+ }
+
+ public void stop() throws Exception {
+ ss.close();
+ }
+
+}
--- /dev/null
+package org.opendaylight.controller.netconf.ssh;
+
+
+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 com.google.common.base.Optional;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import javax.net.ssl.SSLContext;
+import org.opendaylight.controller.netconf.client.NetconfClient;
+import org.opendaylight.controller.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.controller.netconf.client.NetconfClientSession;
+import org.opendaylight.controller.netconf.ssh.authentication.RSAKey;
+import org.opendaylight.controller.netconf.ssh.handler.SSHChannelInboundHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class SocketThread implements Runnable, ServerAuthenticationCallback, ServerConnectionCallback
+{
+
+ private Socket socket;
+ private static final String USER = "netconf";
+ private static final String PASSWORD = "netconf";
+ private NetconfClient netconfClient;
+ private static final InetSocketAddress clientAddress = new InetSocketAddress("127.0.0.1", 12023);
+ private static final Logger logger = LoggerFactory.getLogger(SocketThread.class);
+
+
+ private static ServerConnection conn = null;
+
+ public static void start(Socket socket) throws IOException{
+ new Thread(new SocketThread(socket)).start();
+ }
+ private SocketThread(Socket socket) throws IOException {
+
+ this.socket = socket;
+
+ conn = new ServerConnection(socket);
+ RSAKey keyStore = new RSAKey();
+ conn.setRsaHostKey(keyStore.getPrivateKey());
+ conn.setAuthenticationCallback(this);
+ conn.setServerConnectionCallback(this);
+ conn.connect();
+ }
+
+ @Override
+ public void run() {
+ //noop
+ }
+ public ServerSessionCallback acceptSession(final ServerSession session)
+ {
+ SimpleServerSessionCallback cb = new SimpleServerSessionCallback()
+ {
+ @Override
+ public Runnable requestSubsystem(ServerSession ss, final String subsystem) throws IOException
+ {
+ return new Runnable(){
+ public void run()
+ {
+ if (subsystem.equals("netconf")){
+ logger.info("netconf subsystem received");
+ try {
+ NetconfClientDispatcher clientDispatcher = null;
+ NioEventLoopGroup nioGrup = new NioEventLoopGroup(1);
+ clientDispatcher = new NetconfClientDispatcher(Optional.<SSLContext>absent(), nioGrup, nioGrup);
+ logger.info("dispatcher created");
+ netconfClient = new NetconfClient("ssh_" + clientAddress.toString(),clientAddress,5000,clientDispatcher);
+ logger.info("netconf client created");
+ } catch (Throwable t){
+ logger.error(t.getMessage(),t);
+ }
+ }
+ }
+ };
+ }
+ @Override
+ public Runnable requestPtyReq(final ServerSession ss, final PtySettings pty) throws IOException
+ {
+ return new Runnable()
+ {
+ public void run()
+ {
+ System.out.println("Client requested " + pty.term + " pty");
+ }
+ };
+ }
+
+ @Override
+ public Runnable requestShell(final ServerSession ss) throws IOException
+ {
+ return new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ try (NetconfClientSession session = netconfClient.getClientSession())
+ {
+ session.getChannel().pipeline().addLast(new SSHChannelInboundHandler(ss));
+ byte[] bytes = new byte[1024];
+ while (true)
+ {
+ int size = ss.getStdout().read(bytes);
+ if (size < 0)
+ {
+ System.err.println("SESSION EOF");
+ return;
+ }
+ session.getChannel().write(ByteBuffer.wrap(bytes,0,size));
+ }
+ }
+
+ }
+ catch (IOException e)
+ {
+ System.err.println("SESSION DOWN");
+ e.printStackTrace();
+ }
+ }
+ };
+ }
+ };
+
+ return cb;
+ }
+
+ public String initAuthentication(ServerConnection sc)
+ {
+ return "";
+ }
+
+ public String[] getRemainingAuthMethods(ServerConnection sc)
+ {
+ return new String[] { ServerAuthenticationCallback.METHOD_PASSWORD,
+ ServerAuthenticationCallback.METHOD_PUBLICKEY };
+ }
+
+ 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;
+ }
+
+}
--- /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.ssh.authentication;
+
+import ch.ethz.ssh2.signature.RSAPrivateKey;
+
+public interface KeyStoreHandler {
+ public RSAPrivateKey getPrivateKey();
+}
--- /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.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;
+ }
+}
--- /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.ssh.handler;
+
+import ch.ethz.ssh2.ServerSession;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+
+public class SSHChannelInboundHandler extends SimpleChannelInboundHandler {
+
+ private ServerSession serverSession;
+
+ public SSHChannelInboundHandler(ServerSession serverSession) {
+ this.serverSession = serverSession;
+ }
+
+ @Override
+ protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
+ this.serverSession.getStdin().write( ((ByteBuf)msg).readBytes(((ByteBuf)msg).readableBytes()).array());
+ }
+}
--- /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.ssh;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import java.io.IOException;
+import junit.framework.Assert;
+import org.junit.Before;
+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 = 830;
+ private static final Logger logger = LoggerFactory.getLogger(SSHServerTest.class);
+
+ private class TestSSHServer implements Runnable {
+ public void run() {
+ try {
+ NetconfSSHServer.start();
+ } catch (Exception e) {
+ logger.info(e.getMessage());
+ }
+ }
+ }
+ @Before
+ public void startSSHServer() throws Exception{
+ logger.info("Creating SSH server");
+ Thread sshServerThread = new Thread(new TestSSHServer());
+ 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.requestPTY("");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
<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>