PF_KEYv2接口

隧道XFRM 同时被 2 个专栏收录
32 篇文章 2 订阅
14 篇文章 0 订阅

内核的PF_KEY类型套接口,提供了IPSec的应用层管理程序与内核的交互接口。

套接口初始化

sock_register函数注册PF_KEY套接口pfkey_family_ops,另外函数pfkeyv2_mgr注册一个KM(key manager)秘钥管理器pfkeyv2_mgr。

static int __init ipsec_pfkey_init(void)
{
    err = sock_register(&pfkey_family_ops);
    err = xfrm_register_km(&pfkeyv2_mgr);
}

对于套接口PF_KEY而言,可用的系统调用仅有以下几个poll/sendmsg/recvmsg,当然还有套接口创建调用pfkey_create。

static const struct proto_ops pfkey_ops = {
    .family     =   PF_KEY,
    /* Now the operations that really occur. */
    .release    =   pfkey_release,
    .poll       =   datagram_poll,
    .sendmsg    =   pfkey_sendmsg,
    .recvmsg    =   pfkey_recvmsg,
};
static const struct net_proto_family pfkey_family_ops = {
    .family =   PF_KEY,
    .create =   pfkey_create,
    .owner  =   THIS_MODULE,
};

PF_KEY的秘钥管理器pfkeyv2_mgr主要用于向应用层管理程序发送相关通知。

static struct xfrm_mgr pfkeyv2_mgr =
{
    .notify     = pfkey_send_notify,
    .acquire    = pfkey_send_acquire,
    .compile_policy = pfkey_compile_policy,
    .new_mapping    = pfkey_send_new_mapping,
    .notify_policy  = pfkey_send_policy_notify,
    .migrate    = pfkey_send_migrate,
    .is_alive   = pfkey_is_alive,
};

PF_KEY发送

函数pfkey_sendmsg负责接收应用层发送的数据。其处理逻辑简单,首先函数pfkey_get_base_msg做一些简单的合法性检查,例如数据长度必须大于sadb_msg结构体长度;sadb消息的版本必须为PF_KEY_V2;sadb_msg消息的保留字段必须为零;消息类型SADB_RESERVED(0)和SADB_MAX(24)之间。如果所有检查都通过,将SKB中的数据转换为sadb_msg结构返回。

static int pfkey_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
{
    struct sock *sk = sock->sk;
    struct sk_buff *skb = NULL;
    struct sadb_msg *hdr = NULL;

    hdr = pfkey_get_base_msg(skb, &err);
    if (!hdr)
        goto out;
    err = pfkey_process(sk, skb, hdr);
}

消息处理函数pfkey_process如下。首先,将接收到的sadb消息尝试在当前网络命名空间中的所有PF_KEY套接口上广播,广播对象BROADCAST_PROMISC_ONLY,所以只向设置了混杂模式的套接口发送此消息。之后将看到套接口通告sadb的消息类型SADB_X_PROMISC控制混杂模式。最后,根据消息类型调用相应的处理函数,两者之间的对应关系保存在全局变量pfkey_funcs中。
 

static int pfkey_process(struct sock *sk, struct sk_buff *skb, const struct sadb_msg *hdr)
{
    void *ext_hdrs[SADB_EXT_MAX];

    pfkey_broadcast(skb_clone(skb, GFP_KERNEL), GFP_KERNEL, BROADCAST_PROMISC_ONLY, NULL, sock_net(sk));

    memset(ext_hdrs, 0, sizeof(ext_hdrs));
    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);
    }
}

pfkey_funcs结构如下。

static const pfkey_handler pfkey_funcs[SADB_MAX + 1] = {
    [SADB_RESERVED]     = pfkey_reserved,
    [SADB_GETSPI]       = pfkey_getspi,
    [SADB_UPDATE]       = pfkey_add,
    [SADB_ADD]      = pfkey_add,
    [SADB_DELETE]       = pfkey_delete,
    [SADB_GET]      = pfkey_get,
    [SADB_ACQUIRE]      = pfkey_acquire,
    [SADB_REGISTER]     = pfkey_register,
    [SADB_EXPIRE]       = NULL,
    [SADB_FLUSH]        = pfkey_flush,
    [SADB_DUMP]     = pfkey_dump,
    [SADB_X_PROMISC]    = pfkey_promisc,
    [SADB_X_PCHANGE]    = NULL,
    [SADB_X_SPDUPDATE]  = pfkey_spdadd,
    [SADB_X_SPDADD]     = pfkey_spdadd,
    [SADB_X_SPDDELETE]  = pfkey_spddelete,
    [SADB_X_SPDGET]     = pfkey_spdget,
    [SADB_X_SPDACQUIRE] = NULL,
    [SADB_X_SPDDUMP]    = pfkey_spddump,
    [SADB_X_SPDFLUSH]   = pfkey_spdflush,
    [SADB_X_SPDSETIDX]  = pfkey_spdadd,
    [SADB_X_SPDDELETE2] = pfkey_spdget,
    [SADB_X_MIGRATE]    = pfkey_migrate,
};

在应用层数据sadb_msg之后是可选的扩展字段,parse_exthdrs函数解析扩展字段和进行必要的合法性验证。扩展字段头部通用字段如下:

    |----------------|-----------------|
    |  sadb_ext_len  |  sadb_ext_type  |
    |----------------|-----------------|
    |     16bit      |      16bit      |

扩展长度字段sadb_ext_len单位为8个字节(sizeof(uint64_t))。扩展类型字段sadb_ext_type的合法值在SADB_EXT_RESERVED(0)和SADB_EXT_MAX(26)之间,但是最大值SADB_EXT_MAX为合法值SADB_X_EXT_FILTER(26)。

static int parse_exthdrs(struct sk_buff *skb, const struct sadb_msg *hdr, void **ext_hdrs)
{
    const char *p = (char *) hdr;
    int len = skb->len;
    len -= sizeof(*hdr);
    p += sizeof(*hdr);
    while (len > 0) {
        const struct sadb_ext *ehdr = (const struct sadb_ext *) p;
        ext_len  = ehdr->sadb_ext_len;
        ext_len *= sizeof(uint64_t);
        ext_type = ehdr->sadb_ext_type;
        if (ext_len < sizeof(uint64_t) || ext_len > len || ext_type == SADB_EXT_RESERVED) return -EINVAL;
        if (ext_type <= SADB_EXT_MAX) {
            int min = (int) sadb_ext_min_len[ext_type];
            if (ext_len < min) return -EINVAL;
            if (ext_hdrs[ext_type-1] != NULL) return -EINVAL;
            if (ext_type == SADB_EXT_ADDRESS_SRC || ext_type == SADB_EXT_ADDRESS_DST ||
                ext_type == SADB_EXT_ADDRESS_PROXY || ext_type == SADB_X_EXT_NAT_T_OA) {
                if (verify_address_len(p)) return -EINVAL;
            }
            if (ext_type == SADB_X_EXT_SEC_CTX) {
                if (verify_sec_ctx_len(p)) return -EINVAL;
            }
            ext_hdrs[ext_type-1] = (void *) p;
        }
        p   += ext_len;
        len -= ext_len;
}

内核使用数组sadb_ext_min_len验证数据中每一种扩展类型的长度值是否符合定义其定义的最小长度值。对于扩展类型为SADB_EXT_ADDRESS_SRC/SADB_EXT_ADDRESS_DST/SADB_EXT_ADDRESS_PROXY/SADB_X_EXT_NAT_T_OA等地址相关类型时,验证地址的长度是否合法,即函数verify_address_len。

static const u8 sadb_ext_min_len[] = {
    [SADB_EXT_RESERVED]     = (u8) 0,
    [SADB_EXT_SA]           = (u8) sizeof(struct sadb_sa),
    [SADB_EXT_LIFETIME_CURRENT] = (u8) sizeof(struct sadb_lifetime),
    [SADB_EXT_LIFETIME_HARD]    = (u8) sizeof(struct sadb_lifetime),
    [SADB_EXT_LIFETIME_SOFT]    = (u8) sizeof(struct sadb_lifetime),
}

以下介绍几个比较重要的消息类型。

SADB_REGISTER消息


由消息类型和处理函数的对应关系结构pfkey_funcs可知,SADB_REGISTER注册消息由函数pfkey_register处理。如果此套接口已注册过相同SA类型的消息,直接返回-EEXIST。xfrm_probe_algs函数检查当前系统中可用的哈希、加密和压缩算法的状态,compose_sadb_supported函数将可用的哈希以及加密算法添加到返回的SKB数据中,类型分别为SADB_EXT_SUPPORTED_AUTH和SADB_EXT_SUPPORTED_ENCRYPT。

通过此消息告知内核此套接口应用程序可处理的SA类型。

static int pfkey_register(struct sock *sk, struct sk_buff *skb, const struct sadb_msg *hdr, void * const *ext_hdrs)
{
    struct pfkey_sock *pfk = pfkey_sk(sk);
    struct sk_buff *supp_skb;

    if (hdr->sadb_msg_satype != SADB_SATYPE_UNSPEC) {
        if (pfk->registered & (1<<hdr->sadb_msg_satype)) return -EEXIST;
        pfk->registered |= (1<<hdr->sadb_msg_satype);
    }
    xfrm_probe_algs();
    supp_skb = compose_sadb_supported(hdr, GFP_KERNEL);
    if (!supp_skb) {
        if (hdr->sadb_msg_satype != SADB_SATYPE_UNSPEC)
            pfk->registered &= ~(1<<hdr->sadb_msg_satype);
        return -ENOBUFS;
    }
    pfkey_broadcast(supp_skb, GFP_KERNEL, BROADCAST_REGISTERED, sk, sock_net(sk));
}


SADB_X_SPDADD消息

内核函数pfkey_spdadd处理此消息,应用层下发此安全策略消息,以定义匹配此策略的数据所应使用的安全操作。简单的数据合法性检查之后,在内核中分配对应的xfrm_policy结构,以存储应用层下发的加密策略参数。

static int pfkey_spdadd(struct sock *sk, struct sk_buff *skb, const struct sadb_msg *hdr, void * const *ext_hdrs)
{
	struct xfrm_policy *xp;
    if (!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1], ext_hdrs[SADB_EXT_ADDRESS_DST-1]) || !ext_hdrs[SADB_X_EXT_POLICY-1])
        return -EINVAL;
    pol = ext_hdrs[SADB_X_EXT_POLICY-1];
    if (pol->sadb_x_policy_type > IPSEC_POLICY_IPSEC)
        return -EINVAL;
    if (!pol->sadb_x_policy_dir || pol->sadb_x_policy_dir >= IPSEC_DIR_MAX)
        return -EINVAL;

    xp = xfrm_policy_alloc(net, GFP_KERNEL);

以下,主要初始化内核IPSec策略的selector结构,包括地址簇类型,源和目的IP地址/网段、源端口/目的端口、前缀长度、协议等用来标识策略的元组。

    xp->action = (pol->sadb_x_policy_type == IPSEC_POLICY_DISCARD ? XFRM_POLICY_BLOCK : XFRM_POLICY_ALLOW);
    xp->priority = pol->sadb_x_policy_priority;

    sa = ext_hdrs[SADB_EXT_ADDRESS_SRC-1];
    xp->family = pfkey_sadb_addr2xfrm_addr(sa, &xp->selector.saddr);
    xp->selector.family = xp->family;
    xp->selector.prefixlen_s = sa->sadb_address_prefixlen;
    xp->selector.proto = pfkey_proto_to_xfrm(sa->sadb_address_proto);
    xp->selector.sport = ((struct sockaddr_in *)(sa+1))->sin_port;
    if (xp->selector.sport)
        xp->selector.sport_mask = htons(0xffff);

    sa = ext_hdrs[SADB_EXT_ADDRESS_DST-1];
    pfkey_sadb_addr2xfrm_addr(sa, &xp->selector.daddr);
    xp->selector.prefixlen_d = sa->sadb_address_prefixlen;

    /* Amusing, we set this twice.  KAME apps appear to set same value in both addresses. */
    xp->selector.proto = pfkey_proto_to_xfrm(sa->sadb_address_proto);
    xp->selector.dport = ((struct sockaddr_in *)(sa+1))->sin_port;
    if (xp->selector.dport)
        xp->selector.dport_mask = htons(0xffff);

之后,初始化策略的超时设置,包括soft和hard两种、以及按照字节计量或者数据报文个数计量的限值,再者就是以时间计量的限值。parse_ipsecrequests函数处理模式相关参数,如隧道模式XFRM_MODE_TUNNEL和传输模式XFRM_MODE_TRANSPORT。策略初始化完成之后,内核使用函数xfrm_policy_insert插入到网络命名空间的全局链表中。

    if ((lifetime = ext_hdrs[SADB_EXT_LIFETIME_HARD-1]) != NULL) {
        xp->lft.hard_packet_limit = _KEY2X(lifetime->sadb_lifetime_allocations);
        xp->lft.hard_byte_limit = _KEY2X(lifetime->sadb_lifetime_bytes);
        xp->lft.hard_add_expires_seconds = lifetime->sadb_lifetime_addtime;
        xp->lft.hard_use_expires_seconds = lifetime->sadb_lifetime_usetime;
    }
    if ((lifetime = ext_hdrs[SADB_EXT_LIFETIME_SOFT-1]) != NULL) {
        xp->lft.soft_packet_limit = _KEY2X(lifetime->sadb_lifetime_allocations);
        xp->lft.soft_byte_limit = _KEY2X(lifetime->sadb_lifetime_bytes);
        xp->lft.soft_add_expires_seconds = lifetime->sadb_lifetime_addtime;
        xp->lft.soft_use_expires_seconds = lifetime->sadb_lifetime_usetime;
    }
    xp->xfrm_nr = 0;
    if (pol->sadb_x_policy_type == IPSEC_POLICY_IPSEC &&
        (err = parse_ipsecrequests(xp, pol)) < 0)
        goto out;

    err = xfrm_policy_insert(pol->sadb_x_policy_dir-1, xp, hdr->sadb_msg_type != SADB_X_SPDUPDATE);

函数的最后,调用km_policy_notify通知内核的秘钥管理模块新注册的策略。

    if (hdr->sadb_msg_type == SADB_X_SPDUPDATE)
        c.event = XFRM_MSG_UPDPOLICY;
    else
        c.event = XFRM_MSG_NEWPOLICY;

    c.seq = hdr->sadb_msg_seq;
    c.portid = hdr->sadb_msg_pid;

    km_policy_notify(xp, pol->sadb_x_policy_dir-1, &c);


SADB_ACQUIRE消息


AF_KEY套接口在初始化时,注册了秘钥管理器pfkeyv2_mgr,在内核接收到匹配以上安全策略的数据流后,如果没有可用的安全关联SA,秘钥管理器将发送通知到应用层,以便应用层的秘钥协商程序生成安全关联。内核中相应的通知函数为pfkey_send_acquire。发送到应用层的数据报文包含sadb_msg结构的头部、源和目的地址sadb_address、哈希与加密相关参数和安全策略。

static int pfkey_send_acquire(struct xfrm_state *x, struct xfrm_tmpl *t, struct xfrm_policy *xp)
{
    size = sizeof(struct sadb_msg) +
        (sizeof(struct sadb_address) * 2) +
        (sockaddr_size * 2) +
        sizeof(struct sadb_x_policy);

    if (x->id.proto == IPPROTO_AH)
        size += count_ah_combs(t);
    else if (x->id.proto == IPPROTO_ESP)
        size += count_esp_combs(t);

    if ((xfrm_ctx = x->security)) {
        ctx_size = PFKEY_ALIGN8(xfrm_ctx->ctx_len);
        size +=  sizeof(struct sadb_x_sec_ctx) + ctx_size;
    }
    skb =  alloc_skb(size + 16, GFP_ATOMIC);
    hdr = skb_put(skb, sizeof(struct sadb_msg));
    hdr->sadb_msg_version = PF_KEY_V2;
    hdr->sadb_msg_type = SADB_ACQUIRE;
	...
    return pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_REGISTERED, NULL, xs_net(x));
}


SADB_GETSPI消息


应用层在协商SA之前,SA协商程序使用SADB_GETSPI消息获取SPI值。下发的参数有proto协议(SADB_SATYPE_AH/SADB_SATYPE_ESP/IPPROTO_COMP等),模式mode包括:XFRM_MODE_TRANSPORT、XFRM_MODE_TUNNEL和XFRM_MODE_BEET,以及源和目的地址信息。

static int pfkey_getspi(struct sock *sk, struct sk_buff *skb, const struct sadb_msg *hdr, void * const *ext_hdrs)
{
    proto = pfkey_satype2proto(hdr->sadb_msg_satype);

    if ((sa2 = ext_hdrs[SADB_X_EXT_SA2-1]) != NULL) {
        mode = pfkey_mode_to_xfrm(sa2->sadb_x_sa2_mode);
        reqid = sa2->sadb_x_sa2_reqid;
    } else {
        mode = 0;
        reqid = 0;
    }

    saddr = ext_hdrs[SADB_EXT_ADDRESS_SRC-1];
    daddr = ext_hdrs[SADB_EXT_ADDRESS_DST-1];

内核首先使用消息头部的sadb_msg_seq序号查找对应的ACQUIRE相关结构,在找不到的情况下,使用地址、协议等参数再次进行查找,如果没有对应的ACQUIRE结构,表明内核未发送过SADB_ACQUIRE消息,不处理此GETSPI消息。

    if (hdr->sadb_msg_seq) {
        x = xfrm_find_acq_byseq(net, DUMMY_MARK, hdr->sadb_msg_seq);
        if (x && !xfrm_addr_equal(&x->id.daddr, xdaddr, family)) {
            xfrm_state_put(x);
            x = NULL;
        }
    }

    if (!x)
        x = xfrm_find_acq(net, &dummy_mark, mode, reqid, proto, xdaddr, xsaddr, 1, family);

否则,内核分配新的SPI。

    err = xfrm_alloc_spi(x, min_spi, max_spi);
    resp_skb = err ? ERR_PTR(err) : pfkey_xfrm_state2msg(x);

    out_hdr = (struct sadb_msg *) resp_skb->data;
    out_hdr->sadb_msg_version = hdr->sadb_msg_version;
    out_hdr->sadb_msg_type = SADB_GETSPI;
    out_hdr->sadb_msg_satype = pfkey_proto2satype(proto);
    out_hdr->sadb_msg_errno = 0;
    out_hdr->sadb_msg_reserved = 0;
    out_hdr->sadb_msg_seq = hdr->sadb_msg_seq;
    out_hdr->sadb_msg_pid = hdr->sadb_msg_pid;

    xfrm_state_put(x);
    pfkey_broadcast(resp_skb, GFP_KERNEL, BROADCAST_ONE, sk, net);
}


SADB_ADD消息

在SA协商程序协商完成之后,添加SA到内核中。SADB_ADD消息由函数pfkey_add处理。此函数同时处理SADB_UPDATE消息。

static int pfkey_add(struct sock *sk, struct sk_buff *skb, const struct sadb_msg *hdr, void * const *ext_hdrs)
{   
    struct xfrm_state *x;
    struct km_event c;
    
    x = pfkey_msg2xfrm_state(net, hdr, ext_hdrs);
    
    xfrm_state_hold(x);
    if (hdr->sadb_msg_type == SADB_ADD)
        err = xfrm_state_add(x);
    else
        err = xfrm_state_update(x);
    
    if (hdr->sadb_msg_type == SADB_ADD)
        c.event = XFRM_MSG_NEWSA;
    else
        c.event = XFRM_MSG_UPDSA;
    c.seq = hdr->sadb_msg_seq;
    c.portid = hdr->sadb_msg_pid;
    km_state_notify(x, &c);
}

核心处理函数为xfrm_state_add,其在完成合法性检查之后,使用函数__xfrm_state_insert将新xfrm_state结构插入到三个链表中,分别为以xfrm_dst_hash目的地址哈希链表、xfrm_src_hash源地址哈希链表和SPI哈希链表。另外,插入到网络命名空间的state_all链表中。

static void __xfrm_state_insert(struct xfrm_state *x)
{
    list_add(&x->km.all, &net->xfrm.state_all);

    h = xfrm_dst_hash(net, &x->id.daddr, &x->props.saddr, x->props.reqid, x->props.family);
    hlist_add_head_rcu(&x->bydst, net->xfrm.state_bydst + h);

    h = xfrm_src_hash(net, &x->id.daddr, &x->props.saddr, x->props.family);
    hlist_add_head_rcu(&x->bysrc, net->xfrm.state_bysrc + h);

    if (x->id.spi) {
        h = xfrm_spi_hash(net, &x->id.daddr, x->id.spi, x->id.proto, x->props.family);
        hlist_add_head_rcu(&x->byspi, net->xfrm.state_byspi + h);
    }

    tasklet_hrtimer_start(&x->mtimer, ktime_set(1, 0), HRTIMER_MODE_REL);
    if (x->replay_maxage)
        mod_timer(&x->rtimer, jiffies + x->replay_maxage);
    net->xfrm.state_num++;
    xfrm_hash_grow_check(net, x->bydst.next != NULL);
}

 

内核版本 4.15

 

  • 1
    点赞
  • 0
    评论
  • 3
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值