关于 TCP 打洞的一些问题,有尝试过的请教一下

2019-10-02 18:14:46 +08:00
 myqoo

最近发现家里的垃圾宽带(华数网通)居然支持 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()
})

11664 次点击
所在节点    程序员
21 条回复
des
2019-10-02 19:04:04 +08:00
frp 了解下?
KaneLin1217
2019-10-02 19:06:49 +08:00
zerotier+自建 moon 了解一下?
widewing
2019-10-02 19:48:15 +08:00
@des @KaneLin1217 你俩知道楼主在说啥吗?
wweir
2019-10-02 19:53:07 +08:00
@widewing frp 的 xtcp 倒是和楼主说的是一个东西
reus
2019-10-02 19:58:30 +08:00
不就是 NAT 嘛……
ClarkAbe
2019-10-02 20:06:06 +08:00
github 有个东东叫“N2N”,简化的 tcp 打洞虚拟组网,不过近几年能成功完全 tcp 打洞完全看运气
xieyudi2
2019-10-02 20:22:26 +08:00
这也可以… NAT 都不带验证 remote IP:port 的吗…
我妈也用的是广电的网。连接超过一定时间会被释放掉…
gam2046
2019-10-02 20:32:02 +08:00
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 却不支持,报端口被占用的错

超出射程了,不太了解你指的端口复用是什么。
myqoo
2019-10-02 21:46:56 +08:00
@gam2046 多谢解答。端口复用是指 http 服务 listen 已存在连接的 conn.localPort (相当于两个 socket 用同个源端口),我在 linux 下用 iptables 倒是勉强实现了,但总觉得不优雅,而且需要 root 权限。
fvckDaybyte2
2019-10-02 22:02:13 +08:00
@widewing 第一段看了几遍都没看懂到底在说啥,访问的到底是谁的公网 IP,http 服务是开在哪里的,“连接一个公务服务”是用什么协议连接,又到底是什么服务,最好还是有个图吧……
fvckDaybyte2
2019-10-02 22:27:40 +08:00
看了好几遍终于懂了,很正常的 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 是哪种类型的,很快就能定位了
CallMeReznov
2019-10-02 22:46:38 +08:00
动态 NAT?
wslzy007
2019-10-02 23:43:58 +08:00
基于 tcp 进行打洞的内网穿透工具,见: https://github.com/lazy-luo/smarGate
realpg
2019-10-03 11:49:29 +08:00
NAT 的类型了解一下
j4fun
2019-10-03 14:34:47 +08:00
1、Linux 要 reuseport 需要端口都设置,所以你要先 bind 获取一个端口,再 reuseport,再 conn 你的 STUN 服务器。最后再 reuseport 你的 http server。
2、端口变化很正常,这个取决于运营商 /路由器。NAT 1 (就是你现在的 NAT 类型)类型的有部分用户会变化包括 UDP/TCP。你可以把代码拿给其他 NAT1 的朋友试试有的是不会变的,有的是会几小时变一次。
myqoo
2019-10-03 15:03:28 +08:00
@j4fun 试了下确实如此,给第一个连接指定 localPort 就没问题了。 感谢!
feast
2019-10-03 15:10:54 +08:00
你这种一般属于 NAT 网关极为特殊才会出现这种情况,首先很显然是 Restricted Cone NAT,不验证来访 IP 是否等于请求 IP,特殊的在于 TCP 是有状态的有一个三次握手,绝大多数网关都会有这样一个表用来储存这个特定 TCP 握手信息,只要有一点地址信息不符就会拒绝数据包,所以你这个网关很特殊,一般只有无状态的 UDP 的才能打洞
feast
2019-10-03 15:30:00 +08:00
你真要研究 NAT 打洞,可以看看目前大部分运营商的 CGN NAT PCP 端口控制协议,github.com/libpcp/pcp
LBL584520
2019-10-05 16:07:32 +08:00
@feast

/t/603512

有现成编译好的工具 /软件 来测试吗?
不是专业的,不会编译


有的话麻烦发一个,
我测试一下我这移动宽带
feast
2019-10-06 16:56:06 +08:00
@LBL584520 linux 下 autogen.sh && make 即可

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/606042

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX