最近发现家里的垃圾宽带(华数网通)居然支持 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()
})
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.