`

Linux内核中PF_KEY协议族的实现(3)

阅读更多
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

5.10 添加/更新SPD(安全策略)
// 添加安全策略到安全策略数据库SPDB
static int pfkey_spdadd(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
 int err = 0;
 struct sadb_lifetime *lifetime;
 struct sadb_address *sa;
 struct sadb_x_policy *pol;
 struct xfrm_policy *xp;
 struct km_event c;
 struct sadb_x_sec_ctx *sec_ctx;
// 错误检查, 源目的地址类型要一致, 策略信息非空
 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];
// 策略类型只能是DISCARD, NONE和IPSEC三者之一
 if (pol->sadb_x_policy_type > IPSEC_POLICY_IPSEC)
  return -EINVAL;
// 必须明确该SP作用于哪个方向的的数据: IN, OUT和FORWARD, 最后那个不是RFC标准的
 if (!pol->sadb_x_policy_dir || pol->sadb_x_policy_dir >= IPSEC_DIR_MAX)
  return -EINVAL;
// 分配xfrm策略
 xp = xfrm_policy_alloc(GFP_KERNEL);
 if (xp == NULL)
  return -ENOBUFS;
// 策略的动作: 阻塞还是放行
 xp->action = (pol->sadb_x_policy_type == IPSEC_POLICY_DISCARD ?
        XFRM_POLICY_BLOCK : XFRM_POLICY_ALLOW);
// 策略的优先级
 xp->priority = pol->sadb_x_policy_priority;
// 获取策略源地址及地址类型(v4 or v6)
 sa = ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
 xp->family = pfkey_sadb_addr2xfrm_addr(sa, &xp->selector.saddr);
 if (!xp->family) {
  err = -EINVAL;
  goto out;
 }
// 填充策略选择子结构参数, 选择子中包括用来辨别策略的相关参数, 用来查找匹配策略
// 协议族
 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);
// 获取策略目的地址及地址类型(v4 or v6)
// 和源地址处理类似
 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);
// 用户定义的安全上下文
 sec_ctx = (struct sadb_x_sec_ctx *) ext_hdrs[SADB_X_EXT_SEC_CTX-1];
 if (sec_ctx != NULL) {
  struct xfrm_user_sec_ctx *uctx = pfkey_sadb2xfrm_user_sec_ctx(sec_ctx);
  if (!uctx) {
   err = -ENOBUFS;
   goto out;
  }
  err = security_xfrm_policy_alloc(xp, uctx);
  kfree(uctx);
  if (err)
   goto out;
 }
// lft: xfrm_lifetime_config, 生存时间配置参数
// 关于字节数和包数的软硬限制初始化
 xp->lft.soft_byte_limit = XFRM_INF;
 xp->lft.hard_byte_limit = XFRM_INF;
 xp->lft.soft_packet_limit = XFRM_INF;
 xp->lft.hard_packet_limit = XFRM_INF;
// 如果在消息中定义了限制, 设置之
 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;
// 如果是IPSEC策略, 解析相关请求
 if (pol->sadb_x_policy_type == IPSEC_POLICY_IPSEC &&
     (err = parse_ipsecrequests(xp, pol)) < 0)
  goto out;
// 将策略xp插入内核SPDB
 err = xfrm_policy_insert(pol->sadb_x_policy_dir-1, xp,
// 一般是添加, 也可以是更新
     hdr->sadb_msg_type != SADB_X_SPDUPDATE);
 if (err)
  goto out;
 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.pid = hdr->sadb_msg_pid;
// 策略通知回调处理
 km_policy_notify(xp, pol->sadb_x_policy_dir-1, &c);
 xfrm_pol_put(xp);
 return 0;
out:
 security_xfrm_policy_free(xp);
 kfree(xp);
 return err;
}
/* net/xfrm/xfrm_policy.c */
// 插入策略
int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
{
 struct xfrm_policy *pol;
 struct xfrm_policy *delpol;
 struct hlist_head *chain;
 struct hlist_node *entry, *newpos, *last;
 struct dst_entry *gc_list;
 write_lock_bh(&xfrm_policy_lock);
// 找到具体的hash链表, SPDB也是用HASH表实现的
 chain = policy_hash_bysel(&policy->selector, policy->family, dir);
 delpol = NULL;
 newpos = NULL;
 last = NULL;
// 遍历链表, 该链表是以策略的优先级值进行排序的链表, 因此需要根据新策略的优先级大小
// 将新策略插到合适的位置
 hlist_for_each_entry(pol, entry, chain, bydst) {
// delpol要为空
  if (!delpol &&
// 策略类型比较
      pol->type == policy->type &&
// 选择子比较
      !selector_cmp(&pol->selector, &policy->selector) &&
// 安全上下文比较
      xfrm_sec_ctx_match(pol->security, policy->security)) {
// 找到了
   if (excl) {
// 如果是添加操作, 要插入的策略在数据库中已经存在, 发生错误
    write_unlock_bh(&xfrm_policy_lock);
    return -EEXIST;
   }
// 保存好要删除的策略位置
   delpol = pol;
// 要更新的策略优先级值大于原有的优先级值, 重新循环找到合适的插入位置
// 因为这个链表是以优先级值进行排序的, 不能乱
// 现在delpol已经非空了,  前面的策略查找条件已经不可能满足了
   if (policy->priority > pol->priority)
    continue;
  } else if (policy->priority >= pol->priority) {
// 如果新的优先级不低于当前的优先级, 保存当前节点, 继续查找合适插入位置
   last = &pol->bydst;
   continue;
  }
// 这里是根据新策略的优先级确定的插入位置
  if (!newpos)
   newpos = &pol->bydst;
// 如果已经找到要删除的策略, 中断
  if (delpol)
   break;
  last = &pol->bydst;
 }
 if (!newpos)
  newpos = last;
// 插入策略到按目的地址HASH的链表
 if (newpos)
  hlist_add_after(newpos, &policy->bydst);
 else
  hlist_add_head(&policy->bydst, chain);
 xfrm_pol_hold(policy);
 xfrm_policy_count[dir]++;
 atomic_inc(&flow_cache_genid);
// 如果有相同的老策略, 要从目的地址HASH和索引号HASH这两个表中删除
 if (delpol) {
  hlist_del(&delpol->bydst);
  hlist_del(&delpol->byidx);
  xfrm_policy_count[dir]--;
 }
// 获取策略索引号, 插入索引HASH链表
 policy->index = delpol ? delpol->index : xfrm_gen_index(policy->type, dir);
 hlist_add_head(&policy->byidx, xfrm_policy_byidx+idx_hash(policy->index));
// 策略插入实际时间
 policy->curlft.add_time = (unsigned long)xtime.tv_sec;
 policy->curlft.use_time = 0;
 if (!mod_timer(&policy->timer, jiffies + HZ))
  xfrm_pol_hold(policy);
 write_unlock_bh(&xfrm_policy_lock);
// 释放老策略
 if (delpol)
  xfrm_policy_kill(delpol);
 else if (xfrm_bydst_should_resize(dir, NULL))
  schedule_work(&xfrm_hash_work);
// 下面释放所有策略当前的路由cache
 read_lock_bh(&xfrm_policy_lock);
 gc_list = NULL;
 entry = &policy->bydst;
// 遍历链表, 搜集垃圾路由cache建立链表
 hlist_for_each_entry_continue(policy, entry, bydst) {
  struct dst_entry *dst;
  write_lock(&policy->lock);
  dst = policy->bundles;
  if (dst) {
   struct dst_entry *tail = dst;
   while (tail->next)
    tail = tail->next;
   tail->next = gc_list;
   gc_list = dst;
   policy->bundles = NULL;
  }
  write_unlock(&policy->lock);
 }
 read_unlock_bh(&xfrm_policy_lock);
// 释放垃圾路由cahce
 while (gc_list) {
  struct dst_entry *dst = gc_list;
  gc_list = dst->next;
  dst_free(dst);
 }
 return 0;
}
EXPORT_SYMBOL(xfrm_policy_insert);

5.11 删除SPD

static int pfkey_spddelete(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
 int err;
 struct sadb_address *sa;
 struct sadb_x_policy *pol;
 struct xfrm_policy *xp, tmp;
 struct xfrm_selector sel;
 struct km_event c;
 struct sadb_x_sec_ctx *sec_ctx;
// 错误检查, 源目的地址类型要一致, 策略信息非空
 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_dir || pol->sadb_x_policy_dir >= IPSEC_DIR_MAX)
  return -EINVAL;
 memset(&sel, 0, sizeof(sel));
// 解析消息中的源地址参数填充到选择子结构中
 sa = ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
 sel.family = pfkey_sadb_addr2xfrm_addr(sa, &sel.saddr);
 sel.prefixlen_s = sa->sadb_address_prefixlen;
 sel.proto = pfkey_proto_to_xfrm(sa->sadb_address_proto);
 sel.sport = ((struct sockaddr_in *)(sa+1))->sin_port;
 if (sel.sport)
  sel.sport_mask = htons(0xffff);
// 解析消息中的目的地址参数填充到选择子结构中
 sa = ext_hdrs[SADB_EXT_ADDRESS_DST-1],
 pfkey_sadb_addr2xfrm_addr(sa, &sel.daddr);
 sel.prefixlen_d = sa->sadb_address_prefixlen;
 sel.proto = pfkey_proto_to_xfrm(sa->sadb_address_proto);
 sel.dport = ((struct sockaddr_in *)(sa+1))->sin_port;
 if (sel.dport)
  sel.dport_mask = htons(0xffff);
 sec_ctx = (struct sadb_x_sec_ctx *) ext_hdrs[SADB_X_EXT_SEC_CTX-1];
 memset(&tmp, 0, sizeof(struct xfrm_policy));
// 扩展的用户安全上下文信息处理
 if (sec_ctx != NULL) {
  struct xfrm_user_sec_ctx *uctx = pfkey_sadb2xfrm_user_sec_ctx(sec_ctx);
  if (!uctx)
   return -ENOMEM;
  err = security_xfrm_policy_alloc(&tmp, uctx);
  kfree(uctx);
  if (err)
   return err;
 }
// 根据策略类型, 处理的数据方向, 选择子参数等信息查找策略
// 同时最后一个参数为1表示找到后将策略从系统的SPDB链表中断开后删除
 xp = xfrm_policy_bysel_ctx(XFRM_POLICY_TYPE_MAIN, pol->sadb_x_policy_dir-1,
       &sel, tmp.security, 1);
 security_xfrm_policy_free(&tmp);
// 没有该策略, 出错
 if (xp == NULL)
  return -ENOENT;
 err = 0;
 if ((err = security_xfrm_policy_delete(xp)))
  goto out;
 c.seq = hdr->sadb_msg_seq;
 c.pid = hdr->sadb_msg_pid;
 c.event = XFRM_MSG_DELPOLICY;
// 反方向的策略通知回调处理
 km_policy_notify(xp, pol->sadb_x_policy_dir-1, &c);
out:
// 释放策略
 xfrm_pol_put(xp);
 return err;
}
/* net/xfrm/xfrm_policy.c */
// 根据选择子和上下文查找策略
struct xfrm_policy *xfrm_policy_bysel_ctx(u8 type, int dir,
       struct xfrm_selector *sel,
       struct xfrm_sec_ctx *ctx, int delete)
{
 struct xfrm_policy *pol, *ret;
 struct hlist_head *chain;
 struct hlist_node *entry;
 write_lock_bh(&xfrm_policy_lock);
// 定位HASH表
 chain = policy_hash_bysel(sel, sel->family, dir);
 ret = NULL;
// 遍历链表
 hlist_for_each_entry(pol, entry, chain, bydst) {
// 根据类型, 选择子和上下文进行匹配
  if (pol->type == type &&
      !selector_cmp(sel, &pol->selector) &&
      xfrm_sec_ctx_match(ctx, pol->security)) {
   xfrm_pol_hold(pol);
   if (delete) {
// 要的删除话将策略节点从目的地址HASH链表和索引HASH链表中断开
    hlist_del(&pol->bydst);
    hlist_del(&pol->byidx);
    xfrm_policy_count[dir]--;
   }
   ret = pol;
   break;
  }
 }
 write_unlock_bh(&xfrm_policy_lock);
 if (ret && delete) {
  atomic_inc(&flow_cache_genid);
// 将策略状态置为dead, 并添加到系统的策略垃圾链表进行调度处理准备删除
  xfrm_policy_kill(ret);
 }
 return ret;
}
EXPORT_SYMBOL(xfrm_policy_bysel_ctx);

5.12 获取SPD

static int pfkey_spdget(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
 unsigned int dir;
 int err;
 struct sadb_x_policy *pol;
 struct xfrm_policy *xp;
 struct km_event c;
// 消息中的策略头
 if ((pol = ext_hdrs[SADB_X_EXT_POLICY-1]) == NULL)
  return -EINVAL;
// 根据策略id判断数据方向
 dir = xfrm_policy_id2dir(pol->sadb_x_policy_id);
 if (dir >= XFRM_POLICY_MAX)
  return -EINVAL;
// 根据方向/ID等参数来查找策略, 如果最后一个参数为真的同时删除策略
 xp = xfrm_policy_byid(XFRM_POLICY_TYPE_MAIN, dir, pol->sadb_x_policy_id,
         hdr->sadb_msg_type == SADB_X_SPDDELETE2);
 if (xp == NULL)
  return -ENOENT;
 err = 0;
 c.seq = hdr->sadb_msg_seq;
 c.pid = hdr->sadb_msg_pid;
 if (hdr->sadb_msg_type == SADB_X_SPDDELETE2) {
// 如果要删除策略, 进行通知回调
  c.data.byid = 1;
  c.event = XFRM_MSG_DELPOLICY;
  km_policy_notify(xp, dir, &c);
 } else {
// 将结果填充一个skb返回给用户空间
  err = key_pol_get_resp(sk, xp, hdr, dir);
 }
 xfrm_pol_put(xp);
 return err;
}
 
5.13 输出整个SPD
static int pfkey_spddump(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
 struct pfkey_dump_data data = { .skb = skb, .hdr = hdr, .sk = sk };
// 遍历策略链表输出策略, dump_sa是输出单一SP的函数
 return xfrm_policy_walk(XFRM_POLICY_TYPE_MAIN, dump_sp, &data);
}
// 输出单一SP
static int dump_sp(struct xfrm_policy *xp, int dir, int count, void *ptr)
{
 struct pfkey_dump_data *data = ptr;
 struct sk_buff *out_skb;
 struct sadb_msg *out_hdr;
// 将安全策略填充到skb前的准备操作
 out_skb = pfkey_xfrm_policy2msg_prep(xp);
 if (IS_ERR(out_skb))
  return PTR_ERR(out_skb);
// 将安全策略填充到skb
 pfkey_xfrm_policy2msg(out_skb, xp, dir);
// 填充基本SA消息头
 out_hdr = (struct sadb_msg *) out_skb->data;
 out_hdr->sadb_msg_version = data->hdr->sadb_msg_version;
 out_hdr->sadb_msg_type = SADB_X_SPDDUMP;
 out_hdr->sadb_msg_satype = SADB_SATYPE_UNSPEC;
 out_hdr->sadb_msg_errno = 0;
// SA消息的序号
 out_hdr->sadb_msg_seq = count;
 out_hdr->sadb_msg_pid = data->hdr->sadb_msg_pid;
// 发送到指定的sock
 pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ONE, data->sk);
 return 0;
}

/* net/xfrm/xfrm_policy.c */
// 遍历安全策略
int xfrm_policy_walk(u8 type, int (*func)(struct xfrm_policy *, int, int, void*),
       void *data)
{
 struct xfrm_policy *pol;
 struct hlist_node *entry;
 int dir, count, error;
 read_lock_bh(&xfrm_policy_lock);
 count = 0;
// 先统计符合类型的策略的数量, 方向是双向的
 for (dir = 0; dir < 2*XFRM_POLICY_MAX; dir++) {
  struct hlist_head *table = xfrm_policy_bydst[dir].table;
  int i;
// inexact HASH表
  hlist_for_each_entry(pol, entry,
         &xfrm_policy_inexact[dir], bydst) {
   if (pol->type == type)
    count++;
  }
// bydst HASH表
  for (i = xfrm_policy_bydst[dir].hmask; i >= 0; i--) {
   hlist_for_each_entry(pol, entry, table + i, bydst) {
    if (pol->type == type)
     count++;
   }
  }
 }
 if (count == 0) {
  error = -ENOENT;
  goto out;
 }
// 重新遍历HASH表, 当前的count值作为SA的序号, 因此用户空间收到的序号是递减的
 for (dir = 0; dir < 2*XFRM_POLICY_MAX; dir++) {
  struct hlist_head *table = xfrm_policy_bydst[dir].table;
  int i;
  hlist_for_each_entry(pol, entry,
         &xfrm_policy_inexact[dir], bydst) {
   if (pol->type != type)
    continue;
   error = func(pol, dir % XFRM_POLICY_MAX, --count, data);
   if (error)
    goto out;
  }
  for (i = xfrm_policy_bydst[dir].hmask; i >= 0; i--) {
   hlist_for_each_entry(pol, entry, table + i, bydst) {
    if (pol->type != type)
     continue;
    error = func(pol, dir % XFRM_POLICY_MAX, --count, data);
    if (error)
     goto out;
   }
  }
 }
 error = 0;
out:
 read_unlock_bh(&xfrm_policy_lock);
 return error;
}
EXPORT_SYMBOL(xfrm_policy_walk);

5.14 删除全部SPD
 [SADB_X_SPDFLUSH] = pfkey_spdflush,
// 删除全部SP
static int pfkey_spdflush(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
 struct km_event c;
// 删除全部MAIN类型的SP
 xfrm_policy_flush(XFRM_POLICY_TYPE_MAIN);
 c.data.type = XFRM_POLICY_TYPE_MAIN;
 c.event = XFRM_MSG_FLUSHPOLICY;
 c.pid = hdr->sadb_msg_pid;
 c.seq = hdr->sadb_msg_seq;
// 通知回调处理
 km_policy_notify(NULL, 0, &c);
 return 0;
}
// 删除SP回调
static int key_notify_policy_flush(struct km_event *c)
{
 struct sk_buff *skb_out;
 struct sadb_msg *hdr;
// 分配skb
 skb_out = alloc_skb(sizeof(struct sadb_msg) + 16, GFP_ATOMIC);
 if (!skb_out)
  return -ENOBUFS;
 hdr = (struct sadb_msg *) skb_put(skb_out, sizeof(struct sadb_msg));
// SA消息类型为删除所有SP
 hdr->sadb_msg_type = SADB_X_SPDFLUSH;
 hdr->sadb_msg_seq = c->seq;
 hdr->sadb_msg_pid = c->pid;
 hdr->sadb_msg_version = PF_KEY_V2;
 hdr->sadb_msg_errno = (uint8_t) 0;
 hdr->sadb_msg_len = (sizeof(struct sadb_msg) / sizeof(uint64_t));
// 广播给所有打开PF_KEY类型套接口的用户进程
 pfkey_broadcast(skb_out, GFP_ATOMIC, BROADCAST_ALL, NULL);
 return 0;
}
/* net/xfrm/xfrm_policy.c */
// 删除全部安全策略
void xfrm_policy_flush(u8 type)
{
 int dir;
 write_lock_bh(&xfrm_policy_lock);
 for (dir = 0; dir < XFRM_POLICY_MAX; dir++) {
  struct xfrm_policy *pol;
  struct hlist_node *entry;
  int i, killed;
  killed = 0;
 again1:
// 遍历inexact HASH链表
  hlist_for_each_entry(pol, entry,
         &xfrm_policy_inexact[dir], bydst) {
// 判断类型
   if (pol->type != type)
    continue;
// 将策略从bydst链表中断开
   hlist_del(&pol->bydst);
// 将策略从byidt链表中断开
   hlist_del(&pol->byidx);
   write_unlock_bh(&xfrm_policy_lock);
// 将策略状态置为dead, 并添加到系统的策略垃圾链表进行调度处理准备删除
   xfrm_policy_kill(pol);
   killed++;
   write_lock_bh(&xfrm_policy_lock);
   goto again1;
  }
// 遍历bydst HASH链表
  for (i = xfrm_policy_bydst[dir].hmask; i >= 0; i--) {
 again2:
   hlist_for_each_entry(pol, entry,
          xfrm_policy_bydst[dir].table + i,
          bydst) {
    if (pol->type != type)
     continue;
// 将节点从链表中断开
    hlist_del(&pol->bydst);
    hlist_del(&pol->byidx);
    write_unlock_bh(&xfrm_policy_lock);
// 释放节点
    xfrm_policy_kill(pol);
    killed++;
    write_lock_bh(&xfrm_policy_lock);
    goto again2;
   }
  }
  xfrm_policy_count[dir] -= killed;
 }
 atomic_inc(&flow_cache_genid);
 write_unlock_bh(&xfrm_policy_lock);
}
EXPORT_SYMBOL(xfrm_policy_flush);

...... 待续 ......
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics