Jianing Yang's blog
从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 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
步骤说明
- 在Source上打开IP转发支持
- 在Source上开启对出口为tun2的IP伪装
- 在Source上添加路由到B网络的规则
- 在Obiwen上打开IP转发支持
- 在Obiwen上开启对出口为eth0的IP伪装
- 在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本身也存在这样的问题)。上面这些问题我会在接下来的几篇博客里陆续谈到。
top里面描述进程内存使用量的数据来源于/proc/$pid/statm这个文件。通过观察kernel的代码就能弄清楚SHR,VIRT和RES这些数值的具体含义。
Linux通过一个叫做 task_statm 的函数来返回进程的内存使用状况
int task_statm(struct mm_struct *mm, int *shared, int *text,
int *data, int *resident)
{
*shared = get_mm_counter(mm, file_rss);
*text = (PAGE_ALIGN(mm->end_code) - (mm->start_code & PAGE_MASK))
>> PAGE_SHIFT;
*data = mm->total_vm - mm->shared_vm;
*resident = *shared + get_mm_counter(mm, anon_rss);
return mm->total_vm;
}
上面的代码中shared就是page cache里面实际使用了的物理内存的页数,text是代码所占用的页
数,data是总虚拟内存页数减去共享的虚拟内存页数,resident是所有在使用的物理内存的页
数。最后返回的mm->total_vm是进程虚拟内存的寻址空间大小。
函数get_mm_counter并不会做什么计算,它的功能是保证数值读取的原子性。
上面的数值最后会通过 procfs输出 到/proc/$pid/statm中去。他们与top显示的数值对应关系如下。
- SHR: shared
- RES: resident
- VIRT: mm->total_vm
- CODE: code
- DATA: data
当然top在显示的时候做了一些页到具体数值(KB, MB,…)的转换。通过代码可以看出,VIRT = RES + SHR这种说法纯属谣言。:)
在用xrandr设置双屏幕扩展显示的时候,有可能会提示下面的错误:
xrandr: screen cannot be larger than 1680×1680 (desired size 3120×1050)
该错误说明Xorg的虚拟桌面不够大,可以通过使用Xorg.conf的Virtual指令更改虚拟桌面的大小。例如:
Section "ServerLayout" Identifier "Xorg Configured" Screen 0 "Primary Screen" 0 0 EndSectionSection "Monitor" Identifier "Primary Monitor" Modeline "1400x900" 104.23 1400 1480 1632 1864 900 901 904 932 -HSync +Vsync EndSectionSection "Screen" Identifier "Primary Screen" Monitor "Primary Monitor" DefaultColorDepth 24 SubSection "Display" Depth 24 Modes "1400x900" "1024x768" Virtual 3120 1050 EndSubSection EndSectionSection "DRI" Mode 0666 EndSection