Add workaround for SSH connection issue related to SSHD-1028 17/93117/1
authorTibor Král <tibor.kral@pantheon.tech>
Mon, 26 Oct 2020 03:03:02 +0000 (04:03 +0100)
committerRobert Varga <nite@hq.sk>
Mon, 26 Oct 2020 21:31:59 +0000 (21:31 +0000)
Provide custom version of Nio2Session which overrides the
bugged method.

JIRA: NETCONF-736
Change-Id: I0ef091680a9bdbe6bcab889335bb5ff48e91b703
Signed-off-by: Tibor Král <tibor.kral@pantheon.tech>
(cherry picked from commit 28925cdfe32f72ebcd034c9c7c9c107f176ca452)

netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/client/NetconfSshClient.java
netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/sshd1028/NetconfNio2Connector.java [new file with mode: 0644]
netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/sshd1028/NetconfNio2ServiceFactory.java [new file with mode: 0644]
netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/sshd1028/NetconfNio2ServiceFactoryFactory.java [new file with mode: 0644]
netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/sshd1028/NetconfNio2Session.java [new file with mode: 0644]

index d5d0d97aa955636191257b0ef4e8e792fbb1a9ae..b4f318ebd25e2785baf5c388a1bd89ba0ad41ba0 100644 (file)
@@ -8,8 +8,11 @@
 package org.opendaylight.netconf.nettyutil.handler.ssh.client;
 
 import com.google.common.annotations.Beta;
+import org.opendaylight.netconf.nettyutil.handler.ssh.sshd1028.NetconfNio2ServiceFactoryFactory;
 import org.opendaylight.netconf.shaded.sshd.client.SshClient;
 import org.opendaylight.netconf.shaded.sshd.common.Factory;
+import org.opendaylight.netconf.shaded.sshd.common.io.IoConnector;
+
 
 /**
  * An extension to {@link SshClient} which uses {@link NetconfSessionFactory} to create sessions (leading towards
@@ -18,9 +21,20 @@ import org.opendaylight.netconf.shaded.sshd.common.Factory;
 @Beta
 public class NetconfSshClient extends SshClient {
     public static final Factory<SshClient> DEFAULT_NETCONF_SSH_CLIENT_FACTORY = NetconfSshClient::new;
+    private final NetconfNio2ServiceFactoryFactory nio2ServiceFactoryFactory;
+
+    public NetconfSshClient() {
+        this.nio2ServiceFactoryFactory = new NetconfNio2ServiceFactoryFactory();
+    }
 
     @Override
     protected NetconfSessionFactory createSessionFactory() {
         return new NetconfSessionFactory(this);
     }
+
+    @Override
+    protected IoConnector createConnector() {
+        setIoServiceFactoryFactory(nio2ServiceFactoryFactory);
+        return getIoServiceFactory().createConnector(getSessionFactory());
+    }
 }
diff --git a/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/sshd1028/NetconfNio2Connector.java b/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/sshd1028/NetconfNio2Connector.java
new file mode 100644 (file)
index 0000000..c565ac2
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.netconf.nettyutil.handler.ssh.sshd1028;
+
+import java.nio.channels.AsynchronousChannelGroup;
+import java.nio.channels.AsynchronousSocketChannel;
+import org.opendaylight.netconf.shaded.sshd.common.FactoryManager;
+import org.opendaylight.netconf.shaded.sshd.common.io.IoHandler;
+import org.opendaylight.netconf.shaded.sshd.common.io.nio2.Nio2Connector;
+import org.opendaylight.netconf.shaded.sshd.common.io.nio2.Nio2Session;
+
+/**
+ * Custom Nio2Connector which uses NetconfNio2Session instead of Nio2Session.
+ * Should be removed when SSHD-1028 is fixed.
+ */
+public class NetconfNio2Connector extends Nio2Connector {
+
+    public NetconfNio2Connector(final FactoryManager manager, final IoHandler handler,
+                                final AsynchronousChannelGroup group) {
+        super(manager, handler, group);
+    }
+
+    @Override
+    protected Nio2Session createSession(final FactoryManager manager, final IoHandler handler,
+                                        final AsynchronousSocketChannel socket) throws Throwable {
+        return new NetconfNio2Session(this, manager, handler, socket, null);
+    }
+}
diff --git a/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/sshd1028/NetconfNio2ServiceFactory.java b/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/sshd1028/NetconfNio2ServiceFactory.java
new file mode 100644 (file)
index 0000000..3faeb0c
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.netconf.nettyutil.handler.ssh.sshd1028;
+
+import java.lang.reflect.Field;
+import java.nio.channels.AsynchronousChannelGroup;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import org.opendaylight.netconf.shaded.sshd.common.FactoryManager;
+import org.opendaylight.netconf.shaded.sshd.common.io.IoConnector;
+import org.opendaylight.netconf.shaded.sshd.common.io.IoHandler;
+import org.opendaylight.netconf.shaded.sshd.common.io.nio2.Nio2ServiceFactory;
+import org.opendaylight.netconf.shaded.sshd.common.util.threads.CloseableExecutorService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom Nio2ServiceFactory which creates instances of NetconfNio2Connector instead of Nio2Connector.
+ * Should be removed when SSHD-1028 is fixed.
+ */
+public class NetconfNio2ServiceFactory extends Nio2ServiceFactory {
+    private static final Logger LOG = LoggerFactory.getLogger(NetconfNio2ServiceFactory.class);
+    private static final Field FIELD_GROUP;
+
+    static {
+        final Field fieldGroup;
+        try {
+            fieldGroup = NetconfNio2ServiceFactory.class.getSuperclass().getDeclaredField("group");
+        } catch (NoSuchFieldException e) {
+            LOG.error("Cannot access the ChannelGroup from the " + "Nio2ServiceFactory");
+            throw new ExceptionInInitializerError(e);
+        }
+
+        AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
+            fieldGroup.setAccessible(true);
+            return null;
+        });
+
+        FIELD_GROUP = fieldGroup;
+    }
+
+    public NetconfNio2ServiceFactory(final FactoryManager factoryManager, final CloseableExecutorService service) {
+        super(factoryManager, service);
+    }
+
+    @Override
+    public IoConnector createConnector(final IoHandler handler) {
+        if (FIELD_GROUP != null) {
+            try {
+                final AsynchronousChannelGroup group = (AsynchronousChannelGroup)FIELD_GROUP.get(this);
+                return autowireCreatedService(new NetconfNio2Connector(getFactoryManager(), handler, group));
+            } catch (IllegalAccessException e) {
+                LOG.error("NetconfNio2Connector cannot be instanciated. Creating default Nio2Connector instead.");
+            }
+        }
+
+        return super.createConnector(handler);
+    }
+}
diff --git a/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/sshd1028/NetconfNio2ServiceFactoryFactory.java b/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/sshd1028/NetconfNio2ServiceFactoryFactory.java
new file mode 100644 (file)
index 0000000..b400e0c
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.netconf.nettyutil.handler.ssh.sshd1028;
+
+import org.opendaylight.netconf.shaded.sshd.common.FactoryManager;
+import org.opendaylight.netconf.shaded.sshd.common.io.IoServiceFactory;
+import org.opendaylight.netconf.shaded.sshd.common.io.nio2.Nio2ServiceFactoryFactory;
+
+/**
+ * Custom Nio2ServiceFactoryFactory which creates instances of NetconfNio2ServiceFactory instead of Nio2ServiceFactory.
+ * Should be removed when SSHD-1028 is fixed.
+ */
+public class NetconfNio2ServiceFactoryFactory extends Nio2ServiceFactoryFactory {
+
+    public NetconfNio2ServiceFactoryFactory() {
+        super(null);
+    }
+
+    @Override
+    public IoServiceFactory create(final FactoryManager manager) {
+        return new NetconfNio2ServiceFactory(manager, newExecutor());
+    }
+}
diff --git a/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/sshd1028/NetconfNio2Session.java b/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ssh/sshd1028/NetconfNio2Session.java
new file mode 100644 (file)
index 0000000..9c6e8cf
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.netconf.nettyutil.handler.ssh.sshd1028;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousSocketChannel;
+import org.opendaylight.netconf.shaded.sshd.common.FactoryManager;
+import org.opendaylight.netconf.shaded.sshd.common.io.IoHandler;
+import org.opendaylight.netconf.shaded.sshd.common.io.nio2.Nio2CompletionHandler;
+import org.opendaylight.netconf.shaded.sshd.common.io.nio2.Nio2Service;
+import org.opendaylight.netconf.shaded.sshd.common.io.nio2.Nio2Session;
+import org.opendaylight.netconf.shaded.sshd.common.util.Readable;
+
+/**
+ * Custom Nio2Session which fixes the issue with connections not being properly closed.
+ * Should be removed when SSHD-1028 is fixed.
+ */
+public class NetconfNio2Session extends Nio2Session {
+
+    public NetconfNio2Session(final Nio2Service service, final FactoryManager manager, final IoHandler handler,
+                              final AsynchronousSocketChannel socket, final SocketAddress acceptanceAddress)
+        throws IOException {
+        super(service, manager, handler, socket, acceptanceAddress);
+    }
+
+    /**
+     * This method in sshd-osgi:2.5.0 and 2.5.1 contains a bug. The close(true) statement was removed. We can override
+     * it making a workaround for this issue - until SSHD-1028 is fixed.
+     */
+    @Override
+    @SuppressWarnings("IllegalCatch")
+    protected void handleReadCycleCompletion(final ByteBuffer buffer, final Readable bufReader,
+                                             final Nio2CompletionHandler<Integer, Object> completionHandler,
+                                             final Integer result, final Object attachment) {
+        try {
+            boolean debugEnabled = log.isDebugEnabled();
+            if (result >= 0) {
+                if (debugEnabled) {
+                    log.debug("handleReadCycleCompletion({}) read {} bytes", this, result);
+                }
+                buffer.flip();
+                IoHandler handler = getIoHandler();
+                handler.messageReceived(this, bufReader);
+                if (!closeFuture.isClosed()) {
+                    // re-use reference for next iteration since we finished processing it
+                    buffer.clear();
+                    doReadCycle(buffer, completionHandler);
+                } else {
+                    if (debugEnabled) {
+                        log.debug("handleReadCycleCompletion({}) IoSession has been closed, stop reading", this);
+                    }
+                }
+            } else {
+                if (debugEnabled) {
+                    log.debug("handleReadCycleCompletion({}) Socket has been disconnected (result={}), closing "
+                        + "IoSession now", this, result);
+                }
+                close(true);
+            }
+        } catch (Throwable exc) {
+            completionHandler.failed(exc, attachment);
+        }
+    }
+}