我的家庭网络中,在 NAT 后面的 MacBook 上有一个 Web 服务器,服务于端口 80。我还有一个运行 Ubuntu 的可公开访问的服务器,我想从该服务器访问我的本地 Web 服务器,因此我打开了一个远程 SSH 隧道:
ssh -fnNT -R 8080:localhost:80 remote-host
成功了。在远程机器上,我可以curl localhost:8080
得到预期的答案。
但是当我从docker容器内部尝试这个时,例如:
docker run -it --add-host host.docker.internal:host-gateway ubuntu:latest /bin/bash
# curl -vvv host.docker.internal:8080
* connect to 172.17.0.1 port 8080 failed: Connection timed out
我发现其他地方的“解决方案”,例如使用--network host
docker 选项或让隧道在所有接口上监听(ssh -R 0.0.0.0:8080:localhost:80
)由于安全问题不是一个选择。
从容器内部访问主机系统上的任何其他端口都没有问题,就像curl host.docker.internal:80
主机的 Caddy 服务器的响应一样。
我尝试设置防火墙规则iptables -I INPUT -i docker0 -j ACCEPT
(但并不真正了解其作用)但这并没有改变什么。
我尝试确保启用了 IPv6 数据包转发(net.ipv6.conf.all.forwarding=1
在 中/etc/sysctl.conf
)并且让隧道仅绑定到 IPv4 地址(使用选项-4
),但没有成功。
我尝试使用接口的 IPdocker0
作为绑定地址对于隧道(ssh -R 172.17.0.1:8080:localhost:80
,当然已经GatewayPorts clientspecified
在 sshd_config 中设置了)。然后我可以curl 172.17.0.1:8080
成功地从主机系统访问,但仍然不能从容器内部访问。
一个可能的复杂因素是,我在服务器上使用ufw
,仅允许端口 80、443 和我的 SSH 端口上的流量进入。当我 时sudo ufw disable
,上述 curl 请求以Connection refused
而不是终止timed out
,我发现这很有趣。
我觉得我已经接近了。也许我可以设置一个 iptables 过滤器来使其工作?我没有使用过 iptables。如何对从容器内部到主机系统上的隧道端口的请求进行分类,是进入还是出去?还有其他想法可以调试此问题吗?
答案1
经过两周对网络、docker 网络以及 docker 如何与 iptables 交互的深入研究,我现在可以自信地回答我自己的问题了:
首先,这与 ssh 隧道无关。绑定到环回地址 127.0.0.1 的任何内容都只能从本地计算机本身访问,而不能从计算机网络接口外部访问。Docker 在这里算作“外部”,因为它有自己的网络(默认情况下在 172.16.0.0/12 范围内)。这是 docker 整个隔离概念的关键部分,它将容器与主机和彼此分开。
那么如何实现这一点呢?有以下几种选择:
将您需要的任何服务绑定到docker网关ip:
ssh -fnNT -R 172.17.0.1:8080:localhost:80 remote-host
为了使其工作,你需要一个额外的 iptables 规则,例如:
iptables -I INPUT -i docker0 -d 172.17.0.1 -p tcp --dport 8080 -j ACCEPT
这允许来自默认 docker0 网络的流量到端口 8080 上的 docker 网关 ip。如果没有此规则,这些数据包将被丢弃。
更简单一点,只需将其绑定到所有接口即可:
ssh -fnNT -R 0.0.0.0:8080:localhost:80 remote-host
这里的安全问题可能取决于您的个人用例和网络架构。但是,如果您有一个简单的 VPS 和一个公共 IP,并且您的 iptables INPUT 策略是 DROP,那么这不是问题。我在这里感到困惑,因为当您使用 docker 发布端口时
-p 8080:80
,docker 将创建一个 iptables 规则来接受来自主机外部的请求,因为它是默认不安全如果你将任何其他(非docker)服务绑定到0.0.0.0上的端口,它仍然会被阻止,直到你手动打开它。使用主机作为跳转代理,通过隧道进入容器。这需要您的容器运行 openssh 服务器,这本身就有点棘手,并且端口也需要在主机上发布,例如使用
-p 127.0.0.1:2222:22
。如果您正在使用 docker compose 并且不介意启动其他服务,您可以使用docker-openssh-服务器为了这。那么问题就只是:
ssh -fnNT -R 0.0.0.0:8080:localhost:80 remote-container
.ssh/config
例如,与中的设置结合使用时Host remote-host HostName remote-host.example.com User peter Port 22 Host remote-container ProxyJump remote-host HostName localhost User containerpeter Port 2222
这将首先通过 ssh 进入远程主机,然后从那里打开远程隧道直接进入容器。当然,您可以向公众开放容器 ssh 端口并直接通过隧道进入那里,但跳转会更有趣。
您可以按照建议干预 nat 表中的 iptables PREROUTING 链这里,将请求路由
172.17.0.1
到127.0.0.1
。但我还没有尝试过,所以不能保证结果。您可以使用一个名为的工具,
socat
如解释的那样这里,它可以进行 TCP 转发等许多操作。还没有尝试过这种方法。
如果您现在很想自己解决类似的问题,这里有一些方便的工具。
查看哪些进程监听哪个接口上的哪个端口。以下之一:
lsof -i -P -n | grep LISTEN
netstat -tulpn | grep LISTEN
ss -tulpn | grep LISTEN
通过 iptables 链跟踪请求:
# First insert a rule with target TRACE into the INPUT chain
iptables -I INPUT -p tcp --dport 8080 -j TRACE
# Then look at the packets while they are going through the chains
xtables-monitor --trace
祝您社交愉快。