##应用层通过pfkey,dump sa的步骤:
- 创建pfkeyl类型的一个socket,
- 通过sendmsg发送一个dump sa 的请求
- 然后通过while循环调用recvmsg
##kernel里对应的处理:
内核遍历存放SA的list。将SA的信息转化到一个或多个skb, 将这些skb添加到相应pfkey socket的sk_receive_queue链表里。
每次添加Skb时候会检查是否超出socket允许接受的最大receive字节数。
如果SA的数量比较多,则无法一次将所有的SA信息转化到skb里。
当SA的个数比较少时,一次性输出所有的SA是可以的, 但是当SA的个数非常大时, 一次性输出(遍历) 所有的sa是低效的(消耗大量内存),而且还要受到输出缓冲区buff的限制。
因此xfrm walk 采用分段遍历的方式,每次只顺着net->xfrm.state_all
链表往后推进一步, 这样一次一步逐渐分批次把真个list的SA全部遍历完毕。 而struct xfrm_state_walk
这个结构体就是用来记录当前这次遍历的位置。
具体实现就是将xfrm_state_walk
这个结构体插入到net->xfrm.state_all
这个hlisst上, 并位于最近一次遍历到的SA的后边。
在下一次继续遍历的时候,则从xfrm_state_walk这个结构体继续往后走, 如果遍历完全部SA,则把xfrm_state_walk
这个结构体从net->xfrm.state_all
上删除, 否则移动xfrm_state_walk
这个结构体到本次遍历的最有一个SA的后面,并等待下次继续遍历。 如此反复,可以遍历完所有的SA, 最后删除xfrmwalk。
这是netlink callback机制在内核里的一个具体的实现。还有很多netlink callback的例子。
xfrm state采用的是个单链表。有个callback机制使用的是hlist.
无论hlist还是list,在dump时,是加锁的, 可以保证数据的一致性。
但是当数据量大的时候,callback机制无法保证,两次dump的过程中会有增删的操作。
因此应用层程序,可以有两种方式去解决这个问题:
- 在dump的同时监听相应的增删消息。
- 判断netlink消息里是否有标志。如果有则重新dump。
calltrace
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| == err = pfkey_process(sk, skb, hdr); == == err = pfkey_funcs[hdr->sadb_msg_type](sk, skb, hdr, ext_hdrs); == == == pfkey_dump == == == == pfkey_do_dump == == == == == pfk->dump.msg_pid = hdr->sadb_msg_pid; == == == == == pfk->dump.dump = pfkey_dump_sa; == == == == == pfk->dump.done = pfkey_dump_sa_done; == == == == == xfrm_state_walk_init(&pfk->dump.u.state, proto); == == == return pfkey_do_dump(pfk);
##Data structure
1. 每个net都有一个netns_xfrm结构体,里面保存了xfrm的信息。
```c 35 struct net { ... 87 #ifdef CONFIG_XFRM 88 struct netns_xfrm xfrm; 89 #endif
|
- netns_xfrm下有一个state_all的list(双向循环)挂这改net下的所有的xfrm state。
1 2
| 17 struct netns_xfrm { 18 struct list_head state_all;
|
- 每个xfrm state有一个 xfrm_state_walk类型的km, xfrm_state_walk里有个链all 最终每个xfrm state通过 km.all 挂在 net->xfrm.state_all这条链上。
1 2 3 4
| 129 struct xfrm_state { .... 149 /* Key manager bits */ 150 struct xfrm_state_walk km;
|
1 2 3 4 5 6 7 8 9
| 118 struct xfrm_state_walk { 119 struct list_head all; 120 u8 state; 121 union { 122 u8 dying; 123 u8 proto; 124 }; 125 u32 seq; 126 };
|
因此遍历net->xfrm.state_all可以得到改net下的全部SA。
##functions
pfkey_do_dump
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 279 static int pfkey_do_dump(struct pfkey_sock *pfk) 280 { 281 struct sadb_msg *hdr; 282 int rc; 283 284 rc = pfk->dump.dump(pfk); 285 if (rc == -ENOBUFS) 286 return 0; 287 288 if (pfk->dump.skb) { 289 if (!pfkey_can_dump(&pfk->sk)) 290 return 0; 291 292 hdr = (struct sadb_msg *) pfk->dump.skb->data; 293 hdr->sadb_msg_seq = 0; 294 hdr->sadb_msg_errno = rc; 295 pfkey_broadcast(pfk->dump.skb, GFP_ATOMIC, BROADCAST_ONE, 296 &pfk->sk, sock_net(&pfk->sk)); 297 pfk->dump.skb = NULL; 298 } 299 300 pfkey_terminate_dump(pfk); 301 return rc; 302 }
|
相当于pfkey_dump_sa
1 2 3 4 5
| == 针对SA 每个sa调用 == == dump_sa == == == pfkey_broadcast_one == == == == skb_queue_tail(&sk->sk_receive_queue, *skb2); xfrm_state_walk(net, &pfk->dump.u.state, dump_sa, (void *) pfk);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 1554 int xfrm_state_walk(struct net *net, struct xfrm_state_walk *walk, 1555 int (*func)(struct xfrm_state *, int, void*), 1556 void *data) 1557 { 1558 struct xfrm_state *state; 1559 struct xfrm_state_walk *x; 1560 int err = 0; 1561 1562 if (walk->seq != 0 && list_empty(&walk->all)) 1563 return 0; 1564 1565 spin_lock_bh(&xfrm_state_lock); 1566 if (list_empty(&walk->all)) <=== 根据walk->all 判定是全新的遍历,还是接着上次的位置往后遍历 1567 x = list_first_entry(&net->xfrm.state_all, struct xfrm_state_walk, all); 1568 else 1569 x = list_entry(&walk->all, struct xfrm_state_walk, all); 1570 list_for_each_entry_from(x, &net->xfrm.state_all, all) { 1571 if (x->state == XFRM_STATE_DEAD) 1572 continue; 1573 state = container_of(x, struct xfrm_state, km); 1574 if (!xfrm_id_proto_match(state->id.proto, walk->proto)) 1575 continue; 1576 err = func(state, walk->seq, data); <=== 根据fun的返回决定是继续遍历下一个SA还是停止 1577 if (err) { 1578 list_move_tail(&walk->all, &x->all); 1579 goto out; 1580 } 1581 walk->seq++; 1582 } 1583 if (walk->seq == 0) { 1584 err = -ENOENT; 1585 goto out; 1586 } 1587 list_del_init(&walk->all); 1588 out: 1589 spin_unlock_bh(&xfrm_state_lock); 1590 return err; 1591 } 1592 EXPORT_SYMBOL(xfrm_state_walk);
|
- recvmsg
跟普通socket的recvmsg没有太大的区别。
调用skb_recv_datagram从socket的sk_receive_queue队列里摘一个skb下来, 并将skb的数据copy到用户空间。
检查SA是否已经全部dump了。 如果还有没被dump的sa,并且socket的有比较宽裕的空间,则再次激活SA dump。
宽裕的剩余空间的检查,保证了dump的效率, 避免激活一次SA dump只能dump很少的SA。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| 3609 static int pfkey_recvmsg(struct kiocb *kiocb, 3610 struct socket *sock, struct msghdr *msg, size_t len, 3611 int flags) 3612 { 3613 struct sock *sk = sock->sk; 3614 struct pfkey_sock *pfk = pfkey_sk(sk); 3615 struct sk_buff *skb; 3616 int copied, err; 3617 3618 err = -EINVAL; 3619 if (flags & ~(MSG_PEEK|MSG_DONTWAIT|MSG_TRUNC|MSG_CMSG_COMPAT)) 3620 goto out; 3621 3622 msg->msg_namelen = 0; 3623 skb = skb_recv_datagram(sk, flags, flags & MSG_DONTWAIT, &err); 3624 if (skb == NULL) 3625 goto out; 3626 3627 copied = skb->len; 3628 if (copied > len) { 3629 msg->msg_flags |= MSG_TRUNC; 3630 copied = len; 3631 } 3632 3633 skb_reset_transport_header(skb); 3634 err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); 3635 if (err) 3636 goto out_free; 3637 3638 sock_recv_ts_and_drops(msg, sk, skb); 3639 3640 err = (flags & MSG_TRUNC) ? skb->len : copied; 3641 3642 if (pfk->dump.dump != NULL && 3643 3 * atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf) 3644 pfkey_do_dump(pfk); 3645 3646 out_free: 3647 skb_free_datagram(sk, skb); 3648 out: 3649 return err; 3650 }
|