OpenSSH

通过上一篇的介绍,我们会发现当SSH隧道建立好以后还需要很多配置工作。为了能让隧道在断开后自动恢复,就需要找到一种方法在建立SSH隧道的同时自动完成这些工作。接下来我们将用两个小章节来分别介绍在服务器端和客户端所使用的方法。

自动配置隧道的服务器端

众所周知,在Unix的世界里一项工作有不止一种方法可以完成。这里仅仅给出一种我自己比较喜欢的做法,使用SSH公私钥认证文件~/.ssh/authorized_key来帮助我们完成服务器端的配置工作。接下来的讨论需要你对SSH公私钥认证体系有基本的了解,如果你对这个概念还不清楚,可以参考我的 如何实现安全的免密码ssh登录

首先,我们来看一个配置好的~/.ssh/authorized_key:

no-port-forwarding,no-pty,command=”/root/.ssh/tap-setup” ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA2389WXZZmIIArz6GGyNu/O3TLURtnMgYtsRwTB+VRyN0+RSPhGxIsEPcBI56Wt5DWWInFL711k1m3JCSIZbpagpPCsjWUKvBWHsWfi+JOJBk8aZKvK4rHMkxAVmyAZPSjhR93CiLLnqhIftog/Wb1P2AJSIkpRSKW1oe0CWpH7YiIOYA/9bloHPzOe1yUXp5bmqOOmOPopT26QpuhdAlFAvisEHKTBvpoiRsmhI2Aj7pP2kPQZhqZhGQ5ba/iUw2jMvSyS9FBkoZZbLdtch9wCghnRMPdiuZDvLJ9jwhZIjS+LGhzgGFIyIg4QAkeetHT1k7IxE2JCQDRPPIaZUCyQ== jianingy@nby

与普通的公钥串相比,上面的公钥串在ssh-rsa前面多出了一些参数。它们可以对使用这个公钥验证的用户做出一些限制,这里的几个参数含义如下:

  • no-port-forwarding 对于使用该公钥认证的用户,不允许做端口转发。
  • no-pty 对于使用该公钥认证的用户,不分配伪终端(PTY)。
  • command=”/root/.ssh/tap-setup” (最关键)对于使用该公钥登陆的用户,强制执行脚本/root/.ssh/tap-setup。而用户在登陆时给出的执行命令,将被保存在环境变量SSH_ORIGINAL_COMMAND中。

下面,我们来实现tap-setup脚本。这个脚本需要两类参数。第一,我们需要知道服务器端的tap设备的名字。第二,我们需要知道服务器端tap设备的IP信息。这两类参数都会通过SSH客户端,借助SSH_ORIGINAL_COMMAND环境变量传递给tap-setup脚本。

#!/bin/bash
# author: jianingy.yang (AT) gmail.com

set $SSH_ORIGINAL_COMMAND

device=$1
ipaddr=$2
netmask=${3:-255.255.255.0}

if [ -z "$device" -o -z "$ipaddr" ]; then
  echo "not enough arugments" 2>&1
  exit 255
fi

retries=3
while ! ifconfig $device $ipaddr netmask $netmask
do
  [ $retries -lt 0 ] && break
  ((--retries))
  sleep 5
done

有了tap-setup脚本之后,我们就可以通过如下命令发起SSH连接了。tap-setup会在连接建立好以后帮我们做好服务器端的设置

ssh -w 1:100 -o Tunnel=Ethernet root@server "tap100 192.168.1.250 255.255.255.0"

在客户端自动配置隧道

谈到客户端自动重连SSH的方法,我个人比较喜欢用djb的daemontools。这个工具简单、方便并且还很易于管理多个隧道连接。当然,如果需要一切从简,也可以将一个带有while循环的脚本放在后台去执行。但无论如何我们都要能够在隧道建立成功后进行本地虚拟设备的配置。这里,介绍一个使用SSH客户端LocalCommand参数来进行配置的方法。

LocalCommand用来指定一个在SSH连接建立好以后在本地执行的命令。我们可以将所需的配置包裹在一个脚本中,然后通过LocalCommand来适时地调用我们的脚本。例如,采用如下SSH连接命令可以在连接成功后调用tap-up脚本。

ssh -o PermitLocalCommand=yes -o LocalCommand="./tap-up" -o Tunnel=Ethernet -w 100:1 hostname 

tap-up则可以是类似下面的实现。

retries=3
while ! ifconfig tap1 192.168.1.10 netmask 255.255.255.0
do
  [ $retries -lt 0 ] && break
  ((--retires))
  sleep 5
done

最后,结合我在 SSH隧道技术简介 里面“检查隧道状态”的部分,我们可以让隧道在检测出异常状态时中断并重新连接。

通过bonding模块进行隧道热备份

虽然SSH隧道在意外中断后可以自动重连,但连接终究还是会中断。为了实现“不间断服务”,让我们来借力于Linux的Bonding模块吧。Bonding模块用来将多个二层设备绑定在一起协同工作。它支持很多种工作模式,其中active-backup模式用来在多个二层设备之间进行热备份。举例来说,如果我们将eth0和eth1的物理接口连接在同一个交换机上。然后通过bonding模块将eth0和eth1绑定在一个称作bond0的虚拟设备上。默认情况下,eth0会做为bond0的主设备(Primary)工作。而一旦eth0设备出现问题bonding模块可以在毫秒级别上检测到设备失败并切换到eth1上工作。在这期间已经建立好的TCP链接并不会断开,而只是承受少量的TCP重传。

另人庆幸的是bonding模块只要求被绑定的设备是一个二层设备。因此,SSH创建的二层隧道也可以通过Bonding模块进行绑定从而享受热备份带来的好处。

假设,我们已经创建了两个机器间的两个相同的隧道设备tap100和tap101。我们可以用如下命令在隧道两端制作Bonding虚拟设备。

在客户端:

sudo modprobe bonding mode=active-backup arp_interval=100 arp_ip_target=192.168.1.250
ifenslave bond0 tap100
ifenslave bond0 tap101
ifconfig bond0 192.168.1.10 netmask 0xffffff00

服务器端:

sudo modprobe bonding mode=active-backup arp_interval=100 arp_ip_target=192.168.1.10
ifenslave bond0 tap100
ifenslave bond0 tap101
ifconfig bond0 192.168.1.250 netmask 0xffffff00

modprobe执行的时候向bonding模块传递了3个参数,他们的含义如下:

  • mode bonding设备工作模式,这里使用active-backup也就是热备份模式
  • arp_ip_interval 通过ARP包检测设备联通状态的检测间隔,单位是毫秒。
  • arp_ip_target 通过ARP包检测设备联通状态时,用以发送ARP查询的地址。

模块加载好以后,便可以使用ifenslave命令(该命令一般需要单独安装)来将参与热备的设备加入到bond0中。最后,再为bond0设置好IP地址,整个配置过程就完成了。

模块加载的部分和bond设备IP地址的配置都可以被放到系统启动时完成。对于ifenslave操作,则可以被我们分别放到服务器端和客户端的重连脚本里。这样一来即使隧道处于断开重连状态中,只要还有可工作的隧道存在bond0设备就会一直有效,其上的TCP连接也都能继续保持。

到此为止?

当然不是:)。通过这两篇对SSH隧道的介绍,你应该已经掌握了建立稳定SSH隧道的方法。但仍有很多细节问题需要进一步考察。也许你已经发现了bonding设备的balance-rr模式可以提高SSH隧道的吞吐量,也许你在思考如何在一台服务起上建立多个bonding设备等等。有关SSH隧道及其相关的更多问题我会争取在以后的博文中再逐一解释。

No votes yet

从OpenSSH 5.0开始,OpenSSH加入了对二层和三层加密隧道的支持。这里所说的加密隧道不同于端口转发。以前我们(特别是我自己)经常把通过-R或者-L参数进行的端口转发误称为隧道。事实上端口转发仅仅将单个TCP或者UDP端口映射到其他机器的某个端口上。而隧道通常表现为一种虚拟网络接口设备,具有自己的IP地址(三层)或者MAC地址(二层)。借助这种网络接口设备,我们可以完整的虚拟出一个二层或三层设备,同时又可以享受OpenSSH提供的加密服务。

下面,我们开始介绍如何构建简单的三层隧道,并通过这个隧道连接两个相互不通的网络。首先,还是来看看我们假想的网络环境。

第零阶段:网络环境

网络A里面的机器

  • NAME: Luke
  • INT: eth0
  • IP: 192.168.1.1
  • NETMASK: 255.255.255.0

跳板机

  • NAME: Source
  • INT: eth0
  • IP: 1.1.1.1
  • NETMASK: 255.255.255.0

网络B里面的机器

  • NAME: Obiwen
  • INT: eth0
  • IP: 192.168.2.2
  • NETMASK: 255.255.255.0

在这个环境里,网络A,B之间相互不能联通,但是都能连接到Source这台机器。虽然如此,从Source却不能直接访问A,B两台机器。这是一个典型的两个不同内网机器需要通过一个公网机器进行数据交换的情况。我们的最终目标是,让Luke能够访问所有位于Obiwen子网里的机器,例如:Yoda或者Anakin。

第一阶段:建立隧道

编辑Source机器上的/etc/ssh/sshd_config,修改PermitTunnel和PermitRootLogin选项为,

PermitRootLogin yes
PermitTunnel yes

在Luke上建立到Source的隧道

sudo ssh -w 1:1 -N -f root@source

在Obiwen上建立到Source的隧道

sudo ssh -w 1:2 -N -f root@source

参数解析

  • -w local_tun:remote_tun: 用于建立隧道设备,后面的两个参数用冒号分割。分别用来指定本地设备号和远程设备号。
  • -N : 当ssh登录后什么命令也不执行
  • -f : 把ssh进程放入后台运行

阶段性检查

当第一阶段完成后,我们会看到Luke和Obiwen上面分别多出一个网络接口设备tun1.你可以通过ifconfig命令观察这个接口设备

tun1      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  
          POINTOPOINT NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:500 
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

而Source上会出现类似的两个接口设备tun1和tun2。他们共同构成了一对虚拟的点对点(Point to Point)连接。默认情况下ssh使用的是三层隧道,因此我们看到HWaddr的值是空的,因为三层设备不存在MAC地址。

第二阶段:分配地址

为Luke上的tun1设备分配IP地址

ifconfig tun1 192.168.64.1 pointopoint 192.168.65.1

为Obiwen上的tun1设备分配IP地址

ifconfig tun1 192.168.64.2 pointopoint 192.168.65.1

在Source上配置tun1和tun2设备的地址

ifconfig tun1 192.168.65.1 pointopoint 192.168.64.1
ifconfig tun2 192.168.65.2 pointopoint 192.168.64.2

参数解析

上面这种带有pointopoint关键词的ifconfig命令用于创建一对点对点地址。关键词两端分别是本地的IP地址和远程的IP地址。如果你留意过我们ADSL拨号的连接设备ppp0,你会发现他们的形式很相近。ADSL的拨号连接(PPPoE)也是一种点对点连接。

阶段性检查

完成第二阶段的步骤之后,我们在Source上应该能够ping 192.168.64.1和192.168.64.2 这两个IP地址了。同时在Luke和Obiwen上面我们也可以分别ping 192.168.65.1和192.168.65.2 两个地址。你可能还尝试了在Luke上ping 192.168.64.2,或者在Obiwen上ping 192.168.64.1。并且发现他们相互还不能联通,别着急,我们离成功只有一小步了。

PS: 这里我们假设所有的机器都没有过滤PING包(ICMP ECHO包)。如果你的环境里防火墙过滤了这种包,最好调整防火墙规则。PING对于网络调试是有很大帮助的

第三阶段:设置路由

在Source上的操作

sysctl -w net.ipv4.ip_forward=1                         #1
iptables -t nat -A POSTROUTING -o tun2 -j MASQUERADE    #2
ip route add 192.168.2.0/24 via 192.168.65.2            #3

在Obiwen上的操作

sysctl -w net.ipv4.ip_forward=1                         #4
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE    #5

在Luke上的操作

ip route add 192.168.2.0/24 via 192.168.65.1            #6

步骤说明

  1. 在Source上打开IP转发支持
  2. 在Source上开启对出口为tun2的IP伪装
  3. 在Source上添加路由到B网络的规则
  4. 在Obiwen上打开IP转发支持
  5. 在Obiwen上开启对出口为eth0的IP伪装
  6. 在Luke上添加到B网络路由规则

通过前两步,我们让Source能够像一个路由器一样工作了。在第三步,我们添加一条路由规则。该规则让所有想去往192.168.2.0/24子网的数据包经由192.168.65.2路由。换句话说,Source已经可以将来自不同接口想去往B网络的数据包通过tun2转发出去了。接下来,我们在Obiwen上执行类似的操作,也就是步骤4和步骤5。让Obiwen能够把从tun2获得的数据包通过eth0转发出去。Obiwen上的eth0正是连接B网络的接口。最后,我们在Luke加入一条路由规则,让Luke上访问192.168.2.0/24子网的数据包,会被发到Source上。至此一条从Luke到Source再到Obiwen的路由通过两个三层隧道建立起来了。

阶段性成果

我们已经到达了目的地。Luke可以连接192.168.2.0/24子网也就是网络B内的所有机器了。

接下来…

通过上面的描述,相信你已经对如何用OpenSSH构建一个简单的三层加密隧道以及如何连接两个相互不通的网络有些感觉了。接下来,你可以再去读一下ssh的在线手册,并且应该能够从中轻松的掌握如何搭建二层隧道。

另一方面,我们构建隧道的命令还很不健壮。一旦因为某些原因隧道断开了,他们不会自己重新连接。你可能读过我的 SSH 隧道技术简介 ,打算用里面的方法来让你的隧道自动恢复。但即使那样,隧道还是存在了一段不能服务的时间。最后,也许你已经发现第三阶段的路由设置存在一个安全问题。导致,与Source在同一个子网的机器可以通过Source访问Obiwen网络中的机器(Obiwen本身也存在这样的问题)。上面这些问题我会在接下来的几篇博客里陆续谈到。

Your rating: None Average: 4 (1 vote)

SSH逃逸键的作用有些类似Telnet的”^]“逃逸。可以让你在一个连接好的终端上进一步控制SSH连接。首先,我们看看这个逃逸键能帮我们做什么。在SSH终端里输入回车然后马上输入字符”~“再输入字符”?“,便可以调出SSH逃逸键使用帮助。你应该能看到类似下面的输出:

blackhole:~ $ ~?
Supported escape sequences:
  ~.  - terminate connection (and any multiplexed sessions)
  ~B  - send a BREAK to the remote system
  ~C  - open a command line
  ~R  - Request rekey (SSH protocol 2 only)
  ~^Z - suspend ssh
  ~#  - list forwarded connections
  ~&  - background ssh (when waiting for connections to terminate)
  ~?  - this message
  ~~  - send the escape character by typing it twice
(Note that escapes are only recognized immediately after newline.)

注意最后一行的Note,它说明了逃逸键使用的条件,即必须在回车后马上键入逃逸键。

比较常用的功能可能是用 "~."断开一个连接。有些时候我们在SSH终端上启动了有问题的daemon程序,或是SSH连接因为某种原因进入stalled状态,导致SSH不能正常终止。这时候就可以使用“~.”来终止这个连接。这要比关闭再打开你的终端窗口方便很多。

“~C“会打开一个SSH控制终端。在这个控制终端里,你可以进行端口映射的设置。也就是输入和SSH命令行参数一样的-L或是-R什么的。

“~^Z“是一个比较有趣的功能,可以让你挂起一个SSH连接并回到localhost的终端上。你可以用这个功能做些localhost上的操作再fg回去。也可以在做了端口转发后退出SSH终端并通过bg把它放到后台去。

逃逸键可以通过在执行SSH的时候用-e参数进行就改,具体方法请查看man ssh吧。

No votes yet