本文来自依云's Blog,转载请注明。
又是一篇关于 UDP 打洞的文章。之前写过关于在完全圆锥型(full cone)NAT的文章中如何使用 socat 命令打洞。根据那篇文章里的知识,连接到一个 full cone NAT 后边的 mosh 不成问题。不过,我现在的网络是受限圆锥型(restricted cone)NAT 了呢!
也就是复杂了一些。双方要向中间服务器和对方都发送数据包才可以。另外就是,客户端(mosh-client)这边得使用在打洞期间使用的端口号才行。
打洞流程根据维基百科,双方通过中间服务器(还是我的 udpaddr 啦)交换地址,双方均向得到的地址发送一数据包,然后开始正常通讯。比较麻烦,于是有了这个脚本:
#!/usr/bin/env python3 import socket import re import sys import subprocess udp_server = ('xmpp.vim-cn.com', 2727) addr_re = re.compile(r"\('(?P<ip>[^']+)', (?P<port>\d+)(?:, (?P<cport>\d+))?") def main(server): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(2) print('Send message...') sock.sendto(b'req from holepunch.py\n', udp_server) msg, addr = sock.recvfrom(1024) print('Got answer from %s: %s' % (addr, msg)) m = addr_re.search(msg.decode()) if not m: print("Error: can't parse answer.") sys.exit(1) m_ip = m.group('ip') m_port = int(m.group('port')) port = sock.getsockname()[1] print('Got my IP and Port: (%r, %s, %s).' % (m_ip, m_port, port)) msg = input('> Peer address: ') m = addr_re.search(msg) if not m: print("Error: can't parse input.") sys.exit(2) p_ip = m.group('ip') p_port = int(m.group('port')) c_port = int(m.group('cport')) print('send initial packet and wait for answer...') sock.sendto(b'HELO\n', (p_ip, p_port)) try: msg = sock.recvfrom(1024) print('Received:', msg) except socket.timeout: print("Timed out (it's normal).") if server: sock.close() print('Starting mosh server...') msg = subprocess.check_output(['mosh-server', 'new', '-p', str(port)]) secret = msg.split()[3].decode() print('Connect with:\nMOSH_KEY=%s MOSH_CPORT=%s mosh-client %s %s' % (secret, c_port, m_ip, m_port)) else: print('done.') if __name__ == '__main__': server = len(sys.argv) == 2 and sys.argv[1] == '-s' main(server)
如果 mosh 服务器端位于受限 NAT 后,还需要给 mosh-client 打个(我随手写的很 dirty 的)补丁以便指定客户端使用的 UDP 端口号:
diff --git a/src/network/network.cc b/src/network/network.cc index 2f4e0bf..718f6c5 100644 --- a/src/network/network.cc +++ b/src/network/network.cc @@ -176,6 +176,11 @@ Connection::Socket::Socket() perror( "setsockopt( IP_RECVTOS )" ); } #endif + + if ( getenv("MOSH_CPORT") ) { + int port = atoi(getenv("MOSH_CPORT")); + try_bind( _fd, INADDR_ANY, port, port); + } } void Connection::setup( void )
然后,连接流程如下:
-
mosh 服务端运行
holepunch.py -s
命令,客户端运行holepunch.py
; -
双方看到自己的地址信息(依次是
IP, 外网端口, 本地端口
)后,复制并发送给对方; - 双方输入对方的地址。为了节省时间(mosh-server 只会等一分钟,NAT 上的端口映射也是有时效的),只要输入的一行内包含上述地址信息的文本即可,前后可以有不小心复制过来的多余字符;
- 服务端将得到一行包含 mosh 的密钥的命令。将此命令发送对客户端;客户端运行此命令连接。如果 mosh-client 的名字不是标准名字,需要自行修改;
- 一切顺利的话就连接上啦!
mosh 服务端输出示例:
>>> holepunch.py -s Send message... Got answer from ('202.133.113.62', 2727): b"Your address is ('180.109.80.47', 3169)\n" Got my IP and Port: ('180.109.80.47', 3169, 8127). > Peer address: Port: ('222.95.148.73', 5223, 55473). send initial packet and wait for answer... Timed out (it's normal). Starting mosh server... mosh-server (mosh 1.2.4) Copyright 2012 Keith Winstein <mosh-devel@mit.edu> License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. [mosh-server detached, pid = 5202] Connect with: MOSH_KEY=1apguNqtfN/K4JSvllnJxA MOSH_CPORT=55473 mosh-client 222.95.148.73 5223
mosh 客户端输出示例:
>>> holepunch.py Send message... Got answer from ('202.133.113.62', 2727): b"Your address is ('222.95.148.73', 5223)\n" Got my IP and Port: ('222.95.148.73', 5223, 55473). > Peer address: rt: ('180.109.80.47', 3169, 8127) send initial packet and wait for answer... Timed out (it's normal). done.
2013年10月20日更新:想要通过打出来的 UDP 洞进行 TCP 通信吗?参见文章通过 OpenVPN 让 TCP 使用 UDP 洞。
Oct 13, 2013 05:11:19 PM
你好,我想在我的网站上放一个提问的智慧的网页,想索取一个html版本放在我的网站上,不知能不能提供一下呢?
Oct 13, 2013 11:16:59 PM
这里——http://lilydjwg.vim-cn.com/articles/smart-questions.html 。
Oct 13, 2013 11:24:03 PM
你的网站的页面编码有问题哦,它在 gzip 压缩之后的数据的前边添加了三个字字的 BOM 导致浏览器无法解码。火狐和 Chrome 均如此。
Oct 14, 2013 06:57:48 PM
ie也如此,谢谢啦
Oct 08, 2017 03:47:11 PM
您好!麻烦请教一个关于 mosh 和 frp( https://github.com/fatedier/frp) 的问题。
我想在外边儿也能访问办公室的电脑,于是用 frp 将办公室电脑的 22 端口 tcp 映射到了 vps 的 6000 端口。此时在外网用 ssh -p 6000 user@server_ip 能够成功登录办公室的电脑。
但是如果想用 mosh 的话该怎么配置呢? 我尝试将办公室电脑的 60001 端口 udp 映射到 vps 的 6001 端口,然后在外网用 mosh -p 6001 --ssh="ssh -p 6000" user@server_ip 能出现密码输入的提示,但是输入密码后 mosh 无法连接。请问这该怎么处理呢?
错误提示如下:
mosh did not make a successful connection to x.x.x.x:6001. Please verify that UDP port 6001 is not firewalled and can reach the server.
(By default, mosh uses a UDP port between 60000 and 61000. The -p option selects a specific UDP port number.)
[mosh is exiting.]
谢谢!
Oct 08, 2017 04:01:19 PM
你需要单独手动启动 mosh-server 然后用 mosh-client 去连你转换过的端口。具体请见 manpage。
Oct 08, 2017 04:31:34 PM
啊!原来是这样!刚才试了一下成功了! 非常感谢!
Jan 23, 2019 09:59:06 PM
您好,我也遇到了这个问题,可是设置转发后使用
MOSH_KEY=xxx mosh-client IP port
后连接并不成功,您能指导下具体需要注意的细节么?
Jan 23, 2019 10:03:13 PM
原来是 mosh-server 启动出了问题,搞定了已经。多谢。
Jan 23, 2019 10:08:34 PM
你有外网 IP 么?什么运营商的?
你先使用这个 NATCrackerDiscovery http://lilydjwg.vim-cn.com/share/NATCrackerDiscovery.7z 确定一下 NAT 类型?
Jan 23, 2019 10:23:04 PM
有外网 IP 的,有一台腾讯云作为 frp server.
使用 frp 进行 UDP 端口转发,实现 mosh 连接内网的机器是没有问题的。也就是使用 mosh-server 和 mosh-client 命令手动连接。
但是这个方案不太实用吧?mosh 为了避免认证问题使用的是 ssh 的认证,而 mosh-server 因为安全原因是无法当作 daemon 运行的。如果每次连接都先手动新建 mosh-server,获得 session key,再客户端使用 mosh-client 连接,就过于麻烦了= =
不知道我的理解对不对,还请指教。
Jan 23, 2019 10:28:01 PM
嗯,所以我的方案是有外网 IP 的服务器作为网关,NAT 后的机器通过 wireguard 连接上去,然后它们就可以通过 wireguard 网络接口上的 IP 相互通信了。这样不管是 mosh(命令行)、ssh(传文件)、http(各种基于网页的东西)都能用了。
Jan 23, 2019 10:34:39 PM
cool!
居然忘记了 wireguard
一直使用 RSS 订阅您的博客,不过没评论过,这应该是第一次。感谢长期以来的分享 :)