PF_KEY协议是IPSec的重要组成部分。密钥管理进程利用PF_KEY与内核的SADB进行通信,实现SA(Security Association,安全联盟)和SP(Security Policy,安全策略)的管理。本文将从PF_KEY协议构造和PF_KEY相关系统调用等方面描述OpenBSD内核中的PF_KEY实现。
利用IPSec技术可在IP层实现对数据包进行保护,降低了互联网通信的风险。IPsec的安全服务是由通信双方建立的安全联盟(SA)来提供的。IPsec系统在处理输入/输出IP 流时必须参考安全策略库(SPD),并根据从SPD中提取的策略对IP流进行不同的处理,例如拒绝、绕过和进行IPsec保护。SA和SPD的管理是利用PF_key API来实现用户进程和内核之间的通信,可以通过手工进行,也可以通过IKE来进行动态协商。
PF_KEY是用户进程操作内核中的SADB(Security Associations Database)和SPD的编程接口。下面将从协议族构造、系统调用和PF_KEY socket实现三部分加以描述。
协议族构造
Net/3组把协议关联到一个域中,并且用一个协议族常量来标识每个域。在OpenBSD定义的域包含以下的四个,分别是路由协议族、IP协议族、Unix协议族和PF_KEY协议族。同时定义了全局指针型变量domains,由它将协议族结构链接在一起。
初始化完成后,内核构建了图1所示的数据结构。pfkey_domain定义的协议族为PF_KEY,Socket地址为PF_KEY。
在PF_KEY协议初始化过程中,系统还定义了指针数组,用于指向含有Socket操作函数的不同版本pfkey_version结构。pfkey_version结构定义在sys/net/pfkeyv2.h文件中。
" 图1 初始化后的domain链表和protosw数组
目前OpenBSD实现的PF_KEY版本是2.0,该指针数组结构定义如下:
struct pfkey_version
{ int protocol;
int (*create)(struct socket socket);
int (*release)(struct socket *socket);
int (*send)(struct socket *socket, void *message, int len);
} = { PFKEYV2_PROTOCOL, &pfkeyv2_create, &pfkeyv2_release, &pfkeyv2_send };
PF_KEY socket实现
PF_KEY socket系统调用
当一个应用调用Socket时,进程用系统调用机制将三个独立的整数传给内核。syscall将参数复制到32bit值的数组中,并将数组指针作为第二个参数传给Socket的内核版。内核版的Socket将第二个参数“uap”作为指向sys_socket_args结构的指针。图2显示了上述过程。
" 图2 用户空间Socket参数到内核空间转换图
当进程调用socket(PF_KEY,SOCK_RAW, PF_KEY_V2)时,内核sys_socket()向PF_KEY协议中pfkey_protosw(见图1)定义的pfkey_usrreq()发送PRU_ATTACH,调用如下:
error=(*prp->pr_usrreq)(so,PRU_ATTACH,NULL,(struct mbuf *)(long)proto,NULL);
Pfkey_usrreq()处理PRU_ATTACH请求,创建协议控制块。
PF_KEY close()系统调用
内核中sys_close()对应用户空间的close()系统调用,用来关闭各类描述符。当fd是引用对象的最后描述符时,与对象有关的close函数被调用如下:
error=(*fp->f_ops->fo_close)(fp, p);
Socket的fp->f_ops->fo_close是soo_close()函数。soo_close()是soclose()函数的封装器。soclose()取消Socket上相关连接并释放不需要的数据结构,发送PRU_DETACH请求断开Socket与PF_KEY协议的联系,最后调用sofree()释放PF_KEY socket。
error2=(*so->so_proto->pr_usrreq)(so, PRU_DETACH, NULL,NULL, NULL);
Pfkey_usrreq()响应soclose()的PRU_DETACH请求,释放该Socket协议控制块,并将该Socket从pfkeyv2_sockets链表中删除。






