最近发现家里的垃圾宽带(华数网通)居然支持 TCP 打洞,并成功将内网的 HTTP 服务暴露到公网上。
原理很简单。先连接一个公网服务,获得该连接的公网 IP 和公网端口。然后开启一个 HTTP 服务,端口指定为之前连接的本地端口(端口重用)。然后用其他设备测试,例如手机 4G 访问 http://公网 IP:公网端口
,成功显示出 HTTP 服务。(实现超简单,结尾附上 nodejs 演示)
目前遇到几个疑惑,不知是否为正常现象:
1.每个 http://公网 IP:公网端口
访问几次后就不能再访问了,需要重新打洞更换公网端口才可以。
并且每个连接时间不能太久,否则会被断开。我测试大文件下载,速度不错有 2MB/s (对于¥ 200/年的宽带也算满意了),但大约 10 分钟左右下了一个多 GB 就被断开了。
这是因为该公网 IP 有很多人在用,导致端口被其他人占用了吗?假如我同时发起成千上万的 TCP 连接,是不是也会把同个公网 IP 下其他人正在使用的端口给抢占呢。
2.当其他设备访问 http://公网 IP:公网端口
时,原先的 TCP 连接不再可用(演示中的心跳包无法收到)。
TCP 打洞成功后,原先的连接就不可用了?这意味着,本地任何一个 TCP 连接都可以被恶意者破坏(假如恶意者知道该连接的公网端口)。事实上我用服务器扫了下自己公网 IP 的所有端口,发现确实可以把本地开着的 TCP 连接都断开!!!感觉这是运营商配置的问题,不是正常现象吧,要不然安全性也太低了。
3.OSX 和 Windows 上支持端口重用,但 Linux 却不支持,报端口被占用的错。
我搜了下 nodejs 官网关于 net 模块的介绍 https://nodejs.org/api/net.html 其中提到 All net.Socket are set to SO_REUSEADDR (see socket(7) for details).
不明白为什么 Linux 系统不支持端口重用?
最后贴上 nodejs 的演示代码。我试了很多运营商都不行,但华数宽带可以实现(最好是直接在电脑上拨号,因为有些路由器也会影响打洞):
const net = require('net')
const http = require('http')
function runHttpSvr(port) {
http.createServer((req, res) => {
const ip = req.connection.remoteAddress.replace('::ffff:', '')
const addr = ip + ':' + req.connection.remotePort
console.log('client:', addr)
res.end('Hello: ' + addr + '\n')
}).listen(port, _ => {
console.log('server running...')
})
}
const conn = net.createConnection(50000, '106.75.17.52', () => {
const localPort = conn.localPort
console.log('localPort:', localPort)
let pong = 0
conn.on('data', data => {
if (data == 'PONG') {
console.log('PONG:', pong++)
setTimeout(ping, 1000)
return
}
console.log('curl http://' + data)
runHttpSvr(localPort)
})
conn.on('end', _ => {
console.log('end')
})
conn.on('error', err => {
console.log('error:', err)
})
conn.setEncoding('utf8')
function ping() {
conn.write('PING')
}
ping()
})
1
des 2019-10-02 19:04:04 +08:00 via Android
frp 了解下?
|
2
KaneLin1217 2019-10-02 19:06:49 +08:00 via iPhone
zerotier+自建 moon 了解一下?
|
3
widewing 2019-10-02 19:48:15 +08:00 via Android 4
@des @KaneLin1217 你俩知道楼主在说啥吗?
|
5
reus 2019-10-02 19:58:30 +08:00 1
不就是 NAT 嘛……
|
6
ClarkAbe 2019-10-02 20:06:06 +08:00 via Android
github 有个东东叫“N2N”,简化的 tcp 打洞虚拟组网,不过近几年能成功完全 tcp 打洞完全看运气
|
7
xieyudi2 2019-10-02 20:22:26 +08:00 via Android
这也可以… NAT 都不带验证 remote IP:port 的吗…
我妈也用的是广电的网。连接超过一定时间会被释放掉… |
8
gam2046 2019-10-02 20:32:02 +08:00 3
TCP 打洞....读了两遍原文,基本了解下来就是 NAT 内网穿透,实现方式与原理均一致。你的实现方式大致是这样的
Client -> Router -> Server 举例 C: 10.10.10.1:7890/TCP => R: 10.10.10.1:7890/TCP <--> 61.61.61.1:23456/TCP => S: 61.61.61.1 -> :80/TCP 你的问题 1:每个连接时间不能太久,会被断开 不正常。活跃的端口映射不应该被断开,也就是 R 这里面的映射关系即使在不活跃的状态,也应该保持一段时间(如 2 分钟)更何况你下载时,是处于活跃状态。 你的问题 2:假如我同时发起成千上万的 TCP 连接,是不是也会把同个公网 IP 下其他人正在使用的端口给抢占呢 原则上是本着先到先得,单 IP 上的端口数量有限的,但这类二级运营商大量的飞针走线,内网穿透。真出现这种情况,也许会给你换个出口 IP。不过.....正常情况下,出口路由记录的映射关系是<内网 IP:端口 /出口端口 /目标服务器>,也就是说出口路由上的端口是可以复用的。多个内网 IP 出口均使用同一个公网端口,大家的目标服务器不同,返回的数据也可以依据服务器 IP 来区分转发给内网的哪个 IP 你的问题 3:TCP 打洞成功后,原先的连接就不可用了 讲道理,在出口路上上,只要映射关系还存在,是可以双向通讯的。但这取决于你所处网络环境的 NAT 类型(这个结论是基于 UDP 得到的,TCP 我不确定)。 https://zh.wikipedia.org/wiki/UDP%E6%89%93%E6%B4%9E 你的问题 4:OSX 和 Windows 上支持端口重用,但 Linux 却不支持,报端口被占用的错 超出射程了,不太了解你指的端口复用是什么。 |
9
myqoo OP @gam2046 多谢解答。端口复用是指 http 服务 listen 已存在连接的 conn.localPort (相当于两个 socket 用同个源端口),我在 linux 下用 iptables 倒是勉强实现了,但总觉得不优雅,而且需要 root 权限。
|
10
fvckDaybyte2 2019-10-02 22:02:13 +08:00
@widewing 第一段看了几遍都没看懂到底在说啥,访问的到底是谁的公网 IP,http 服务是开在哪里的,“连接一个公务服务”是用什么协议连接,又到底是什么服务,最好还是有个图吧……
|
11
fvckDaybyte2 2019-10-02 22:27:40 +08:00 4
看了好几遍终于懂了,很正常的 NAT traversal 现象,具体参考 RFC4787,概括来说就是,如果客户端 A 访问服务器 B 的 20000 端口,且数据经过一层 NAT,而 NAT 将此次访问映射到了端口 10000,则在一段时间内这个映射关系会被保留
此时 NAT 分为 3 个类型: Endpoint-Independent Mapping:在这段时间内任何服务器发往 10000 端口的数据都将被转发给 B (最容易打洞的类型) Address-Dependent Mapping:在这段时间内只有来自服务器 B 的数据会被转发给 B (一般家庭宽带常用的 NAT 类型) Address and Port-Dependent Mapping:在这段时间内只有来自服务器 B 的 20000 端口的数据才会被转发(一般 4G 网络的 NAT 类型) 因此: “2.当其他设备访问 http://公网 IP:公网端口 时,原先的 TCP 连接不再可用” 很明显你的宽带 NAT 不可能是第一种类型,因此会拒绝来自其他 IP 的数据包 1.每个 http://公网 IP:公网端口 访问几次后就不能再访问了,需要重新打洞更换公网端口才可以。 有可能是宽带 NAT 映射关系过期了,必须有 TCP 或者 UDP 的 keepalive 包来保持这个映射,也有可能是你手机 4G 的 NAT 映射关系过期了,因此每次发出来的包端口不一样,而你的宽带是第三种类型会拒绝不同端口的数据包。两边都是 NAT 会有许多种情况。 写一个小程序先验证一下 NAT mapping behavior 是哪种类型的,很快就能定位了 |
12
CallMeReznov 2019-10-02 22:46:38 +08:00
动态 NAT?
|
13
wslzy007 2019-10-02 23:43:58 +08:00
基于 tcp 进行打洞的内网穿透工具,见: https://github.com/lazy-luo/smarGate
|
14
realpg 2019-10-03 11:49:29 +08:00
NAT 的类型了解一下
|
15
j4fun 2019-10-03 14:34:47 +08:00 1
1、Linux 要 reuseport 需要端口都设置,所以你要先 bind 获取一个端口,再 reuseport,再 conn 你的 STUN 服务器。最后再 reuseport 你的 http server。
2、端口变化很正常,这个取决于运营商 /路由器。NAT 1 (就是你现在的 NAT 类型)类型的有部分用户会变化包括 UDP/TCP。你可以把代码拿给其他 NAT1 的朋友试试有的是不会变的,有的是会几小时变一次。 |
17
feast 2019-10-03 15:10:54 +08:00 via Android
你这种一般属于 NAT 网关极为特殊才会出现这种情况,首先很显然是 Restricted Cone NAT,不验证来访 IP 是否等于请求 IP,特殊的在于 TCP 是有状态的有一个三次握手,绝大多数网关都会有这样一个表用来储存这个特定 TCP 握手信息,只要有一点地址信息不符就会拒绝数据包,所以你这个网关很特殊,一般只有无状态的 UDP 的才能打洞
|
18
feast 2019-10-03 15:30:00 +08:00 via Android
你真要研究 NAT 打洞,可以看看目前大部分运营商的 CGN NAT PCP 端口控制协议,github.com/libpcp/pcp
|
19
LBL584520 2019-10-05 16:07:32 +08:00 via iPhone
|
20
feast 2019-10-06 16:56:06 +08:00 via Android
@LBL584520 linux 下 autogen.sh && make 即可
|
21
ofblyt 2019-12-26 14:13:49 +08:00 1
|