IPSEC实现
IPSEC介绍与实现
一、介绍
IPSec 协议不是一个单独的协议,它给出了应用于IP层上网络数据安全的一整套体系结构,包括网络认证协议 Authentication Header(AH)、封装安全载荷协议Encapsulating Security Payload(ESP)、密钥管理协议Internet Key Exchange (IKE)和用于网络认证及加密的一些算法等。IPSec 规定了如何在对等层之间选择安全协议、确定安全算法和密钥交换,向上提供了访问控制、数据源认证、数据加密等网络安全服务。
1、安全特性
IPSec的安全特性主要有:
·不可否认性 "不可否认性"可以证实消息发送方是唯一可能的发送者,发送者不能否认发送过消息。"不可否认性"是采用公钥技术的一个特征,当使用公钥技术时,发送方用 私钥产生一个数字签名随消息一起发送,接收方用发送者的公钥来验证数字签名。由于在理论上只有发送者才唯一拥有私钥,也只有发送者才可能产生该数字签名, 所以只要数字签名通过验证,发送者就不能否认曾发送过该消息。但"不可否认性"不是基于认证的共享密钥技术的特征,因为在基于认证的共享密钥技术中,发送 方和接收方掌握相同的密钥。
·反重播性 "反重播"确保每个IP包的唯一性,保证信息万一被截取复制后,不能再被重新利用、重新传输回目的地址。该特性可以防止攻击者截取破译信息后,再用相同的信息包冒取非法访问权(即使这种冒取行为发生在数月之后)。
·数据完整性 防止传输过程中数据被篡改,确保发出数据和接收数据的一致性。IPSec利用Hash函数为每个数据包产生一个加密检查和,接收方在打开包前先计算检查和,若包遭篡改导致检查和不相符,数据包即被丢弃。
·数据可靠性(加密) 在传输前,对数据进行加密,可以保证在传输过程中,即使数据包遭截取,信息也无法被读。该特性在IPSec中为可选项,与IPSec策略的具体设置相关。
·认证 数据源发送信任状,由接收方验证信任状的合法性,只有通过认证的系统才可以建立通信连接。
2、基于电子证书的公钥认证
一个架构良好的公钥体系,在信任状的传递中不造成任何信息外泄,能解决很多安全问题。 IPSec与特定的公钥体系相结合,可以提供基于电子证书的认证。公钥证书认证在Windows 2000中,适用于对非Windows 2000主机、独立主机,非信任域成员的客户机、或者不运行Kerberos v5认证协议的主机进行身份认证。
3、预置共享密钥认证
IPSec也可以使用预置共享密钥进行认证。预共享意味着通信双方必须在IPSec策略设置中 就共享的密钥达成一致。之后在安全协商过程中,信息在传输前使用共享密钥加密,接收端使用同样的密钥解密,如果接收方能够解密,即被认为可以通过认证。但 在Windows 2000 IPSec策略中,这种认证方式被认为不够安全而一般不推荐使用。
4、公钥加密
IPSec的公钥加密用于身份认证和密钥交换。公钥加密,也被称为"不对称加密法",即加解密过程需要两把不同的密钥,一把用来产生数字签名和加密数据,另一把用来验证数字签名和对数据进行解密。
使用公钥加密法,每个用户拥有一个密钥对,其中私钥仅为其个人所知,公钥则可分发给任意需要与 之进行加密通信的人。例如:A想要发送加密信息给B,则A需要用B的公钥加密信息,之后只有B才能用他的私钥对该加密信息进行解密。虽然密钥对中两把钥匙 彼此相关,但要想从其中一把来推导出另一把,以目前计算机的运算能力来看,这种做法几乎完全不现实。因此,在这种加密法中,公钥可以广为分发,而私钥则需 要仔细地妥善保管。
5、Hash函数和数据完整性
Hash信息验证码HMAC(Hash message authentication codes)验证接收消息和发送消息的完全一致性(完整性)。这在数据交换中非常关键,尤其当传输媒介如公共网络中不提供安全保证时更显其重要性。
HMAC结合hash算法和共享密钥提供完整性。Hash散列通常也被当成是数字签名,但这种说法不够准确,两者的区别在于:Hash散列使用共享密钥,而数字签名基于公钥技术。hash算法也称为消息摘要或单向转换。称它为单向转换是因为:
1)双方必须在通信的两个端头处各自执行Hash函数计算;
2)使用Hash函数很容易从消息计算出消息摘要,但其逆向反演过程以目前计算机的运算能力几乎不可实现。
Hash散列本身就是所谓加密检查和或消息完整性编码MIC(Message Integrity Code),通信双方必须各自执行函数计算来验证消息。举例来说,发送方首先使用HMAC算法和共享密钥计算消息检查和,然后将计算结果A封装进数据包中 一起发送;接收方再对所接收的消息执行HMAC计算得出结果B,并将B与A进行比较。如果消息在传输中遭篡改致使B与A不一致,接收方丢弃该数据包。
有两种最常用的hash函数:
·HMAC-MD5 MD5(消息摘要5)基于RFC1321。MD5对MD4做了改进,计算速度比MD4稍慢,但安全性能得到了进一步改善。MD5在计算中使用了64个32位常数,最终生成一个128位的完整性检查和。
·HMAC-SHA 安全Hash算法定义在NIST FIPS 180-1,其算法以MD5为原型。 SHA在计算中使用了79个32位常数,最终产生一个160位完整性检查和。SHA检查和长度比MD5更长,因此安全性也更高。
6、加密和数据可靠性
IPSec使用的数据加密算法是DES--Data Encryption Standard(数据加密标准)。DES密钥长度为56位,在形式上是一个64位数。DES以64位(8字节)为分组对数据加密,每64位明文,经过 16轮置换生成64位密文,其中每字节有1位用于奇偶校验,所以实际有效密钥长度是56位。 IPSec还支持3DES算法,3DES可提供更高的安全性,但相应地,计算速度更慢。
7、密钥管理
·动态密钥更新
IPSec策略使用"动态密钥更新"法来决定在一次通信中,新密钥产生的频率。动态密钥指在通 信过程中,数据流被划分成一个个"数据块",每一个"数据块"都使用不同的密钥加密,这可以保证万一攻击者中途截取了部分通信数据流和相应的密钥后,也不 会危及到所有其余的通信信息的安全。动态密钥更新服务由Internet密钥交换IKE(Internet Key Exchange)提供,详见IKE介绍部分。
IPSec策略允许专家级用户自定义密钥生命周期。如果该值没有设置,则按缺省时间间隔自动生成新密钥。
·密钥长度
密钥长度每增加一位,可能的密钥数就会增加一倍,相应地,破解密钥的难度也会随之成指数级加大。IPSec策略提供多种加密算法,可生成多种长度不等的密钥,用户可根据不同的安全需求加以选择。
·Diffie-Hellman算法
要启动安全通讯,通信两端必须首先得到相同的共享密钥(主密钥),但共享密钥不能通过网络相互发送,因为这种做法极易泄密。
Diffie-Hellman算法是用于密钥交换的最早最安全的算法之一。DH算法的基本工作 原理是:通信双方公开或半公开交换一些准备用来生成密钥的"材料数据",在彼此交换过密钥生成"材料"后,两端可以各自生成出完全一样的共享密钥。在任何 时候,双方都绝不交换真正的密钥。
通信双方交换的密钥生成"材料",长度不等,"材料"长度越长,所生成的密钥强度也就越高,密钥破译就越困难。 除进行密钥交换外,IPSec还使用DH算法生成所有其他加密密钥。
二、IPSEC使用
1.编译kernel 2.6
必须选择下面的选择
CONFIG_INET_AH
CONFIG_INET_ESP
CONFIG_XFRM_USER
可能还要安装module-init-tool
如何生成kernel看另外的文档
2.ipsec-tools
/configure --prefix=/
make
make install
3.两台机器的通讯
linux(192.168.0.254) host-A--------------linux box(192.168.0.141)host-B
在一台linux中
#加入pf_sock
modprobe af_key
#加密
modprobe md5
modprobe des
#AH
modprobe ah4
#esp
modprobe esp4
cat >setkey.sh <<EOF
#!/sbin/setkey -f
flush;
spdflush;
# AH
add 192.168.0.141 192.168.0.254 ah 15700 -A hmac-md5 "1234567890123456";
add 192.168.0.254 192.168.0.141 ah 24500 -A hmac-md5 "1234567890123456";
# ESP
add 192.168.0.141 192.168.0.254 esp 15701 -E 3des-cbc "123456789012123456789012";
add 192.168.0.254 192.168.0.141 esp 24501 -E 3des-cbc "123456789012123456789012";
spdadd 192.168.0.141 192.168.0.254 any -P out ipsec
esp/transport//require
ah/transport//require;
spdadd 192.168.0.254 192.168.0.141 any -P in ipsec
esp/transport//require
ah/transport//require;
EOF
执行setkey后,就可以通讯了
速度测试:
没有ipsec 有ipsec
A->B 10.21M/s 2.43M/s
B->A 10.94M/s 2.27M/s
上面的用的是手工密钥,可以还可以用Preshared Keys,X.509 Certificates。
其中/usr/share/ssl/misc/CA可以用来生成X.509 Certificates
生成证书:
mkdir certs
cd certs
/usr/share/ssl/misc/CA -newca
# 254 passwd :ca254
# 141 passwd :ca141
/usr/share/ssl/misc/CA -newreq
# 254 passwd :cert254
# 141 passwd :cert141
#sign it using the certificate authority??
/usr/share/ssl/misc/CA -sign
mv newcert.pem vpngateway_cert.pem
mv newreq.pem vpngateway_key.pem
mkdir /etc/certs
cp ~/certs/*.pem /etc/certs/
#因为racoon不认这个key的格式,转一下
cd /etc/
openssl rsa -in 254_key.pem -out 254_key.pem
#input cert254
4.网关之间的通讯
C(192.168.0.119)---(192.168.0.114)linux(10.0.0.12)---(10.0.0.13)linux(192.168.0.115)----C(192.168.0.253)
和上面差不多
IPSEC实现
Linux2.6内核中自带了IPSEC的实现
该实现包括以下几个部分:
PF_KEY类型套接口, 用来提供和用户层空间进行PF_KEY通信,代码在net/key目录下.
安全联盟SA和安全策略SP管理,是使用xfrm库来实现的,代码在net/xfrm/目录下定义.
ESP,AH等协议实现,在net/ipv4()下定义.
加密认证算法库,在crypto目录下定义,这些算法都是标准代码了. 本文主要描述XFRM库的实现以及在IPV4下相关协议的处理部分, IPV6的忽略.
xfrm是内核中变化比较大的部分,每个版本中都有不小的差异, 同时也说明了该模块的不成熟性。
在net/xfrm目录下的各文件大致功能说明如下:
xfrm_state.c: xfrm状态管理
xfrm_policy.c: xfrm策略管理
xfrm_algo.c: 算法管理
xfrm_hash.c: HASH计算函数
xfrm_input.c: 安全路径(sec_path)处理,用于进入的ipsec包
xfrm_user.c: netlink接口的SA和SP管理
在net/ipv4目录下的和ipsec相关各文件大致功能说明如下:
ah4.c: IPV4的AH协议处理
esp4.c: IPV4的ESP协议处理
ipcomp.c: IP压缩协议处理
xfrm4_input: 接收的IPV4的IPSEC包处理
xfrm4_output: 发出的IPV4的IPSEC包处理
xfrm4_state: IPV4的SA处理
xfrm4_policy: IPV4的策略处理
xfrm4_tunnel: IPV4的通道处理
xfrm4_mode_transport: 传输模式
xfrm4_mode_tunnel: 通道模式
xfrm4_mode_beet: BEET模式 本文Linux内核代码版本为2.6.24 [数据结构]
内核SA的定义用xfrm_state结构定义,SP用xfrm_policy结构定义,在include/net/xfrm.h中定义.
struct xfrm_state
{
struct hlist_node bydst; // 按目的地址HASH
struct hlist_node bysrc; // 按源地址HASH
struct hlist_node byspi; // 按SPI值HASH atomic_t refcnt;// 所有使用计数
spinlock_t lock; // 状态锁 struct xfrm_id id; // ID结构, 即目的地址,SPI,协议三元组
struct xfrm_selector sel; // 状态选择器 u32 genid; // 状态的标志值, 防止发生碰撞
struct { // KEY回调管理处理结构参数
u8 state;
u8 dying;
u32 seq;
} km;
/* Parameters of this state. */
struct { // SA相关参数结构
u32 reqid; // 请求ID
u8 mode; // 模式: 传输/通道
u8 replay_window; // 回放窗口
u8 aalgo, ealgo, calgo; // 认证,加密,压缩算法ID值
u8 flags; // 一些标志
u16 family; // 协议族
xfrm_address_t saddr; // 源地址
int header_len; // 添加的协议头长度
int trailer_len; //踪迹长度
} props; struct xfrm_lifetime_cfg lft; // 生存时间配置 /* Data for transformer */
struct xfrm_algo *aalg; // 认证算法
struct xfrm_algo *ealg; // 加密算法
struct xfrm_algo *calg; // 压缩算法 /* Data for encapsulator */
struct xfrm_encap_tmpl *encap; // NAT-T封装信息 /* Data for care-of address */
xfrm_address_t *coaddr; /* IPComp needs an IPIP tunnel for handling uncompressed packets */
struct xfrm_state *tunnel; // 通道, 实际是另一个SA /* If a tunnel, number of users + 1 */
atomic_t tunnel_users; // 通道的使用数 /* State for replay detection */
struct xfrm_replay_state replay; // 回放检测结构,包含各种序列号掩码等信息 /* Replay detection state at the time we sent the last notification */
struct xfrm_replay_state preplay; // 上次的回放记录值 /* internal flag that only holds state for delayed aevent at the moment */
u32 xflags; // 标志 /* Replay detection notification settings */
u32 replay_maxage; // 回放最大时间间隔
u32 replay_maxdiff; // 回放最大差值 /* Replay detection notification timer */
struct timer_list rtimer; // 回放检测定时器 /* Statistics */
struct xfrm_stats stats; // 统计值 struct xfrm_lifetime_cur curlft; // 当前时间计数器
struct timer_list timer; // SA定时器 /* Last used time */
u64 lastused; // 上次使用时间 /* Reference to data common to all the instances of this transformer. */
struct xfrm_type *type; // 协议, ESP/AH/IPCOMP
struct xfrm_mode *inner_mode; // 进入或输出的模式, 通道或传输
struct xfrm_mode *outer_mode; /* Security context */
struct xfrm_sec_ctx *security; // 安全上下文, 加密时使用 /* Private data of this transformer, format is opaque,
* interpreted by xfrm_type methods. */
void *data; // 内部数据
};
struct xfrm_policy
{
struct xfrm_policy *next; // 下一个策略
struct hlist_node bydst; // 按目的地址HASH的链表
struct hlist_node byidx; // 按索引号HASH的链表 /* This lock only affects elements except for entry. */
rwlock_t lock; // 策略结构锁
atomic_t refcnt; // 引用次数
struct timer_list timer; // 策略定时器 u32 priority; // 策略优先级
u32 index; // 策略索引号
struct xfrm_selector selector;// 选择器
struct xfrm_lifetime_cfg lft; // 策略生命期
struct xfrm_lifetime_cur curlft; // 当前的生命期数据
struct dst_entry *bundles;// 路由链表
u16 family; // 协议族
u8 type; // 类型
u8 action; // 策略动作, 接受/加密/阻塞等
u8 flags; // 标志
u8 dead; // 策略死亡标志
u8 xfrm_nr;// 使用的xfrm_vec的数量 /* XXX 1 byte hole, try to pack */
struct xfrm_sec_ctx *security;// 安全上下文
struct xfrm_tmpl xfrm_vec[XFRM_MAX_DEPTH];// 状态模板
};
xfrm模板结构, 用于状态和策略的查询
struct xfrm_tmpl
{
/* id in template is interpreted as:
* daddr - destination of tunnel, may be zero for transport mode.
* spi - zero to acquire spi. Not zero if spi is static, then
* daddr must be fixed too.
* proto - AH/ESP/IPCOMP
*/
struct xfrm_id id; // SA三元组, 目的地址, 协议, SPI
xfrm_address_t saddr;// 源地址
unsigned short encap_family;
__u32 reqid;// 请求ID /* Mode: transport, tunnel etc. */
__u8 mode; /* Sharing mode: unique, this session only, this user only etc. */
__u8 share; /* May skip this transfomration if no SA is found */
__u8 optional; /* Bit mask of algos allowed for acquisition */
__u32 aalgos;
__u32 ealgos;
__u32 calgos;
};
对ESP, AH, IPCOMP等协议的描述是通过xfrm_type结构来描述的, 多个协议的封装就是靠多个协议结构形成的链表来实现
struct xfrm_type
{
char *description; // 描述字符串
struct module *owner; // 协议模块
__u8 proto; // 协议值
__u8 flags; // 标志
#define XFRM_TYPE_NON_FRAGMENT 1
#define XFRM_TYPE_REPLAY_PROT 2
int (*init_state)(struct xfrm_state *x); // 初始化状态
void (*destructor)(struct xfrm_state *); // 析构函数
int (*input)(struct xfrm_state *, struct sk_buff *skb); // 数据输入函数
int (*output)(struct xfrm_state *, struct sk_buff *pskb); // 数据输出函数
int (*reject)(struct xfrm_state *, struct sk_buff *, struct flowi *); // 拒绝函数
int (*hdr_offset)(struct xfrm_state *, struct sk_buff *, u8 **); // 头部偏移
xfrm_address_t *(*local_addr)(struct xfrm_state *, xfrm_address_t *); // 本地地址
xfrm_address_t *(*remote_addr)(struct xfrm_state *, xfrm_address_t *);// 远程地址
/* Estimate maximal size of result of transformation of a dgram */
u32 (*get_mtu)(struct xfrm_state *, int size); // 最大数据报长度 }; 模式结构用于描述IPSEC连接描述, 可为通道模式或传输模式两种.
struct xfrm_mode {
int (*input)(struct xfrm_state *x, struct sk_buff *skb); // 数据输入函数
int (*output)(struct xfrm_state *x,struct sk_buff *skb); // 数据输出函数 struct xfrm_state_afinfo *afinfo; //策略的相关协议处理结构
struct module *owner;
unsigned int encap; // 封装
int flags; //标志
}; 以下结构用于描述具体协议族下的的策略处理.
struct xfrm_policy_afinfo {
unsigned short family; // 协议族
struct dst_ops *dst_ops;// 目的操作结构
void (*garbage_collect)(void);// 垃圾搜集
int (*dst_lookup)(struct xfrm_dst **dst, struct flowi *fl);// 路由选择
int (*get_saddr)(xfrm_address_t *saddr, xfrm_address_t *daddr);// 获取源地址
struct dst_entry *(*find_bundle)(struct flowi *fl, struct xfrm_policy *policy);// 查找路由项
int (*bundle_create)(struct xfrm_policy *policy, struct xfrm_state **xfrm,
int nx, struct flowi *fl, struct dst_entry **dst_p);// 创建新路由项
void (*decode_session)(struct sk_buff *skb, struct flowi *fl);// 解码会话
}; 状态的相关协议处理结构
struct xfrm_state_afinfo {
unsigned int family;// 协议族
struct module *owner;
struct xfrm_type *type_map[IPPROTO_MAX];
struct xfrm_mode *mode_map[XFRM_MODE_MAX];
int (*init_flags)(struct xfrm_state *x);// 初始化标志
void (*init_tempsel)(struct xfrm_state *x, struct flowi *fl, struct xfrm_tmpl *tmpl,
xfrm_address_t *daddr, xfrm_address_t *saddr);// 初始化模板选择
int (*tmpl_sort)(struct xfrm_tmpl **dst, struct xfrm_tmpl **src, int n);// 模板排序
int (*state_sort)(struct xfrm_state **dst, struct xfrm_state **src, int n);// 状态排序
int (*output)(struct sk_buff *skb);
};
IPV4的状态相关协议处理结构
static struct xfrm_state_afinfo xfrm4_state_afinfo = { //net/ipv4/xfrm4_state.c
.family = AF_INET,
.owner = THIS_MODULE,
.init_flags = xfrm4_init_flags,
.init_tempsel = __xfrm4_init_tempsel,
.output = xfrm4_output,
};
回调通知信息结构
struct xfrm_mgr
{
struct list_head list;
char *id;
int (*notify)(struct xfrm_state *x, struct km_event *c);// 状态通知
int (*acquire)(struct xfrm_state *x, struct xfrm_tmpl *, struct xfrm_policy *xp, int dir);// 获取, 如获取SA
struct xfrm_policy *(*compile_policy)(struct sock *sk, int opt, u8 *data, int len, int *dir);// 编译策略
int (*new_mapping)(struct xfrm_state *x, xfrm_address_t *ipaddr, __be16 sport);// 映射
int (*notify_policy)(struct xfrm_policy *x, int dir, struct km_event *c);// 策略通知
int (*report)(u8 proto, struct xfrm_selector *sel, xfrm_address_t *addr);// 报告
int (*migrate)(struct xfrm_selector *sel, u8 dir, u8 type, struct xfrm_migrate *m, int num_bundles);//迁移
};
static struct xfrm_mgr pfkeyv2_mgr = // net/key/pf_key.c
{
.id = "pfkeyv2",
.notify = pfkey_send_notify,
.acquire = pfkey_send_acquire,
.compile_policy = pfkey_compile_policy,
.new_mapping = pfkey_send_new_mapping,
.notify_policy = pfkey_send_policy_notify,
.migrate = pfkey_send_migrate,
};
[/数据结构]
[初始化]
int __init ip_rt_init(void) //ip路由初始化函数
{
......
#ifdef CONFIG_XFRM
xfrm_init();
xfrm4_init();
#endif
......
}
net/xfrm/xfrm_policy.c
xfrm初始化函数包括状态, 策略和输入处理的初始化函数
xfrm是不支持模块方式的
void __init xfrm_init(void)
{
xfrm_state_init();
xfrm_policy_init();
xfrm_input_init();
}
状态初始化
void __init xfrm_state_init(void)
{
unsigned int sz;
//初始HASH表不大, 每个HASH中初始化为8个链表, 但随着状态数量的增加会动态增加HASH表数量
sz = sizeof(struct hlist_head) * ;
//建立3组HASH, 分别按SA的源地址, 目的地址和SPI值
xfrm_state_bydst = xfrm_hash_alloc(sz);
xfrm_state_bysrc = xfrm_hash_alloc(sz);
xfrm_state_byspi = xfrm_hash_alloc(sz); if (!xfrm_state_bydst || !xfrm_state_bysrc || !xfrm_state_byspi)
panic("XFRM: Cannot allocate bydst/bysrc/byspi hashes.");
//xfrm_state_hmask初始值为=7
xfrm_state_hmask = ((sz / sizeof(struct hlist_head)) - );
//初始化工作队列work_queue, 完成对状态垃圾的搜集和释放
INIT_WORK(&xfrm_state_gc_work, xfrm_state_gc_task);
}
策略初始化
static void __init xfrm_policy_init(void)
{
unsigned int hmask, sz;
int dir;
//建立一个内核cache, 用于分配xfrm_dst结构
xfrm_dst_cache = kmem_cache_create("xfrm_dst_cache", sizeof(struct xfrm_dst),
, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); //分配状态HASH表, 初始是8个HASH链表,以后随着策略数量的增加会动态增加HASH表的数量
hmask = - ;
sz = (hmask+) * sizeof(struct hlist_head);
//该HASH表是按策略的index参数进行索引的
xfrm_policy_byidx = xfrm_hash_alloc(sz);
xfrm_idx_hmask = hmask;
if (!xfrm_policy_byidx)
panic("XFRM: failed to allocate byidx hash\n");
//输入, 输出, 转发三个处理点, 双向
for (dir = ; dir < XFRM_POLICY_MAX * ; dir++) {
struct xfrm_policy_hash *htab;
//初始化inexact链表头, inexact处理选择子相关长度不是标准值的一些特别策略
INIT_HLIST_HEAD(&xfrm_policy_inexact[dir]);
//分配按地址HASH的HASH表
htab = &xfrm_policy_bydst[dir];
htab->table = xfrm_hash_alloc(sz);
htab->hmask = hmask;
if (!htab->table)
panic("XFRM: failed to allocate bydst hash\n");
}
//初始化策略垃圾搜集的工作队列, 完成对策略垃圾的搜集和释放
INIT_WORK(&xfrm_policy_gc_work, xfrm_policy_gc_task);
//登记网卡通知,参看下面网卡通知回调实现
register_netdevice_notifier(&xfrm_dev_notifier);
}
输入初始化
struct sec_path结构是对输入的加密包进行层层解包的处理, 在sk_buff中有该结构的指针sp, 如果sp非空表示这是个IPSEC解密后的包.
void __init xfrm_input_init(void)
{
//建立一个内核cache, 用于分配sec_path结构(安全路径)
secpath_cachep = kmem_cache_create("secpath_cache", sizeof(struct sec_path),
, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
}
//协议相关的状态策略初始化,注册两个具体的相关协议的处理结构
void __init xfrm4_init(void)
{
xfrm4_state_init();//xfrm_state_register_afinfo(&xfrm4_state_afinfo);
xfrm4_policy_init();//xfrm_policy_register_afinfo(&xfrm4_policy_afinfo);
}
协议初始化
在net/ipv4/xfrm4_tunnel.c中定义ipip协议的接收处理函数
static struct xfrm_tunnel xfrm_tunnel_handler = {
.handler = xfrm_tunnel_rcv,
.err_handler = xfrm_tunnel_err,
.priority = ,
};
static int __init ipip_init(void)
{
if (xfrm_register_type(&ipip_type, AF_INET) < ) { //参考上面数据结构部分
printk(KERN_INFO "ipip init: can't add xfrm type\n");
return -EAGAIN;
}
if (xfrm4_tunnel_register(&xfrm_tunnel_handler, AF_INET)) { //参考ip隧道基础研究文章
printk(KERN_INFO "ipip init: can't add xfrm handler for AF_INET\n");
xfrm_unregister_type(&ipip_type, AF_INET);
return -EAGAIN;
}
......
return ;
}
在net/ipv4/esp4.c中定义了esp协议的接收处理函数.
static struct net_protocol esp4_protocol = {
.handler = xfrm4_rcv,
.err_handler = esp4_err,
.no_policy = ,
};
static int __init esp4_init(void)
{
if (xfrm_register_type(&esp_type, AF_INET) < ) { //参考上面数据结构部分
printk(KERN_INFO "ip esp init: can't add xfrm type\n");
return -EAGAIN;
}
if (inet_add_protocol(&esp4_protocol, IPPROTO_ESP) < ) { //参考ip隧道基础研究文章
printk(KERN_INFO "ip esp init: can't add protocol\n");
xfrm_unregister_type(&esp_type, AF_INET);
return -EAGAIN;
}
return ;
}
在net/ipv4/ah4.c中定义了ah协议的接收处理函数,与esp基本一样
static struct net_protocol ah4_protocol = {
.handler = xfrm4_rcv,
.err_handler = ah4_err,
.no_policy = ,
};
static int __init ah4_init(void)
{
if (xfrm_register_type(&ah_type, AF_INET) < ) {
printk(KERN_INFO "ip ah init: can't add xfrm type\n");
return -EAGAIN;
}
if (inet_add_protocol(&ah4_protocol, IPPROTO_AH) < ) {
printk(KERN_INFO "ip ah init: can't add protocol\n");
xfrm_unregister_type(&ah_type, AF_INET);
return -EAGAIN;
}
return ;
}
[/初始化]
[数据接收]
下面我们先看ipip协议的接收处理函数.
static int xfrm_tunnel_rcv(struct sk_buff *skb)
{
return xfrm4_rcv_spi(skb, IPPROTO_IPIP, ip_hdr(skb)->saddr);
}
esp和ah协议的接收处理函数
int xfrm4_rcv(struct sk_buff *skb)
{
return xfrm4_rcv_spi(skb, ip_hdr(skb)->protocol, );
}
都调用到同样的函数
static inline int xfrm4_rcv_spi(struct sk_buff *skb, int nexthdr, __be32 spi)
{
return xfrm4_rcv_encap(skb, nexthdr, spi, );
}
实际就是
int xfrm4_rcv_encap(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
{
int err;
__be32 seq;
struct xfrm_state *xfrm_vec[XFRM_MAX_DEPTH];
struct xfrm_state *x;
int xfrm_nr = ;
int decaps = ;
unsigned int nhoff = offsetof(struct iphdr, protocol); //协议字段在ip头中的偏移位置
seq = ; //获取skb中的spi和序列号信息,ipip时spi不为0
if (!spi && (err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) != )
goto drop;
//进入循环进行解包操作
do {
const struct iphdr *iph = ip_hdr(skb);
if (xfrm_nr == XFRM_MAX_DEPTH)//循环解包次数太深的话放弃,目前定义是 6
goto drop; //根据地址, SPI和协议查找SA
//主要调用__xfrm_state_lookup(daddr, spi, proto, family);上下有锁保护
x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi, nexthdr, AF_INET);
if (x == NULL)
goto drop; // 以下根据SA定义的操作对数据解码
spin_lock(&x->lock);
if (unlikely(x->km.state != XFRM_STATE_VALID))
goto drop_unlock; //检查由SA指定的封装类型是否和函数指定的封装类型相同
if ((x->encap ? x->encap->encap_type : ) != encap_type)
goto drop_unlock; //SA重放窗口检查
if (x->props.replay_window && xfrm_replay_check(x, seq))
goto drop_unlock; //SA生存期检查
if (xfrm_state_check_expire(x))
goto drop_unlock; //type可为esp,ah,ipcomp, ipip等, 调用具体协议的输入函数对输入数据解密,参看下面具体协议实现
nexthdr = x->type->input(x, skb);
if (nexthdr <= )
goto drop_unlock; skb_network_header(skb)[nhoff] = nexthdr; //修改为解密后的协议
/* only the first xfrm gets the encap type */
encap_type = ; //更新重放窗口
if (x->props.replay_window)
xfrm_replay_advance(x, seq); //包数,字节数统计
x->curlft.bytes += skb->len;
x->curlft.packets++; spin_unlock(&x->lock);
//保存数据解封用的SA, 增加SA数量计数
xfrm_vec[xfrm_nr++] = x; //mode可为通道,传输等模式, 调用具体模式输入函数对数据解封装,参看下面模式函数实现
if (x->outer_mode->input(x, skb))
goto drop; //如果是IPSEC通道模式,将decaps参数置1,否则表示是传输模式
if (x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL) {
decaps = ;
break;
}
//看内层协议是否还要继续解包, 不需要解时返回1, 需要解时返回0, 错误返回负数
//协议类型可以多层封装的,比如用AH封装ESP, 就得先解完AH再解ESP
err = xfrm_parse_spi(skb, nexthdr, &spi, &seq);
if (err < )
goto drop;
} while (!err); /* Allocate new secpath or COW existing one. */
// 为skb包建立新的安全路径(struct sec_path)
if (!skb->sp || atomic_read(&skb->sp->refcnt) != ) {
struct sec_path *sp;
sp = secpath_dup(skb->sp);
if (!sp)
goto drop; if (skb->sp)
secpath_put(skb->sp); skb->sp = sp;
}
if (xfrm_nr + skb->sp->len > XFRM_MAX_DEPTH)
goto drop; //将刚才循环解包用到的SA拷贝到安全路径
//因此检查一个数据包是否是普通明文包还是解密后的明文包就看skb->sp参数是否为空
memcpy(skb->sp->xvec + skb->sp->len, xfrm_vec, xfrm_nr * sizeof(xfrm_vec[]));
skb->sp->len += xfrm_nr; nf_reset(skb); //去掉ip_conntrack和bridge
if (decaps) {//通道模式,IPIP协议
dst_release(skb->dst); //去掉路由缓存
skb->dst = NULL;
netif_rx(skb); //重新进入网卡接收函数
return ;
} else { //传输模式
#ifdef CONFIG_NETFILTER
//如果定义NETFILTER, 进入PRE_ROUTING链处理,然后进入路由选择处理,其实现在已经处于INPUT点,
//但解码后需要将该包作为一个新包看待。可能需要进行目的NAT操作, 这时候可能目的地址就会改变不是到自身的了,
//因此需要将其相当于是放回PRE_PROUTING点去操作, 重新找路由.
//这也说明可以制定针对解码后明文包的NAT规则,在还是加密包的时候不匹配但解码后能匹配上
__skb_push(skb, skb->data - skb_network_header(skb));
ip_hdr(skb)->tot_len = htons(skb->len);
ip_send_check(ip_hdr(skb)); NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, skb->dev, NULL, xfrm4_rcv_encap_finish);
return ;
#else
//内核不支持NETFILTER, 该包肯定就是到自身的了。返回IP协议的负值, 表示重新进行IP层协议的处理
//用解码后的内层协议来处理数据,具体看ip_local_deliver_finish函数实现
return -ip_hdr(skb)->protocol;
#endif
}
drop_unlock:
spin_unlock(&x->lock);
xfrm_state_put(x);
drop:
while (--xfrm_nr >= )
xfrm_state_put(xfrm_vec[xfrm_nr]); kfree_skb(skb);
return ;
}
解析AH,ESP数据包中的SPI和序号,返回值是网络序的
int xfrm_parse_spi(struct sk_buff *skb, u8 nexthdr, __be32 *spi, __be32 *seq)
{
int offset, offset_seq;
int hlen;
//通过nexthdr参数来判断协议类型, nexthdr是IPV6里的说法, 在IPV4中就是IP头里的协议字段
//根据不同协议确定数据中SPI和序列号相对数据起始点的偏移
switch (nexthdr) {
case IPPROTO_AH:
hlen = sizeof(struct ip_auth_hdr);
offset = offsetof(struct ip_auth_hdr, spi);
offset_seq = offsetof(struct ip_auth_hdr, seq_no);
break;
case IPPROTO_ESP:
hlen = sizeof(struct ip_esp_hdr);
offset = offsetof(struct ip_esp_hdr, spi);
offset_seq = offsetof(struct ip_esp_hdr, seq_no);
break;
case IPPROTO_COMP:
if (!pskb_may_pull(skb, sizeof(struct ip_comp_hdr)))
return -EINVAL; //SPI值取第3,4字节的数据, 序号为0
*spi = htonl(ntohs(*(__be16*)(skb_transport_header(skb) + )));
*seq = ;
return ;
default:
return ;
}
if (!pskb_may_pull(skb, hlen))
return -EINVAL;
//根据偏移获取SPI和序号, 注意是网络序的值
*spi = *(__be32*)(skb_transport_header(skb) + offset);
*seq = *(__be32*)(skb_transport_header(skb) + offset_seq);
return ;
}
根据SPI进行HASH后查找
static struct xfrm_state *__xfrm_state_lookup(xfrm_address_t *daddr, __be32 spi, u8 proto, unsigned short family)
{
unsigned int h = xfrm_spi_hash(daddr, spi, proto, family);//根据SPI进行HASH
struct xfrm_state *x;
struct hlist_node *entry; //循环相应的SPI链表
hlist_for_each_entry(x, entry, xfrm_state_byspi+h, byspi) {
//比较协议族, SPI, 和协议是否相同
if (x->props.family != family || x->id.spi != spi || x->id.proto != proto)
continue; switch (family) { //比较目的地址是否相同
case AF_INET:
if (x->id.daddr.a4 != daddr->a4)
continue;
break;
case AF_INET6:
if (!ipv6_addr_equal((struct in6_addr *)daddr, (struct in6_addr *)x->id.daddr.a6))
continue;
break;
}
xfrm_state_hold(x);
return x;
}
}
static inline int xfrm4_rcv_encap_finish(struct sk_buff *skb)
{
//如果没有路由, 重新查找路由
if (skb->dst == NULL) {
const struct iphdr *iph = ip_hdr(skb);
if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev))
goto drop;
}
//调用相关的路由输入函数
return dst_input(skb);
drop:
kfree_skb(skb);
return NET_RX_DROP;
}
[/数据接收]
[数据发送]
IPV4的IPSEC数据发送处理在net/ipv4/xfrm4_output.c中定义,作为安全路由的输出函数.
dst_output -> xfrm_dst->route->output == xfrm4_output
int xfrm4_output(struct sk_buff *skb)
{
//就是一个条件HOOK, 当skb包不带IPSKB_REROUTED标志时进入POSTROUTING点的NAT操作
//这是数据在xfrm策略中多个bundle时会多次调用, 也就是数据在封装完成前可以进行源NAT操作
//HOOK出口函数为xfrm4_output_finish
return NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, skb->dst->dev,
xfrm4_output_finish, !(IPCB(skb)->flags & IPSKB_REROUTED));
}
发送结束处理
static int xfrm4_output_finish(struct sk_buff *skb)
{
struct sk_buff *segs;
#ifdef CONFIG_NETFILTER
//如果内核定义了NETFILTER, 当到达最后一个路由(普通路由)时, 设置IPSKB_REROUTED标志,
//进行普通路由发出函数(ip_output), 设置该标志后不进行源NAT操作
if (!skb->dst->xfrm) {
IPCB(skb)->flags |= IPSKB_REROUTED;
return dst_output(skb);
}
#endif
//如果skb包不是是gso, 转xfrm4_output_finish2
if (!skb_is_gso(skb))
return xfrm4_output_finish2(skb);
//处理gso数据包, 最终也是使用xfrm4_output_finish2处理数据包
skb->protocol = htons(ETH_P_IP);
segs = skb_gso_segment(skb, );
kfree_skb(skb);
if (unlikely(IS_ERR(segs)))
return PTR_ERR(segs);
do { //循环发送
struct sk_buff *nskb = segs->next;
int err;
segs->next = NULL;
err = xfrm4_output_finish2(segs); if (unlikely(err)) {
while ((segs = nskb)) {
nskb = segs->next;
segs->next = NULL;
kfree_skb(segs);
}
return err;
}
segs = nskb;
} while (segs);
return ;
}
第2级发送结束处理
static int xfrm4_output_finish2(struct sk_buff *skb)
{
int err;
//根据安全路由包装要发送的数据
while (likely((err = xfrm4_output_one(skb)) == )) {
//处理成功,释放skb中的netfilter信息
nf_reset(skb);
//重新将该包作为初始发送包, 进入OUTPUT点处理, 注意这是个函数而不是宏
//如果内核没定义NETFILTER, 该函数只是个空函数
//返回1表示NF_ACCEPT
err = nf_hook(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, skb->dst->dev, dst_output);
if (unlikely(err != ))
break; //如果已经没有SA, 就只是个普通包了, 路由发送(ip_output)返回, 退出循环
if (!skb->dst->xfrm)
return dst_output(skb); //如果还有SA, 目前还只是中间状态, 还可以进行SNAT操作, 进入POSTROUTING点处理
err = nf_hook(PF_INET, NF_IP_POST_ROUTING, skb, NULL, skb->dst->dev, xfrm4_output_finish2);
if (unlikely(err != ))
break;
}
return err;
}
按安全路由链表的安全路由处理数据, 该链表反映了多个SA对数据包进行处理
链表是在__xfrm4_bundle_create函数中建立的.
static inline int xfrm4_output_one(struct sk_buff *skb)
{
struct dst_entry *dst = skb->dst; //安全路由
struct xfrm_state *x = dst->xfrm;//相关SA
struct iphdr *iph;
int err; //如果是通道模式, 检查skb数据长度, 并进行相关处理,
//通道模式下封装后的数据包长度可能会超过1500字节的
if (x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL) {
err = xfrm4_tunnel_check_size(skb);
if (err)
goto error_nolock;
}
err = xfrm_output(skb); //具体封装
if (err)
goto error_nolock; iph = ip_hdr(skb);
iph->tot_len = htons(skb->len); //最新的长度
ip_send_check(iph); //重新计算效验和 //skb中设置IPSKB_XFRM_TRANSFORMED标志
//有该标志的数据包NAT操作后将不进行一些特殊检查
IPCB(skb)->flags |= IPSKB_XFRM_TRANSFORMED;
err = ;
out_exit:
return err;
error_nolock:
kfree_skb(skb);
goto out_exit;
}
int xfrm_output(struct sk_buff *skb)
{
struct dst_entry *dst = skb->dst;
struct xfrm_state *x = dst->xfrm;
int err;
//skb包校验和计算
if (skb->ip_summed == CHECKSUM_PARTIAL) {
err = skb_checksum_help(skb);
if (err)
goto error_nolock;
}
do {
spin_lock_bh(&x->lock);
err = xfrm_state_check(x, skb);//SA合法性检查
if (err)
goto error; if (x->type->flags & XFRM_TYPE_REPLAY_PROT) {
XFRM_SKB_CB(skb)->seq = ++x->replay.oseq;
if (xfrm_aevent_is_on())
xfrm_replay_notify(x, XFRM_REPLAY_UPDATE); }
err = x->outer_mode->output(x, skb);//调用模式输出函数, 如通道封装时外部IP头协议为IPIP,参考下面模式函数实现
if (err)
goto error; //更新SA中的当前生命期结构中的包和字节计数
x->curlft.bytes += skb->len;
x->curlft.packets++;
spin_unlock_bh(&x->lock); //调用协议输出, 如对应ESP协议来说是esp4_output, 此时外部IP头协议会改为ESP,参考下面具体协议实现
err = x->type->output(x, skb);
if (err)
goto error_nolock; //转移到下一个子路由
if (!(skb->dst = dst_pop(dst))) {
err = -EHOSTUNREACH;
goto error_nolock;
}
//dst和x参数更新为子路由中的安全路由和SA
dst = skb->dst;
x = dst->xfrm;
} while (x && !(x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL));//循环条件是SA非空, 而且SA模式不是通道模式 err = ;
error_nolock:
return err;
error:
spin_unlock_bh(&x->lock);
goto error_nolock;
}
[/数据发送]
[具体协议实现]
与IPSEC相关的安全协议是AH()和ESP(), IPSEC使用这两个协议对普通数据包进行封装, AH只认证不加密, ESP既加密又认证,
当ESP和AH同时使用时, 一般都是先进行ESP封装, 再进行AH封装, 因为AH是对整个IP包进行验证的, 而ESP只验证负载部分.
具体的协议结构定义如下, 通常只定义初始化,析构,输入和输出四个成员函数.
static struct xfrm_type ah_type = // net/ipv4/ah4.c
{
.description = "AH4",
.owner = THIS_MODULE,
.proto = IPPROTO_AH,
.flags = XFRM_TYPE_REPLAY_PROT,
.init_state = ah_init_state, //状态初始化
.destructor = ah_destroy,//协议释放
.input = ah_input, //协议输入
.output = ah_output //协议输出
};
状态初始化
ah_data数据结构
struct ah_data //include/net/ah.h
{
u8 *work_icv; //工作初始化向量
int icv_full_len; //初始化向量完整长度
int icv_trunc_len; //初始化向量截断长度
struct crypto_hash *tfm; //HASH算法
};
该函数被xfrm状态(SA)初始化函数xfrm_init_state调用,用来生成SA中所有的AH数据处理结构相关信息
static int ah_init_state(struct xfrm_state *x)
{
struct ah_data *ahp = NULL;
struct xfrm_algo_desc *aalg_desc;
struct crypto_hash *tfm; if (!x->aalg) //对AH协议的SA, 认证算法是必须的, 否则就没法进行AH认证了
goto error; if (x->encap)//如果要进行UDP封装(进行NAT穿越), 错误, 因为AH是不支持NAT的
goto error; ahp = kzalloc(sizeof(*ahp), GFP_KERNEL);//分配ah_data数据结构空间
if (ahp == NULL)
return -ENOMEM; //分配认证算法HASH结构指针并赋值给AH数据结构
//算法是固定相同的, 但在每个应用使用算法时的上下文是不同的, 该结构就是描述具体应用时的相关处理的上下文数据的
tfm = crypto_alloc_hash(x->aalg->alg_name, , CRYPTO_ALG_ASYNC);
if (IS_ERR(tfm))
goto error; ahp->tfm = tfm;
//设置认证算法密钥
if (crypto_hash_setkey(tfm, x->aalg->alg_key, (x->aalg->alg_key_len + ) / ))
goto error;
/*
* Lookup the algorithm description maintained by xfrm_algo,
* verify crypto transform properties, and store information
* we need for AH processing. This lookup cannot fail here
* after a successful crypto_alloc_hash().
*/
aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, );//查找算法描述结构
BUG_ON(!aalg_desc); if (aalg_desc->uinfo.auth.icv_fullbits / != crypto_hash_digestsize(tfm)) {
printk(KERN_INFO "AH: %s digestsize %u != %hu\n", x->aalg->alg_name, crypto_hash_digestsize(tfm),
aalg_desc->uinfo.auth.icv_fullbits/);
goto error;
}
//AH数据结构的初始化向量的总长和截断长度的赋值
ahp->icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/;
ahp->icv_trunc_len = aalg_desc->uinfo.auth.icv_truncbits/; BUG_ON(ahp->icv_trunc_len > MAX_AH_AUTH_LEN);
//分配初始化向量空间, 没对其赋值, 其初始值就是随机值, 这也是初始化向量所需要的
ahp->work_icv = kmalloc(ahp->icv_full_len, GFP_KERNEL);
if (!ahp->work_icv)
goto error; //AH类型SA中AH头长度: ip_auth_hdr结构和初始化向量长度, 按8字节对齐
//反映在AH封装操作时要将数据包增加的长度
x->props.header_len = XFRM_ALIGN8(sizeof(struct ip_auth_hdr) + ahp->icv_trunc_len); if (x->props.mode == XFRM_MODE_TUNNEL)//如果是通道模式, 增加IP头长度
x->props.header_len += sizeof(struct iphdr);
x->data = ahp;//SA数据指向AH数据结构
return ;
error:
if (ahp) {
kfree(ahp->work_icv);
crypto_free_hash(ahp->tfm);
kfree(ahp);
}
return -EINVAL;
}
接收数据处理, 在xfrm4_rcv_encap()函数中调用,进行AH认证, 剥离AH头
static int ah_input(struct xfrm_state *x, struct sk_buff *skb)
{
int ah_hlen;
int ihl;
int nexthdr;
int err = -EINVAL;
struct iphdr *iph;
struct ip_auth_hdr *ah;
struct ah_data *ahp;
char work_buf[]; //IP头备份空间 if (!pskb_may_pull(skb, sizeof(*ah)))//skb数据包要准备留出AH头空间
goto out; ah = (struct ip_auth_hdr *)skb->data; //IP上层数据为AH数据
//SA相关的AH处理数据
ahp = x->data;
nexthdr = ah->nexthdr;
ah_hlen = (ah->hdrlen + ) << ; //AH头部长度合法性检查
if (ah_hlen != XFRM_ALIGN8(sizeof(*ah) + ahp->icv_full_len) && ah_hlen != XFRM_ALIGN8(sizeof(*ah) + ahp->icv_trunc_len))
goto out;
//skb数据包要准备留出实际AH头空间
if (!pskb_may_pull(skb, ah_hlen))
goto out;
/* We are going to _remove_ AH header to keep sockets happy, so... Later this can change. */
if (skb_cloned(skb) && pskb_expand_head(skb, , , GFP_ATOMIC))//对于clone的包要复制成独立包
goto out; skb->ip_summed = CHECKSUM_NONE; ah = (struct ip_auth_hdr *)skb->data;//可能包已经进行了复制, 所以对ah重新赋值
iph = ip_hdr(skb); ihl = skb->data - skb_network_header(skb); //IP头长度
memcpy(work_buf, iph, ihl); //备份外部IP头数据 //将IP头中的一些参数清零, 这些参数不进行认证
iph->ttl = ;
iph->tos = ;
iph->frag_off = ;
iph->check = ; if (ihl > sizeof(*iph)) {//IP头长度超过20字节时,处理IP选项参数
__be32 dummy;
if (ip_clear_mutable_options(iph, &dummy))
goto out;
}
{
u8 auth_data[MAX_AH_AUTH_LEN];//认证数据缓冲区 memcpy(auth_data, ah->auth_data, ahp->icv_trunc_len);//拷贝数据包中的认证数据到缓冲区
skb_push(skb, ihl);//包括IP头部分数据 err = ah_mac_digest(ahp, skb, ah->auth_data);//计算认证值是否匹配, 非0表示出错
if (err)
goto out;
err = -EINVAL;
if (memcmp(ahp->work_icv, auth_data, ahp->icv_trunc_len)) {//复制一定长度的认证数据作为初始化向量
x->stats.integrity_failed++;
goto out;
}
}
skb->network_header += ah_hlen; //更新网络头字段
memcpy(skb_network_header(skb), work_buf, ihl);//将原来IP头数据拷贝到原来AH头后面作为新IP头
skb->transport_header = skb->network_header;
__skb_pull(skb, ah_hlen + ihl);//skb包缩减原来的IP头和AH头, 以新IP头作为数据开始 return nexthdr; //返回AH内部包裹的协议
out:
return err;
}
发送数据处理, 在xfrm4_output_one()中调用,计算AH认证值, 添加AH头
static int ah_output(struct xfrm_state *x, struct sk_buff *skb)
{
int err;
struct iphdr *iph, *top_iph;
struct ip_auth_hdr *ah;
struct ah_data *ahp;
union {//临时IP头缓冲区, 最大IP头60字节
struct iphdr iph;
char buf[];
} tmp_iph; skb_push(skb, -skb_network_offset(skb)); //data指针移动到网络头位置
top_iph = ip_hdr(skb);//当前的IP头将作为最外部IP头
iph = &tmp_iph.iph; //临时IP头,用于临时保存IP头内部分字段数据 //将当前IP头中不进行认证的字段数据复制到临时IP头
iph->tos = top_iph->tos;
iph->ttl = top_iph->ttl;
iph->frag_off = top_iph->frag_off; if (top_iph->ihl != ) {//如果有IP选项, 处理IP选项
iph->daddr = top_iph->daddr;
memcpy(iph+, top_iph+, top_iph->ihl* - sizeof(struct iphdr));
err = ip_clear_mutable_options(top_iph, &top_iph->daddr);
if (err)
goto error;
}
//AH头定位在外部IP头后面, skb缓冲中已经预留出AH头的数据部分了,
//这是通过mode->output函数预留的, 通常调用type->output前要调用mode->oputput,参考上面函数xfrm_output
ah = ip_auth_hdr(skb);
ah->nexthdr = *skb_mac_header(skb);//AH中的下一个头用mac中指定的协议
*skb_mac_header(skb) = IPPROTO_AH; //mac中协议字段改为AH //将外部IP头的不进行认证计算的部分字段清零
top_iph->tos = ;
top_iph->tot_len = htons(skb->len);
top_iph->frag_off = ;
top_iph->ttl = ;
top_iph->check = ; ahp = x->data;//AH数据处理结构
ah->hdrlen = (XFRM_ALIGN8(sizeof(*ah) + ahp->icv_trunc_len) >> ) - ;//AH头长度对齐 //AH头参数赋值
ah->reserved = ;
ah->spi = x->id.spi;//SPI值
ah->seq_no = htonl(XFRM_SKB_CB(skb)->seq);//序列号 spin_lock_bh(&x->lock);
err = ah_mac_digest(ahp, skb, ah->auth_data);//对skb进行AH认证值的计算
memcpy(ah->auth_data, ahp->work_icv, ahp->icv_trunc_len);//赋值初始化向量值到认证数据部分
spin_unlock_bh(&x->lock); if (err)
goto error; // 恢复原来IP头的的不认证部分的值
top_iph->tos = iph->tos;
top_iph->ttl = iph->ttl;
top_iph->frag_off = iph->frag_off; if (top_iph->ihl != ) { //处理ip选项
top_iph->daddr = iph->daddr;
memcpy(top_iph+, iph+, top_iph->ihl* - sizeof(struct iphdr));
}
err = ;
error:
return err;
}
static struct xfrm_type esp_type = //net/ipv4/esp4.c
{
.description = "ESP4",
.owner = THIS_MODULE,
.proto = IPPROTO_ESP,
.flags = XFRM_TYPE_REPLAY_PROT,
.init_state = esp_init_state,
.destructor = esp_destroy,
.get_mtu = esp4_get_mtu, //获取mtu
.input = esp_input,
.output = esp_output
};
esp_data数据结构
struct esp_data
{
struct scatterlist sgbuf[ESP_NUM_FAST_SG]; /* Confidentiality */
struct {//加密使用的相关数据
int padlen; //填充长度 /* 0..255 */
/* ivlen is offset from enc_data, where encrypted data start.
* It is logically different of crypto_tfm_alg_ivsize(tfm).
* We assume that it is either zero (no ivec), or
* >= crypto_tfm_alg_ivsize(tfm). */
int ivlen; //初始化向量长度
int ivinitted; //初始化向量是否初始化标志
u8 *ivec; //初始化向量 /* ivec buffer */
struct crypto_blkcipher *tfm; //加密算法 /* crypto handle */
} conf;
/* Integrity. It is active when icv_full_len != 0 */
struct {//认证使用的相关数据
u8 *work_icv;//初始化向量
int icv_full_len;//初始化向量全长
int icv_trunc_len;//初始化向量截断长度
struct crypto_hash *tfm;//HASH算法
} auth;
};
ESP的esp_data数据结构初始化
static int esp_init_state(struct xfrm_state *x)
{
struct esp_data *esp = NULL;
struct crypto_blkcipher *tfm;
u32 align; if (x->ealg == NULL)//ESP加密算法是必须的
goto error; esp = kzalloc(sizeof(*esp), GFP_KERNEL);//分配esp_data数据结构空间
if (esp == NULL)
return -ENOMEM; //如果定义了认证算法, 初始化认证算法参数, 和AH类似
if (x->aalg) {
struct xfrm_algo_desc *aalg_desc;
struct crypto_hash *hash;
//分配HASH算法的实现
hash = crypto_alloc_hash(x->aalg->alg_name, , CRYPTO_ALG_ASYNC);
if (IS_ERR(hash))
goto error; esp->auth.tfm = hash;
//设置HASH算法密钥
if (crypto_hash_setkey(hash, x->aalg->alg_key, (x->aalg->alg_key_len + ) / ))
goto error; //找到算法描述
aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, );
BUG_ON(!aalg_desc); //检查算法初始化向量长度合法性
if (aalg_desc->uinfo.auth.icv_fullbits/ != crypto_hash_digestsize(hash)) {
NETDEBUG(KERN_INFO "ESP: %s digestsize %u != %hu\n", x->aalg->alg_name,
crypto_hash_digestsize(hash), aalg_desc->uinfo.auth.icv_fullbits/);
goto error;
}
//初始化向量的全长和截断长度
esp->auth.icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/;
esp->auth.icv_trunc_len = aalg_desc->uinfo.auth.icv_truncbits/;
esp->auth.work_icv = kmalloc(esp->auth.icv_full_len, GFP_KERNEL);//分配全长度的初始化向量空间
if (!esp->auth.work_icv)
goto error;
}
//查找加密算法的具体实现结构
tfm = crypto_alloc_blkcipher(x->ealg->alg_name, , CRYPTO_ALG_ASYNC);
if (IS_ERR(tfm))
goto error;
esp->conf.tfm = tfm;
esp->conf.ivlen = crypto_blkcipher_ivsize(tfm);//初始化向量大小
esp->conf.padlen = ;//填充数据长度初始化为0 if (esp->conf.ivlen) {//初始化向量长度非0, 分配具体的初始化向量空间
esp->conf.ivec = kmalloc(esp->conf.ivlen, GFP_KERNEL);
if (unlikely(esp->conf.ivec == NULL))
goto error;
esp->conf.ivinitted = ;
}
//设置加密算法密钥
if (crypto_blkcipher_setkey(tfm, x->ealg->alg_key, (x->ealg->alg_key_len + ) / ))
goto error;
//定义SA中ESP头部长度: ESP头加初始化向量长度
//反映在ESP封装操作时要将数据包增加的长度
x->props.header_len = sizeof(struct ip_esp_hdr) + esp->conf.ivlen; if (x->props.mode == XFRM_MODE_TUNNEL)//如果是通道模式, 还需要增加IP头长度
x->props.header_len += sizeof(struct iphdr);
else if (x->props.mode == XFRM_MODE_BEET)//如果是BEET模式, 还需要增加IP头长度
x->props.header_len += IPV4_BEET_PHMAXLEN;
if (x->encap) {//如果要进行UDP封装
struct xfrm_encap_tmpl *encap = x->encap;
switch (encap->encap_type) {
default:
goto error;
case UDP_ENCAP_ESPINUDP://该类型封装增加UDP头长度
x->props.header_len += sizeof(struct udphdr);
break;
case UDP_ENCAP_ESPINUDP_NON_IKE://该类型封装增加UDP头长度外加加8字节
x->props.header_len += sizeof(struct udphdr) + * sizeof(u32);
break;
}
}
x->data = esp;//将esp_data作为SA的data指针 align = ALIGN(crypto_blkcipher_blocksize(esp->conf.tfm), ); //加密块长度, 按4字节对齐
if (esp->conf.padlen)
align = max_t(u32, align, esp->conf.padlen);
x->props.trailer_len = align + + esp->auth.icv_trunc_len; //加密块长度 + 1 + 认证长度
return ;
......
}
获取mtu
static u32 esp4_get_mtu(struct xfrm_state *x, int mtu)
{
struct esp_data *esp = x->data;
u32 blksize = ALIGN(crypto_blkcipher_blocksize(esp->conf.tfm), );//加密块长度, 按4字节对齐
u32 align = max_t(u32, blksize, esp->conf.padlen);
u32 rem; mtu -= x->props.header_len + esp->auth.icv_trunc_len;
rem = mtu & (align - );
mtu &= ~(align - ); ///mtu对齐 switch (x->props.mode) {
case XFRM_MODE_TUNNEL:
break;
default:
case XFRM_MODE_TRANSPORT://传输模式下
/* The worst case */
mtu -= blksize - ;
mtu += min_t(u32, blksize - , rem);
break;
case XFRM_MODE_BEET:
/* The worst case. */
mtu += min_t(u32, IPV4_BEET_PHMAXLEN, rem);
break;
}
return mtu - ;
}
接收数据处理, 在xfrm4_rcv_encap()函数中调用
进行ESP认证解密, 剥离ESP头, 解密成普通数据包, 数据包长度减少
static int esp_input(struct xfrm_state *x, struct sk_buff *skb)
{
struct iphdr *iph;
struct ip_esp_hdr *esph;
struct esp_data *esp = x->data;
struct crypto_blkcipher *tfm = esp->conf.tfm;
struct blkcipher_desc desc = { .tfm = tfm };
struct sk_buff *trailer;
int blksize = ALIGN(crypto_blkcipher_blocksize(tfm), );
int alen = esp->auth.icv_trunc_len;//认证初始化向量截断长度
//需要加密的数据长度: 总长减ESP头, 加密初始化向量长度, 认证初始化向量长度
int elen = skb->len - sizeof(*esph) - esp->conf.ivlen - alen;
int nfrags;
int ihl;
u8 nexthdr[];
struct scatterlist *sg;
int padlen;
int err; if (!pskb_may_pull(skb, sizeof(*esph)))//在skb头要有足够的ESP头空间
goto out; if (elen <= || (elen & (blksize-)))//检查需要加密的数据长度, 必须大于0而且按块大小对齐的
goto out; /* If integrity check is required, do this. */
if (esp->auth.icv_full_len) {//认证计算处理
u8 sum[alen];
//计算认证值, 认证值保存在esp_data结构中
err = esp_mac_digest(esp, skb, , skb->len - alen);
if (err)
goto out; //将skb中的认证初始化向量部分数据拷贝到缓冲区sum中
if (skb_copy_bits(skb, skb->len - alen, sum, alen))
BUG(); //比较sum中的向量值和认证算法结构中的向量值是否匹配, 数据包正常情况下应该是相同的
if (unlikely(memcmp(esp->auth.work_icv, sum, alen))) {
x->stats.integrity_failed++;
goto out;
}
}
//使数据包可写,返回使用了多少个内存页
if ((nfrags = skb_cow_data(skb, , &trailer)) < )
goto out; skb->ip_summed = CHECKSUM_NONE;
//定位在数据包中的ESP头位置, 为当前的data位置
esph = (struct ip_esp_hdr *)skb->data; /* Get ivec. This can be wrong, check against another impls. */
if (esp->conf.ivlen)//设置加密算法的初始化向量
crypto_blkcipher_set_iv(tfm, esph->enc_data, esp->conf.ivlen);//拷贝esph头后的数据到加密算法结构中 sg = &esp->sgbuf[]; if (unlikely(nfrags > ESP_NUM_FAST_SG)) {//内存页超过 4
sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC);
if (!sg)
goto out;
}
sg_init_table(sg, nfrags);
skb_to_sgvec(skb, sg, sizeof(*esph) + esp->conf.ivlen, elen); //解密操作, 返回非0表示失败
err = crypto_blkcipher_decrypt(&desc, sg, sg, elen);
if (unlikely(sg != &esp->sgbuf[]))
kfree(sg);
if (unlikely(err))//解密失败返回
return err; if (skb_copy_bits(skb, skb->len-alen-, nexthdr, ))//拷贝两字节数据
BUG(); padlen = nexthdr[]; //填充数据长度
if (padlen+ >= elen)
goto out;
/* RFC4303: Drop dummy packets without any error */
if (nexthdr[] == IPPROTO_NONE)
goto out; iph = ip_hdr(skb);//IP头
ihl = iph->ihl * ; if (x->encap) {//如果是NAT穿越情况, 进行一些处理
struct xfrm_encap_tmpl *encap = x->encap;//xfrm封装模板
struct udphdr *uh = (void *)(skb_network_header(skb) + ihl);//定位UDP数据头位置, 在IP头之后 //如果IP头源地址和SA提议中的源地址不同或源端口不同
if (iph->saddr != x->props.saddr.a4 || uh->source != encap->encap_sport) {
xfrm_address_t ipaddr;
ipaddr.a4 = iph->saddr;//保存当前IP头源地址
km_new_mapping(x, &ipaddr, uh->source);//进行NAT通知回调处理
}
if (x->props.mode == XFRM_MODE_TRANSPORT)//如果是传输模式, 设置不需要计算校验和
skb->ip_summed = CHECKSUM_UNNECESSARY;
}
//缩减skb数据包长度,调整len和tail字段
pskb_trim(skb, skb->len - alen - padlen - ); //调整data到esph头和数据后面
__skb_pull(skb, sizeof(*esph) + esp->conf.ivlen);
skb_set_transport_header(skb, -ihl); //定位传输层头,data前面20字节处 return nexthdr[]; //返回记录的IP头中协议
out:
return -EINVAL;
}
发送数据处理, 在xfrm4_output_one()中调用
添加ESP头, 对数据包进行加密和认证处理, 数据包长度扩大
在NAT穿越情况下会封装为UDP数据
static int esp_output(struct xfrm_state *x, struct sk_buff *skb)
{
int err;
struct ip_esp_hdr *esph;
struct crypto_blkcipher *tfm;
struct blkcipher_desc desc;
struct esp_data *esp;
struct sk_buff *trailer;
u8 *tail;
int blksize;
int clen;
int alen;
int nfrags; /* skb is pure payload to encrypt */
err = -ENOMEM; /* Round to block size */
clen = skb->len;//加密块的初始值 esp = x->data;//获取SA的esp_data数据结构
alen = esp->auth.icv_trunc_len;//认证初始化向量截断长度
tfm = esp->conf.tfm;//加密算法
//给块加密算法描述结构赋值
desc.tfm = tfm;
desc.flags = ;
//每个加密块大小
blksize = ALIGN(crypto_blkcipher_blocksize(tfm), );
clen = ALIGN(clen + , blksize);//对齐要加密的数据总长 if (esp->conf.padlen)//如果要考虑填充, 继续对齐
clen = ALIGN(clen, esp->conf.padlen);
//使数据包可写
if ((nfrags = skb_cow_data(skb, clen-skb->len+alen, &trailer)) < )
goto error; /* Fill padding... */
tail = skb_tail_pointer(trailer);//长度对齐后填充多余长度部分内容
do {
int i;
for (i = ; i < clen - skb->len - ; i++)
tail[i] = i + ;
} while ();
//最后两字节表示填充数据的长度和ip中原来的协议
tail[clen - skb->len - ] = (clen - skb->len) - ; //数据长度
pskb_put(skb, trailer, clen - skb->len);//调整skb->tail位置 //将IP头部分扩展回来,data调整到网络头位置
skb_push(skb, -skb_network_offset(skb));
esph = ip_esp_hdr(skb);//esp头跟在IP头后
*(skb_tail_pointer(trailer) - ) = *skb_mac_header(skb); //记录ip中原来的协议
*skb_mac_header(skb) = IPPROTO_ESP; //修改ip头中协议 spin_lock_bh(&x->lock);
/* this is non-NULL only with UDP Encapsulation */
if (x->encap) {//NAT穿越情况下要将数据封装为UDP包
struct xfrm_encap_tmpl *encap = x->encap;
struct udphdr *uh;
__be32 *udpdata32; uh = (struct udphdr *)esph;//IP头后改为UDP头
//填充UDP头参数, 源端口, 目的端口, UDP数据长度
uh->source = encap->encap_sport;
uh->dest = encap->encap_dport;
uh->len = htons(skb->len + alen - skb_transport_offset(skb));
uh->check = ;//校验和为0, 表示不需要计算校验和, ESP本身就进行认证了 switch (encap->encap_type) {
default:
case UDP_ENCAP_ESPINUDP://在该模式下ESP头跟在UDP头后面
esph = (struct ip_esp_hdr *)(uh + );
break;
case UDP_ENCAP_ESPINUDP_NON_IKE://在该模式下ESP头跟在UDP头后面2字节处
udpdata32 = (__be32 *)(uh + );
udpdata32[] = udpdata32[] = ;
esph = (struct ip_esp_hdr *)(udpdata32 + );
break;
}
*skb_mac_header(skb) = IPPROTO_UDP;//外部IP头协议是UDP
}
//填充ESP头中的SPI和序列号
esph->spi = x->id.spi;
esph->seq_no = htonl(XFRM_SKB_CB(skb)->seq); //如果加密初始化向量长度非零, 设置加密算法中的初始化向量
if (esp->conf.ivlen) {
if (unlikely(!esp->conf.ivinitted)) {
get_random_bytes(esp->conf.ivec, esp->conf.ivlen);
esp->conf.ivinitted = ;
}
crypto_blkcipher_set_iv(tfm, esp->conf.ivec, esp->conf.ivlen);
}
//加密操作
do {
struct scatterlist *sg = &esp->sgbuf[];
if (unlikely(nfrags > ESP_NUM_FAST_SG)) {
sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC);
if (!sg)
goto unlock;
}
sg_init_table(sg, nfrags);
skb_to_sgvec(skb, sg, esph->enc_data + esp->conf.ivlen - skb->data, clen);//对数据加密
err = crypto_blkcipher_encrypt(&desc, sg, sg, clen);
if (unlikely(sg != &esp->sgbuf[]))
kfree(sg);
} while (); if (esp->conf.ivlen) {//将加密算法初始化向量拷贝到数据包
memcpy(esph->enc_data, esp->conf.ivec, esp->conf.ivlen);
crypto_blkcipher_get_iv(tfm, esp->conf.ivec, esp->conf.ivlen);
} if (esp->auth.icv_full_len) {//认证计算, 计算出HASH值并拷贝到数据包中
err = esp_mac_digest(esp, skb, (u8 *)esph - skb->data, sizeof(*esph) + esp->conf.ivlen + clen);
memcpy(pskb_put(skb, trailer, alen), esp->auth.work_icv, alen);
}
unlock:
spin_unlock_bh(&x->lock);
error:
return err;
}
static struct xfrm_type ipcomp_type = { //net/ipv4/ipcomp.c ip压缩协议
.description = "IPCOMP4",
.owner = THIS_MODULE,
.proto = IPPROTO_COMP,
.init_state = ipcomp_init_state,
.destructor = ipcomp_destroy,
.input = ipcomp_input,
.output = ipcomp_output
};
static struct xfrm_type ipip_type = { ///net/ipv4/xfrm4_tunnel.c ipip协议
.description = "IPIP",
.owner = THIS_MODULE,
.proto = IPPROTO_IPIP,
.init_state = ipip_init_state,
.destructor = ipip_destroy,
.input = ipip_xfrm_rcv,
.output = ipip_output
};
static int ipip_output(struct xfrm_state *x, struct sk_buff *skb)
{
skb_push(skb, -skb_network_offset(skb));
return ;
}
static int ipip_xfrm_rcv(struct xfrm_state *x, struct sk_buff *skb)
{
return ip_hdr(skb)->protocol;
}
[/具体协议实现]
[模式函数实现]
static struct xfrm_mode xfrm4_tunnel_mode = { //通道模式结构定义 net/ipv4/xfrm4_mode_tunnel.c
.input = xfrm4_tunnel_input,
.output = xfrm4_tunnel_output,
.owner = THIS_MODULE,
.encap = XFRM_MODE_TUNNEL,
.flags = XFRM_MODE_FLAG_TUNNEL,
};
通道模式下的接收函数, 解封装
static int xfrm4_tunnel_input(struct xfrm_state *x, struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);
const unsigned char *old_mac;
int err = -EINVAL; switch (iph->protocol){//IP协议为IPPROTO_IPIP(4)
case IPPROTO_IPIP:
break;
#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
case IPPROTO_IPV6:
break;
#endif
default:
goto out;
}
//需要在skb头留出IP头的长度(20字节)
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto out; //如果是clone包,重新拷贝一个
if (skb_cloned(skb) && (err = pskb_expand_head(skb, , , GFP_ATOMIC)))
goto out; iph = ip_hdr(skb);
if (iph->protocol == IPPROTO_IPIP) {
if (x->props.flags & XFRM_STATE_DECAP_DSCP)//复制dscp字段
ipv4_copy_dscp(iph, ipip_hdr(skb)); if (!(x->props.flags & XFRM_STATE_NOECN))//非XFRM_STATE_NOECN时进行ECN解封装
ipip_ecn_decapsulate(skb);
}
#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
else {
if (!(x->props.flags & XFRM_STATE_NOECN))
ipip6_ecn_decapsulate(iph, skb);
skb->protocol = htons(ETH_P_IPV6);
}
#endif
//将硬件地址挪到数据包缓冲区前
old_mac = skb_mac_header(skb);//取出mac头位置
//现在skb->data指向内部ip头,所以下面在内部ip头前面把原来的mac头复制过去
skb_set_mac_header(skb, -skb->mac_len);
memmove(skb_mac_header(skb), old_mac, skb->mac_len);
skb_reset_network_header(skb);//重置网络头,网络头为内部ip头,现在外部ip头已经被剥离(被mac头复盖了)
err = ;
out:
return err;
}
通道模式下的数据发出函数, 进行封装
static int xfrm4_tunnel_output(struct xfrm_state *x, struct sk_buff *skb)
{
struct dst_entry *dst = skb->dst;
struct xfrm_dst *xdst = (struct xfrm_dst*)dst;
struct iphdr *iph, *top_iph;
int flags; iph = ip_hdr(skb);
//数据头部增加外部IP头的长度,重置网络头位置
skb_set_network_header(skb, -x->props.header_len);
skb->mac_header = skb->network_header + offsetof(struct iphdr, protocol);//mac头指向ip协议字段
skb->transport_header = skb->network_header + sizeof(*iph);//传输头指向ip头后面 top_iph = ip_hdr(skb); //获取外部ip头
//填写外部IP头参数
top_iph->ihl = ;
top_iph->version = ; flags = x->props.flags; /* DS disclosed */
if (xdst->route->ops->family == AF_INET) {
top_iph->protocol = IPPROTO_IPIP;//外部IP头内的协议号为IPIP(4)
top_iph->tos = INET_ECN_encapsulate(iph->tos, iph->tos);//重新计算TOS
top_iph->frag_off = (flags & XFRM_STATE_NOPMTUDISC) ? : (iph->frag_off & htons(IP_DF));//处理分片包情况
}
#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
else {
struct ipv6hdr *ipv6h = (struct ipv6hdr*)iph;
top_iph->protocol = IPPROTO_IPV6;
top_iph->tos = INET_ECN_encapsulate(iph->tos, ipv6_get_dsfield(ipv6h));
top_iph->frag_off = ;
}
#endif
if (flags & XFRM_STATE_NOECN)
IP_ECN_clear(top_iph); if (!top_iph->frag_off)
__ip_select_ident(top_iph, dst->child, ); top_iph->ttl = dst_metric(dst->child, RTAX_HOPLIMIT); top_iph->saddr = x->props.saddr.a4;//外部源地址用proposal中的源地址
top_iph->daddr = x->id.daddr.a4;//外部目的地址是SA中的目的地址 skb->protocol = htons(ETH_P_IP); memset(&(IPCB(skb)->opt), , sizeof(struct ip_options));//IP选项部分设置为0
return ;
}
static struct xfrm_mode xfrm4_transport_mode = { //传输模式结构定义 net/ipv4/xfrm4_mode_transport.c
.input = xfrm4_transport_input,
.output = xfrm4_transport_output,
.owner = THIS_MODULE,
.encap = XFRM_MODE_TRANSPORT,
};
传输模式下的数据输入函数
static int xfrm4_transport_input(struct xfrm_state *x, struct sk_buff *skb)
{
int ihl = skb->data - skb_transport_header(skb); //获取ip头长度 if (skb->transport_header != skb->network_header) {
//移动网络头到传输头位置,现在传输层头指向的是data前ip头长度字节位置
memmove(skb_transport_header(skb), skb_network_header(skb), ihl);
skb->network_header = skb->transport_header;//设置网络头
}
ip_hdr(skb)->tot_len = htons(skb->len + ihl); //重新对数据包长度赋值
skb_reset_transport_header(skb); //重置传输头位置,为data位置
return ;
}
传输模式下的数据发出函数
static int xfrm4_transport_output(struct xfrm_state *x, struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);
int ihl = iph->ihl * ; skb_set_network_header(skb, -x->props.header_len); //重新设置网络头位置,向前移出一个props.header_len长的空位
skb->mac_header = skb->network_header + offsetof(struct iphdr, protocol); //mac头值设置为ip头中协议字段的位置
skb->transport_header = skb->network_header + ihl; //传输头在网络头 + ip头长度后面的位置
__skb_pull(skb, ihl);//将skb->data移动到ip头后面 memmove(skb_network_header(skb), iph, ihl); //拷贝原始ip头到新网络头位置
return ;
}
static struct xfrm_mode xfrm4_beet_mode = { //beet模式 net/ipv4/xfrm4_mode_beet.c
.input = xfrm4_beet_input,
.output = xfrm4_beet_output,
.owner = THIS_MODULE,
.encap = XFRM_MODE_BEET,
.flags = XFRM_MODE_FLAG_TUNNEL,
};
[/模式函数实现]
[IPV4下的xfrm策略]
IPV4的策略协议相关处理结构定义如下.
static struct xfrm_policy_afinfo xfrm4_policy_afinfo = { //net/ipv4/xfrm4_policy.c
.family = AF_INET,
.dst_ops = &xfrm4_dst_ops,
.dst_lookup = xfrm4_dst_lookup,
.get_saddr = xfrm4_get_saddr,
.find_bundle = __xfrm4_find_bundle,
.bundle_create = __xfrm4_bundle_create,
.decode_session = _decode_session4,
};
IPV4的路由查找, 就是普通是路由查找方法,返回0成功
static int xfrm4_dst_lookup(struct xfrm_dst **dst, struct flowi *fl)
{
return __ip_route_output_key((struct rtable**)dst, fl);
}
查找地址, 这个函数是在通道模式下, 源地址没明确指定时调用的,查找获取外部头中的源地址
static int xfrm4_get_saddr(xfrm_address_t *saddr, xfrm_address_t *daddr)
{
struct rtable *rt;
//通道的流结构定义,用于查找路由
struct flowi fl_tunnel = {
.nl_u = {
.ip4_u = {
.daddr = daddr->a4,
}, },
};
//根据目的地址找路由
if (!xfrm4_dst_lookup((struct xfrm_dst **)&rt, &fl_tunnel)) {
saddr->a4 = rt->rt_src;//将找到的路由项中的源地址作为通道模式下的外部源地址
dst_release(&rt->u.dst);
return ;
}
return -EHOSTUNREACH;
}
查找策略中的安全路由, 查找条件是流结构的定义的参数
static struct dst_entry * __xfrm4_find_bundle(struct flowi *fl, struct xfrm_policy *policy)
{
struct dst_entry *dst;
read_lock_bh(&policy->lock); //遍历策略的安全路由链表
for (dst = policy->bundles; dst; dst = dst->next) {
struct xfrm_dst *xdst = (struct xfrm_dst*)dst;
//比较网卡位置, 目的地址, 源地址, TOS值是否匹配
//同时检查该安全路由是否可用
if (xdst->u.rt.fl.oif == fl->oif && /*XXX*/
xdst->u.rt.fl.fl4_dst == fl->fl4_dst &&
xdst->u.rt.fl.fl4_src == fl->fl4_src &&
xdst->u.rt.fl.fl4_tos == fl->fl4_tos &&
xfrm_bundle_ok(policy, xdst, fl, AF_INET, )) {
dst_clone(dst);
break;
}
}
read_unlock_bh(&policy->lock);
return dst;
}
创建安全路由
static int __xfrm4_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx, struct flowi *fl, struct dst_entry **dst_p)
{
struct dst_entry *dst, *dst_prev;
struct rtable *rt0 = (struct rtable*)(*dst_p);
struct rtable *rt = rt0;
struct flowi fl_tunnel = {
.nl_u = {
.ip4_u = {
.saddr = fl->fl4_src,
.daddr = fl->fl4_dst,
.tos = fl->fl4_tos
}
}
};
int i;
int err;
int header_len = ;
int trailer_len = ; dst = dst_prev = NULL;
dst_hold(&rt->u.dst); //循环次数为策略中SA的数量, 每个SA对应一个安全路由, 一个安全路由对应对数据包的一个操作: 如压缩, ESP封装, AH封装等
for (i = ; i < nx; i++) {
//分配安全路由, 安全路由的操作结构是xfrm4_dst_ops
//因为定义了很多不同类型的路由, 每种路由都有各自的操作结构, 这样在上层可用统一的接口进行路由处理
struct dst_entry *dst1 = dst_alloc(&xfrm4_dst_ops);
struct xfrm_dst *xdst; if (unlikely(dst1 == NULL)) {
err = -ENOBUFS;
dst_release(&rt->u.dst);
goto error;
} if (!dst)//第一次循环
dst = dst1; else {//将新分配的安全路由作为前一个路由的child
dst_prev->child = dst1;
dst1->flags |= DST_NOHASH;
dst_clone(dst1);
} xdst = (struct xfrm_dst *)dst1;
//安全路由中保留相应的普通路由
xdst->route = &rt->u.dst;
xdst->genid = xfrm[i]->genid;
//新节点的next是上一个节点
dst1->next = dst_prev;
dst_prev = dst1;//现在prev指向新节点 header_len += xfrm[i]->props.header_len;
trailer_len += xfrm[i]->props.trailer_len; //如果是通道模式, 需要重新包裹外部IP头, 需要重新寻找外部IP头的路由
if (xfrm[i]->props.mode != XFRM_MODE_TRANSPORT) {
unsigned short encap_family = xfrm[i]->props.family;
switch (encap_family) {
case AF_INET:
fl_tunnel.fl4_dst = xfrm[i]->id.daddr.a4;
fl_tunnel.fl4_src = xfrm[i]->props.saddr.a4;
break;
#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
case AF_INET6:
ipv6_addr_copy(&fl_tunnel.fl6_dst, (struct in6_addr*)&xfrm[i]->id.daddr.a6);
ipv6_addr_copy(&fl_tunnel.fl6_src, (struct in6_addr*)&xfrm[i]->props.saddr.a6);
break;
#endif
default:
BUG_ON();
}
err = xfrm_dst_lookup((struct xfrm_dst **)&rt, &fl_tunnel, encap_family);
if (err)
goto error;
} else
dst_hold(&rt->u.dst);
}
//将最新节点的child指向最后的普通路由
dst_prev->child = &rt->u.dst;
//最开始分配的安全路由的path指向最后的普通路由
dst->path = &rt->u.dst; //将最开始的全路由点作为要返回的路由节点链表头
*dst_p = dst;
dst = dst_prev;//dst现在是最新分配的节点
dst_prev = *dst_p;//prev现在指向最开始分配的全节点
i = ; 为更好理解上面的操作, 用图来表示. 以上循环形成了下图水平方向的一个链表, 链表中的最左边的路由项节点dst为最老的安全路由项,
新分配的安全路由项通过child链接成链表, child通过next指向老节点, 最后一项是数据包封装完后的最后普通路由项.
垂直方向的链表是在xfrm_lookup()中形成的, 是多个策略同时起作用的情况, 一般情况下就只有一个策略, 本文中可不考虑多策略的情况. rt0.u.dst rt.u.dst rt.u.dst
^ ^ ^
route | route | route |
| child | child |
bundle +-----+ -----> +-----+ -----> +-----+ child
policy ------->| dst | <----- | dst | <----- ... | dst | -----> rt.u.dst (普通路由)
+-----+ next +-----+ next +-----+
|
|next
|
V child child
+-----+ -----> +-----+ -----> +-----+ child
| dst | <----- | dst | <----- ...| dst | -----> rt.u.dst
+-----+ next +-----+ next +-----+
|
|next
|
V
....
//对新生成的每个安全路由项填充结构参数
for (; dst_prev != &rt->u.dst; dst_prev = dst_prev->child) {
struct xfrm_dst *x = (struct xfrm_dst*)dst_prev;
x->u.rt.fl = *fl; dst_prev->xfrm = xfrm[i++];
dst_prev->dev = rt->u.dst.dev;
if (rt->u.dst.dev)
dev_hold(rt->u.dst.dev); dst_prev->obsolete = -;
dst_prev->flags |= DST_HOST;
dst_prev->lastuse = jiffies;
dst_prev->header_len = header_len;
dst_prev->nfheader_len = ;
dst_prev->trailer_len = trailer_len;
memcpy(&dst_prev->metrics, &x->route->metrics, sizeof(dst_prev->metrics)); /* Copy neighbout for reachability confirmation */
dst_prev->neighbour = neigh_clone(rt->u.dst.neighbour);
dst_prev->input = rt->u.dst.input;
//注意安全路由的输出函数是xfrm4_output,参考上面数据发送
dst_prev->output = dst_prev->xfrm->outer_mode->afinfo->output;
if (rt0->peer)
atomic_inc(&rt0->peer->refcnt); x->u.rt.peer = rt0->peer; /* Sheit... I remember I did this right. Apparently,
* it was magically lost, so this code needs audit */
x->u.rt.rt_flags = rt0->rt_flags&(RTCF_BROADCAST|RTCF_MULTICAST|RTCF_LOCAL);
x->u.rt.rt_type = rt0->rt_type;
x->u.rt.rt_src = rt0->rt_src;
x->u.rt.rt_dst = rt0->rt_dst;
x->u.rt.rt_gateway = rt0->rt_gateway;
x->u.rt.rt_spec_dst = rt0->rt_spec_dst;
x->u.rt.idev = rt0->idev;
in_dev_hold(rt0->idev);
header_len -= x->u.dst.xfrm->props.header_len;
trailer_len -= x->u.dst.xfrm->props.trailer_len;
}
//初始化路由项的MTU值
xfrm_init_pmtu(dst);
return ;
error:
if (dst) dst_free(dst);
return err;
}
解码skb数据, 填充流结构
static void _decode_session4(struct sk_buff *skb, struct flowi *fl)
{
struct iphdr *iph = ip_hdr(skb);
//xprth指向IP头后的上层协议头起始
u8 *xprth = skb_network_header(skb) + iph->ihl * ;
memset(fl, , sizeof(struct flowi)); if (!(iph->frag_off & htons(IP_MF | IP_OFFSET))) {//数据包必须不是分片包
switch (iph->protocol) {
//对UDP(17), TCP(6), SCTP(132)和DCCP(33)协议, 要提取源端口和目的端口
//头4字节是源端口和目的端口
case IPPROTO_UDP:
case IPPROTO_UDPLITE:
case IPPROTO_TCP:
case IPPROTO_SCTP:
case IPPROTO_DCCP:
//要让skb预留出IP头长度加4字节的长度, 在IP层data应该指向最外面的IP头
if (pskb_may_pull(skb, xprth + - skb->data)) {
__be16 *ports = (__be16 *)xprth;
fl->fl_ip_sport = ports[];
fl->fl_ip_dport = ports[];
}
break;
case IPPROTO_ICMP://对ICMP(1)协议要提取ICMP包的类型和编码, 2字节
if (pskb_may_pull(skb, xprth + - skb->data)) {
u8 *icmp = xprth;
fl->fl_icmp_type = icmp[];
fl->fl_icmp_code = icmp[];
}
break;
case IPPROTO_ESP://对于ESP(50)协议要提取其中的SPI值, 4字节
if (pskb_may_pull(skb, xprth + - skb->data)) {
__be32 *ehdr = (__be32 *)xprth;
fl->fl_ipsec_spi = ehdr[];
}
break;
case IPPROTO_AH://对于AH(51)协议要提取其中的SPI值, 4字节
if (pskb_may_pull(skb, xprth + - skb->data)) {
__be32 *ah_hdr = (__be32*)xprth;
fl->fl_ipsec_spi = ah_hdr[];
}
break;
case IPPROTO_COMP://对于COMP(108)协议要提取其中CPI值作为SPI值, 2字节
if (pskb_may_pull(skb, xprth + - skb->data)) {
__be16 *ipcomp_hdr = (__be16 *)xprth;
fl->fl_ipsec_spi = htonl(ntohs(ipcomp_hdr[]));
}
break;
default:
fl->fl_ipsec_spi = ;
break;
}
}
//填充协议,源地址,目的地址, TOS参数
fl->proto = iph->protocol;
fl->fl4_dst = iph->daddr;
fl->fl4_src = iph->saddr;
fl->fl4_tos = iph->tos;
}
[/IPV4下的xfrm策略] [网卡通知回调实现]
xfrm的网卡通知回调结构
static struct notifier_block xfrm_dev_notifier = {
xfrm_dev_event,
NULL, };
static int xfrm_dev_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct net_device *dev = ptr;
if (dev->nd_net != &init_net)
return NOTIFY_DONE; switch (event) {
case NETDEV_DOWN: //如果网卡down掉的话, 清除相关的所有的xfrm路由项
xfrm_flush_bundles(); 实现为xfrm_prune_bundles(stale_bundle);
}
return NOTIFY_DONE;
}
[/网卡通知回调实现]
IPSEC实现的更多相关文章
- 【转】CentOS上部署PPTP和L2TP over IPSec简要笔记
PPTP部署 安装 PPTP 需要 MPPE 和较高版本的 ppp ( > 2.4.3 ) 支持,不过 CentOS 5.0/RHEL 5 的 2.6.18 内核已经集成了 MPPE 和高版本的 ...
- ipsec IP安全策略操作 win7
//禁止 win7 连接 public static void BannedWINRunCmd() { string str = Console.ReadLine(); System.Diagnost ...
- 跨云应用部署第一步:使用IPSEC VPN连接AWS中国版和Windows Azure中国版
随着公有云的普及,越来越多的客户将关键应用迁移到云端.但是事实证明,没有哪家云服务提供商可以提供100%的SLA,无论是例行维护还是意外中断服务,对于客户的关键应用而言,都会受到不同程度的影响.此外, ...
- [转]Mac下配置基于SecurID的Cisco IPSec VPN全攻略(有图)
来自: http://www.eefocus.com/Kevin/blog/11-09/230878_53c71.html RSA的SecurID长的是这个样子滴: Mac里面,可以设置VPN, 方法 ...
- 配置L2TP IPsec VPN (CentOS 6.5)
1. 安装相关包 yum install -y ppp iptables make gcc gmp-devel xmlto bison flex libpcap-devel lsof vim-enha ...
- 在EC2上搭建L2TP over IPSec VPN服务器
注意(:wq保存文件 putty登陆用户名为ec2-user) 安装与配置: 环境介绍: OS:CentOS 6.4 x86_64 Minimal 1. 修改 /etc/sysctl.conf,新增如 ...
- 安全协议系列(五)---- IKE 与 IPSec(中)
在上一篇中,搭建好了实验环境.完整运行一次 IKE/IPSec 协议,收集相关的输出及抓包,就可以进行协议分析.分析过程中,我们将使用 IKE 进程的屏幕输出和 Wireshark 抓包,结合相关 R ...
- 安全协议系列(五)---- IKE 与 IPSec(上)
IKE/IPSec 属于网络层安全协议,保护 IP 及上层的协议安全.自上个世纪末面世以来,关于这两个协议的研究.应用,已经非常成熟.协议本身,也在不断地进化.仅以 IKE 为例,其对应的 RFC 编 ...
- AIX上通过IPSEC进行IP包过滤
AIX上的IPSEC 在AIX可以通过以下步骤打开IP Security smitty ipsec4 --> Start/Stop IP Security --> Start IP Sec ...
- Ubuntu 15.10搭建IPSec L2TP服务器
以下步骤完全使用于Ubuntu 14.04版本 首先安装以下所需包 sudo apt-get install openswan xl2tpd ppp lsof!注意!Ubuntu 15.10会提示无法 ...
随机推荐
- 再探Linux动态链接 -- 关于动态库的基础知识
在近一段时间里,由于多次参与相关专业软件Linux运行环境建设,深感有必要将这些知识理一理,供往后参考. 编译时和运行时 纵观程序编译整个过程,细分可分为编译(Compiling,指的是语言到平台 ...
- Java项目经验
Java项目经验 转自CSDN. Java就是用来做项目的!Java的主要应用领域就是企业级的项目开发!要想从事企业级的项目开发,你必须掌握如下要点:1.掌握项目开发的基本步骤2.具备极强的面向对象的 ...
- Install GTK in Ubuntu
reference: http://www.cnblogs.com/niocai/archive/2011/07/15/2107472.html 一.安装 1.安装gcc/g++/gdb/make 等 ...
- iOS 隔离导航控制器
题外话:最近这两个月一直很闲,项目上基本没有啥大的需求.对于程序员来说,如果没有需求其实是一件很难受的事情,之前好多次在项目中没事找事,该优化的优化,该整理的整理.可能好多程序员都遇到过与我类似的情况 ...
- MFC: Create Directory
Original link: How to check if Directory already Exists in MFC(VC++)? MSDN Links: CreateDirectory fu ...
- 安装kali之后
更新源(以下设置均基于这些源) #vi /etc/apt/sources.list 把这些源加进去 ############################ debian wheezy ####### ...
- iscsiadm用法简介
已知192.168.14.112节点,存在目标器 iqn.2015.06.cn.hrbyg.www.ygcs.c0a802b8:wzg,未设置CHAP,存在目标器 iqn.2015.06.cn.hrb ...
- mysql中的sql时间格式转换
from_unixtime(unix_timestamp, format) 把时间戳转化为指定的格式 as: select from_unixtime(addTime, '%Y-%m-%d %h:%i ...
- 、Dll文件的编写 调用 说明
1>新建Dll文件TestLib.dll 新建Unit文件U_TestFunc U_TestFunc代码如下: unit U_TestFunc; interface uses //尽可能的少us ...
- 微信支付JS API使用心得
微信的接口真的很坑爹,只返回成功或失败,从来不会告诉你为什么失败.这个微信支付的js接口也是调了一个下午才成功,期间踩了不少坑,在这里总结一下,而且把支付接口封装成了一个js文件,这样以后调用就很方便 ...