This commit is contained in:
Looly 2023-10-19 12:38:16 +08:00
parent b865d8b728
commit 7f709f2730
6 changed files with 105 additions and 126 deletions

View File

@ -13,8 +13,10 @@
package org.dromara.hutool.extra.ssh; package org.dromara.hutool.extra.ssh;
import org.dromara.hutool.core.func.Wrapper; import org.dromara.hutool.core.func.Wrapper;
import org.dromara.hutool.core.io.IORuntimeException;
import java.io.Closeable; import java.io.Closeable;
import java.net.InetSocketAddress;
/** /**
* SSH Session抽象 * SSH Session抽象
@ -29,4 +31,47 @@ public interface Session extends Wrapper<Object>, Closeable {
* @return 是否连接状态 * @return 是否连接状态
*/ */
boolean isConnected(); boolean isConnected();
// region bindPort
/**
* 绑定端口到本地 一个会话可绑定多个端口<br>
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param localPort 本地端口
* @param remoteAddress 远程主机和端口
* @return 成功与否
*/
default boolean bindLocalPort(final int localPort, final InetSocketAddress remoteAddress) {
return bindLocalPort(new InetSocketAddress(localPort), remoteAddress);
}
/**
* 绑定端口到本地 一个会话可绑定多个端口<br>
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param localAddress 本地主机和端口
* @param remoteAddress 远程主机和端口
* @return 成功与否
*/
boolean bindLocalPort(final InetSocketAddress localAddress, final InetSocketAddress remoteAddress);
/**
* 解除本地端口映射
*
* @param localPort 需要解除的本地端口
* @throws IORuntimeException 端口解绑失败异常
*/
default void unBindLocalPort(final int localPort){
unBindLocalPort(new InetSocketAddress(localPort));
}
/**
* 解除本地端口映射
*
* @param localAddress 需要解除的本地地址
*/
void unBindLocalPort(final InetSocketAddress localAddress);
// endregion
} }

View File

@ -40,7 +40,7 @@ public class GanymedSession implements Session {
private Connection connection; private Connection connection;
private final ch.ethz.ssh2.Session raw; private final ch.ethz.ssh2.Session raw;
private Map<Integer, LocalPortForwarder> localPortForwarderMap; private Map<String, LocalPortForwarder> localPortForwarderMap;
/** /**
* 构造 * 构造
@ -91,37 +91,11 @@ public class GanymedSession implements Session {
} }
} }
/** @Override
* 绑定端口到本地 一个会话可绑定多个端口<br> public boolean bindLocalPort(final InetSocketAddress localAddress, final InetSocketAddress remoteAddress) throws IORuntimeException {
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param remoteHost 远程主机
* @param remotePort 远程端口
* @param localPort 本地端口
* @return 成功与否
* @throws IORuntimeException 端口绑定失败异常
*/
public boolean bindLocalPort(final String remoteHost, final int remotePort, final int localPort) throws IORuntimeException {
return bindLocalPort(remoteHost, remotePort, Ipv4Util.LOCAL_IP, localPort);
}
/**
* 绑定端口到本地 一个会话可绑定多个端口<br>
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param remoteHost 远程主机
* @param remotePort 远程端口
* @param localHost 本地主机
* @param localPort 本地端口
* @return 成功与否
* @throws IORuntimeException 端口绑定失败异常
*/
public boolean bindLocalPort(final String remoteHost, final int remotePort, final String localHost, final int localPort) throws IORuntimeException {
final LocalPortForwarder localPortForwarder; final LocalPortForwarder localPortForwarder;
try { try {
localPortForwarder = this.connection.createLocalPortForwarder(new InetSocketAddress(localHost, localPort), remoteHost, remotePort); localPortForwarder = this.connection.createLocalPortForwarder(localAddress, remoteAddress.getHostName(), remoteAddress.getPort());
} catch (final IOException e) { } catch (final IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
} }
@ -131,23 +105,18 @@ public class GanymedSession implements Session {
} }
//加入记录 //加入记录
this.localPortForwarderMap.put(localPort, localPortForwarder); this.localPortForwarderMap.put(localAddress.toString(), localPortForwarder);
return true; return true;
} }
/** @Override
* 解除本地端口映射 public void unBindLocalPort(final InetSocketAddress localAddress) throws IORuntimeException {
*
* @param localPort 需要解除的本地端口
* @throws IORuntimeException 端口解绑失败异常
*/
public void unBindLocalPort(final int localPort) throws IORuntimeException {
if (MapUtil.isEmpty(this.localPortForwarderMap)) { if (MapUtil.isEmpty(this.localPortForwarderMap)) {
return; return;
} }
final LocalPortForwarder localPortForwarder = this.localPortForwarderMap.remove(localPort); final LocalPortForwarder localPortForwarder = this.localPortForwarderMap.remove(localAddress.toString());
if (null != localPortForwarder) { if (null != localPortForwarder) {
try { try {
localPortForwarder.close(); localPortForwarder.close();

View File

@ -12,10 +12,12 @@
package org.dromara.hutool.extra.ssh.engine.jsch; package org.dromara.hutool.extra.ssh.engine.jsch;
import com.jcraft.jsch.*; import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.JSchException;
import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.io.IoUtil; import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.net.Ipv4Util;
import org.dromara.hutool.core.util.ByteUtil; import org.dromara.hutool.core.util.ByteUtil;
import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.core.util.CharsetUtil;
import org.dromara.hutool.extra.ssh.Connector; import org.dromara.hutool.extra.ssh.Connector;
@ -25,6 +27,7 @@ import org.dromara.hutool.extra.ssh.SshException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.Charset; import java.nio.charset.Charset;
/** /**
@ -70,53 +73,23 @@ public class JschSession implements Session {
JschUtil.close(this.raw); JschUtil.close(this.raw);
} }
/** @Override
* 绑定端口到本地 一个会话可绑定多个端口<br> public boolean bindLocalPort(final InetSocketAddress localAddress, final InetSocketAddress remoteAddress) throws SshException {
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param remoteHost 远程主机
* @param remotePort 远程端口
* @param localPort 本地端口
* @return 成功与否
* @throws SshException 端口绑定失败异常
*/
public boolean bindLocalPort(final String remoteHost, final int remotePort, final int localPort) throws SshException {
return bindLocalPort(remoteHost, remotePort, Ipv4Util.LOCAL_IP, localPort);
}
/**
* 绑定端口到本地 一个会话可绑定多个端口<br>
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param remoteHost 远程主机
* @param remotePort 远程端口
* @param localHost 本地主机
* @param localPort 本地端口
* @return 成功与否
* @throws SshException 端口绑定失败异常
*/
public boolean bindLocalPort(final String remoteHost, final int remotePort, final String localHost, final int localPort) throws SshException {
if (isConnected()) { if (isConnected()) {
try { try {
this.raw.setPortForwardingL(localHost, localPort, remoteHost, remotePort); this.raw.setPortForwardingL(localAddress.getHostName(), localAddress.getPort(), remoteAddress.getHostName(), remoteAddress.getPort());
} catch (final JSchException e) { } catch (final JSchException e) {
throw new SshException(e, "From [{}:{}] mapping to [{}:{}] error", remoteHost, remotePort, localHost, localPort); throw new SshException(e, "From [{}] mapping to [{}] error", localAddress, remoteAddress);
} }
return true; return true;
} }
return false; return false;
} }
/** @Override
* 解除远程端口映射 public void unBindLocalPort(final InetSocketAddress localAddress) {
*
* @param localPort 需要解除的本地端口
*/
public void unBindLocalPort(final int localPort) {
try { try {
this.raw.delPortForwardingL(localPort); this.raw.delPortForwardingL(localAddress.getHostName(), localAddress.getPort());
} catch (final JSchException e) { } catch (final JSchException e) {
throw new SshException(e); throw new SshException(e);
} }

View File

@ -15,6 +15,7 @@ package org.dromara.hutool.extra.ssh.engine.mina;
import org.apache.sshd.client.SshClient; import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.channel.ChannelShell; import org.apache.sshd.client.channel.ChannelShell;
import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.util.net.SshdSocketAddress;
import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.io.IoUtil; import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.extra.ssh.Connector; import org.dromara.hutool.extra.ssh.Connector;
@ -22,6 +23,7 @@ import org.dromara.hutool.extra.ssh.Session;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.Charset; import java.nio.charset.Charset;
/** /**
@ -63,6 +65,25 @@ public class MinaSession implements Session {
IoUtil.closeQuietly(this.sshClient); IoUtil.closeQuietly(this.sshClient);
} }
@Override
public boolean bindLocalPort(final InetSocketAddress localAddress, final InetSocketAddress remoteAddress) throws IORuntimeException {
try {
this.raw.startLocalPortForwarding(new SshdSocketAddress(localAddress), new SshdSocketAddress(remoteAddress));
} catch (final IOException e) {
throw new IORuntimeException(e);
}
return true;
}
@Override
public void unBindLocalPort(final InetSocketAddress localAddress) throws IORuntimeException {
try {
this.raw.stopLocalPortForwarding(new SshdSocketAddress(localAddress));
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
/** /**
* 执行Shell命令 * 执行Shell命令
* *
@ -100,16 +121,16 @@ public class MinaSession implements Session {
* 此方法单次发送一个命令到服务端自动读取环境变量执行结束后自动关闭channel不会产生阻塞 * 此方法单次发送一个命令到服务端自动读取环境变量执行结束后自动关闭channel不会产生阻塞
* </p> * </p>
* *
* @param cmd 命令 * @param cmd 命令
* @param charset 发送和读取内容的编码 * @param charset 发送和读取内容的编码
* @param errStream 异常输出位置 * @param errStream 异常输出位置
* @return 结果 * @return 结果
*/ */
public String execByShell(final String cmd, final Charset charset, final OutputStream errStream){ public String execByShell(final String cmd, final Charset charset, final OutputStream errStream) {
final ChannelShell shellChannel; final ChannelShell shellChannel;
try { try {
shellChannel = this.raw.createShellChannel(); shellChannel = this.raw.createShellChannel();
if(null != errStream){ if (null != errStream) {
shellChannel.setErr(errStream); shellChannel.setErr(errStream);
} }
shellChannel.open().verify(); shellChannel.open().verify();
@ -118,6 +139,6 @@ public class MinaSession implements Session {
} }
IoUtil.write(shellChannel.getInvertedIn(), charset, false, cmd); IoUtil.write(shellChannel.getInvertedIn(), charset, false, cmd);
return IoUtil.read(shellChannel.getInvertedOut(),charset); return IoUtil.read(shellChannel.getInvertedOut(), charset);
} }
} }

View File

@ -19,11 +19,9 @@ import net.schmizz.sshj.connection.channel.forwarded.SocketForwardingConnectList
import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.io.IoUtil; import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.map.MapUtil; import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.net.Ipv4Util;
import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.core.util.CharsetUtil;
import org.dromara.hutool.extra.ssh.Connector; import org.dromara.hutool.extra.ssh.Connector;
import org.dromara.hutool.extra.ssh.Session; import org.dromara.hutool.extra.ssh.Session;
import org.dromara.hutool.extra.ssh.SshException;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -44,7 +42,7 @@ public class SshjSession implements Session {
private SSHClient ssh; private SSHClient ssh;
private final net.schmizz.sshj.connection.channel.direct.Session raw; private final net.schmizz.sshj.connection.channel.direct.Session raw;
private Map<Integer, ServerSocket> localPortForwarderMap; private Map<String, ServerSocket> localPortForwarderMap;
/** /**
* 构造 * 构造
@ -95,40 +93,16 @@ public class SshjSession implements Session {
return new SshjSftp(this.ssh, charset); return new SshjSftp(this.ssh, charset);
} }
/** @Override
* 绑定端口到本地 一个会话可绑定多个端口<br> public boolean bindLocalPort(final InetSocketAddress localAddress, final InetSocketAddress remoteAddress) throws IORuntimeException {
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br> final Parameters params = new Parameters(
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等 localAddress.getHostName(), localAddress.getPort(),
* remoteAddress.getHostName(), remoteAddress.getPort());
* @param remoteHost 远程主机
* @param remotePort 远程端口
* @param localPort 本地端口
* @return 成功与否
* @throws SshException 端口绑定失败异常
*/
public boolean bindLocalPort(final String remoteHost, final int remotePort, final int localPort) throws SshException {
return bindLocalPort(remoteHost, remotePort, Ipv4Util.LOCAL_IP, localPort);
}
/**
* 绑定端口到本地 一个会话可绑定多个端口<br>
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param remoteHost 远程主机
* @param remotePort 远程端口
* @param localHost 本地主机
* @param localPort 本地端口
* @return 成功与否
* @throws IORuntimeException 端口绑定失败异常
*/
public boolean bindLocalPort(final String remoteHost, final int remotePort, final String localHost, final int localPort) throws IORuntimeException {
final Parameters params = new Parameters(localHost, localPort, remoteHost, remotePort);
final ServerSocket ss; final ServerSocket ss;
try { try {
ss = new ServerSocket(); ss = new ServerSocket();
ss.setReuseAddress(true); ss.setReuseAddress(true);
ss.bind(new InetSocketAddress(params.getLocalHost(), params.getLocalPort())); ss.bind(localAddress);
ssh.newLocalPortForwarder(params, ss).listen(); ssh.newLocalPortForwarder(params, ss).listen();
} catch (final IOException e) { } catch (final IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
@ -139,23 +113,18 @@ public class SshjSession implements Session {
} }
//加入记录 //加入记录
this.localPortForwarderMap.put(localPort, ss); this.localPortForwarderMap.put(localAddress.toString(), ss);
return true; return true;
} }
/** @Override
* 解除本地端口映射 public void unBindLocalPort(final InetSocketAddress localAddress) throws IORuntimeException {
*
* @param localPort 需要解除的本地端口
* @throws IORuntimeException 端口解绑失败异常
*/
public void unBindLocalPort(final int localPort) throws IORuntimeException {
if (MapUtil.isEmpty(this.localPortForwarderMap)) { if (MapUtil.isEmpty(this.localPortForwarderMap)) {
return; return;
} }
IoUtil.closeQuietly(this.localPortForwarderMap.remove(localPort)); IoUtil.closeQuietly(this.localPortForwarderMap.remove(localAddress.toString()));
} }
/** /**

View File

@ -21,6 +21,8 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.net.InetSocketAddress;
/** /**
* Jsch工具类单元测试 * Jsch工具类单元测试
* *
@ -36,7 +38,7 @@ public class JschTest {
//新建会话此会话用于ssh连接到跳板机堡垒机此处为10.1.1.1:22 //新建会话此会话用于ssh连接到跳板机堡垒机此处为10.1.1.1:22
final JschSession session = new JschSession(new Connector("looly.centos", 22, "test", "123456")); final JschSession session = new JschSession(new Connector("looly.centos", 22, "test", "123456"));
// 将堡垒机保护的内网8080端口映射到localhost我们就可以通过访问http://localhost:8080/访问内网服务了 // 将堡垒机保护的内网8080端口映射到localhost我们就可以通过访问http://localhost:8080/访问内网服务了
session.bindLocalPort("172.20.12.123", 8080, 8080); session.bindLocalPort(8080, new InetSocketAddress("172.20.12.123", 8080));
} }
@SuppressWarnings("resource") @SuppressWarnings("resource")