10
13
2013
13

通过 UDP 打洞连接 NAT 后边的 mosh

本文来自依云'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 )

然后,连接流程如下:

  1. mosh 服务端运行holepunch.py -s命令,客户端运行holepunch.py
  2. 双方看到自己的地址信息(依次是IP, 外网端口, 本地端口)后,复制并发送给对方;
  3. 双方输入对方的地址。为了节省时间(mosh-server 只会等一分钟,NAT 上的端口映射也是有时效的),只要输入的一行内包含上述地址信息的文本即可,前后可以有不小心复制过来的多余字符;
  4. 服务端将得到一行包含 mosh 的密钥的命令。将此命令发送对客户端;客户端运行此命令连接。如果 mosh-client 的名字不是标准名字,需要自行修改;
  5. 一切顺利的话就连接上啦!

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 洞

Category: 网络 | Tags: python 网络 mosh UDP | Read Count: 16628
5169.info 说:
Oct 13, 2013 05:11:19 PM

你好,我想在我的网站上放一个提问的智慧的网页,想索取一个html版本放在我的网站上,不知能不能提供一下呢?

Avatar_small
依云 说:
Oct 13, 2013 11:16:59 PM

这里——http://lilydjwg.vim-cn.com/articles/smart-questions.html 。

Avatar_small
依云 说:
Oct 13, 2013 11:24:03 PM

你的网站的页面编码有问题哦,它在 gzip 压缩之后的数据的前边添加了三个字字的 BOM 导致浏览器无法解码。火狐和 Chrome 均如此。

ShadeScript 说:
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.]

谢谢!

Avatar_small
依云 说:
Oct 08, 2017 04:01:19 PM

你需要单独手动启动 mosh-server 然后用 mosh-client 去连你转换过的端口。具体请见 manpage。

ShadeScript 说:
Oct 08, 2017 04:31:34 PM

啊!原来是这样!刚才试了一下成功了! 非常感谢!

wogong 说:
Jan 23, 2019 09:59:06 PM

您好,我也遇到了这个问题,可是设置转发后使用
MOSH_KEY=xxx mosh-client IP port
后连接并不成功,您能指导下具体需要注意的细节么?

wogong 说:
Jan 23, 2019 10:03:13 PM

原来是 mosh-server 启动出了问题,搞定了已经。多谢。

Avatar_small
依云 说:
Jan 23, 2019 10:08:34 PM

你有外网 IP 么?什么运营商的?

你先使用这个 NATCrackerDiscovery http://lilydjwg.vim-cn.com/share/NATCrackerDiscovery.7z 确定一下 NAT 类型?

wogong 说:
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 连接,就过于麻烦了= =

不知道我的理解对不对,还请指教。

Avatar_small
依云 说:
Jan 23, 2019 10:28:01 PM

嗯,所以我的方案是有外网 IP 的服务器作为网关,NAT 后的机器通过 wireguard 连接上去,然后它们就可以通过 wireguard 网络接口上的 IP 相互通信了。这样不管是 mosh(命令行)、ssh(传文件)、http(各种基于网页的东西)都能用了。

wogong 说:
Jan 23, 2019 10:34:39 PM

cool!

居然忘记了 wireguard

一直使用 RSS 订阅您的博客,不过没评论过,这应该是第一次。感谢长期以来的分享 :)


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

| Theme: Aeros 2.0 by TheBuckmaker.com