You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2018/04/19 06:47:12 UTC
[1/2] mina-sshd git commit: [SSHD-821] Support for async keyboard
authentication
Repository: mina-sshd
Updated Branches:
refs/heads/master 5fc90bd04 -> 598c991fe
[SSHD-821] Support for async keyboard authentication
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/5c1c8a98
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/5c1c8a98
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/5c1c8a98
Branch: refs/heads/master
Commit: 5c1c8a9830ad5b622b15055bfc7205aa2fd53e98
Parents: 5fc90bd
Author: Guillaume Nodet <gn...@apache.org>
Authored: Wed Apr 18 14:17:28 2018 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Thu Apr 19 08:42:02 2018 +0200
----------------------------------------------------------------------
.../sshd/server/auth/AsyncAuthException.java | 94 ++++++++++++
.../org/apache/sshd/server/auth/UserAuth.java | 6 +-
.../auth/password/PasswordAuthenticator.java | 5 +-
.../auth/pubkey/PublickeyAuthenticator.java | 4 +-
.../server/session/ServerUserAuthService.java | 21 ++-
.../server/auth/AsyncAuthInteractiveTest.java | 106 +++++++++++++
.../apache/sshd/server/auth/AsyncAuthTest.java | 103 +++++++++++++
.../sshd/server/auth/AsyncAuthTestBase.java | 147 +++++++++++++++++++
sshd-mina/pom.xml | 2 +
9 files changed, 483 insertions(+), 5 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/main/java/org/apache/sshd/server/auth/AsyncAuthException.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/AsyncAuthException.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/AsyncAuthException.java
new file mode 100644
index 0000000..0a95986
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/AsyncAuthException.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.auth;
+
+import java.lang.reflect.Array;
+import java.util.function.Consumer;
+
+import org.apache.sshd.common.RuntimeSshException;
+
+/**
+ *
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class AsyncAuthException extends RuntimeSshException {
+
+ private static final long serialVersionUID = 6741236101797649869L;
+
+ protected Object listener;
+ protected Boolean authed;
+
+ public AsyncAuthException() {
+ super();
+ }
+
+ public void setAuthed(boolean authed) {
+ Object listener;
+ synchronized (this) {
+ if (this.authed != null) {
+ return;
+ }
+ this.authed = authed;
+ listener = this.listener;
+ }
+ if (listener != null) {
+ if (listener instanceof Consumer) {
+ asListener(listener).accept(authed);
+ } else {
+ int l = Array.getLength(listener);
+ for (int i = 0; i < l; i++) {
+ Consumer<Boolean> lst = asListener(Array.get(listener, i));
+ if (lst != null) {
+ lst.accept(authed);
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected static Consumer<Boolean> asListener(Object listener) {
+ return (Consumer<Boolean>) listener;
+ }
+
+ public void addListener(Consumer<Boolean> listener) {
+ Boolean result;
+ synchronized (this) {
+ if (this.listener == null) {
+ this.listener = listener;
+ } else if (this.listener instanceof Consumer) {
+ this.listener = new Object[] {this.listener, listener };
+ } else {
+ Object[] ol = (Object[]) this.listener;
+ int l = ol.length;
+ Object[] nl = new Object[l + 1];
+ System.arraycopy(ol, 0, nl, 0, l);
+ nl[l] = listener;
+ this.listener = nl;
+ }
+ result = this.authed;
+ }
+ if (result != null) {
+ listener.accept(result);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java
index 91b919c..d26da38 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java
@@ -42,9 +42,10 @@ public interface UserAuth extends ServerSessionHolder, UserAuthInstance<ServerSe
* @param buffer the request buffer containing parameters specific to this request
* @return <code>true</code> if the authentication succeeded, <code>false</code> if the authentication
* failed and {@code null} if not finished yet
+ * @throws AsyncAuthException if the service is willing to perform an asynchronous authentication
* @throws Exception if the authentication fails
*/
- Boolean auth(ServerSession session, String username, String service, Buffer buffer) throws Exception;
+ Boolean auth(ServerSession session, String username, String service, Buffer buffer) throws AsyncAuthException, Exception;
/**
* Handle another step in the authentication process.
@@ -52,9 +53,10 @@ public interface UserAuth extends ServerSessionHolder, UserAuthInstance<ServerSe
* @param buffer the request buffer containing parameters specific to this request
* @return <code>true</code> if the authentication succeeded, <code>false</code> if the authentication
* failed and {@code null} if not finished yet
+ * @throws AsyncAuthException if the service is willing to perform an asynchronous authentication
* @throws Exception if the authentication fails
*/
- Boolean next(Buffer buffer) throws Exception;
+ Boolean next(Buffer buffer) throws AsyncAuthException, Exception;
/**
* Free any system resources used by the module.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordAuthenticator.java
index 902c128..fcc91e1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordAuthenticator.java
@@ -18,6 +18,7 @@
*/
package org.apache.sshd.server.auth.password;
+import org.apache.sshd.server.auth.AsyncAuthException;
import org.apache.sshd.server.session.ServerSession;
/**
@@ -36,6 +37,8 @@ public interface PasswordAuthenticator {
* @return {@code true} indicating if authentication succeeded
* @throws PasswordChangeRequiredException If the password is expired or
* not strong enough to suit the server's policy
+ * @throws AsyncAuthException If the authentication is performed asynchronously
*/
- boolean authenticate(String username, String password, ServerSession session) throws PasswordChangeRequiredException;
+ boolean authenticate(String username, String password, ServerSession session)
+ throws PasswordChangeRequiredException, AsyncAuthException;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
index 5146ea7..2d7a908 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
@@ -20,6 +20,7 @@ package org.apache.sshd.server.auth.pubkey;
import java.security.PublicKey;
+import org.apache.sshd.server.auth.AsyncAuthException;
import org.apache.sshd.server.session.ServerSession;
/**
@@ -38,6 +39,7 @@ public interface PublickeyAuthenticator {
* @param key the key
* @param session the server session
* @return a boolean indicating if authentication succeeded or not
+ * @throws AsyncAuthException If the authentication is performed asynchronously
*/
- boolean authenticate(String username, PublicKey key, ServerSession session);
+ boolean authenticate(String username, PublicKey key, ServerSession session) throws AsyncAuthException;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
index 28ea4d4..2c72b6d 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
@@ -55,6 +55,7 @@ import org.apache.sshd.common.util.closeable.AbstractCloseable;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.server.ServerAuthenticationManager;
import org.apache.sshd.server.ServerFactoryManager;
+import org.apache.sshd.server.auth.AsyncAuthException;
import org.apache.sshd.server.auth.UserAuth;
import org.apache.sshd.server.auth.UserAuthNoneFactory;
import org.apache.sshd.server.auth.WelcomeBannerPhase;
@@ -143,7 +144,7 @@ public class ServerUserAuthService extends AbstractCloseable implements Service,
}
@Override
- public void process(int cmd, Buffer buffer) throws Exception {
+ public synchronized void process(int cmd, Buffer buffer) throws Exception {
Boolean authed = Boolean.FALSE;
ServerSession session = getServerSession();
boolean debugEnabled = log.isDebugEnabled();
@@ -196,6 +197,9 @@ public class ServerUserAuthService extends AbstractCloseable implements Service,
currentAuth = ValidateUtils.checkNotNull(factory.create(), "No authenticator created for method=%s", method);
try {
authed = currentAuth.auth(session, username, service, buffer);
+ } catch (AsyncAuthException async) {
+ async.addListener(authenticated -> asyncAuth(cmd, buffer, authenticated));
+ return;
} catch (Exception e) {
if (debugEnabled) {
log.debug("process({}) Failed ({}) to authenticate using factory method={}: {}",
@@ -228,6 +232,9 @@ public class ServerUserAuthService extends AbstractCloseable implements Service,
buffer.rpos(buffer.rpos() - 1);
try {
authed = currentAuth.next(buffer);
+ } catch (AsyncAuthException async) {
+ async.addListener(authenticated -> asyncAuth(cmd, buffer, authenticated));
+ return;
} catch (Exception e) {
// Continue
if (debugEnabled) {
@@ -249,6 +256,18 @@ public class ServerUserAuthService extends AbstractCloseable implements Service,
}
}
+ protected synchronized void asyncAuth(int cmd, Buffer buffer, boolean authed) {
+ try {
+ if (authed) {
+ handleAuthenticationSuccess(cmd, buffer);
+ } else {
+ handleAuthenticationFailure(cmd, buffer);
+ }
+ } catch (Exception e) {
+ log.warn("Error performing async authentication: {}", e.getMessage(), e);
+ }
+ }
+
protected void handleAuthenticationInProgress(int cmd, Buffer buffer) throws Exception {
String username = (currentAuth == null) ? null : currentAuth.getUsername();
if (log.isDebugEnabled()) {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthInteractiveTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthInteractiveTest.java b/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthInteractiveTest.java
new file mode 100644
index 0000000..42763ef
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthInteractiveTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.auth;
+
+import com.jcraft.jsch.ChannelShell;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.UserInfo;
+
+import org.junit.FixMethodOrder;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class AsyncAuthInteractiveTest extends AsyncAuthTestBase {
+
+ public AsyncAuthInteractiveTest() {
+ super();
+ }
+
+ protected boolean authenticate() throws Exception {
+
+ JSch jsch = new JSch();
+ Session session;
+ ChannelShell channel;
+
+ session = jsch.getSession("whatever", "localhost", port);
+ session.setUserInfo(new UserInfo() {
+ @Override
+ public String getPassphrase() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPassword() {
+ return "whocares";
+ }
+
+ @Override
+ public boolean promptPassword(String s) {
+ return true;
+ }
+
+ @Override
+ public boolean promptPassphrase(String s) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean promptYesNo(String s) {
+ return true;
+ }
+
+ @Override
+ public void showMessage(String s) {
+ // Do nothing
+ }
+ });
+ try {
+ session.connect();
+ } catch (JSchException e) {
+ switch (e.getMessage()) {
+ case "Auth cancel":
+ case "Auth fail":
+ return false;
+ default:
+ throw e;
+ }
+ }
+ channel = (ChannelShell) session.openChannel("shell");
+ channel.connect();
+
+ try {
+ channel.disconnect();
+ } catch (Exception ignore) {
+ // ignore
+ }
+
+ try {
+ session.disconnect();
+ } catch (Exception ignore) {
+ // ignore
+ }
+
+ return true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTest.java b/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTest.java
new file mode 100644
index 0000000..5f8f591
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTest.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.auth;
+
+import com.jcraft.jsch.ChannelShell;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.UserInfo;
+
+import org.junit.FixMethodOrder;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class AsyncAuthTest extends AsyncAuthTestBase {
+
+ public AsyncAuthTest() {
+ super();
+ }
+
+ protected boolean authenticate() throws Exception {
+
+ JSch jsch = new JSch();
+ Session session;
+ ChannelShell channel;
+
+ session = jsch.getSession("whatever", "localhost", port);
+ session.setPassword("whocares");
+ session.setUserInfo(new UserInfo() {
+ @Override
+ public String getPassphrase() {
+ return null;
+ }
+
+ @Override
+ public String getPassword() {
+ return null;
+ }
+
+ @Override
+ public boolean promptPassword(String s) {
+ return false;
+ }
+
+ @Override
+ public boolean promptPassphrase(String s) {
+ return false;
+ }
+
+ @Override
+ public boolean promptYesNo(String s) {
+ return true;
+ } // Accept all server keys
+
+ @Override
+ public void showMessage(String s) {
+ // Do nothing
+ }
+ });
+ try {
+ session.connect();
+ } catch (JSchException e) {
+ if (e.getMessage().equals("Auth cancel")) {
+ return false;
+ } else {
+ throw e;
+ }
+ }
+ channel = (ChannelShell) session.openChannel("shell");
+ channel.connect();
+
+ try {
+ channel.disconnect();
+ } catch (Exception ignore) {
+ }
+
+ try {
+ session.disconnect();
+ } catch (Exception ignore) {
+ }
+
+ return true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTestBase.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTestBase.java b/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTestBase.java
new file mode 100644
index 0000000..4328117
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTestBase.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.auth;
+
+import java.io.File;
+
+import com.jcraft.jsch.JSchException;
+
+import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.password.PasswordAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.EchoShellFactory;
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AsyncAuthTestBase extends BaseTestSupport {
+
+ SshServer server;
+ int port;
+
+ private PasswordAuthenticator authenticator;
+
+ public AsyncAuthTestBase() {
+ super();
+ }
+
+ public void startServer() throws Exception {
+ startServer(null);
+ }
+
+ public void startServer(Integer timeout) throws Exception {
+ if (server != null) {
+ fail("Server already started");
+ }
+ server = SshServer.setUpDefaultServer();
+ if (timeout != null) {
+ server.getProperties().put(FactoryManager.AUTH_TIMEOUT, timeout.toString());
+ }
+ server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath()));
+ server.setPasswordAuthenticator((username, password, session) -> authenticator.authenticate(username, password, session));
+ server.setShellFactory(new EchoShellFactory());
+ server.start();
+ port = server.getPort();
+ }
+
+ @After
+ public void stopServer() throws Exception {
+ if (server != null) {
+ server.stop();
+ }
+ server = null;
+ }
+
+ @Test
+ public void testSyncAuthFailed() throws Exception {
+ startServer();
+ authenticator = (username, x, sess) -> false;
+ assertFalse(authenticate());
+ }
+
+ @Test
+ public void testSyncAuthSucceeded() throws Exception {
+ startServer();
+ authenticator = (username, x, sess) -> true;
+ assertTrue(authenticate());
+ }
+
+ @Test
+ public void testAsyncAuthFailed() throws Exception {
+ startServer();
+ authenticator = (username, x, sess) -> async(200, false);
+ assertFalse(authenticate());
+ }
+
+ @Test
+ public void testAsyncAuthSucceeded() throws Exception {
+ startServer();
+ authenticator = (username, x, sess) -> async(200, true);
+ assertTrue(authenticate());
+ }
+
+ @Test
+ public void testAsyncAuthTimeout() throws Exception {
+ startServer(500);
+ authenticator = (username, x, sess) -> asyncTimeout();
+ try {
+ authenticate();
+ } catch (JSchException e) {
+ assertTrue("Unexpected failure " + e.getMessage(), e.getMessage().startsWith("SSH_MSG_DISCONNECT"));
+ }
+ }
+
+ @Test
+ public void testAsyncAuthSucceededAfterTimeout() throws Exception {
+ startServer(500);
+ authenticator = (username, x, sess) -> async(1000, true);
+ try {
+ authenticate();
+ } catch (JSchException e) {
+ assertTrue("Unexpected failure " + e.getMessage(), e.getMessage().startsWith("SSH_MSG_DISCONNECT"));
+ }
+ }
+
+ private boolean asyncTimeout() {
+ throw new AsyncAuthException();
+ }
+
+ private boolean async(int delay, boolean result) {
+ AsyncAuthException auth = new AsyncAuthException();
+ new Thread(() -> doAsync(delay, result, auth)).start();
+ throw auth;
+ }
+
+ private void doAsync(int delay, boolean result, AsyncAuthException auth) {
+ try {
+ Thread.sleep(delay);
+ } catch (InterruptedException ignore) {
+ // ignore
+ } finally {
+ auth.setAuthed(result);
+ }
+ }
+
+ protected abstract boolean authenticate() throws Exception;
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-mina/pom.xml
----------------------------------------------------------------------
diff --git a/sshd-mina/pom.xml b/sshd-mina/pom.xml
index 1000f8a..bb62820 100644
--- a/sshd-mina/pom.xml
+++ b/sshd-mina/pom.xml
@@ -180,6 +180,8 @@
<exclude>**/MacTest.java</exclude>
<exclude>**/SpringConfigTest.java</exclude>
<exclude>**/ConcurrentConnectionTest.java</exclude>
+ <exclude>**/AsyncAuthTest.java</exclude>
+ <exclude>**/AsyncAuthInteractiveTest.java</exclude>
</excludes>
<!-- No need to re-run core tests that do not involve session creation -->
<excludedGroups>org.apache.sshd.util.test.NoIoTestCase</excludedGroups>
[2/2] mina-sshd git commit: [SSHD-817] Netty nio provider
Posted by gn...@apache.org.
[SSHD-817] Netty nio provider
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/598c991f
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/598c991f
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/598c991f
Branch: refs/heads/master
Commit: 598c991fe4cc609c43f972fe025775fcf734933b
Parents: 5c1c8a9
Author: Guillaume Nodet <gn...@apache.org>
Authored: Tue Apr 17 22:16:55 2018 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Thu Apr 19 08:43:30 2018 +0200
----------------------------------------------------------------------
pom.xml | 1 +
sshd-netty/pom.xml | 214 +++++++++++++++++++
.../org/apache/sshd/netty/NettyIoAcceptor.java | 174 +++++++++++++++
.../org/apache/sshd/netty/NettyIoConnector.java | 125 +++++++++++
.../org/apache/sshd/netty/NettyIoService.java | 55 +++++
.../sshd/netty/NettyIoServiceFactory.java | 75 +++++++
.../netty/NettyIoServiceFactoryFactory.java | 49 +++++
.../org/apache/sshd/netty/NettyIoSession.java | 211 ++++++++++++++++++
.../org/apache/sshd/netty/NettySupport.java | 45 ++++
...pache.sshd.common.io.IoServiceFactoryFactory | 20 ++
10 files changed, 969 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index e3f9353..1d75f92 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1007,6 +1007,7 @@
<module>sshd-core</module>
<module>sshd-sftp</module>
<module>sshd-mina</module>
+ <module>sshd-netty</module>
<module>sshd-ldap</module>
<module>sshd-git</module>
<module>sshd-contrib</module>
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/pom.xml
----------------------------------------------------------------------
diff --git a/sshd-netty/pom.xml b/sshd-netty/pom.xml
new file mode 100644
index 0000000..fdb0f3e
--- /dev/null
+++ b/sshd-netty/pom.xml
@@ -0,0 +1,214 @@
+<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/maven-v4_0_0.xsd">
+
+
+ <!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd</artifactId>
+ <version>1.7.1-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+
+ <artifactId>sshd-netty</artifactId>
+ <name>Apache Mina SSHD :: Netty</name>
+ <packaging>jar</packaging>
+ <inceptionYear>2008</inceptionYear>
+
+ <properties>
+ <projectRoot>${project.basedir}/..</projectRoot>
+ <netty.version>4.1.1.Final</netty.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty-transport</artifactId>
+ <version>${netty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty-handler</artifactId>
+ <version>${netty.version}</version>
+ </dependency>
+
+ <!-- test dependencies -->
+ <dependency>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd-core</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd-sftp</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.i2p.crypto</groupId>
+ <artifactId>eddsa</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.jcraft</groupId>
+ <artifactId>jsch</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.jcraft</groupId>
+ <artifactId>jzlib</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-httpclient</groupId>
+ <artifactId>commons-httpclient</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.ethz.ganymed</groupId>
+ <artifactId>ganymed-ssh2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.servicemix.bundles</groupId>
+ <artifactId>org.apache.servicemix.bundles.not-yet-commons-ssl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <testSourceDirectory>${build.directory}/test-sources</testSourceDirectory>
+ <testResources>
+ <testResource>
+ <directory>${build.directory}/test-resources</directory>
+ </testResource>
+ </testResources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-test-resources</id>
+ <phase>generate-test-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${build.directory}/test-resources</outputDirectory>
+ <resources>
+ <resource>
+ <directory>${projectRoot}/sshd-core/src/test/resources</directory>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-test-sources</id>
+ <phase>generate-test-sources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${build.directory}/test-sources</outputDirectory>
+ <resources>
+ <resource>
+ <directory>${projectRoot}/sshd-core/src/test/java</directory>
+ <excludes>
+ <exclude>**/ProxyTest.java</exclude>
+ <exclude>**/PortForwardingTest.java</exclude>
+ <exclude>**/PortForwardingLoadTest.java</exclude>
+ </excludes>
+ </resource>
+ <resource>
+ <directory>${projectRoot}/sshd-sftp/src/test/java</directory>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <redirectTestOutputToFile>true</redirectTestOutputToFile>
+ <reportsDirectory>${project.build.directory}/surefire-reports-netty</reportsDirectory>
+ <systemProperties>
+ <org.apache.sshd.common.io.IoServiceFactoryFactory>org.apache.sshd.netty.NettyIoServiceFactoryFactory</org.apache.sshd.common.io.IoServiceFactoryFactory>
+ </systemProperties>
+ <excludes>
+ <!-- These tests use NIO explicitly -->
+ <exclude>**/*LoadTest.java</exclude>
+ <exclude>**/ProxyTest.java</exclude>
+ <exclude>**/Nio2ServiceTest.java</exclude>
+ <!-- TODO need some more research as to why this fails on MINA but not on NIO2 -->
+ <exclude>**/ClientDeadlockTest.java</exclude>
+ <exclude>**/ApacheServer*Test.java</exclude>
+ <exclude>**/ClientTest.java</exclude>
+ <exclude>**/SpaceAvailableExtensionImplTest.java</exclude>
+ </excludes>
+ <!-- No need to re-run core tests that do not involve session creation -->
+ <excludedGroups>org.apache.sshd.util.test.NoIoTestCase</excludedGroups>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <additionalparam>-Xdoclint:none</additionalparam>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoAcceptor.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoAcceptor.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoAcceptor.java
new file mode 100644
index 0000000..8d88cdf
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoAcceptor.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.netty;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.io.IoAcceptor;
+import org.apache.sshd.common.io.IoHandler;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import io.netty.util.concurrent.GlobalEventExecutor;
+
+/**
+ * The Netty based IoAcceptor implementation.
+ *
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NettyIoAcceptor extends NettyIoService implements IoAcceptor {
+
+ protected final ServerBootstrap bootstrap = new ServerBootstrap();
+ protected final DefaultCloseFuture closeFuture = new DefaultCloseFuture(toString(), lock);
+ protected final Map<SocketAddress, Channel> boundAddresses = new HashMap<>();
+ protected final IoHandler handler;
+
+ public NettyIoAcceptor(NettyIoServiceFactory factory, IoHandler handler) {
+ this.factory = factory;
+ this.handler = handler;
+ channelGroup = new DefaultChannelGroup("sshd-acceptor-channels", GlobalEventExecutor.INSTANCE);
+ bootstrap.group(factory.eventLoopGroup)
+ .channel(NioServerSocketChannel.class)
+ .option(ChannelOption.SO_BACKLOG, 100)
+ .handler(new LoggingHandler(LogLevel.INFO))
+ .childHandler(new ChannelInitializer<SocketChannel>() {
+ @Override
+ public void initChannel(SocketChannel ch) throws Exception {
+ ChannelPipeline p = ch.pipeline();
+ p.addLast(new NettyIoSession(NettyIoAcceptor.this, handler).adapter);
+ }
+ });
+ }
+
+ @Override
+ public void bind(Collection<? extends SocketAddress> addresses) throws IOException {
+ for (SocketAddress address : addresses) {
+ bind(address);
+ }
+ }
+
+ @Override
+ public void bind(SocketAddress address) throws IOException {
+ InetSocketAddress inetAddress = (InetSocketAddress) address;
+ ChannelFuture f = bootstrap.bind(inetAddress);
+ Channel channel = f.channel();
+ channelGroup.add(channel);
+ try {
+ f.sync();
+ SocketAddress bound = channel.localAddress();
+ boundAddresses.put(bound, channel);
+ channel.closeFuture().addListener(fut -> boundAddresses.remove(bound));
+ } catch (InterruptedException e) {
+ throw (InterruptedIOException) new InterruptedIOException().initCause(e);
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public void unbind(Collection<? extends SocketAddress> addresses) {
+ CountDownLatch latch = new CountDownLatch(addresses.size());
+ for (SocketAddress address : addresses) {
+ Channel channel = boundAddresses.get(address);
+ if (channel != null) {
+ ChannelFuture fut;
+ if (channel.isOpen()) {
+ fut = channel.close();
+ } else {
+ fut = channel.closeFuture();
+ }
+ fut.addListener(f -> latch.countDown());
+ } else {
+ latch.countDown();
+ }
+ }
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ @Override
+ public void unbind(SocketAddress address) {
+ Channel channel = boundAddresses.get(address);
+ if (channel != null) {
+ ChannelFuture fut;
+ if (channel.isOpen()) {
+ fut = channel.close();
+ } else {
+ fut = channel.closeFuture();
+ }
+ try {
+ fut.sync();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ @Override
+ public void unbind() {
+ Collection<SocketAddress> addresses = getBoundAddresses();
+ if (log.isDebugEnabled()) {
+ log.debug("Unbinding {}", addresses);
+ }
+
+ unbind(addresses);
+ }
+
+ @Override
+ public Set<SocketAddress> getBoundAddresses() {
+ return new HashSet<>(boundAddresses.keySet());
+ }
+
+ @Override
+ protected CloseFuture doCloseGracefully() {
+ channelGroup.close().addListener(fut -> closeFuture.setClosed());
+ return closeFuture;
+ }
+
+ @Override
+ protected void doCloseImmediately() {
+ doCloseGracefully();
+ super.doCloseImmediately();
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoConnector.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoConnector.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoConnector.java
new file mode 100644
index 0000000..ebced0d
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoConnector.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.netty;
+
+import java.net.SocketAddress;
+
+import org.apache.sshd.common.future.DefaultSshFuture;
+import org.apache.sshd.common.io.IoConnectFuture;
+import org.apache.sshd.common.io.IoConnector;
+import org.apache.sshd.common.io.IoHandler;
+import org.apache.sshd.common.io.IoSession;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import io.netty.util.concurrent.GlobalEventExecutor;
+
+/**
+ * The Netty based IoConnector implementation.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NettyIoConnector extends NettyIoService implements IoConnector {
+
+ protected final Bootstrap bootstrap = new Bootstrap();
+ protected final IoHandler handler;
+
+ public NettyIoConnector(NettyIoServiceFactory factory, IoHandler handler) {
+ this.factory = factory;
+ this.handler = handler;
+ channelGroup = new DefaultChannelGroup("sshd-connector-channels", GlobalEventExecutor.INSTANCE);
+ bootstrap.group(factory.eventLoopGroup)
+ .channel(NioSocketChannel.class)
+ .option(ChannelOption.SO_BACKLOG, 100)
+ .handler(new ChannelInitializer<SocketChannel>() {
+ @Override
+ protected void initChannel(SocketChannel ch) throws Exception {
+ NettyIoSession session = new NettyIoSession(NettyIoConnector.this, handler);
+ ChannelPipeline p = ch.pipeline();
+ p.addLast(new LoggingHandler(LogLevel.INFO));
+ p.addLast(session.adapter);
+ }
+ });
+ }
+
+ @Override
+ public IoConnectFuture connect(SocketAddress address) {
+ boolean debugEnabled = log.isDebugEnabled();
+ if (debugEnabled) {
+ log.debug("Connecting to {}", address);
+ }
+
+ IoConnectFuture future = new DefaultIoConnectFuture(address, null);
+ ChannelFuture chf = bootstrap.connect(address);
+ Channel channel = chf.channel();
+ channel.attr(CONNECT_FUTURE_KEY).set(future);
+ chf.addListener(cf -> {
+ Throwable t = chf.cause();
+ if (t != null) {
+ future.setException(t);
+ } else if (chf.isCancelled()) {
+ future.cancel();
+ }
+ });
+ return future;
+ }
+
+ public static class DefaultIoConnectFuture extends DefaultSshFuture<IoConnectFuture> implements IoConnectFuture {
+ public DefaultIoConnectFuture(Object id, Object lock) {
+ super(id, lock);
+ }
+
+ @Override
+ public IoSession getSession() {
+ Object v = getValue();
+ return v instanceof IoSession ? (IoSession) v : null;
+ }
+
+ @Override
+ public Throwable getException() {
+ Object v = getValue();
+ return v instanceof Throwable ? (Throwable) v : null;
+ }
+
+ @Override
+ public boolean isConnected() {
+ return getValue() instanceof IoSession;
+ }
+
+ @Override
+ public void setSession(IoSession session) {
+ setValue(session);
+ }
+
+ @Override
+ public void setException(Throwable exception) {
+ setValue(exception);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoService.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoService.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoService.java
new file mode 100644
index 0000000..9bd3ca3
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoService.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.netty;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.sshd.common.io.IoConnectFuture;
+import org.apache.sshd.common.io.IoService;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.util.closeable.AbstractCloseable;
+
+import io.netty.channel.group.ChannelGroup;
+import io.netty.util.AttributeKey;
+
+/**
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NettyIoService extends AbstractCloseable implements IoService {
+
+ public static final AttributeKey<IoConnectFuture> CONNECT_FUTURE_KEY = AttributeKey.valueOf(IoConnectFuture.class.getName());
+
+ protected final AtomicLong sessionSeq = new AtomicLong();
+ protected final Map<Long, IoSession> sessions = new ConcurrentHashMap<>();
+ protected NettyIoServiceFactory factory;
+ protected ChannelGroup channelGroup;
+
+ public NettyIoService() {
+ super();
+ }
+
+ @Override
+ public Map<Long, IoSession> getManagedSessions() {
+ return sessions;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactory.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactory.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactory.java
new file mode 100644
index 0000000..2bc3f97
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactory.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.netty;
+
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.io.IoAcceptor;
+import org.apache.sshd.common.io.IoConnector;
+import org.apache.sshd.common.io.IoHandler;
+import org.apache.sshd.common.io.IoServiceFactory;
+import org.apache.sshd.common.util.closeable.AbstractCloseable;
+
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+
+/**
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NettyIoServiceFactory extends AbstractCloseable implements IoServiceFactory {
+
+ protected final EventLoopGroup eventLoopGroup;
+ protected final boolean closeEventLoopGroup;
+
+ public NettyIoServiceFactory() {
+ this(null);
+ }
+
+ public NettyIoServiceFactory(EventLoopGroup group) {
+ this.eventLoopGroup = group != null ? group : new NioEventLoopGroup();
+ this.closeEventLoopGroup = group == null;
+ }
+
+ @Override
+ public IoConnector createConnector(IoHandler handler) {
+ return new NettyIoConnector(this, handler);
+ }
+
+ @Override
+ public IoAcceptor createAcceptor(IoHandler handler) {
+ return new NettyIoAcceptor(this, handler);
+ }
+
+ @Override
+ protected CloseFuture doCloseGracefully() {
+ if (closeEventLoopGroup) {
+ eventLoopGroup.shutdownGracefully().addListener(fut -> closeFuture.setClosed());
+ } else {
+ closeFuture.setClosed();
+ }
+ return closeFuture;
+ }
+
+ @Override
+ protected void doCloseImmediately() {
+ doCloseGracefully();
+ super.doCloseImmediately();
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactoryFactory.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactoryFactory.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactoryFactory.java
new file mode 100644
index 0000000..bead4aa
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactoryFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.netty;
+
+import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.io.IoServiceFactory;
+import org.apache.sshd.common.io.IoServiceFactoryFactory;
+
+import io.netty.channel.EventLoopGroup;
+
+/**
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NettyIoServiceFactoryFactory implements IoServiceFactoryFactory {
+
+ protected final EventLoopGroup eventLoopGroup;
+
+ public NettyIoServiceFactoryFactory() {
+ this(null);
+ }
+
+ public NettyIoServiceFactoryFactory(EventLoopGroup eventLoopGroup) {
+ this.eventLoopGroup = eventLoopGroup;
+ }
+
+ @Override
+ public IoServiceFactory create(FactoryManager manager) {
+ return new NettyIoServiceFactory(eventLoopGroup);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoSession.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoSession.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoSession.java
new file mode 100644
index 0000000..57aecc6
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoSession.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.netty;
+
+import java.net.SocketAddress;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.io.AbstractIoWriteFuture;
+import org.apache.sshd.common.io.IoConnectFuture;
+import org.apache.sshd.common.io.IoHandler;
+import org.apache.sshd.common.io.IoService;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.io.IoWriteFuture;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.closeable.AbstractCloseable;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelPromise;
+
+/**
+ * The Netty based IoSession implementation.
+ *
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NettyIoSession extends AbstractCloseable implements IoSession {
+
+ protected final Map<Object, Object> attributes = new HashMap<>();
+ protected final NettyIoService service;
+ protected final IoHandler handler;
+ protected final DefaultCloseFuture closeFuture = new DefaultCloseFuture(toString(), lock);
+ protected final long id;
+ protected ChannelHandlerContext context;
+ protected SocketAddress remoteAddr;
+ protected ChannelFuture prev;
+ protected final ChannelInboundHandlerAdapter adapter = new Adapter();
+
+ public NettyIoSession(NettyIoService service, IoHandler handler) {
+ this.service = service;
+ this.handler = handler;
+ this.id = service.sessionSeq.incrementAndGet();
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ @Override
+ public Object getAttribute(Object key) {
+ return attributes.get(key);
+ }
+
+ @Override
+ public Object setAttribute(Object key, Object value) {
+ return attributes.put(key, value);
+ }
+
+ @Override
+ public Object setAttributeIfAbsent(Object key, Object value) {
+ return attributes.putIfAbsent(key, value);
+ }
+
+ @Override
+ public Object removeAttribute(Object key) {
+ return attributes.remove(key);
+ }
+
+ @Override
+ public SocketAddress getRemoteAddress() {
+ return remoteAddr;
+ }
+
+ @Override
+ public SocketAddress getLocalAddress() {
+ return context.channel().localAddress();
+ }
+
+ @Override
+ public IoWriteFuture writePacket(Buffer buffer) {
+ ByteBuf buf = Unpooled.buffer(buffer.available());
+ buf.writeBytes(buffer.array(), buffer.rpos(), buffer.available());
+ DefaultIoWriteFuture msg = new DefaultIoWriteFuture(getRemoteAddress(), null);
+ ChannelPromise next = context.newPromise();
+ prev.addListener(whatever -> {
+ if (context != null) {
+ context.writeAndFlush(buf, next);
+ }
+ });
+ prev = next;
+ next.addListener(fut -> {
+ if (fut.isSuccess()) {
+ msg.setValue(Boolean.TRUE);
+ } else {
+ msg.setValue(fut.cause());
+ }
+ });
+ return msg;
+ }
+
+ @Override
+ public IoService getService() {
+ return service;
+ }
+
+ @Override
+ protected CloseFuture doCloseGracefully() {
+ context.writeAndFlush(Unpooled.EMPTY_BUFFER).
+ addListener(ChannelFutureListener.CLOSE).
+ addListener(fut -> {
+ closeFuture.setClosed();
+ });
+ return closeFuture;
+ }
+
+ @Override
+ protected void doCloseImmediately() {
+ context.close();
+ super.doCloseImmediately();
+ }
+
+ protected void channelActive(ChannelHandlerContext ctx) throws Exception {
+ context = ctx;
+ service.channelGroup.add(ctx.channel());
+ service.sessions.put(id, NettyIoSession.this);
+ prev = context.newPromise().setSuccess();
+ remoteAddr = context.channel().remoteAddress();
+ handler.sessionCreated(NettyIoSession.this);
+ IoConnectFuture future = ctx.channel().attr(NettyIoService.CONNECT_FUTURE_KEY).get();
+ if (future != null) {
+ future.setSession(NettyIoSession.this);
+ }
+ }
+
+ protected void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ service.sessions.remove(id);
+ handler.sessionClosed(NettyIoSession.this);
+ context = null;
+ }
+
+ protected void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ ByteBuf buf = (ByteBuf) msg;
+ handler.messageReceived(NettyIoSession.this, NettySupport.asReadable(buf));
+ }
+
+ protected void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ handler.exceptionCaught(NettyIoSession.this, cause);
+ }
+
+ protected static class DefaultIoWriteFuture extends AbstractIoWriteFuture {
+
+ public DefaultIoWriteFuture(Object id, Object lock) {
+ super(id, lock);
+ }
+ }
+
+ /**
+ * Simple netty adapter to use as a bridge.
+ */
+ protected class Adapter extends ChannelInboundHandlerAdapter {
+
+ public Adapter() {
+ super();
+ }
+
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ NettyIoSession.this.channelActive(ctx);
+ }
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ NettyIoSession.this.channelInactive(ctx);
+ }
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ NettyIoSession.this.channelRead(ctx, msg);
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ NettyIoSession.this.exceptionCaught(ctx, cause);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettySupport.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettySupport.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettySupport.java
new file mode 100644
index 0000000..9f9de89
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettySupport.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.netty;
+
+import org.apache.sshd.common.util.Readable;
+
+import io.netty.buffer.ByteBuf;
+
+public final class NettySupport {
+
+ private NettySupport() {
+ throw new UnsupportedOperationException("No instance allowed");
+ }
+
+ public static Readable asReadable(final ByteBuf buffer) {
+ return new Readable() {
+ @Override
+ public int available() {
+ return buffer.readableBytes();
+ }
+
+ @Override
+ public void getRawBytes(byte[] data, int offset, int len) {
+ buffer.getBytes(0, data, offset, len);
+ }
+ };
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/resources/META-INF/services/org.apache.sshd.common.io.IoServiceFactoryFactory
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/resources/META-INF/services/org.apache.sshd.common.io.IoServiceFactoryFactory b/sshd-netty/src/main/resources/META-INF/services/org.apache.sshd.common.io.IoServiceFactoryFactory
new file mode 100644
index 0000000..be71649
--- /dev/null
+++ b/sshd-netty/src/main/resources/META-INF/services/org.apache.sshd.common.io.IoServiceFactoryFactory
@@ -0,0 +1,20 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements. See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership. The ASF licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License. You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing,
+## software distributed under the License is distributed on an
+## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+## KIND, either express or implied. See the License for the
+## specific language governing permissions and limitations
+## under the License.
+##
+
+org.apache.sshd.netty.NettyIoServiceFactoryFactory