Linux内核中PF_KEY协议族的兑现(1)
Linux内核中PF_KEY协议族的实现(1)
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
1. 前言 在Linux2.6内核中自带了PF_KEY协议族的实现,这样就不用象2.4那样打补丁来实现了。内核中PF_KEY实现要完成的功能是实现维护内核的安全联盟(SA)和安全策略(SP)数据库, 以及和用户空间的接口。 以下内核代码版本为2.6.19.2, PF_KEY相关代码在net/key/目录下,定义了内核中PF_KEY与用户空间的接口,这个接口是RFC定义的,因此各种实现都基本类似;但具体关于SA和SP的内部的实现和管理则是与实现相关的,各种实现各自不同,在linux内核是使用xfrm库来实现的,代码在net/xfrm/目录下定义。 2. 数据结构 关于SA和SP的数据结构已经在RFC2367中定义, 头文件为include/linux/pfkeyv2.h, 这些是用户空间和内核空间共享的,只是作为接口的数据结构;而内核中具体使用的数据结构为xfrm定义的结构,在include/net/xfrm.h中定义。 2.1 PF_KEY类型的sock struct pfkey_sock { /* struct sock must be the first member of struct pfkey_sock */ struct sock sk; // 比普通sock添加两个参数 // 是否进行登记 int registered; // 是否是混杂模式 int promisc; }; 2.2 状态(SA) xfrm状态用来描述SA在内核中的具体实现: struct xfrm_state { /* Note: bydst is re-used during gc */ // 每个状态结构挂接到三个HASH链表中 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 struct xfrm_selector sel; // 状态选择子 u32 genid; /* Key manger bits */ struct { u8 state; u8 dying; u32 seq; } km; /* Parameters of this state. */ struct { u32 reqid; u8 mode; u8 replay_window; u8 aalgo, ealgo, calgo; 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; // hash算法 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; /* 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; /* Last used time */ u64 lastused; /* Reference to data common to all the instances of this * transformer. */ struct xfrm_type *type; struct xfrm_mode *mode; /* Security context */ struct xfrm_sec_ctx *security; /* Private data of this transformer, format is opaque, * interpreted by xfrm_type methods. */ void *data; }; 2.3 策略(SP) 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; u8 type; 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 action; __u8 flags; __u8 dead; __u8 xfrm_nr; struct xfrm_sec_ctx *security; struct xfrm_tmpl xfrm_vec[XFRM_MAX_DEPTH]; }; 2.4 事件 struct km_event { union { u32 hard; u32 proto; u32 byid; u32 aevent; u32 type; } data; u32 seq; u32 pid; u32 event; }; 3. 初始化 /* net/key/af_key.c */ static int __init ipsec_pfkey_init(void) { // 登记key_proto结构, 该结构定义如下: // static struct proto key_proto = { // .name = "KEY", // .owner = THIS_MODULE, // .obj_size = sizeof(struct pfkey_sock), //}; // 最后一个参数为0, 表示不进行slab的分配, 只是简单的将key_proto结构 // 挂接到系统的网络协议链表中,这个结构最主要是告知了pfkey sock结构的大小 int err = proto_register(&key_proto, 0); if (err != 0) goto out; // 登记pfkey协议族的的操作结构 err = sock_register(&pfkey_family_ops); if (err != 0) goto out_unregister_key_proto; #ifdef CONFIG_PROC_FS err = -ENOMEM; // 建立只读的pfkey的PROC文件: /proc/net/pfkey if (create_proc_read_entry("net/pfkey", 0, NULL, pfkey_read_proc, NULL) == NULL) goto out_sock_unregister; #endif // 登记通知(notify)处理pfkeyv2_mgr err = xfrm_register_km(&pfkeyv2_mgr); if (err != 0) goto out_remove_proc_entry; out: return err; out_remove_proc_entry: #ifdef CONFIG_PROC_FS remove_proc_entry("net/pfkey", NULL); out_sock_unregister: #endif sock_unregister(PF_KEY); out_unregister_key_proto: proto_unregister(&key_proto); goto out; } 4. pfkey套接口操作 4.1 建立套接口 /* net/key/af_key.c */ // pfkey协议族操作, 在用户程序使用socket打开pfkey类型的socket时调用, // 相应的create函数在__sock_create(net/socket.c)函数中调用: static struct net_proto_family pfkey_family_ops = { .family = PF_KEY, .create = pfkey_create, .owner = THIS_MODULE, }; // 在用户空间每次打开pfkey socket时都会调用此函数: static int pfkey_create(struct socket *sock, int protocol) { struct sock *sk; int err; // 建立PFKEY的socket必须有ROOT权限 if (!capable(CAP_NET_ADMIN)) return -EPERM; // socket类型必须是RAW, 协议为PF_KEY_V2 if (sock->type != SOCK_RAW) return -ESOCKTNOSUPPORT; if (protocol != PF_KEY_V2) return -EPROTONOSUPPORT; err = -ENOMEM; // 分配sock结构, 并清零 sk = sk_alloc(PF_KEY, GFP_KERNEL, &key_proto, 1); if (sk == NULL) goto out; // PFKEY类型socket的操作 sock->ops = &pfkey_ops; // 初始化socket参数 sock_init_data(sock, sk); // 初始化sock的族类型和释放函数 sk->sk_family = PF_KEY; sk->sk_destruct = pfkey_sock_destruct; // 增加使用数 atomic_inc(&pfkey_socks_nr); // 将sock挂接到系统的sock链表 pfkey_insert(sk); return 0; out: return err; } 4.2 PF_KEY套接口操作 static const struct proto_ops pfkey_ops = { .family = PF_KEY, .owner = THIS_MODULE, /* Operations that make no sense on pfkey sockets. */ .bind = sock_no_bind, .connect = sock_no_connect, .socketpair = sock_no_socketpair, .accept = sock_no_accept, .getname = sock_no_getname, .ioctl = sock_no_ioctl, .listen = sock_no_listen, .shutdown = sock_no_shutdown, .setsockopt = sock_no_setsockopt, .getsockopt = sock_no_getsockopt, .mmap = sock_no_mmap, .sendpage = sock_no_sendpage, /* Now the operations that really occur. */ .release = pfkey_release, .poll = datagram_poll, .sendmsg = pfkey_sendmsg, .recvmsg = pfkey_recvmsg, }; PF_KEY类型的sock中大多数操作都没有定义, 这是因为PF_KEY的数据都是本机内的内核空间于用户空间的交换, 因此实际和网络相关的操作都不用定义, 所谓发送和介绍数据也只是内核与用户空间之间的通信。 4.2.1 释放套接口 static int pfkey_release(struct socket *sock) { // 从socket到sock结构转换 struct sock *sk = sock->sk; if (!sk) return 0; // 将sock从系统的sock链表断开 pfkey_remove(sk); // 设置sock状态为DEAD, 清空sock中的socket和sleep指针 sock_orphan(sk); sock->sk = NULL; // 清除当前数据队列 skb_queue_purge(&sk->sk_write_queue); // 释放sock sock_put(sk); return 0; } 4.2.2 描述符选择 使用的是标准的数据报选择函数: datagram_poll 4.2.3 发送数据 实际是将数据从内核空间发送给用户空间的程序: static int pfkey_sendmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, size_t len) { struct sock *sk = sock->sk; struct sk_buff *skb = NULL; struct sadb_msg *hdr = NULL; int err; err = -EOPNOTSUPP; // PF_KEY不支持MSG_OOB标志 if (msg->msg_flags & MSG_OOB) goto out; err = -EMSGSIZE; // 一次发送的数据长度不能太大 if ((unsigned)len > sk->sk_sndbuf - 32) goto out; err = -ENOBUFS; // 获取一个空闲的skbuff skb = alloc_skb(len, GFP_KERNEL); if (skb == NULL) goto out; err = -EFAULT; // 从缓冲区中拷贝数据到skbuff中 if (memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len)) goto out; // 获取SADB数据头的指针 hdr = pfkey_get_base_msg(skb, &err); if (!hdr) goto out; mutex_lock(&xfrm_cfg_mutex); // 处理PFKEY数据的发送 err = pfkey_process(sk, skb, hdr); mutex_unlock(&xfrm_cfg_mutex); out: if (err && hdr && pfkey_error(hdr, err, sk) == 0) err = 0; if (skb) kfree_skb(skb); return err ? : len; } static int pfkey_process(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr) { void *ext_hdrs[SADB_EXT_MAX]; int err; // 向混杂模式的sock发送SA消息 pfkey_broadcast(skb_clone(skb, GFP_KERNEL), GFP_KERNEL, BROADCAST_PROMISC_ONLY, NULL); memset(ext_hdrs, 0, sizeof(ext_hdrs)); // 解析SADB数据头中的消息类型 err = parse_exthdrs(skb, hdr, ext_hdrs); if (!err) { err = -EOPNOTSUPP; // 根据消息类型调用相关的处理函数进行处理 if (pfkey_funcs[hdr->sadb_msg_type]) err = pfkey_funcs[hdr->sadb_msg_type](sk, skb, hdr, ext_hdrs); } return err; } 4.2.4 接收数据 实际是将数据从用户空间发送给内核空间: static int pfkey_recvmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, size_t len, int flags) { struct sock *sk = sock->sk; struct sk_buff *skb; int copied, err; err = -EINVAL; // 只支持4类标志 if (flags & ~(MSG_PEEK|MSG_DONTWAIT|MSG_TRUNC|MSG_CMSG_COMPAT)) goto out; msg->msg_namelen = 0; // 接收数据包 skb = skb_recv_datagram(sk, flags, flags & MSG_DONTWAIT, &err); if (skb == NULL) goto out; copied = skb->len; // 接收到的数据超过了接收缓冲区长度, 设置截断标志 if (copied > len) { msg->msg_flags |= MSG_TRUNC; copied = len; } skb->h.raw = skb->data; // 将数据包中信息拷贝到接收缓冲区 err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); if (err) goto out_free; // 设置时间戳 sock_recv_timestamp(msg, sk, skb); err = (flags & MSG_TRUNC) ? skb->len : copied; out_free: skb_free_datagram(sk, skb); out: return err; } 4.2.5 pfkey广播 pfkey广播是将内核到用户空间的回应信息, 所有打开了PF_KEY类型socket的用户空间程序都可以收到, 所以用户空间程序在收到消息的时候要判断是否该消息是给自己的, 不是就忽略掉,这和netlink的广播比较类似。 /* Send SKB to all pfkey sockets matching selected criteria. */ #define BROADCAST_ALL 0 #define BROADCAST_ONE 1 #define BROADCAST_REGISTERED 2 #define BROADCAST_PROMISC_ONLY 4 static int pfkey_broadcast(struct sk_buff *skb, gfp_t allocation, int broadcast_flags, struct sock *one_sk) { struct sock *sk; struct hlist_node *node; struct sk_buff *skb2 = NULL; int err = -ESRCH; /* XXX Do we need something like netlink_overrun? I think * XXX PF_KEY socket apps will not mind current behavior. */ if (!skb) return -ENOMEM; pfkey_lock_table(); // 遍历所有的pfkey sock表, sk_for_each(sk, node, &pfkey_table) { // 获取pfkey sock用于发送消息 struct pfkey_sock *pfk = pfkey_sk(sk); int err2; /* Yes, it means that if you are meant to receive this * pfkey message you receive it twice as promiscuous * socket. */ // 该pfkey sock是混杂模式, 先发送一次, 由于后面还会广播发送, 所以设置了混杂模式的pfkey // sock一般情况下会收到两次 if (pfk->promisc) pfkey_broadcast_one(skb, &skb2, allocation, sk); /* the exact target will be processed later */ // 指定了one_sk的话这个one_sk对应的用户程序将最后才收到包, 现在在循环中不发 // 以后才发 if (sk == one_sk) continue; // 如果不是广播给所有的进程, #define BROADCAST_ALL 0 if (broadcast_flags != BROADCAST_ALL) { // 如果只广播给pfkey混杂模式的进程, 跳过, 继续循环 if (broadcast_flags & BROADCAST_PROMISC_ONLY) continue; // 如果只广播给登记的进程而该sock没登记, 跳过, 继续循环 if ((broadcast_flags & BROADCAST_REGISTERED) && !pfk->registered) continue; // 只广播给一个, 和one_sk配合使用, 这样消息就只会发送给one_sk和所有混杂模式的pfkey sock if (broadcast_flags & BROADCAST_ONE) continue; } // 发送给该pfkey sock err2 = pfkey_broadcast_one(skb, &skb2, allocation, sk); /* Error is cleare after succecful sending to at least one * registered KM */ if ((broadcast_flags & BROADCAST_REGISTERED) && err) err = err2; } pfkey_unlock_table(); // 如果指定one_sk, 再向该pfkey sock发送, 该sock是最后一个收到消息的 if (one_sk != NULL) err = pfkey_broadcast_one(skb, &skb2, allocation, one_sk); // 释放skb if (skb2) kfree_skb(skb2); kfree_skb(skb); return err; } // 发送一个包 static int pfkey_broadcast_one(struct sk_buff *skb, struct sk_buff **skb2, gfp_t allocation, struct sock *sk) { int err = -ENOBUFS; sock_hold(sk); if (*skb2 == NULL) { // skb2是skb的一个克隆包 if (atomic_read(&skb->users) != 1) { *skb2 = skb_clone(skb, allocation); } else { *skb2 = skb; // 因为发送会减少skb的使用计数 atomic_inc(&skb->users); } } if (*skb2 != NULL) { // 实际发送的时skb2 if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf) { skb_orphan(*skb2); skb_set_owner_r(*skb2, sk); skb_queue_tail(&sk->sk_receive_queue, *skb2); sk->sk_data_ready(sk, (*skb2)->len); *skb2 = NULL; err = 0; } } sock_put(sk); return err; } ...... 待续 ...... 发表于: 2007-05-07,修改于: 2007-05-07 20:10,已浏览3512次,有评论2条 推荐 投诉 网友: 本站网友 时间:2007-05-30 10:54:31 IP地址:58.211.149.★ 好象pfkey_sendmsg和pfkey_recvmsg方向搞反了吧? pfkey_sendmsg对应socket write操作,是从用户空间往内核空间发数据(写); pfkey_recvmsg对应socket read操作,是从内核往用户发消息,用户来读. BTW: 哥们,代码只有联系起来看,才能看出它究竟是干什么的.否则只不过是一句句的C语言,有什么用呢? 网友: yfydz 时间:2007-05-30 13:43:09 IP地址:218.247.216.★ 恩,是写反了 代码再怎么联系起来看,不也得一句句看?我已经划开成块了,不想看太细节可以只看大概流程就可以