给定BoxA(可以运行 OSX 或 Linux)上127.0.0.X
( X
> ) 范围内的额外环回接口,我想要将这个额外环回接口的端口 22 绑定到指向 BoxB 的正向 SSH 隧道(即本地端口转发)。1
在 OSX 上,这工作得很好(回想起来很奇怪)。[取X
= 2
] 在使用 调出环回别名后ifconfig lo0 alias 127.0.0.2 up
,SSH 可以与 建立隧道ssh -NfL 127.0.0.2:22:localhost:22 BoxB
。然后在 BoxA 上的新 shell 中,ssh 127.0.0.2
将我登录到 BoxB。
在 Ubuntu 上,我可以在 BoxA 上调出环回别名,但在尝试建立 SSH 隧道时,ssh
会提示无法绑定(并因此转发)BoxA 的端口 22。随后ssh 127.0.0.2
(在 BoxA 上的新 shell 中)会给出指纹警告,如果绕过该警告,我会重新登录到 BoxA。这很有道理 - sshd
BoxA 正在监听全部接口。
查看sshd_config
每一个,两者都配置为监听0.0.0.0
(以及::
监听 IPv6)。
lsof
对于 OSX 给出:
launchd 1 root 40u IPv6 0xddfcabed61001f0d 0t0 TCP *:ssh (LISTEN)
launchd 1 root 41u IPv4 0xddfcabed6100413d 0t0 TCP *:ssh (LISTEN)
launchd 1 root 43u IPv6 0xddfcabed61001f0d 0t0 TCP *:ssh (LISTEN)
launchd 1 root 44u IPv4 0xddfcabed6100413d 0t0 TCP *:ssh (LISTEN)
对于 Ubuntu:
sshd 1287 0 3u IPv4 21903340 0t0 TCP *:ssh (LISTEN)
因此,两者都在所有接口上进行监听,尽管我不确定为什么 OSX 使用 4 个进程。无论如何,Ubuntu 给出了预期的行为。为什么 OSX 的行为不同?
当然,后续的问题是如何让 Ubuntu 在这方面表现得像 OSX。
虽然我希望sshd_config
具有状态、通配符和/或逻辑运算符(例如“不监听127.0.0.*
;监听127.0.0.1
”)iptables
,但事实似乎并非如此......
答案1
这可以使用socat
(1.7.3.2-2
,而不是1.7.3.1-2+deb9u1
哪个忽略 reuseport
) 在 Debian 上(与 Ubuntu 类似):
term1$ socat -d -d TCP4-LISTEN:5555,reuseaddr,fork -
2018/08/03 08:20:43 socat[25084] N listening on AF=2 0.0.0.0:5555
term2$ socat -d -d TCP4-LISTEN:5555,bind=127.0.0.2,reuseaddr,fork -
2018/08/03 08:21:00 socat[25085] E bind(5, {AF=2 127.0.0.2:5555}, 16): Address already in use
2018/08/03 08:21:00 socat[25085] N exit(1)
现在如果你添加reuseport
双方:
term1$ socat -d -d TCP4-LISTEN:5555,reuseaddr,reuseport,fork -
2018/08/03 08:21:28 socat[25086] N listening on AF=2 0.0.0.0:5555
term2$ socat -d -d TCP4-LISTEN:5555,bind=127.0.0.2,reuseaddr,reuseport,fork -
2018/08/03 08:21:56 socat[25092] N listening on AF=2 127.0.0.2:5555
otherterm$ netstat -tnlp 2>/dev/null|grep :5555
tcp 0 0 127.0.0.2:5555 0.0.0.0:* LISTEN 25092/socat
tcp 0 0 0.0.0.0:5555 0.0.0.0:* LISTEN 25086/socat
使用其他socat
或 netcat 进行连接将显示它socat
根据 IP 地址正确路由到正确的监听。
因此,行为差异肯定在于bind
和setsockopt
在 MacOS 和 Ubuntu 上对 ssh 进行了调用(也许 launchd 也起着一定的作用?)。使用strace
,在工作socat
案例中添加了以下内容:
setsockopt(5, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0
更新:
虽然我会回答 OP 问题的字面意思:让它发挥作用,但我还认为这个问题并没有说明更广泛的目标,而这个目标可能会通过完全不同的解决方案来实现。
因此,为了在绑定套接字时改变sshd
行为ssh
,可以使用dlsym()
包装器将bind(3)
在实际之前改变函数的行为bind(2)
系统调用。
使用以下命令编译以下文件reuseport-wrapper.c
:
gcc -shared -fPIC -o reuseport-wrapper.so reuseport-wrapper.c -ldl
#define _GNU_SOURCE
#include <dlfcn.h>
#include <sys/socket.h>
#include <stddef.h> /* NULL */
int bind(int socket, const struct sockaddr *address, socklen_t address_len) {
static const int optval=1;
static int (*orig_bind)(int, const struct sockaddr *,socklen_t)=NULL;
if (orig_bind == NULL && (orig_bind=dlsym(RTLD_NEXT,"bind")) == NULL)
return -1;
if (address != NULL && (address->sa_family == AF_INET || address->sa_family == AF_INET6)) {
if (setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof optval) != 0) {
return -1;
}
}
return orig_bind(socket, address, address_len);
}
这将在绑定或套接字之前bind(3)
始终设置一个包装器。SO_REUSEPORT
inet
inet6
将其放置在某处(例如,/usr/local/lib/reuseport-wrapper.so
但这实际上并不重要,因为它像插件一样明确加载)。
编辑/etc/default/ssh
并添加:
LD_PRELOAD=/usr/local/lib/reuseport-wrapper.so
重新启动 ssh 服务器(例如service ssh restart
)
并以 root 身份运行任何其他工具(包括ssh
)(请参阅下文原因)并LD_PRELOAD=/usr/local/lib/reuseport-wrapper.so
导出。例如:
$ sudo su -
[...]
# LD_PRELOAD=/usr/local/lib/reuseport-wrapper.so ssh -NfL 127.0.0.2:22:localhost:22 userB@BoxB
此命令必须以 root 身份启动,原因有二:端口 22 需要 root 才能绑定,即使具有一些附加功能,也存在一个附加限制,如socket(7)
:
SO_REUSEPORT(自 Linux 3.9 起)
允许将多个 AF_INET 或 AF_INET6 套接字绑定到相同的套接字地址。在调用套接字上的 bind(2) 之前,必须在每个套接字(包括第一个套接字)上设置此选项。 为了防止端口劫持,绑定到同一地址的所有进程必须具有相同的有效 UID。此选项可与 TCP 和 UDP 套接字一起使用。
完成后:
# netstat -tnlp |grep -w 22
tcp 0 0 127.0.0.2:22 0.0.0.0:* LISTEN 157/ssh
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 143/sshd
tcp6 0 0 :::22 :::* LISTEN 143/sshd
隧道将按预期运行。