首页 > 代码库 > ssh和SSH服务(包含隧道内容)

ssh和SSH服务(包含隧道内容)

ssh和SSH服务(包含隧道内容)

72.16.10.6:/etc/fstab-->/172.16.10.3:/tmp/a.txt。

[root@xuexi ~]# scp 172.16.10.6:/etc/fstab 172.16.10.3:/tmp/a.txt
fstab                                          100%  501     0.5KB/s   00:00   
Connection to 172.16.10.6 closed.

1.7.1 scp拷贝机制分析

scp的拷贝实质是建立ssh连接,然后通过此连接来传输数据。如果是远程1拷贝到远程2,则是将scp命令转换后发送到远程1上执行,在远程1上建立和远程2的ssh连接,并通过此连接来传输数据。

在远程复制到远程的过程中,例如在本地(172.16.10.5)执行scp命令将A主机(172.16.10.6)上的/tmp/copy.txt复制到B主机(172.16.10.3)上的/tmp目录下,如果使用-v选项查看调试信息的话,会发现它的步骤类似是这样的。

# 以下是从结果中提取的过程
# 首先输出本地要执行的命令
Executing: /usr/bin/ssh -v -x -oClearAllForwardings yes -t -l root 172.16.10.6 scp -v /tmp/copy.txt root@172.16.10.3:/tmp
 
# 从本地连接到A主机
debug1: Connecting to 172.16.10.6 [172.16.10.6] port 22.
debug1: Connection established.
 
# 要求验证本地和A主机之间的连接
debug1: Next authentication method: password
root@172.16.10.6‘s password:
 
# 将scp命令行修改后发送到A主机上
debug1: Sending command: scp -v /tmp/copy.txt root@172.16.10.3:/tmp
 
# 在A主机上执行scp命令
Executing: program /usr/bin/ssh host 172.16.10.3, user root, command scp -v -t /tmp
 
# 验证A主机和B主机之间的连接
debug1: Next authentication method: password
root@172.16.10.3‘s password:
 
# 从A主机上拷贝源文件到最终的B主机上
debug1: Sending command: scp -v -t /tmp
Sending file modes: C0770 24 copy.txt
Sink: C0770 24 copy.txt
copy.txt                                                           100%   24     0.0KB/s  
 
# 关闭本地主机和A主机的连接
Connection to 172.16.10.6 closed.

也就是说,远程主机A到远程主机B的复制,实际上是将scp命令行从本地传输到主机A上,由A自己去执行scp命令。也就是说,本地主机不会和主机B有任何交互行为,本地主机就像是一个代理执行者一样,只是帮助传送scp命令行以及帮助显示信息。

其实从本地主机和主机A上的~/.ssh/know_hosts文件中可以看出,本地主机只是添加了主机A的host key,并没有添加主机B的host key,而在主机A上则添加了主机B的host key。

技术分享

 

1.8 基于公钥认证机制实现双机互信

在身份验证阶段,由于默认情况下基于公钥认证的机制顺序优先于基于密码认证的机制,所以基于公钥认证身份,就可以免输入密码,即实现双机互信(实际上只是单方向的信任)。

基于公钥认证机制的认证过程在前文已经详细说明过了,如还不清楚,请跳回上文。

1.8.1 实现步骤

以下是实现基于公钥认证的实现步骤:

(1).在客户端使用ssh-keygen生成密钥对,存放路径按照配置文件的指示,默认是在~/.ssh/目录下。

[root@xuexi ~]# ssh-keygen -t rsa    # -t参数指定算法,可以是rsa或dsa
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):  # 询问私钥保存路径
Enter passphrase (empty for no passphrase):               # 询问是否加密私钥文件
Enter same passphrase again:            
Your identification has been saved in /root/.ssh/id_rsa. 
Your public key has been saved in /root/.ssh/id_rsa.pub. 

如果不想被询问,则可以使用下面一条命令完成:"-f"指定私钥文件,"-P"指定passphrase,或者"-N"也一样。

[root@xuexi ~]# ssh-keygen -t rsa -f ~/.ssh/id_rsa -P ‘‘   # 指定加密私钥文件的密码为空密码,即不加密

[root@xuexi ~]# ssh-keygen -t rsa -f ~/.ssh/id_rsa -N ‘‘   # 同上

查看~/.ssh/目录下私钥的权限。私钥文件有严格的权限要求,当私钥文件的非所有者有可读权限时,将直接忽略该私钥文件导致公钥认证失败。

[root@xuexi ~]# ls -l ~/.ssh
total 12
-rw------- 1 root root 1671 Jun 29 00:18 id_rsa      # 私钥权限必须600,属主为自己
-rw-r--r-- 1 root root  406 Jun 29 00:18 id_rsa.pub
-rw-r--r-- 1 root root  393 Jun 29 05:56 known_hosts

(2).将上面生成的公钥使用ssh-copy-id分发(即复制)到远程待信任主机上。

ssh-copy-id用法很简单,只需指定待信任主机及目标用户即可。如果生成的公钥文件,路径不是~/.ssh/id_rsa.pub,则使用"-i"选项指定要分发的公钥。

ssh-copy-id [-i [identity_file]] [user@]machine

例如,将公钥分发到172.16.10.6上的root用户家目录下:

[root@xuexi ~]# ssh-copy-id 172.16.10.6

ssh-copy-id唯一需要注意的是,如果ssh服务端的端口不是22,则需要给ssh-copy-id传递端口号,传递方式为"-p port_num [user@]hostname",例如"-p 22222 root@172.16.10.6"。之所以要如此传递,见下面摘自ssh-copy-id中公钥分发的命令部分。

{ eval "$GET_ID" ; } | ssh $1 "umask 077; test -d ~/.ssh || mkdir ~/.ssh ; cat >> ~/.ssh/authorized_keys && (test -x /sbin/restorecon && /sbin/restorecon ~/.ssh ~/.ssh/authorized_keys >/dev/null 2>&1 || true)" || exit 1

其中"{ eval "$GET_ID" ; }"可理解为待分发的本地公钥内容,"(test -x /sbin/restorecon && /sbin/restorecon ~/.ssh ~/.ssh/authorized_keys >/dev/null 2>&1 || true)"和selinux有关,不用管,所以上述命令简化为:

cat ~/.ssh/id_rsa.pub | ssh $1 "umask 077; test -d ~/.ssh || mkdir ~/.ssh ; cat >> ~/.ssh/authorized_keys || exit 1

可见,ssh-copy-id的所有参数都是存储在位置变量$1中传递给ssh,所以应该将ssh的端口选项"-p port_num"和user@hostname放在一起传递。

通过分析上面的命令,也即知道了ssh-copy-id的作用:在目标主机的指定用户的家目录下,检测是否有~/.ssh目录,如果没有,则以700权限创建该目录,然后将本地的公钥追加到目标主机指定用户家目录下的~/.ssh/authorized_keys文件中。

1.8.2 一键shell脚本

就这样简单的两步就实现了基于公钥的身份认证。当然,不使用ssh-copy-id,也一样能实现上述过程。更便捷地,可以写一个简单的脚本,简化上述两个步骤为1个步骤。

#!/bin/bash
 
###########################################################
#  description: public key authentication in one step     #
#  author     : 骏马金龙                                   #
#  blog       : http://www.cnblogs.com/f-ck-need-u/       #
###########################################################
 
privkey="$HOME/.ssh/id_rsa"
publickey="$HOME/.ssh/id_rsa.pub"
 
# Usage help
if [ $# -ne 1 ];then
   echo "Usage:$0 [user@]hostname"
   exit 1
fi
 
# test private/publick key exist or not, and the privilege 600 or not
if [ -f "$privkey" -a -f "$publickey" ];then
   privkey_priv=`stat -c %a $privkey`
   if [ "$privkey_priv" -ne 600 ];then
       echo "The privilege of private key ~/.ssh/id_rsa is not 600, exit now."
       exit 1
   fi
else
   echo "private/public key is not exist, it will create it"
   ssh-keygen -t rsa -f $privkey -N ‘‘
   echo "keys created over, it located on $HOME/.ssh/"
fi
 
ssh-copy-id "-o StrictHostKeyChecking=no $1"
 
if [ $? -eq 0 ];then
   echo -e "\e[31m publickey copy over \e[0m"
else
   echo "ssh can‘t to the remote host"
   exit 1
fi

该脚本将检查本地密钥对~/.ssh/{id_rsa,id_rsa.pub}文件是否存在,还检查私钥文件的权限是否为600。如果缺少某个文件,将自动新创建密钥对文件,最后分发公钥到目标主机。

1.8.3 公钥认证之——服务端分发私钥

对于基于公钥认证的身份验证机制,除了上面客户端分发公钥到服务端的方法,还可以通过分发服务端私钥到客户端来实现。

先理清下公钥认证的原理:客户端要连接服务端,并告诉服务端要使用那对密钥对,然后客户端访问自己的私钥,服务端检测对应的公钥来决定该公钥所对应的客户端是否允许连接。所以对于基于公钥认证的身份验证,无论是客户端分发公钥,还是服务端分发私钥,最终客户端保存的一定是私钥,服务端保存的一定是公钥。

那么服务端分发私钥实现公钥认证是如何实现的呢?步骤如下:假如客户端为172.16.10.5,服务端为172.16.10.6。

(1).在服务端使用ssh-keygen生成密钥对。

[root@xuexi ~]# ssh-keygen -f ~/.ssh/id_rsa -P ‘‘

(2).将上面生成的公钥追加到自己的authorized_keys文件中。

[root@xuexi ~]# ssh-copy-id 172.16.10.6

(3).将私钥拷贝到客户端,且路径和文件名为~/.ssh/id_rsa。

[root@xuexi ~]# scp -p ~/.ssh/id_rsa* 172.16.10.5:/root/.ssh/

注意,第三步中也拷贝了公钥,原因是客户端连接服务端时会比较自己的公钥和私钥是否配对,如果不配对将直接忽略公钥认证机制,所以会要求输入密码。可以将客户端的公钥删除掉,或者将服务端生成的公钥覆盖到客户端的公钥上,都能完成公钥认证。

虽说,服务端分发私钥的方式很少用,但通过上面的步骤,想必对ssh基于公钥认证的身份验证过程有了更深入的理解。

1.9 expect实现ssh/scp完全非交互(批量)

expect工具可以在程序发出交互式询问时按条件传递所需的字符串,例如询问yes/no自动传递y或yes,询问密码时自动传递指定的密码等,这样就能让脚本完全实现非交互。

显然,ssh等客户端命令基于密码认证时总是会询问密码,即使是基于公钥认证,在建立公钥认证时也要询问一次密码。另外,在ssh主机验证时还会询问是否保存host key。这一切都可以通过expect自动回答。

关于expect工具,它使用的是tcl语言,虽说应用在expect上的tcl语言并非太复杂,但这并非本文内容,如有兴趣,可网上搜索或直接阅读man文档。

首先安装expect工具。

[root@xuexi ~]# yum -y install expect

1.9.1 scp自动应答脚本

以下是scp自动问答的脚本。

[root@xuexi ~]# cat autoscp.exp
#!/usr/bin/expect
########################################################### # description: scp without interactive # # author : 骏马金龙 # # blog : http://www.cnblogs.com/f-ck-need-u/ # ########################################################### set timeout 10 set user_hostname [lindex $argv 0] set src_file [lindex $argv 1] set dest_file [lindex $argv 2] set password [lindex $argv 3] spawn scp $src_file $user_hostname:$dest_file expect { "(yes/no)?" { send "yes\n" expect "*assword:" { send "$password\n"} } "*assword:" { send "$password\n" } } expect "100%" expect eof

用法:autoscp.exp [user@]hostname src_file dest_file [password]

该自动回答脚本可以自动完成主机验证和密码认证,即使已经是实现公钥认证的机器也没问题,因为公钥认证机制默认优先于密码认证,且此脚本的password项是可选的,当然,在没有实现公钥认证的情况下,password是必须项,否则expect实现非交互的目的就失去意义了。

以下是几个示例:

[root@xuexi ~]# ./autoscp.exp 172.16.10.6 /etc/fstab /tmp 123456
spawn scp /etc/fstab 172.16.10.6:/tmp
The authenticity of host ‘172.16.10.6 (172.16.10.6)‘ can‘t be established.
RSA key fingerprint is f3:f8:e2:33:b4:b1:92:0d:5b:95:3b:97:d9:3a:f0:cf.
Are you sure you want to continue connecting (yes/no)? yes          # 主机验证时询问是否保存host key,自动回答yes
Warning: Permanently added ‘172.16.10.6‘ (RSA) to the list of known hosts.
root@172.16.10.6‘s password:                                        # 密码认证过程,自动回答指定的密码"123456"
fstab                                                        100%  805     0.8KB/s   00:00

也可以指定完成的用户名和主机名。

[root@xuexi ~]# ./autoscp.exp root@172.16.10.6 /etc/fstab /tmp 123456
spawn scp /etc/fstab root@172.16.10.6:/tmp
root@172.16.10.6‘s password:         
fstab                                 100%  805     0.8KB/s   00:00

1.9.2 ssh-copy-id自动应答脚本

以下是在建立公钥认证机制时,ssh-copy-id拷贝公钥到服务端的自动应答脚本。

[root@xuexi ~]# cat /tmp/autocopy.exp
#!/usr/bin/expect

###########################################################
#  description: scp without interactive                   #
#  author     : 骏马金龙                                   #
#  blog       : http://www.cnblogs.com/f-ck-need-u/       #
###########################################################

set timeout 10
set user_hostname [lindex $argv 0]
set password [lindex $argv 1]
spawn ssh-copy-id $user_hostname
    expect {
        "(yes/no)?"
        {
            send "yes\n"
            expect "*assword:" { send "$password\n"}
        }
        "*assword:"
        {
            send "$password\n"
        }
    }
expect eof

用法:autocopy.exp [user@]hostname password

以下是一个示例,

[root@xuexi ~]# /tmp/autocopy.exp root@172.16.10.6 123456
spawn ssh-copy-id root@172.16.10.6
The authenticity of host ‘172.16.10.6 (172.16.10.6)‘ can‘t be established.
RSA key fingerprint is f3:f8:e2:33:b4:b1:92:0d:5b:95:3b:97:d9:3a:f0:cf.
Are you sure you want to continue connecting (yes/no)? yes      # 主机认证时,自动应答yes
Warning: Permanently added ‘172.16.10.6‘ (RSA) to the list of known hosts.
root@172.16.10.6‘s password:                                    # 密码认证时自动输入密码"123456"
Now try logging into the machine, with "ssh ‘root@172.16.10.6‘", and check in:
 
  .ssh/authorized_keys
 
to make sure we haven‘t added extra keys that you weren‘t expecting.

如果要实现批量非交互,则可以写一个shell脚本调用该expect脚本。例如:

[root@xuexi ~]# cat /tmp/sci.sh
#!/bin/bash

###########################################################
#  description: scp without interactive                   #
#  author     : 骏马金龙                                   #
#  blog       : http://www.cnblogs.com/f-ck-need-u/       #
###########################################################
 
passwd=123456               # 指定要传递的密码为123456
user_host=`awk ‘{print $3}‘ ~/.ssh/id_rsa.pub`   # 此变量用于判断远程主机中是否已添加本机信息成功
 
for i in $@  
do
        /tmp/autocopy.exp $i $passwd >&/dev/null
        ssh $i "grep "$user_host" ~/.ssh/authorized_keys" >&/dev/null  # 判断是否添加本机信息成功
        if [ $? -eq 0 ];then
                echo "$i is ok"
        else
                echo "$i is not ok"
        fi
done

用法:/tmp/sci.sh [user@]hostname

其中hostname部分可以使用花括号展开方式枚举。但有个bug,最好ssh-copy-id的目标不要是脚本所在的本机,可能会强制输入本机密码,但批量脚本autocopy.exp则没有此bug。

例如:

[root@xuexi tmp]# /tmp/sci.sh 172.16.10.3 172.16.10.6
172.16.10.3 is ok
172.16.10.6 is ok
[root@xuexi tmp]# /tmp/sci.sh 172.16.10.{3,6}
172.16.10.3 is ok
172.16.10.6 is ok
[root@xuexi tmp]# /tmp/sci.sh root@172.16.10.3 172.16.10.6
root@172.16.10.3 is ok
172.16.10.6 is ok

1.10 ssh连接速度慢的几个原因和解决办法

ssh连接包括两个阶段:主机验证阶段和身份验证阶段。这两个阶段都可能导致连接速度慢。

具体是哪个阶段的速度慢,完全可以通过肉眼看出来:

(1).卡着很久才提示保存host key肯定是主机验证过程慢。

(2).主机验证完成后卡着很久才提示输入密码,肯定是身份验证过程慢。

其中主机验证过程慢的原因,可能是网络连接慢、DNS解析慢等原因。网络连接慢,ssh对此毫无办法,而DNS解析慢,ssh是可以解决的,解决方法是将ssh服务端的配置文件中UseDNS设置为no(默认为yes)。

而身份验证慢的原因,则考虑ssh的身份验证顺序:gssapi,host-based,publickey,keyboard-interactive,password。其中gssapi认证顺序是比较慢的,所以解决方法一是在ssh客户端配置文件中将GSSAPI认证机制给关掉,解决方法二是在ssh客户端配置文件中使用PreferredAuthentications指令修改身份验证顺序。

方法一修改:GSSAPIAuthentication yes

方法二修改:PreferredAuthentications publickey,password,gssapi,host-based,keyboard-interactive

如果感受不到哪个阶段导致速度变慢,可以使用ssh或scp等客户端工具的"-vvv"选项进行调试,看看是卡在哪个地方,不过,想看懂"-vvv"的过程,还是比较考验耐心的。

1.11 SSH安全隧道(ssh跃墙之术)

1.11.1 ssh安全隧道(一):本地端口转发

如下图,假如host3和host1、host2都同互相通信,但是host1和host2之间不能通信,如何从host1连接上host2?

对于实现ssh连接来说,实现方式很简单,从host1 ssh到host3,再ssh到host2,也就是将host3作为跳板的方式。但是如果不是ssh,而是http的80端口呢?如何让host1能访问host2的80端口?

技术分享

ssh支持本地端口转发,语法格式为:

ssh -L [bind_addr:]local_port:remote:remote_port middle_host

以上图为例,实现方式是在host1上执行:

[root@xuexi ~]# ssh -g -L 2222:host2:80 host3

其中"-L"选项表示本地端口转发,其工作方式为:在本地指定一个由ssh监听的转发端口(2222),将远程主机的端口(host2:80)映射为本地端口(2222),当有主机连接本地映射端口(2222)时,本地ssh就将此端口的数据包转发给中间主机(host3),然后host3再与远程主机的端口(host2:80)通信。

现在就可以通过访问host1的2222端口来达到访问host2:80的目的了。例如:

技术分享

再来解释下"-g"选项,指定该选项表示允许外界主机连接本地转发端口(2222),如果不指定"-g",则host4将无法通过访问host1:2222达到访问host2:80的目的。甚至,host1自身也不能使用172.16.10.5:2222,而只能使用localhost:2222或127.0.0.1:2222这样的方式达到访问host2:80的目的,之所以如此,是因为本地转发端口默认绑定在回环地址上。可以使用bind_addr来改变转发端口的绑定地址,例如:

[root@xuexi ~]# ssh -L 172.16.10.5:2222:host2:80 host3

这样,host1自身就能通过访问172.16.10.5:2222的方式达到访问host2:80的目的。

一般来说,使用转发端口,都建议同时使用"-g"选项,否则将只有自身能访问转发端口。

再来分析下转发端口通信的过程。

技术分享

当host4发起172.16.10.5:2222的连接时(即步骤①),数据包的目标地址和端口为"172.16.10.5:2222"。由于host1上ssh已经监听了2222端口,并且知道该端口映射自哪台主机哪个端口,所以将会把该数据包目标地址和端口替换为"172.16.10.3:80",并将此数据包通过转发给host3。当host3收到该数据包时,发现是host1转发过来请求访问host2:80的数据包,所以host3将代为访问host2的80端口。

所以,host1和host3之间的通信方式是SSH协议,这段连接是安全加密的,因此称为"安全隧道",而host3和host2之间通信协议则是HTTP而不是ssh。

现在再来考虑下,通过本地端口转发的方式如何实现ssh跳板的功能呢?仍以上图为例:

[root@xuexi ~]# ssh -g -L 22333:host2:22 host3

这样只需使用ssh连上host1的22333端口就等于连接了host2的22端口。

甚至还可以在host3上执行:

1
2
3
4
5
[root@xuexi ~]# ssh -L 172.16.10.5:22333:host2:80 host2
 
或:
 
[root@xuexi ~]# ssh -L 172.16.10.5:22333:host2:80 host3

这样在host3就开启了一个转发端口22333供host1连接。当host1连接到host3:22333时,host3将转发给host2或host3自身,再由host2或host3自身与host2通信。

最后,关于端口转发有一个需要注意的问题:ssh命令中带有要执行的命令。考虑了下面的三条在host1上执行的命令的区别。

[root@xuexi ~]# ssh -g -L 22333:host2:22 host3

[root@xuexi ~]# ssh -g -L 22333:host2:22 host3 "ifconfig"

[root@xuexi ~]# ssh -g -L 22333:host2:22 host3 "sleep 10"

第一条命令开启了本地端口转发,且是以登录到host3的方式开启的,所以执行完该命令后,将跳到host3主机上,当退出host3时,端口转发功能将被关闭。另外,host1上之所以要开启端口转发,目的是为了与host2进行通信,而不是跳到host3上,所以应该在ssh命令行上加上"-f"选项让ssh在本机host1上以后台方式提供端口转发功能,而不是跳到host3上来提供端口转发功能。

第二条命令在开启本地转发的时候还指定了要在host3上执行"ifconfig"命令,但是ssh的工作机制是远程命令执行完毕的那一刻,ssh关闭连接,所以此命令开启的本地端口转发功能有效期只有执行ifconfig命令的一瞬间。

第三条命令和第二条命令类似,只不过指定的是睡眠10秒命令,所以此命令开启的本地转发功能有效期只有10秒。

结合上面的分析,开启端口转发功能时,建议让ssh以后台方式提供端口转发功能,且明确指示不要执行任何ssh命令行上的远程命令。即最佳开启方式为:

[root@xuexi ~]# ssh -f -N -g -L 22333:host2:22 host3

1.11.2 ssh安全隧道(二):远程端口转发

ssh除了支持本地端口转发,还支持远程端口转发。顾名思义,远程端口转发表示的是将远程端口的数据转发到本地。

如下图:假如host3是内网主机,它能和host2互相通信,也能和host1通信,但反过来,host1不能和host3通信。这时要让host1访问host3或host2就没办法通过本地端口转发了,因为要在host1上开启本地端口转发,必须要和host3通信请求建立隧道。

技术分享

可以通过在host3上发起远程端口转发来实现,因为host3能和host1通信,host3可以请求在host1和host3之间建立隧道。

语法如下:

ssh -R [bind_addr:]remote1_port:host:port remote1

以上图为例,实现方式是在host3上执行:

[root@xuexi ~]# ssh -R 22333:host2:80 host1

这表示host3请求host1上的sshd服务,在host1上建立一个套接字监听22333端口,它是host2端口的映射,当有主机连接host1:22333时,此连接中的数据全部都通过host1和host3之间的安全隧道转发给host3,再由host3向host2的80端口发起访问。由于host3请求开启的转发端口是在远程主机host1上的,所以称为"远程端口转发"。

再考虑下面这条命令所开启的远程转发端口,它是在host3上执行的。

[root@xuexi ~]# ssh -R 22333:host3:80 host1

该命令将自身的host3:80映射到host1:22333上,这也能让host1和host2、host3通信,因为隧道是建立在host1:22333<-->host3:80上的。

但是,远程端口转发和本地端口转发最大的一个区别是,远程转发端口是由host1上的sshd服务控制的,默认配置情况下,sshd服务只允许本地开启的远程转发端口(22333)绑定在环回地址(127.0.0.1)上,即使显式指定了bind_addr也无法覆盖。例如:

[root@xuexi ~]# ssh -R *:22333:host2:80 host1

[root@xuexi ~]# netstat -tnlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name  
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      8405/sshd          
tcp        0      0 127.0.0.1:25                0.0.0.0:*                   LISTEN      1422/master        
tcp        0      0 127.0.0.1:22333             0.0.0.0:*                   LISTEN      8407/sshd          
tcp        0      0 :::22                       :::*                        LISTEN      8405/sshd          
tcp        0      0 ::1:25                      :::*                        LISTEN      1422/master         
tcp        0      0 ::1:22333                   :::*                        LISTEN      8407/sshd

要允许本地的远程转发端口绑定在非环回地址上,需要在host1的sshd配置文件中启用"GatewayPorts"项,它的默认值为no。启动该选项后,不给定bind_addr或bind_addr设置为"*"都表示绑定在所有地址上。如下:

[root@xuexi ~]# ssh -g -R *:22333:host2:80 host1

[root@xuexi ~]# netstat -tnlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name  
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      8466/sshd          
tcp        0      0 127.0.0.1:25                0.0.0.0:*                   LISTEN      1422/master        
tcp        0      0 0.0.0.0:22333               0.0.0.0:*                   LISTEN      8468/sshd           
tcp        0      0 :::22                       :::*                        LISTEN      8466/sshd          
tcp        0      0 ::1:25                      :::*                        LISTEN      1422/master        
tcp        0      0 :::22333                    :::*                        LISTEN      8468/sshd  

和前面的本地转发端口一样,建议的几个选项是:"-g"、"-f"、"-N"。即推荐的命令写法是:

[root@xuexi ~]# ssh -fgN -R 22333:host2:80 host1

现在,就可以通过访问host1:22333达到访问host2:80的目的了。如下图所示。

技术分享

 

1.11.3 ssh安全隧道(三):动态端口转发(SOCKS代理)

无论是本地端口转发还是远程端口转发,都是将某固定主机及其端口映射到本地或远程转发端口上,例如将host2:80映射到host1:2222。也就是说,本地或远程转发端口和目标端口所代表的应用层协议是一对一的关系,2222端口必须对应的是http的80端口,使用浏览器向host1:2222端口发起http请求当然没问题,但是使用ssh工具向host1:2222发起连接将会被拒绝,因为host2上http服务只能解析http请求,不能解析ssh连接请求。

ssh支持动态端口转发,由ssh来判断发起请求的工具使用的是什么应用层协议,然后根据判断出的协议结果决定目标端口。

以下图为例进行说明,host1处在办公内网,能和host3互相通信,但它无法直接和互联网和host2通信,而host3则可以和host2以及互联网通信。

技术分享

要让host1访问互联网,又能和host2的22端口即ssh服务通信,显然在host1上仅设置一个本地端口转发是不够的,虽然可以设置多个本地转发端口分别映射不同的端口,但这显然比较笨重和麻烦。使用动态端口转发即可。

语法格式为:

ssh -D [bind_addr:]port remote

以上图为例,在host1上执行:

[root@xuexi ~]# ssh -Nfg -D 2222 host3

执行完上面的命令,host1将在本地开启SOCKS4或SOCKS5服务来监听2222端口。只要客户端程序工具(隐含了使用的应用层协议类型)将其自身的代理设置为host1:2222,则该程序所有产生的数据都将转发到host1:2222,再由host1:2222将数据通过隧道转发给host3,最后由host3和互联网或host2上对应客户端工具的应用层协议的端口进行通信。

其实很简单,假如host4使用IE浏览器作为客户端工具,并将IE浏览器的代理设置为host1:2222,由于IE浏览器发起的请求使用的是http协议(此处不考虑其他可能的协议),那么IE浏览器产生的数据都转发到host1:2222,再由host1:2222通过隧道转发给host3,host3能联网,所以host4就实现了联网功能。如下图设置:

技术分享

再比如host4上的QQ客户端也可以设置代理。这样QQ产生的数据都将通过host1:2222转发出去,host1:2222再将QQ的数据转发到host3上,host3知道这些数据使用的协议是oicq,所以host3会去连接腾讯的QQ服务器(oicq服务对应的端口)。

技术分享

ssh只支持socks4和socks5两种代理,有些客户端工具中需要明确指明代理类型。

和本地、远程端口转发一样,建议的选项是:"-f"、"-N"和"-g"。

由于ssh动态端口转发是ssh客户端的功能,所以不使用ssh命令,使用SecurtCRT、putty等ssh客户端工具都可以实现代理上网。例如,本地主机不能上网,但能和172.16.10.6的SSH服务通信,而172.16.10.6能上网,则可以在本地主机先使用SecurtCRT连接172.16.10.6,再在对应的会话选项上做如下设置,使得本地主机也能上网。(注意:我没说可以FQ啊,好公民不FQ!!!)

技术分享

然后,在本地主机查看下是否监听了SecurtCRT中指定的8888动态转发端口。

技术分享

现在,本机所有数据包都通过SecurtCRT所连接的172.16.10.6流向外界。

 

回到系列文章大纲:http://www.cnblogs.com/f-ck-need-u/p/7048359.html

转载请注明出处:http://www.cnblogs.com/f-ck-need-u/p/7129122.html

ssh和SSH服务(包含隧道内容)