本文发自 http://www.binss.me/blog/using-mosh-to-solve-ssh-delay-problem/,转载请注明出处。

总所周知,由于某W的原因,到境外的服务器的连接都收到干扰,包括ssh。于是敲个命令卡卡卡卡,或者索性假死,简直是怒怒怒怒。

在网上搜索一番后,发现了mosh这款软件。

介绍

官方的介绍如下:

Remote terminal application that allows roaming, supports intermittent connectivity, and provides intelligent local echo and line editing of user keystrokes. Mosh is a replacement for SSH. It's more robust and responsive, especially over Wi-Fi, cellular, and long-distance links. Mosh is free software, available for GNU/Linux, FreeBSD, Solaris, Mac OS X, and Android.

根据官方的一些文档,相比于ssh,其主要解决了以下几个问题:

  1. 切换网络/暂时断网后连接断开、会话丢失

  2. 休眠唤醒后连接断开、会话丢失

  3. 在高延迟高丢包网络下糟糕的用户体验

原理

在谈mosh的工作原理之前,先谈谈SSH是怎么工作的:

  1. 建立会话

    客户端和服务端建立TCP连接,服务端收到登陆请求后,把自己的公钥发给客户端。用户随机产生一个会话密钥(对称),用服务端发来的公钥加密后,发送给服务端。服务端利用自己的私钥解密获得会话密钥。此时双方都拥有相同的会话密钥,此后的通行都使用该会话密钥进行加密,这样就在客户端和服务端间建立了一个安全的会话连接。

  2. 认证

    在建立了安全的会话连接后,此后客户端和服务端之间的通信全部基于 进入认证阶段,服务端需要判断客户端是否对本机有登陆权限。认证分为密码认证和密钥认证两种:

    密码认证:客户端把密码发给给服务端进行认证,如果密码正确,则认证成功,允许用户登录。

    密钥认证:服务端发送一段随机字符串(挑战)给客户端,客户端用自己的私钥对字符串加密后,发送回服务端;服务端利用事先设置的公钥解密字符串,如果字符串正确,则认证成功,允许用户登录。

  3. 通信

    此后客户端即可通过该会话连接向服务端终端发送指令、上传文件等。于是诞生了一种利用ssh-tunnel来Fuck Wall的方法,即通过ssh通道将将客户端的某个端口的流量转发到服务端,由服务端把流量转发到真正的目标地址。但是由于ssh握手特征太明显,建立每一个连接都需要建立会话、认证等操作,因此极易被Wall盯上,于是就诞生了特征不太明显的SS。咳咳,扯远了,回到通过ssh向服务端终端发送指令的过程上:

    每当用户在客户端终端敲下一个字符,ssh都会将该字符发送到服务端,服务端将响应发送回客户端,客户端回显,然后允许用户接着敲下一个字符。这种实时的交互在网络状况良好的情况下工作正常,然而在高延迟高丢包环境下(比如连接海外VPS)会产生每敲一个字符都卡卡卡卡卡的问题。

    Mosh是怎么解决这个问题的呢?

    Mosh制定了状态同步协议(State Synchronization Protocol),其通过UDP数据报来传输数据,数据报采用递增的序列号来判断顺序。客户端通过该协议向服务端发送用户敲下的字符,服务端通过该协议向客户端发送当前终端的状态,这种基于状态数据报的通信方式使得无需像流一样需要每个字符发送一次,而是根据网络状况攒到一定量时才发送。用户每次按键时会通过mosh的预测机制先显示出来,而数据由mosh在后台慢慢发送。因此在极端网络情形下,只有最后enter(结果难以预测)时会卡。

    此外,基于UDP数据报而不是TCP连接允许客户端在断网或网络切换后依然能够保持和服务端的连接,因为状态是通过应用层协议来保持的,不会因为连接的中断而丢失。附带的好处就是不会因为某WALL的RST包而导致连接中断。

    Mosh的工作流程

    1. 利用别的协议(SSH)来协商

    2. 服务端监听一个高位(60000-61000)UDP端口,且将它的端口号和一个AES-128的会话密钥通过SSH发送回客户端

    3. 客户端用会话密钥加密数据,并向该端口发送UDP数据报

    4. 服务端用会话密钥解密收到的数据报,执行相应的指令,将屏幕状态数据加密后通过UDP数据报发送给客户端

缺点

  1. 状态同步、维持心跳、协助预测的ACK包都增加了传输的数据量

  2. 服务端需要开放大量端口,增加了不安全性

安装

经过研究,mosh非常适合我当前的状况,果断装起:

MAC端

brew install mobile-shell

Server端

sudo apt-get install mosh
sudo ufw allow 60000:61000/udp

没装ufw童鞋的可以用iptables来放行端口:

sudo iptables -I INPUT 1 -p udp --dport 60000:61000 -j ACCEPT

使用

通过ssh来使用mosh:

mosh [email protected] --ssh="ssh"

如果ssh在config中配置了Host,可以像ssh一样直接用Host登陆:

mosh linode

总结

经过测试,虽然不是完全不卡,但比起用ssh还是好多了。

2016-04-29更新

经过多天的使用,感觉还是有些坑:

  1. 如果使用了powerline,颜色显示会背景色显示不出来,前景色不科学等问题,而且Mosh官方不打算改。见https://github.com/mobile-shell/mosh/issues/507

  2. iTerm2设置的通过鼠标滚轮来滚动屏幕的方法在Mosh下变成滚动命令。

  3. 在低延迟(<100ms)情况下,效果没比ssh好多少。

参考

http://www-h.eng.cam.ac.uk/help/jpmg/ssh/ssh-detail.html

http://www.linuxhowtos.org/Security/understandssh.htm

https://www.digitalocean.com/community/tutorials/how-to-install-and-use-mosh-on-a-vps

Mosh: An Interactive Remote Shell for Mobile Clients