读书人

Linux内核中游量控制(18)

发布时间: 2012-07-15 20:11:40 作者: rapoo

Linux内核中流量控制(18)
Linux内核中流量控制(18)

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

7.9 RSVPRSVP同时支持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;#endifrestart:#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 voidrsvp_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;}...... 待续 ......

读书人网 >UNIXLINUX

热点推荐