Linux内核中游量控制(18)

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

Linux内核中游量控制(18)

相关文章:

你感兴趣的文章:

标签云: