Linux中IP隧道的分析与建议

****************************************************************

作者的话:

本文系在阅读Linux源码及一些相关资料的基础上写成的 。

欢迎就文章的各个方面提出建议和批评意见 , 作者希望更多的交流和探讨 。

欢迎在保留原文完整性的前提下在网上转贴 , 需部分引用请通知作者 。

传统媒体转载和引用此文 , 请务必经过作者同意 。

欢迎在实际的应用中使用此文提出的思想 , 希望同时知会作者 。



作者信箱: xiaoman04@hotmail.com



欢迎来信!!
*****************************************************************

O、

由于网络的日益普及 , 网络的安全成为目前的热门话题 。本文对隧道技术的分析 ,

就侧重安全领域 , 对利用隧道技术实现虚拟专网提出建议 。



为什么需要IP隧道?没有接触过这个概念的人自然提出这样的疑问 。实际上概念

最初的提出很简单 , 为了在TCP/IP网络中传输其他协议的数据包 。设想IPX协议或

X.25封装的数据包如何通过Internet网进行传输 , 在已经使用多年的桥接技术中是

通过在源协议数据包上再套上一个IP协议头来实现 , 形成的IP数据包通过Internet后

卸去IP头 , 还原成源协议数据包 , 传送给目的站点 。对源协议数据来说 , 就如被IP

带着过了一条隧道 。这种技术在业余无线网络(Amateur;Packet;Radio;network ,

应该怎么翻 , 请告诉我)得到了最广泛的应用 。

利用IP隧道来传送的协议包也包括IP数据包 , 本文主要分析的IPIP封包就是如此 , 从字

面来理解IPIP就对了 , 就是把一个IP数据包又套在一个IP包里 。为什么要这么做呢?

多此一举嘛 。其实不然 , 见过一些应用就会明白 , 移动IP(Mobile-IP)和IP多点广播

(IP-Multicast)是两个通常的例子 。目前 , IP隧道技术在构筑虚拟专网(;Virtual;

Private;Network)中也显示出极大的魅力 。本文也将对利用IP隧道技术构筑VPN做

简单设想 。







背景:隧道的多种理解和实现



Internet的研究者多年前就感到需要在网络中建立隧道 , 最初的理解是在网络

中建立一条固定的路径 , 以绕过一些可能失效的网关 。可以说 , 隧道就是一条

特定的路径 。

这样的隧道是通过IP报头中的源路由选项来实现的 , 在目前看来 , 这个方法的缺陷

十分明显 。要设置源路由选项就必须知道数据包要经过的确切路径 , 而且目前

多数路由实现中都不支持源路由 。



另一个实现隧道的机制是开发一种新的IP选项 , 用来表明源数据包的信息 , 原IP头

可能成为此选项的一部分 。这种隧道的意义与我们所说的隧道已十分接近 。但它的

不足在于要对目前IP选项的实现和处理做较大的修改 , 也缺乏灵活性 。



最后常用的一种实现方法是开发一种新的IP封包协议 , 仍然套用当前的IP头格式 。

通过IP封包 , 不须指明网络路径 , 封包就能透明地到达目的地 。也可以通过封包空

间把未直接连接的机器绑在一起 , 从而创建虚拟网络 。这种方法易行、可靠、可扩

展性强 , Linux采用了这一方法 , 这也是目前我们所理解的隧道思想 。







一、

封包协议的结构和实现



封包协议的实现原理十分简单 。先看看通过隧道传送的数据报在网络中如何流动 ,

如图一 。

为了叙述简便 , 我把在隧道中传送的IP数据包称为封包 。



--------------;;;;-----------

/;;;;子网A;;;;;;;/;;;子网C;

//;;;

|;;|;;;;;;|;;;;;;;|

|;;;;;&;;;;|;;;;;;|;;;;;;;|

|;;;;; ;;;;;;;|;;;;;;|;;;;;;*****;;;;|

|;;;;;;;; ;;;;|;;;;;;|;;;;;;*;;;*;;;;|

|;;;;; ;;;;|;;;;;;|;;*****;;;*;;;;|

;;;/;;-----------;;;*;;;;;;;*;;;;/;;----------

>;#;*;**>(#);*;;;;;;;***>;#;;;;;

--------------;;/;**;;;;------------;;/;;;

|;;**;;;|;;;;;;|;;;; ;|

|;;**;;;|;;;;;;|;;;; ;|

|;;*****;;;;*;;;|;;;;;;|;;;;;;;|

|;;;;;;*****;;;;|;;;;;;|;;V;;;|

|;;;;;;;|;;;;;;|;;&;;;|

/;;;;;/

子网B;;;/;;;;子网D;;;;/

-----------;;;;----------



;;;;;;;原数据报

******;;;;;;;封装后的数据包(封包)

#;;;;封装/解封

&;;;;用户主机



图一.;;封包协议实现模型





看图中的设备;# , 分别处于隧道的两端 , 分别起打包(封装)和解包(解封)

的作用 , 在整个数据包的传送路径中 , 除了隧道两端的;#;设备 , 其他网关把

数据包看成一个普通的IP包进行转发 。

设备;#;就是一个封包基于的两个实现部件--封装部件和解封部件 。封装和解封

部件(设备)都应当同时属于两个子网 。封装部件对接收到的数据报加上封包头

, 然后以解封部件地址作为目的地址转发出去;而解封部件则在收到封包后 , 还

原原数据报 , 转发到目的子网 。



隧道的源端(封装部件)对进入隧道的数据包进行封装 , 形成封包 。一个完整

的封包如图二所示 。



/;; -----------------

|;;|;;;;封包IP头;;;;;|;

封包头;;;|;; -----------------

|;;|;;;封包协议头;;;;|

-----------------

/;;|;;;;原协议头;;;;;|;

|;; -----------------

|;;|;|

原数据报;;|;;|;;;原协议数据;;;;|;

|;;.;.

|;;.;.

|;;|;|

-----------------



图二.;;;;;;封包结构







二、

Linux中的实现



本人分析的版本是Linux2.0.34(RedHat5.2采用) 。



在Linux中 , 隧道的实现主要基于两个文件new_tunnel.c和ipip.c



同时Linux定义了一种新的协议类型--IPIP(IPPROTO_IPIP) , 与上面所说封包

类型类似 。



基本思路

在Linux中IP;Tunnel的实现也分为两个部件:封装部件和解封部件 , 分别司职发送和接

收 。但这两个部分是在不同的层次以不同的方式实现的 。

封装部件是在数据链路层以虚设备的方式实现 。所有源代码见

/usr/src/linux/drivers/net/new_tunnel.c

为实现封装 , Linux实现一个称为tunl的网络设备(类似loopback设备) , 此设备

具有其他网络设备共有的特征 , 对于使用此设备的上层应用来说 , 对这些网络设备

不加区分 , 调用及处理方法当然也完全一样 。

tunnel_init()和tunnel_xmit()是new_tunnel.c中的两个主要过程 。

tunnel_init()初始化与设备tunl相关的device结构 。

而tunnel_xmit()在从tunl设备发送数据时被调用 , tunl设备作为实现IP隧道

技术的封装部分 , 在此过程中完成对相应的数据报进行封装所需的全部操作 ,

形成IPIP类型的IP包 , 并重新转发此数据包(ip_forward()) 。

解封部件在IP的上层实现 , 系统把它作为一个虚的传输层(实际上与传输层毫无

关系) , 具体处理见文件

/usr/src/linux/net/ipv4/ipip.c 。

我们知道 , 每一个IP数据包均交由ip_rcv函数处理 , 在进行一些必要的判断后 , ip_rcv

对于发送给本机的数据包将交给上层处理程序 。对于IPIP包来说 , 其处理函数是

ipip_rcv(就如TCP包的处理函数是tcp_rcv一样 , IP层不加区分) 。也就是说 , 当

一个目的地址为本机的封包到达后 , ip_rcv函数进行一些基本检查并除去IP头 , 然后

交由ipip_rcv解封 。

ipip_rcv所做的工作就是去掉封包头 , 还原数据包 , 然后把还原后的数据包放入相应的

接收队列(netif_rx()) 。



从以上IP;Tunnel实现的思想来看 , 思路十分清晰 , 但由于IP;Tunnel的特殊性 , 其

实现的层次并不单纯 。实际上 , 它的封装和解封部件不能简单地象上面所说的那样

分层 。tunl设备虽应算进链路层 , 但其发送程序中做了更多的工作 , 如制作IPIP头

及新的IP头(这些一般认为是传输层或网络层的工作) , 调用ip_forward转发新包

也不是一个网络设备应当做的事 。可以说 , tunl借网络设备之名 , 一把抓干了不少

工作 , 真是‘高效’ 。而解封部件宏观上看在网络层之上 , 解出IPIP头 , 恢复原数据包

是它分内的事 , 但在它解出数据包(即原完整的协议数据包)后 , 它把这个包

放入相应的协议接收队列 。这种事可不是一个上层协议干的 , 这是网络设备中断

接收程序的义务 。看到了 , 在这点上 , 它好象到了数据链路层 。

是不是有点乱 , 隧道机制就是这样 , 你有没有更好的办法?









三、

为实现VPN的扩展



实际上Linux只为实现隧道机制提供了一个框架 , 图二中的封包协议头在

Linux中被忽略了 , 也就是说 , 封包头只含封包IP头 , 其后紧跟原IP数据包 。

这样的结构用于传输公开数据没有关系 , 但对于一个VPN来说 , 安全保密是

不可缺少的重要功能 。我们希望通过隧道的数据可靠且不可窃取和冒充的 ,

那么 , 加密和认证就必不可少 。

为实现这一构想 , 设计以下封包协议头:



0;;;;4;;;;;8;;16;;;24;;31;

----- ----- ----------- ------------------------

|;ver;|type;|;;;hlen;;;;|;;;;;;OldPacketLen;;;;;;|

----------------------- ------------------------

|DeviceID;;;;;;;|;;;;;;;EncapID;;|

----------------------- ------------------------

|;Flags;|;;;;;;;CheckSum;|

------------------------------------------------

|;IPIP;Options(;If;any;);|

------------------------------------------------

.;;;;;;|;padding;|

..

------------------------------------------------



图三、;IPIP头设想图



ver:;;版本号 , 利于扩展

type: 用于建立不同目的的隧道(可能处理上有差别)

OldPacketLen:;;进入隧道的原数据包长度

DeviceID: 对数据包进行封装的设备标识

EncapID: 此封包的ID号

Flags:;标志位 , 共16位 , 初步定义如下:

0 保留

1 有否加密

2 有否做摘要

3 有否签名

4 保留

5 有否传送消息密钥

6 消息密钥有否加密

7 消息密钥是否需保留

8-15 保留



CheckSum: 头校验

IPIP;Options:;;用来传送一些必要的数据 , 比如消息密钥、签名等

格式:; -------------------------------------

|;类型;|;长度;|;数据;...;;;;;;|

-------------------------------------



好了 , 有了这个东西 , 我们就可以扩展Linux;IP;Tunnel为我们的VPN服务了 。

首先 , 改写new_tunnel.c和ipip.c两个文件 , 加入对IPIP头的处理 。



接着 , 我们要实现一种密钥的管理和传送机制 。

当然 , 对称密钥是必需的 , 而对IP数据包加密要使用序列密码 。从全体考虑 ,

我们可以提出建立VPN的逻辑步骤;

1、准备工作:建网安装系统完成配置等等

2、隧道的两端分别向对方发送自己的公开密码和设备号

3、如有必要 , 产生序列密码 , 后加密签名传给对方

4、正常通信 , ----放心 , 你的数据已经很保险了 。



在一个VPN的隧道中 , 一个封包的格式应如图四所示 。



/;; -----------------

|;;|;;;;封包IP头;;;;;|;

封包头;;;|;; -----------------

|;;|;;;封包协议头;;;;|

-----------------

/;;|;|;

|;;|;;;;原协议头;;;;;|

|;;|;;;;;;;及|

封包数据;;|;;|;;;原协议数据;;;;|;

|;;.;;;;(密文);;;;;.

|;;.;.

|;;|;|

-----------------





图四.;;;;;;VPN封包结构







你的几种使用方法 。

事情往往不能两全其美 , 你在安全强度和通信速度上必须作出选择 ,

(不然你就需要在安全强度和Money的耗费中做选择 。)

使用这样的协议 , 根据你的需求不同 , 你可有不同的使用方法 , 下面列举

一些:

0、跨Internet的公司多个内部网之间进行通信 , 保密性并不重要

直接使用原框架机制 , 无任何加密措施

这样速度快、效率高 , 公司也不用申请多个IP地址 , 方便可行

1、一般性的商业应用 , 具有保密要求

利用事先产生的序列密码 , 每次对原数据包加密

安全度提高了 , 是一种十分实用的方法 。只要强度足够 , 一般很难破译

速度快

2、密码不变的方式你认为不够安全

你可以自己实现一种密码传送方法 , 每隔一段时间更换一次密码 。

其中一些握手关系需要完善 , 有兴趣的欢迎探讨 。

如果发展成熟 , 此法相信很有前途 。

4、高度机密领域

敬请使用一次一密 , 并进行每次签名 。

每次产生新密钥和签名十分费时 , 在目前我国Internet网络的速度下

几乎不可行 。

但相信有此需要的部门也能够设法提高其网络带宽 , 让网络状况适合

这种应用 。



另外 , 当然还可以就加密强度自身作出选择 , 比如选择128位 , 还是512位、1024位









四、

待完善



主要牵涉到隧道的管理 , 在封包的传送过程中如果出现错误是十分正常的 ,

当一台路由器检测到错误时 , 它会发送一个ICMP包给隧道的发送端 , 但遗憾的是

ICMP返回的数据除了IP头外 , 只含8个字节的上层协议信息 。只凭这个难以对

ICMP信息作出反应 , 因此 , 在隧道端保留一些状态信息是必须的 。这些信息

主要包括:

隧道的另一端的可达性

隧道的拥塞状况

隧道的MTU

同时所发送的封包信息也是需要保留的 , 举例说 , 当一个路由不可达信息

到来时 , 封包的发送者要能够找出所封装的数据来自何方 , 并发送相应的ICMP包 。



强调一点 , MTU的更新对隧道来将很重要 , 因为一个灵活的隧道的下一级设备是

不定的 , 同时一些数据包本身也要求更改MTU 。


所有这些 , 在Linux中的处理都不够或根本没有处理 。大家努力呀!