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;
}
...... 待续 ......
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux令牌桶流量控制是一种网络流量控制机制,用于限制进入或离开系统的数据包速率。它基于一个令牌桶模型,在这个模型,一个固定数量的令牌以固定速率被添加到令牌桶。每当有一个数据包到达时,系统将从令牌桶取出一个令牌,只有当令牌桶有足够的令牌时才允许数据包通过。如果令牌桶没有足够的令牌,那么数据包就会被丢弃或延迟。 通过配置Linux内核参数和使用工具如tc(Traffic Control),可以实现令牌桶流量控制。以下是一些关键步骤: 1. 启用Linux内核的Traffic Control子系统。这可以通过加载“sch_htb”模块实现。 ``` $ modprobe sch_htb ``` 2. 使用tc命令创建一个类别(class)和队列(qdisc)来实现流量控制。例如,以下命令将创建一个带宽为1mbps、延迟为10ms的队列。 ``` $ tc qdisc add dev eth0 root handle 1: htb default 1 $ tc class add dev eth0 parent 1: classid 1:1 htb rate 1mbit ceil 1mbit $ tc qdisc add dev eth0 parent 1:1 handle 10: netem delay 10ms ``` 3. 根据需求,可以添加过滤规则(filter)以便仅对特定的流量应用流量控制。例如,以下命令将对源IP为192.168.0.1的流量应用限制。 ``` $ tc filter add dev eth0 protocol ip parent 1: prio 1 u32 match ip src 192.168.0.1 flowid 1:1 ``` 通过以上步骤,你可以实现针对特定网络接口、IP地址或其他条件的流量控制。你可以根据具体需求进行调整和配置,以满足你的流量控制需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值