首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
宝塔
V2EX  ›  程序员

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

  •  
  •   myqoo · 45 天前 · 3480 次点击
    这是一个创建于 45 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近发现家里的垃圾宽带(华数网通)居然支持 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()
    })
    
    
    20 回复  |  直到 2019-10-06 16:56:06 +08:00
        1
    des   45 天前 via Android
    frp 了解下?
        2
    KaneLin1217   45 天前 via iPhone
    zerotier+自建 moon 了解一下?
        3
    widewing   45 天前 via Android   ♥ 1
    @des @KaneLin1217 你俩知道楼主在说啥吗?
        4
    wweir   45 天前 via Android
    @widewing frp 的 xtcp 倒是和楼主说的是一个东西
        5
    reus   45 天前   ♥ 1
    不就是 NAT 嘛……
        6
    ClarkAbe   45 天前 via Android
    github 有个东东叫“N2N”,简化的 tcp 打洞虚拟组网,不过近几年能成功完全 tcp 打洞完全看运气
        7
    xieyudi2   45 天前 via Android
    这也可以… NAT 都不带验证 remote IP:port 的吗…
    我妈也用的是广电的网。连接超过一定时间会被释放掉…
        8
    gam2046   45 天前   ♥ 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   45 天前
    @gam2046 多谢解答。端口复用是指 http 服务 listen 已存在连接的 conn.localPort (相当于两个 socket 用同个源端口),我在 linux 下用 iptables 倒是勉强实现了,但总觉得不优雅,而且需要 root 权限。
        10
    fvckDaybyte2   45 天前
    @widewing 第一段看了几遍都没看懂到底在说啥,访问的到底是谁的公网 IP,http 服务是开在哪里的,“连接一个公务服务”是用什么协议连接,又到底是什么服务,最好还是有个图吧……
        11
    fvckDaybyte2   45 天前   ♥ 2
    看了好几遍终于懂了,很正常的 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   45 天前
    动态 NAT?
        13
    wslzy007   45 天前
    基于 tcp 进行打洞的内网穿透工具,见: https://github.com/lazy-luo/smarGate
        14
    realpg   44 天前
    NAT 的类型了解一下
        15
    j4fun   44 天前   ♥ 1
    1、Linux 要 reuseport 需要端口都设置,所以你要先 bind 获取一个端口,再 reuseport,再 conn 你的 STUN 服务器。最后再 reuseport 你的 http server。
    2、端口变化很正常,这个取决于运营商 /路由器。NAT 1 (就是你现在的 NAT 类型)类型的有部分用户会变化包括 UDP/TCP。你可以把代码拿给其他 NAT1 的朋友试试有的是不会变的,有的是会几小时变一次。
        16
    myqoo   44 天前
    @j4fun 试了下确实如此,给第一个连接指定 localPort 就没问题了。 感谢!
        17
    feast   44 天前 via Android
    你这种一般属于 NAT 网关极为特殊才会出现这种情况,首先很显然是 Restricted Cone NAT,不验证来访 IP 是否等于请求 IP,特殊的在于 TCP 是有状态的有一个三次握手,绝大多数网关都会有这样一个表用来储存这个特定 TCP 握手信息,只要有一点地址信息不符就会拒绝数据包,所以你这个网关很特殊,一般只有无状态的 UDP 的才能打洞
        18
    feast   44 天前 via Android
    你真要研究 NAT 打洞,可以看看目前大部分运营商的 CGN NAT PCP 端口控制协议,github.com/libpcp/pcp
        19
    LBL584520   42 天前 via iPhone
    @feast

    /t/603512

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


    有的话麻烦发一个,
    我测试一下我这移动宽带
        20
    feast   41 天前 via Android
    @LBL584520 linux 下 autogen.sh && make 即可
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   870 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 24ms · UTC 21:07 · PVG 05:07 · LAX 13:07 · JFK 16:07
    ♥ Do have faith in what you're doing.