Linux對ipsec的支援

daa20發表於2019-06-17

轉: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

相關文章