Linux对ipsec的支持

转:https://blog.csdn.net/cxw06023273/article/details/83809492

  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/目录下定义。

  1. 数据结构

关于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;
    };
  1. 初始化/* 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;
    }
  2. 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;
    }… 待续 …看着就觉得很奇怪,果然写反了。。 socket是给用户空间用的,用户的send操作是写入数据到内核,rcv操作也是如此啊。
    恩,是写反了 代码再怎么联系起来看,不也得一句句看?我已经划开成块了,不想看太细节可以只看大概流程就可以
    好象pfkey_sendmsg和pfkey_recvmsg方向搞反了吧? pfkey_sendmsg对应socket write操作,是从用户空间往内核空间发数据(写); pfkey_recvmsg对应socket read操作,是从内核往用户发消息,用户来读. BTW: 哥们,代码只有联系起来看,才能看出它究竟是干什么的.否则只不过是一句句的C语言,有什么用呢?
    http://blog.chinaunix.net/uid-127037-id-2919547.html
发布了207 篇原创文章 · 获赞 77 · 访问量 29万+

猜你喜欢

转载自blog.csdn.net/daa20/article/details/92545634
今日推荐