本文的主要目的是利用使 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:reuseaddr internal:0.0.0.0 port = 1080 external:eth0 logoutput:/var/log/sockd/sockd clientmethod:none method:none user.privileged:root user.notprivileged:solrex connecttimeout:60 iotimeout:86400 ## client access rules client 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 1023 pass { 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 packets pass { from: 0.0.0.0/0 to: 0.0.0.0/0 command: bind bindreply connect udpassociate udpreply log: connect error } #log the rest block { from: 0.0.0.0/0 to: 0.0.0.0/0 log: connect error }
下面是 Dante 的启动脚本 /etc/init.d/sockd:
#!/bin/sh set -e . /lib/lsb/init-functions [ -f /usr/local/sbin/sockd ] || exit 0 [ ! -f /etc/sockd.conf ] && exit 1 SOCKD_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 1 esac exit 0
二、ssh 服务器
服务器端 ssh 服务器最好采用 Openssh,Windows 下应该使用 Cygwin 运行 sshd,FreeSSHd 实践证明崩溃比较频繁。同一台机器的 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_info case "$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 ;; esac exit 0
将上面脚本加入 crontab 每十分钟运行一次,就能实现在服务器端对客户端进行有限的控制。
运气好真是没话说-_-!
需要输入密码,我试试看的输入了solrex,竟然密码对了0o0
抱歉,没看懂。
将客户机(127.0.0.1)的 1080 端口=====为什么不是对方的ip呢?比如2.2.2.2,而是一个回环地址?我在其他ssh隧道场合下,填写localhost或者127.0.0.1都不可以的。!