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;}...... 待续 ......