纯手工自制的内网穿透瑞士军刀 Socket Pipe

Javascript

今年撸的内部工具比较多,其中比较有用的我认为Socket Pipe绝对算一个。如果你有如下需求

  1. 需要做端口转发(udp or tcp)又懒得去配置复杂的iptables规则甚至还要用到nginx这种牛刀
  2. 需要把内网的某个监听端口反向穿透复杂的路由影射到一个固定的外网端口上(我知道有ngork,但是使用上限制较多而且还要收费不是吗)
  3. 你的茫茫多内网http服务需要在外网也能访问

那么我开发的这款小工具就再适合你不过了,它究竟有什么功能呢?我们先从最简单的说起

对了,忘了说如何安装了,这款工具是基于node.js的,所以你要先有它的运行环境,然后直接用npm安装即可

npm install -g socket-pipe

端口转发

这个理解起来很简单,就是把源地址的某个端口映射到当前运行 socket-pipe 这台机器的指定端口上,相当于做了一个透明的网络代理。比如我要把 192.168.1.10080 端口映射到本地的 80 端口,那么直接运行

socket-pipe -l 127.0.0.1@80 -r 192.168.1.100@80 -t tcp

它同时支持 tcp 和 udp 协议,只要把 -t 参数改成 udp 即可

tcp 反向代理

这个在我们平时的开发需求中也很大,比如我们在内网已经开发好了一套网络服务,现在要放到公网上调试,以前你可能还要在公网上搭一套环境,然后运行程序,调试出错起来特别麻烦。而使用 socket-pipe 可以让公网的访问流量直接到达你的本机,并且不需要在路由器上做任何设置

首先你需要一个拥有公网ip的服务器(在这里比如 ip 地址是 123.123.123.123),安装好 socket-pipe 以后,假如我们要求外网访问它的 8888 端口那么

socket-pipe -l 123.123.123.123@18888 -r 123.123.123.123@8888 -t tserver

注意这里的 -r 参数指定了能被任意访问的监听地址,而 -l 参数则指定了一个接受局域网里的服务器反向代理网络请求的地址,后面我们会用到这个地址

然后我们在本地局域网的服务器上同样运行 socket-pipe,假如本地(192.168.1.100)要调试的这个网络服务端口是 7777 那么

socket-pipe -l 192.168.1.100@7777 -r 123.123.123.123@18888 -t tclient

这样我们就把本地 192.168.1.1007777 端口,完全透明地映射到远程 123.123.123.1238888 端口上了。

http 反向代理

可能你也注意到了,上面的 tcp 反向代理也可以用来代理 http 协议,因为其本身就是基于 tcp 的。确实是可以的,但是你如果只有一台服务器需要代理还好说,如果你有非常多的 http 服务需要暴露在公网上,你可能还要在公网服务器的 socket-pipe 前端加一台 nginx 之类的,把它的每个域名分配给每个连接上来的端口,然后还要启动茫茫多的 socket-pipe 进程来代理每台内网服务器,并且每次新增一台还要手动去改 nginx 配置。

这显然成本太高,但 ngork 不就是解决这个问题的么。确实可以解决,但首先 ngork 网络流量不可控,涉及到一些敏感信息的会有泄密的可能,其次它速度比较慢,我们自己有更快的服务器为什么不能用自己的,最后,它的一些高级服务或者更多的数量需要收费。

所以 socket-pipe 的最后这一个功能可以彻底干掉 ngork 了,它可以把无限多的内网 http 反向映射到外网去

同样我们先在外网服务器上运行,既然是代理 http 服务,那我们就直接监听 80 端口好了

socket-pipe -l 123.123.123.123@10080 -r 123.123.123.123@80 -t hserver

同样我们指定了一个 10080 端口来接受反向代理请求。然后在局域网的服务器上

socket-pipe -l 192.168.1.100@80 -r 123.123.123.123@10080 -t hclient -x git.dev.com -s git

在这里多了两个可选参数

  1. -x 这个参数可以将外网请求的 Host 头转化为指定的地址,比如你在外网访问的域名是 www.example.com,到本地http服务器上的时候就被 socket-pipe 转换为了 git.dev.com。如果你不填这个参数,那么就不会转换任何地址
  2. -s 指定了一个外网访问的二级域名。因为我们上面说了,有一堆 http 都被映射到了同一台外网服务器上,那么如何区分并单独访问他们呢,就是二级域名。比如你把 *.example.com 的泛域名全部解析到了 123.123.123.123 上,那么通过 -s git 参数,你访问 git.example.com 这个地址,流量就会被转发到当前指定的地址上。如果这个参数不填,socket-pipe 会自动创建一个随机字符串作为二级域名,并且会输出告诉你。

写在最后

目前这个工具已经在我们内部使用了一段时间,使用起来还是非常舒爽的,还有几个小问题需要解决

欢迎大家给我提bug和需求
https://github.com/joyqi/socket-pipe

已有 17 条评论
  1. 终于能访问啦~原来这两年博客也没动静0.0 上来就给如此牛B的大福利!我有ss就不贪了嘿嘿。。(其实是node盲- -)

  2. 飞翔的企鹅
    飞翔的企鹅

    很好用,谢谢博主!

  3. 请问能实现多个端口转发吗?

      1. 目前多个端口转发只能用起不同的进程来实现

  4. 试了半天,一直不成功。浏览器显示“Not found”。服务器上显示:
    C:\Users\Administrator\AppData\Roaming\npm>socket-pipe -l 60.205.161.*@10090 -
    r 60.205.161.*@8011 -t hserver
    Piping 60.205.161.@10090 to 60.205.161.@8011 via hserver
    connected 223.20.195.196:61243 = d67d1250-1e74-11e7-ba67-c7ab42fb63dd null
    accept 60.205.161.*:56358
    request 60.205.161.*:8011
    close socket 0
    accept 60.205.161.*:56359
    request 60.205.161.*:8011

      1. 你需要使用 -s 指定子域名来访问,要不然只能用 d67d1250-1e74-11e7-ba67-c7ab42fb63dd 来做子域名

  5. 如何保证在一直在服务器后台运行?

  6. 阿里云centos服务器无法打开监听: Error: listen EADDRNOTAVAIL xxx.xxx.xxx.xxx:10080

  7. 监听命令:socket-pipe -l ip@10080 -r ip@80 -t hserver

    ifconfig信息:
    [root@jdu4e00u53f7 ~]# ifconfig
    eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450

        inet 192.168.0.3  netmask 255.255.240.0  broadcast 192.168.15.255
        inet6 fe80::f816:3eff:fe83:ed22  prefixlen 64  scopeid 0x20<link>
        ether fa:16:3e:83:ed:22  txqueuelen 1000  (Ethernet)
        RX packets 29388  bytes 31010099 (29.5 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 16401  bytes 1769838 (1.6 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    

    lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536

        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1  (Local Loopback)
        RX packets 2  bytes 276 (276.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2  bytes 276 (276.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    

    [root@jdu4e00u53f7 ~]#

      1. 按你给出的信息,-l 监听的本地ip应该是192.168.0.3

  8. 和SSH端口转发比较怎么样?

  9. 请问云服务器80端口被nginx占用的话怎么处理啊,换成88的话用域名:88能进来,但是本地没反应,只提示NotFound

      1. 云服务器里是用的内网ip,外网无法配置生效,本地访问的云服务器外网ip,这样不行吧?

  10. 你好,感谢提供这个工具,但是在使用的过程中有问题
    1.数据的传输会有以下错误
    Error: read ECONNRESET

    at TCP.onStreamRead (internal/stream_base_commons.js:205:27) {

    errno: 'ECONNRESET',
    code: 'ECONNRESET',
    syscall: 'read'
    }
    Error [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed

    at doWrite (_stream_writable.js:437:19)
    at writeOrBuffer (_stream_writable.js:425:5)
    at Socket.Writable.write (_stream_writable.js:316:11)
    at Timeout._onTimeout (C:\Users\Administrator\AppData\Roaming\npm\node_modules\socket-pipe\build\tclient.js:35:39)
    at listOnTimeout (internal/timers.js:549:17)
    at processTimers (internal/timers.js:492:7) {

    code: 'ERR_STREAM_DESTROYED'
    }
    Error [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed

    at doWrite (_stream_writable.js:437:19)
    at writeOrBuffer (_stream_writable.js:425:5)
    at Socket.Writable.write (_stream_writable.js:316:11)
    at Timeout._onTimeout (C:\Users\Administrator\AppData\Roaming\npm\node_modules\socket-pipe\build\tclient.js:35:39)
    at listOnTimeout (internal/timers.js:549:17)
    at processTimers (internal/timers.js:492:7) {

    code: 'ERR_STREAM_DESTROYED'
    }
    Error: read ECONNRESET

    at TCP.onStreamRead (internal/stream_base_commons.js:205:27) {

    errno: 'ECONNRESET',
    code: 'ECONNRESET',
    syscall: 'read'
    }

    2多连几次后不能再连接了,只能重新输入命令运行才行

    谢谢