`

Linux内核中流量控制(18)

阅读更多
Linux内核中流量控制(18)

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

7.9 RSVP

RSVP同时支持IPv4和IPv6, 分别在net/sched/cls_rsvp.c和net/sched/cls_rsvp6.c中定义, 可这两个文件只简单定义了几个按协议不同的参数, 其他处理都是相同的, 都在net/sched/cls_rsvp.h中定义, 没错,是个.h的头文件, 该方法是根据IPv4(6)数据包的地址, 协议, 端口等信息进行分类的, 不过不知道RSVP是什么的缩写。

7.9.1 数据结构和过滤器操作结构

// 根节点, 是整个RSVP规则表的入口点
struct rsvp_head
{
// 映射表
 u32   tmap[256/32];
 u32   hgenerator;
 u8   tgenerator;
// 会话哈希表: 256个, 是用目的地址协议等信息进行哈希
 struct rsvp_session *ht[256];
};

// RSVP的查找规则不是象netfilter那样直接由五元组一级查找, 而是分两级, 先根据目的信息和
// 协议, 然后再根据源信息来定位。
//
// RSVP会话, 根据固定目的地址,协议和目的上层协议三元组参数来定义的连接
struct rsvp_session
{
// 链表中下一项
 struct rsvp_session *next;
// 目的地址, RSVP_DST_LEN根据是V4还是V6分别取1和4
 u32   dst[RSVP_DST_LEN];
// 上层协议相关参数,如TCP/UDP的端口, AH/ESP的SPI等, 通过偏移量定位, 可设掩码
 struct tc_rsvp_gpi  dpi;
// 协议
 u8   protocol;
// 通道ID
 u8   tunnelid;
// 就两个u8, 没进行4字节对齐, 要浪费2字节了
 /* 16 (src,sport) hash slots, and one wildcard source slot */
// 17个rsvp_filter哈希表头, 前16个是正常匹配, 第17个是通配用的, 根据源地址进行哈希
 struct rsvp_filter *ht[16+1];
};

// RSVP过滤器结构
struct rsvp_filter
{
// 下一项
 struct rsvp_filter *next;
// 源地址, RSVP_DST_LEN如果是V4是1, V6时是4
 u32   src[RSVP_DST_LEN];
// 上层协议相关参数,如TCP/UDP的端口, AH/ESP的SPI等, 通过偏移量定位, 可设掩码
 struct tc_rsvp_gpi spi;
// 封装通道参数, 当是封装包,如IPIP时非0
 u8   tunnelhdr;
// TC分类器分类结果和扩展结构
 struct tcf_result res;
 struct tcf_exts  exts;
// 句柄
 u32   handle;
// 回指向rsvp_session结构
 struct rsvp_session *sess;
};
 

// 操作结构
static struct tcf_proto_ops RSVP_OPS = {
 .next  = NULL,
// 这是个宏定义, 根据是v4和v6取不同的值
 .kind  = RSVP_ID,
 .classify = rsvp_classify,
 .init  = rsvp_init,
 .destroy = rsvp_destroy,
 .get  = rsvp_get,
 .put  = rsvp_put,
 .change  = rsvp_change,
 .delete  = rsvp_delete,
 .walk  = rsvp_walk,
 .dump  = rsvp_dump,
 .owner  = THIS_MODULE,
};
 
7.9.2 初始化
 
static int rsvp_init(struct tcf_proto *tp)
{
 struct rsvp_head *data;
// 分配RSVP根节点链表头结构
 data = kzalloc(sizeof(struct rsvp_head), GFP_KERNEL);
 if (data) {
// 作为tcf_proto结构的过滤表根节点
  tp->root = data;
  return 0;
 }
 return -ENOBUFS;
}

7.9.3 分类

static int rsvp_classify(struct sk_buff *skb, struct tcf_proto *tp,
    struct tcf_result *res)
{
// RSVP会话的哈希表头
 struct rsvp_session **sht = ((struct rsvp_head*)tp->root)->ht;
 struct rsvp_session *s;
 struct rsvp_filter *f;
 unsigned h1, h2;
 u32 *dst, *src;
 u8 protocol;
 u8 tunnelid = 0;
 u8 *xprt;
// 外部IP头
#if RSVP_DST_LEN == 4
// IPV6
 struct ipv6hdr *nhptr = skb->nh.ipv6h;
#else
// IPV4
 struct iphdr *nhptr = skb->nh.iph;
#endif
restart:
#if RSVP_DST_LEN == 4
// IPV6的目的和源地址指针
 src = &nhptr->saddr.s6_addr32[0];
 dst = &nhptr->daddr.s6_addr32[0];
// 协议, 但问题是IPV6中的第一个nexthdr有可能是IPV6选项,而不是真正的上层协议号
 protocol = nhptr->nexthdr;
// 上层协议头位置
 xprt = ((u8*)nhptr) + sizeof(struct ipv6hdr);
#else
// IPV4的目的和源地址指针
 src = &nhptr->saddr;
 dst = &nhptr->daddr;
 protocol = nhptr->protocol;
// 上层协议头位置, 如TCP/UDP头的位置
 xprt = ((u8*)nhptr) + (nhptr->ihl<<2);
 if (nhptr->frag_off&__constant_htons(IP_MF|IP_OFFSET))
  return -1;
#endif
// 计算源和地址的哈希值, 计算目的时还需要协议的通道ID
 h1 = hash_dst(dst, protocol, tunnelid);
 h2 = hash_src(src);
// 遍历目的地址哈希值指定的链表
 for (s = sht[h1]; s; s = s->next) {
// 比较源地址是否相同
  if (dst[RSVP_DST_LEN-1] == s->dst[RSVP_DST_LEN-1] &&
// 协议是否相同
      protocol == s->protocol &&
// 这个相当于比较TCP/UDP目的端口, AH,ESP的SPI
      !(s->dpi.mask & (*(u32*)(xprt+s->dpi.offset)^s->dpi.key))
#if RSVP_DST_LEN == 4
// 如果是V6还要比较地址的前3个32位, 因为V6是4个32位
      && dst[0] == s->dst[0]
      && dst[1] == s->dst[1]
      && dst[2] == s->dst[2]
#endif
// 通道ID是否相同
      && tunnelid == s->tunnelid) {
// 遍历该会话按源地址哈希值指定的链表
   for (f = s->ht[h2]; f; f = f->next) {
// 比较源地址和源端口
    if (src[RSVP_DST_LEN-1] == f->src[RSVP_DST_LEN-1] &&
        !(f->spi.mask & (*(u32*)(xprt+f->spi.offset)^f->spi.key))
#if RSVP_DST_LEN == 4
        && src[0] == f->src[0]
        && src[1] == f->src[1]
        && src[2] == f->src[2]
#endif
        ) {
// 源和目的相关参数都匹配
// 分类查找成功, 填返回参数
     *res = f->res;
     RSVP_APPLY_RESULT();
matched:
// 如果不是IP通道封装处理模式(如IPIP), 可以返回成功了
     if (f->tunnelhdr == 0)
      return 0;
// 否则是通道封装, 需要更新使用内部地址参数进行匹配
// 将通道ID赋值为返回结果的类别ID值
     tunnelid = f->res.classid;
// 定位内部IP头, 返回起始点重新查找
     nhptr = (void*)(xprt + f->tunnelhdr - sizeof(*nhptr));
     goto restart;
    }
   }
// 结束循环是正常源地址匹配失败的情况
   /* And wildcard bucket... */
// 遍历通配链表
   for (f = s->ht[16]; f; f = f->next) {
// 如果有元素的话, 直接作为结果返回
    *res = f->res;
    RSVP_APPLY_RESULT();
    goto matched;
   }
// 如果没有通配元素, 分类失败
   return -1;
  }
 }
// 遍历结束, 没有匹配的, 返回失败
 return -1;
}

7.9.4 释放

static void rsvp_destroy(struct tcf_proto *tp)
{
// 将tcf_proto的规则根节点交换出来准备释放处理
 struct rsvp_head *data = xchg(&tp->root, NULL);
 struct rsvp_session **sht;
 int h1, h2;
// 规则表为空, 直接返回
 if (data == NULL)
  return;
// 起始链表头
 sht = data->ht;
// 遍历所有256个链表
 for (h1=0; h1<256; h1++) {
  struct rsvp_session *s;
// 遍历单个链表
  while ((s = sht[h1]) != NULL) {
// 保存链表下一项
   sht[h1] = s->next;
// 准备释放s
// 遍历s的16个源地址哈希链表
   for (h2=0; h2<=16; h2++) {
    struct rsvp_filter *f;
// 遍历链表
    while ((f = s->ht[h2]) != NULL) {
     s->ht[h2] = f->next;
// 释放单个rsvp_filter结构
     rsvp_delete_filter(tp, f);
    }
   }
// 释放s结构
   kfree(s);
  }
 }
// 释放根节点
 kfree(data);
}

// 释放rsvp_filter结构
static inline void
rsvp_delete_filter(struct tcf_proto *tp, struct rsvp_filter *f)
{
// 实际是调用Qdisc_class_ops的unbind_tcf函数
 tcf_unbind_filter(tp, &f->res);
// 释放tcf扩展结构
 tcf_exts_destroy(tp, &f->exts);
// 释放rsvp_filter结构
 kfree(f);
}

7.9.5 获取

// 根据handle查找rsvp_session结构
static unsigned long rsvp_get(struct tcf_proto *tp, u32 handle)
{
// 根节点
 struct rsvp_session **sht = ((struct rsvp_head*)tp->root)->ht;
 struct rsvp_session *s;
 struct rsvp_filter *f;
// 取handle的最低8位作为目的地址哈希表的索引
 unsigned h1 = handle&0xFF;
// 取handle的8~15位作为源地址哈希表的索引
 unsigned h2 = (handle>>8)&0xFF;
// 源地址哈希最大是16+1个表
 if (h2 > 16)
  return 0;
// 遍历h1号目的地址链表
 for (s = sht[h1]; s; s = s->next) {
// 遍历h2号源地址链表
  for (f = s->ht[h2]; f; f = f->next) {
// 比较handle值, 相同的话返回该rsvp_session结构地址参数
   if (f->handle == handle)
    return (unsigned long)f;
  }
 }
// 没找到返回0
 return 0;
}

7.9.6 放下
// 空函数
static void rsvp_put(struct tcf_proto *tp, unsigned long f)
{
}
 
7.9.7 修改

// 增加和修改tc filter规则时调用
static int rsvp_change(struct tcf_proto *tp, unsigned long base,
         u32 handle,
         struct rtattr **tca,
         unsigned long *arg)
{
// 哈希根节点
 struct rsvp_head *data = tp->root;
 struct rsvp_filter *f, **fp;
 struct rsvp_session *s, **sp;
 struct tc_rsvp_pinfo *pinfo = NULL;
// 选项参数
 struct rtattr *opt = tca[TCA_OPTIONS-1];
 struct rtattr *tb[TCA_RSVP_MAX];
 struct tcf_exts e;
 unsigned h1, h2;
 u32 *dst;
 int err;
// 选项结构为空, 如果定义了handle, 返回非法参数错误, 否则不用进行任何操作了
 if (opt == NULL)
  return handle ? -EINVAL : 0;
// 参数解析, 解析后的参数指针保存在tb数组
 if (rtattr_parse_nested(tb, TCA_RSVP_MAX, opt) < 0)
  return -EINVAL;
// tcf_exts结构e初始化
 err = tcf_exts_validate(tp, tb, tca[TCA_RATE-1], &e, &rsvp_ext_map);
 if (err < 0)
  return err;
 if ((f = (struct rsvp_filter*)*arg) != NULL) {
  /* Node exists: adjust only classid */
// 如果rsvp_filter结构已经存在
// 比较handle是否匹配
  if (f->handle != handle && handle)
   goto errout2;
  if (tb[TCA_RSVP_CLASSID-1]) {
// 更新类别ID
   f->res.classid = *(u32*)RTA_DATA(tb[TCA_RSVP_CLASSID-1]);
   tcf_bind_filter(tp, &f->res, base);
  }
// tcf扩展结构修改后返回
  tcf_exts_change(tp, &f->exts, &e);
  return 0;
 }
// rsvp_filter结构不存在, 需要新建
 /* Now more serious part... */
 err = -EINVAL;
// handle必须为0
 if (handle)
  goto errout2;
// 目的地址参数必须存在
 if (tb[TCA_RSVP_DST-1] == NULL)
  goto errout2;
 err = -ENOBUFS;
// 分配rsvp_filter过滤器结构空间
 f = kzalloc(sizeof(struct rsvp_filter), GFP_KERNEL);
 if (f == NULL)
  goto errout2;
 h2 = 16;
// 填充源地址参数
 if (tb[TCA_RSVP_SRC-1]) {
  err = -EINVAL;
  if (RTA_PAYLOAD(tb[TCA_RSVP_SRC-1]) != sizeof(f->src))
   goto errout;
  memcpy(f->src, RTA_DATA(tb[TCA_RSVP_SRC-1]), sizeof(f->src));
  h2 = hash_src(f->src);
 }
// 填充协议信息参数
 if (tb[TCA_RSVP_PINFO-1]) {
  err = -EINVAL;
  if (RTA_PAYLOAD(tb[TCA_RSVP_PINFO-1]) < sizeof(struct tc_rsvp_pinfo))
   goto errout;
  pinfo = RTA_DATA(tb[TCA_RSVP_PINFO-1]);
  f->spi = pinfo->spi;
  f->tunnelhdr = pinfo->tunnelhdr;
 }
// 填充类别ID参数
 if (tb[TCA_RSVP_CLASSID-1]) {
  err = -EINVAL;
  if (RTA_PAYLOAD(tb[TCA_RSVP_CLASSID-1]) != 4)
   goto errout;
  f->res.classid = *(u32*)RTA_DATA(tb[TCA_RSVP_CLASSID-1]);
 }
 err = -EINVAL;
// 目的地址参数
 if (RTA_PAYLOAD(tb[TCA_RSVP_DST-1]) != sizeof(f->src))
  goto errout;
 dst = RTA_DATA(tb[TCA_RSVP_DST-1]);
// 计算目的地址哈希值
 h1 = hash_dst(dst, pinfo ? pinfo->protocol : 0, pinfo ? pinfo->tunnelid : 0);
 err = -ENOMEM;
// 产生rsvp_filter结构的handle
 if ((f->handle = gen_handle(tp, h1 | (h2<<8))) == 0)
  goto errout;
 if (f->tunnelhdr) {
// 如果是通道封装的数据包
  err = -EINVAL;
// 类别ID不能超过0xff
  if (f->res.classid > 255)
   goto errout;
  err = -ENOMEM;
// 类别ID为0的话生成新的类别ID
  if (f->res.classid == 0 &&
      (f->res.classid = gen_tunnel(data)) == 0)
   goto errout;
 }
// 遍历链表, 查找是否已经存在相同目的地址, 协议, 通道ID和目的协议信息的rsvp_session节点
 for (sp = &data->ht[h1]; (s=*sp) != NULL; sp = &s->next) {
// 目的地址比较
  if (dst[RSVP_DST_LEN-1] == s->dst[RSVP_DST_LEN-1] &&
// 协议比较
      pinfo && pinfo->protocol == s->protocol &&
// 上层协议参数比较
      memcmp(&pinfo->dpi, &s->dpi, sizeof(s->dpi)) == 0
#if RSVP_DST_LEN == 4
      && dst[0] == s->dst[0]
      && dst[1] == s->dst[1]
      && dst[2] == s->dst[2]
#endif
// 通道ID比较
      && pinfo->tunnelid == s->tunnelid) {
// rsvp_session找到, 准备将新的过滤器节点插入该链表
insert:
   /* OK, we found appropriate session */
// 会话哈希数组中的h2号链表
   fp = &s->ht[h2];
// 过滤器的session指针回指
   f->sess = s;
// 如果不是IP封装的, 绑定过滤器
   if (f->tunnelhdr == 0)
    tcf_bind_filter(tp, &f->res, base);
// 设置TCF扩展信息
   tcf_exts_change(tp, &f->exts, &e);
// 遍历h2号过滤器链表, 查找插入将新过滤器节点插入链表中的位置
   for (fp = &s->ht[h2]; *fp; fp = &(*fp)->next)
// 根据源协议参数进行比较, 是找第一个比新节点的mask范围大的节点, 也就是链表是根据
// mask范围排序的, mask范围越小越靠前, (mask=0xffffffff时最小)
    if (((*fp)->spi.mask&f->spi.mask) != f->spi.mask)
     break;
// 将新节点插到找到的节点的前面
   f->next = *fp;
   wmb();
   *fp = f;
// 新过滤器结构作为返回参数, 操作成功
   *arg = (unsigned long)f;
   return 0;
  }
 }
 /* No session found. Create new one. */
// 没有合适的rsvp_session节点, 新创建一个会话节点
 err = -ENOBUFS;
// 分类过滤器空间
 s = kzalloc(sizeof(struct rsvp_session), GFP_KERNEL);
 if (s == NULL)
  goto errout;
// 复制目的地址参数
 memcpy(s->dst, dst, sizeof(s->dst));
 if (pinfo) {
// 填写目的上层协议信息
// dst protocol info
  s->dpi = pinfo->dpi;
// 协议
  s->protocol = pinfo->protocol;
// 通道ID
  s->tunnelid = pinfo->tunnelid;
 }
// 遍历h1号会话链表, 查找插入将新会话节点插入链表中的位置
 for (sp = &data->ht[h1]; *sp; sp = &(*sp)->next) {
// 根据目的协议参数进行比较, 是找第一个比新节点的mask范围大的节点, 也就是链表是根据
// mask范围排序的, mask范围越小越靠前, (mask=0xffffffff时最小)
  if (((*sp)->dpi.mask&s->dpi.mask) != s->dpi.mask)
   break;
 }
// 插入节点
 s->next = *sp;
 wmb();
 *sp = s;
// 跳转到前面进行过滤器插入操作, 现在会话结构肯定能找到了
 goto insert;
errout:
 kfree(f);
errout2:
 tcf_exts_destroy(tp, &e);
 return err;
}

7.9.8 删除

// 该函数肯定是返回成功的, 不会失败
static int rsvp_delete(struct tcf_proto *tp, unsigned long arg)
{
 struct rsvp_filter **fp, *f = (struct rsvp_filter*)arg;
// 过滤器的handle
 unsigned h = f->handle;
 struct rsvp_session **sp;
// 过滤器所在的会话链表
 struct rsvp_session *s = f->sess;
 int i;

// handle的8~15位是作为16个源地址HASH链表的链表定位值, 这地方没检查是否超过16了
 for (fp = &s->ht[(h>>8)&0xFF]; *fp; fp = &(*fp)->next) {
// 比较是否找到该过滤器节点
  if (*fp == f) {
   tcf_tree_lock(tp);
// 找到, 从链表中断开
   *fp = f->next;
   tcf_tree_unlock(tp);
// 删除该RSVP过滤器
   rsvp_delete_filter(tp, f);
   /* Strip tree */
// 如果该会话里还有其他过滤器节点, 可以返回
   for (i=0; i<=16; i++)
    if (s->ht[i])
     return 0;
   /* OK, session has no flows */
// 否则, 该会话中的过滤器都为空, 也删除该会话本身
// handle的低8位作为256个会话HASH链表的定位值
// 遍历该链表
   for (sp = &((struct rsvp_head*)tp->root)->ht[h&0xFF];
        *sp; sp = &(*sp)->next) {
// 查找该会话
    if (*sp == s) {
// 找到, 从链表中断开
     tcf_tree_lock(tp);
     *sp = s->next;
     tcf_tree_unlock(tp);
// 释放会话本身空间
     kfree(s);
     return 0;
    }
   }
// 如果没找到会话, 也没关系, 也属于删除成功
   return 0;
  }
 }
// 没找到该过滤器节点, 也属于成功
 return 0;
}
 

7.9.9 遍历
 
static void rsvp_walk(struct tcf_proto *tp, struct tcf_walker *arg)
{
 struct rsvp_head *head = tp->root;
 unsigned h, h1;
// 设置了停止标志, 返回
 if (arg->stop)
  return;
// 遍历256个按目的地址等参数HASH的会话链表
 for (h = 0; h < 256; h++) {
  struct rsvp_session *s;
// 遍历单个会话链表
  for (s = head->ht[h]; s; s = s->next) {
// 遍历16个按源地址等参数HASH的过滤器链表
   for (h1 = 0; h1 <= 16; h1++) {
    struct rsvp_filter *f;
// 遍历单个过滤器链表
    for (f = s->ht[h1]; f; f = f->next) {
// 比较跳过的过滤器数量
     if (arg->count < arg->skip) {
// 计数器也增加
      arg->count++;
      continue;
     }
// 执行相关操作
     if (arg->fn(tp, (unsigned long)f, arg) < 0) {
// 操作失败, 设置停止标志, 返回
      arg->stop = 1;
      return;
     }
// 处理的过滤器计数
     arg->count++;
    }
   }
  }
 }
}

7.9.10 输出
 
static int rsvp_dump(struct tcf_proto *tp, unsigned long fh,
       struct sk_buff *skb, struct tcmsg *t)
{
// 要输出参数的rsvp过滤器
 struct rsvp_filter *f = (struct rsvp_filter*)fh;
 struct rsvp_session *s;
// 数据包要填写数据的缓冲区位置定位
 unsigned char  *b = skb->tail;
 struct rtattr *rta;
 struct tc_rsvp_pinfo pinfo;
// 过滤器为空, 直接返回
 if (f == NULL)
  return skb->len;
// 找到会话
 s = f->sess;
// 过滤器的句柄
 t->tcm_handle = f->handle;
// 将缓冲区视为要返回的netlink属性参数结构进行填充
 rta = (struct rtattr*)b;
// 清零
 RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
// 填充目的地址
 RTA_PUT(skb, TCA_RSVP_DST, sizeof(s->dst), &s->dst);
// 协议信息, 上层协议信息等参数
 pinfo.dpi = s->dpi;
 pinfo.spi = f->spi;
 pinfo.protocol = s->protocol;
 pinfo.tunnelid = s->tunnelid;
 pinfo.tunnelhdr = f->tunnelhdr;
 pinfo.pad = 0;
// 填充协议信息
 RTA_PUT(skb, TCA_RSVP_PINFO, sizeof(pinfo), &pinfo);
// 填充类别ID
 if (f->res.classid)
  RTA_PUT(skb, TCA_RSVP_CLASSID, 4, &f->res.classid);
// 填充源地址信息
 if (((f->handle>>8)&0xFF) != 16)
  RTA_PUT(skb, TCA_RSVP_SRC, sizeof(f->src), f->src);
// 填充TCF扩展信息
 if (tcf_exts_dump(skb, &f->exts, &rsvp_ext_map) < 0)
  goto rtattr_failure;
// 所添加的新数据的长度
 rta->rta_len = skb->tail - b;
// 填充扩展的统计信息
 if (tcf_exts_dump_stats(skb, &f->exts, &rsvp_ext_map) < 0)
  goto rtattr_failure;
// 返回最后填充好的数据包总长
 return skb->len;
rtattr_failure:
 skb_trim(skb, b - skb->data);
 return -1;
}
...... 待续 ......

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics