本文的主要目的是利用使 ssh 服务器(外网)通过客户端(内网)代理上网以及反向控制客户端(内网),不需要网络管理员权限,不需要 NAT,
内网穿透反向隧道代理技术
。即可上网主机 A 位于内网,不可上网主机 B 位于外网,A 能直接访问 B,但 B 无法访问防火墙内的 A,这样 B 就可以使用 SSH 隧道访问 A 主机上的代理服务器上网。(估计有这种变态需求的人只存在于大学中)B(210.77.*.*)---->(210.77.*.*)FW(192.168.0.*)|--->(192.168.0.*)A---->(192.168.0.*)FW(123.*.*.*)---->Internet
B(210.77.*.*)< ----(210.77.*.*)FW(192.168.0.*)-----(192.168.0.*)A<----(192.168.0.*)FW(123.*.*.*)<----Internet
一、代理
首先在客户端上安装 socks 代理(HTTP 代理可以使用 Squid,同理)。socks 代理软件使用 Dante。Dante 的启动以及配置需要手动调整,下面是 Dante 的配置文件 /etc/sockd.conf:
compatibility:reuseaddrinternal:0.0.0.0 port = 1080external:eth0logoutput:/var/log/sockd/sockdclientmethod:nonemethod:noneuser.privileged:rootuser.notprivileged:solrexconnecttimeout:60iotimeout:86400## client access rulesclient pass { from: 127.0.0.1/0 port 1-65535 to: 0.0.0.0/0 log: connect disconnect}## server operation access rules#allow bind to ports greater than 1023pass { from: 0.0.0.0/0 to: 0.0.0.0/0 port gt 1023 command: bind bindreply udpassociate udpreply log: connect disconnect}pass { from: 0.0.0.0/0 to: 0.0.0.0/0 port 1-65535 protocol: tcp udp log: connect disconnect}#allow outgoing connections (tcp and udp)pass { from: 127.0.0.1/0 to: 0.0.0.0/0 command: bind bindreply connect udpassociate udpreply log: connect disconnect}#allow replies to bind, and incoming udp packetspass { from: 0.0.0.0/0 to: 0.0.0.0/0 command: bind bindreply connect udpassociate udpreply log: connect error}#log the restblock { from: 0.0.0.0/0 to: 0.0.0.0/0 log: connect error}
下面是 Dante 的启动脚本 /etc/init.d/sockd:
#!/bin/shset -e. /lib/lsb/init-functions[ -f /usr/local/sbin/sockd ] || exit 0[ ! -f /etc/sockd.conf ] && exit 1SOCKD_CONF="-f /etc/sockd.conf"SOCKD_OPTS="-D"case "$1" in start) # Start daemons. log_daemon_msg "Starting Dante socks proxy server" "sockd" if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/sockd.pid --exec /usr/local/sbin/sockd -- $SOCKD_OPTS; then log_end_msg 0 else log_end_msg 1 fi ;; stop) # Stop daemons. log_daemon_msg "Stoping Dante socks proxy server" "sockd" if start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/sockd.pid; then log_end_msg 0 else log_end_msg 1 fi ;; restart) log_daemon_msg "Restarting Dante socks proxy server" "sockd" start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile /var/run/sockd.pid if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/sockd.pid --exec /usr/local/sbin/sockd -- $SOCKD_OPTS; then log_end_msg 0 else log_end_msg 1 fi ;; *) log_action_msg "Usage: /etc/init.d/sockd {start|stop|restart}\n" exit 1esacexit 0
二、ssh 服务器
服务器端 ssh 服务器最好采用 Openssh,Windows 下应该使用 Cygwin 运行 sshd,FreeSSHd 实践证明崩溃比较频繁,
电脑资料
《内网穿透反向隧道代理技术》(http://meiwen.anslib.com)。同一台机器的 Windows 和 Linux 上运行的 ssh 服务器最好使用相同的 host key,将 /etc/ssh/ 下面的文件保持一致即可。最好配置服务器为使用公钥进行登录验证,这样能使用自动脚本进行端口映射。三、手动建立隧道(客户端到服务器端口映射)
用下面命令建立客户端到服务器的端口映射,将客户端的 socks 代理端口 1080 映射到服务器的端口 8080。这样服务器就可以通过自己的 8080 端口反向隧道到客户端的 socks 代理 1080 端口上网。ssh -C -f -N -g -o PreferredAuthentications=publickey -R SERVER:8080:127.0.0.1:1080 USRNAME@SERVER
# 参数含义:
# -C: 要求对数据进行压缩
# -f:要求 ssh 执行完交互后进入后台运行
# -N:不用建立一个终端
# -g:允许远程服务器连接客户端转发端口
# -R SERVER:8080:127.0.0.1:1080:将客户机(127.0.0.1)的 1080 端口绑定到服务器(SERVER)的 8080 端口。
# USRNAME@SERVER:ssh服务器的用户名和密码
# -p 3022:ssh服务器的端口
为了方便控制,最好也将客户端的 ssh 服务器端口映射到服务器端,服务器端就可以通过登录自己的 8022 端口来登录客户端的 ssh 服务器:
ssh -C -f -N -g -o PreferredAuthentications=publickey -R SERVER:8022:127.0.0.1:22 USRNAME@SERVER
这样两台被防火墙隔开的主机就能实现双向控制。
四、自动建立隧道
手动建立隧道的缺陷是服务器端必须长期开机,并且连接只能用户在客户端手动发起。可以考虑间接的办法,比如使用两台主机均可访问的服务器作为跳板,或者客户端自动登录聊天软件,利用聊天软件接受指令。下面给出一种使用共享服务器的方法:
#!/bin/bash# {start|stop|restart:username:ipaddress}COMMAND="stop"SERVER="0.0.0.0"USRNAME=""PORT="22"INFOURL="http://someserver.com/somepage"SSHOPTS="-C -f -N -g -o PreferredAuthentications=publickey -o StrictHostKeyChecking=no"get_server_info(){ #info=`wget -nv -O - $INFOURL 2> /dev/null | iconv -f gbk -t utf8 | grep -o -e "{.*}" | tr -d '{}'` info=`wget -nv -O - $INFOURL 2> /dev/null | iconv -f gbk -t utf8 | sed -n "/{*}/s/.*{\(.*\)}.*/\1/p"` COMMAND=${info%%:*} SERVER=${info#*:} USRNAME=${SERVER%:*} SERVER=${SERVER#*:}}tunneling_status(){ tun_ps=`ps aux | grep "ssh -C" | wc -l` if [ $tun_ps -gt 4 ]; then echo -n "running" else echo -n "died" fi}start_tunneling(){ ssh $SSHOPTS -R 3128:127.0.0.1:3128 ${USRNAME}@${SERVER} -p $PORT ssh $SSHOPTS -R 8022:127.0.0.1:22 ${USRNAME}@${SERVER} -p $PORT}failsafe_tunneling(){ ssh $SSHOPTS -R 8022:127.0.0.1:22 ${USRNAME}@${SERVER} -p $PORT}stop_tunneling(){ killall -e ssh}echo -n "[`date +%F\ %R`] "get_server_infocase "$COMMAND" in start) if [ $(tunneling_status) = "running" ]; then echo "Starting ssh tunneling.(started, do nothing)" else echo "Starting ssh tunneling." start_tunneling fi ;; restart) if [ $(tunneling_status) = "running" ]; then echo "Restarting ssh tunneling." stop_tunneling start_tunneling else echo "Restarting ssh tunneling." start_tunneling fi ;; stop) if [ $(tunneling_status) = "running" ]; then echo "Stoping ssh tunneling." stop_tunneling else echo "Stoping ssh tunneling.(stoped, do nothing)" fi ;; failsafe) echo "Starging ssh tunneling.(failsafe mode)" failsafe_tunneling ;; sleep) echo "Sleeping." exit 0 ;; *) echo "Unrecogenized server command ($COMMAND)." exit 1 ;;esacexit 0
将上面脚本加入 crontab 每十分钟运行一次,就能实现在服务器端对客户端进行有限的控制。