日期:2014-05-16  浏览次数:20655 次

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.o