2010年8月25日 星期三

SSH Connection Multiplexing And DynamicForward Control Script

一直以來,上網在中國就是個大問題,很多東西因為有萬里防火牆的關係無法 access,比如 twitter、facebook、wikipedia。來了中國以後,一直都是依靠塞外的機器做 ssh tunnel。最早的時候用的是 local port forward,需要在 remote machine 上面建 http proxy,然後用以下的語法連到遠端的 http proxy 去:

ssh -f -N -L <local port number>:<http proxy hostname>:<http proxy port> <user>@<ssh login hostname>

這樣 data 的傳遞路徑是  Application -> local port -> SSH -> login host -> proxy host,通常 login host 就是 proxy host,但是也可以不一樣,以前用過家裡的機器當做 ssh host,然後 proxy host 設定成 proxy.seed.net.tw,這樣指令就變成

ssh -f -N -L 1080:proxy.seed.net.tw:1080 myname@home.machine

-f 是要求在輸入密碼(或是不需要輸入密碼)後,ssh process 就到背景去執行,-N 則是要求進入背景後不需要 timeout,也就是說不管這個tunnel上有沒有資料傳送,都一直維持連接的狀態。這樣再把 browser 的 http proxy 設定成 localhost:1080就可以開始用了。
這樣的作法有兩個問題:

  • 只有 http/https protocol 可以用
  • 控制這個 tunnel 的建立和停止機制很麻煩,只能用 ps -auxwww|grep ssh 找出 pid 然後送 -9 給它。

後來發現了有 -D 這個 option,可以建立 SOCKS4/SOCKS5 proxy,就可以把指令改為:

ssh -f -N -D 1080 <user>@<ssh login hostname>

其中的 -g 表示打開 GatewayPort,讓別的機器也可以把這個 port 當做 proxy 來用。改成這樣以後,OS/X 上只要可以用 SOCKS4/SOCKS5 的程式(幾乎是所有會 honor 系統 proxy 設定的程式)都可以用到這個 proxy,而不僅限於 http/https protocol。

只是改成這樣還是無法解決第二個 control 的問題,每次都還是要很工人智慧去用 ps, grep, kill。一直到昨天晚上看到 OpenSSH 5.6 的 release 公告,才重新發現有個叫做 multiplexing connection 的東西。

這個功能其實從 OpenSSH 3.9 就已經放進去了,主要是提供一個 local unix socket,透過這個 socket 重複使用已經建好的 connection,省去 authentication handshake 的時間。這個功能要在 .ssh/config 裡面設定

ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h:%p

這樣建一個 tunnel 或是啟動任何 ssh session 就會在這裡生一個新的 socket 出來,並且把這個主要的 connection 設定為 Master,以後建立到同樣的 user@host 的 ssh connection 就會利用這個 local socket 來 multiplex。對於這個 Master 我們可以送一些控制的 command,目前只有兩個(according to the manpage)exit 和 check。送 exit 當然就會讓 Master 結束,送 check 的話,會 return 這個 Master 目前的狀態和使用的 pid。看到了嘛?出現 pid 了喔,好像可以解決上面第二個問題了,但是有個更簡單的方法,直接送 exit 就好了。所以最後簡化了建立 SOCKS tunnel 的 shellscript,變成如下的片段:

 #!/bin/sh
port=1080
ssh=/usr/bin/ssh


if [ "$2" = "" ] ; then
echo 'Usage: '$0' {start|stop|check} user@hostname'
fi

if [ "$2" = "start" ]; then
$ssh -f -g -D $port -N $1
fi

if [ "$2" = "stop" ]; then
$ssh -D $port -O exit $1
fi
if [ "$2" = "check" ]; then
$ssh -D $port -O check $1
fi

加上 host, hostname 的設定,就可以用 ./socks.sh start hostname 這樣的形式來啟動 tunnel,然後用 ./socks.sh stop hostname 來停止,用 ./socks.sh check hostname 來檢查目前的 tunnel 狀態了。

 

沒有留言:

張貼留言