u32过滤器

本文详细介绍了u32过滤器的命令行用法,包括handle参数、classid和flowid的作用,以及SELECTOR的各种匹配方式。同时,探讨了u32在内核中的数据结构,如tc_u_common、tc_u_hnode和tc_u_knode,并详细解析了u32的初始化、获取和修改过程。内容涵盖协议头字段匹配、mark匹配及示例应用。
摘要由CSDN通过智能技术生成


u32是最通用的一个数据包filter,它基于数据包的任意偏移位置匹配数据包,所以功能最强大,当然语法也最繁琐。个人理解这块重点掌握u32过滤器怎么用,不用过多关注其实现细节,这里之所以展开分析,主要是为了通过其对内核的流控框架有个比较清晰的认知,方便需要时进一步仔细研究。

命令行说明

# 简化版命令格式如下:
tc  filter ... [ handle HANDLE ] u32 OPTION_LIST [ classid CLASSID ]

HANDLE := { u12_hex_htid:[u8_hex_hash:[u12_hex_nodeid] | 0xu32_hex_value }
OPTION_LIST := [ OPTION_LIST ] OPTION
CLASSID := { root | none | [u16_major]:u16_minor | u32_hex_value }

OPTION := { match SELECTOR | action ACTION }
SELECTOR := { u32 VAL_MASK_32 | u16 VAL_MASK_16 | u8 VAL_MASK_8 | ip IP | ip6 IP6 | { tcp | udp } \
    TCPUDP | icmp ICMP | mark VAL_MASK_32 | ether ETHER }
IP := { { src | dst } { default | any | all | ip_address [ / { prefixlen | netmask } ] } \
    AT | { dsfield | ihl | protocol | icmp_type | icmp_code } VAL_MASK_8 | \
    { sport | dport } VAL_MASK_16 | nofrag | firstfrag | df | mf }
IP6  :=  {  {  src  |  dst  }  {  default | any | all | ip6_address [/prefixlen ] } \
    AT | priority VAL_MASK_8 | { protocol | icmp_type | icmp_code } VAL_MASK_8 \
    | flowlabel VAL_MASK_32 | { sport | dport } VAL_MASK_16 }
TCPUDP := { src | dst } VAL_MASK_16
ICMP := { type VAL_MASK_8 | code VAL_MASK_8 }
ETHER := { src | dst } ether_address AT
VAL_MASK_32 := u32_value u32_hex_mask [ AT ]
VAL_MASK_16 := u16_value u16_hex_mask [ AT ]
VAL_MASK_8 := u8_value u8_hex_mask [ AT ]
AT := [ at [ nexthdr+ ] int_value ]
  • handle参数指定filter的句柄;一般来说该字段不需要,让内核自己生成即可,当要配置非常多的filter时该字段才能派上用场。u32的句柄分为3部分:高12位代表hasn表ID,对应内核tc_u_hnode.handle,通过它可以在tc_u_common.hlist中索引hnode对象;中间8位代表hash ID,可以用其索引hnode.ht数组索引;低12位代表node ID,每条filter命令携带的一组match条件,它们对应内核的tc_u_knode对象,低12位可以来索引该对象;
  • classid参数和flowid的作用相同,表示该filter的过滤结果,即将数据包分类到哪个class;

最灵活的参数是SELECTOR,它告诉u32要怎么匹配数据包。可以对它进行简单分类:

  1. 基本的u32、u16、u8匹配;
  2. 协议头字段匹配;
  3. mark匹配;

基本匹配

u32、u16、u8分别表示按照4字节、2字节和1字节在指定偏移位置匹配数据包内容,匹配条件以value和mask的方式指定。偏移位置用at关键字加一个正整数表示从网络层首部开头的偏移量,如果指定了nexthdr+,那么偏移量从网络层的上一层开头开始偏移。

协议头字段匹配

ip、ip6、tcp、udp、icmp以及ether分别表示匹配对应协议的头部字段。

对于ip协议和ipv6协议:

  • { src | dst } { default | any | all | ip_address [ / { prefixlen | netmask } ] } AT表示匹配IP地址,default、any、all等关键字表示匹配任意的IP地址,也可以携带前缀或者子网掩码;
  • dsfiled VAL_MASK_8表示匹配tos和优先级字段;
  • ihl VAL_MASK_8表示匹配首部长度字段,值的单位位4字节;
  • protocol VAL_MASK_8表示匹配协议字段;
  • {icmp_type | icmp_code } VAL_MASK_8假定下一个头部是icmp,检查其type和code,该选项最好不要使用,它假定IP首部是不包含选项的;
  • { sport | dport } VAL_MASK_16假定下一个头部是tcp或者udp,去匹配其端口号,类似的原因,最好不要使用该选项;
  • nofrag | firstfrag | df | mf分别表示匹配非分片报文、第一个分片报文以及df和mf标记是否设置;
  • priority VAL_MASK_8匹配Ipv6报文的优先级字段;
  • flowlabel VAL_MASK_32匹配Ipv6报文的flow id字段;

对于tcp和udp协议:

  • { src | dst } VAL_MASK_16分别匹配tcp和udp报文的源端口以及目的端口;

对于icmp协议:

  • { type VAL_MASK_8 | code VAL_MASK_8 }分别匹配icmp报文的type和code字段;

对于以太帧:

  • ether ETHER可以匹配以太帧的源和目的mac地址,最好不要使用该选项,它假定报文的开头是以太帧内容;

mark匹配

  • mark VAL_MASK_32可以匹配数据包的fwmark值,这使得u32可以配合Netfilter工作;

示例

tc qdisc add dev eth0 root handle 1: htb default 20

tc class add dev eth0 parent 1: classid 1:10 htb rate 50Kbps ceil 50Kbps
tc class add dev eth0 parent 1:10 classid 1:20 htb rate 20Kbps ceil 50Kbps
tc class add dev eth0 parent 1:10 classid 1:30 htb rate 30Kbps ceil 50Kbps

tc filter add dev eth0 parent 1: prio 10 u32 match u8 64 0xff at 8 flowid 1:20
tc filter add dev eth0 parent 1: prio 10 u32 match u8 64 0xff at 8 flowid 1:30

数据结构

match参数: tc_u32_key/tc_u32_sel

// 一个32位匹配,表示数据包的"off&offmask"偏移量处的4字节数据和"val&mask"是否相同
struct tc_u32_key {
    __be32 mask;
    __be32 val;
	int	off;
	int	offmask;
};

struct tc_u32_sel {
    unsigned char flags;
    unsigned char offshift;
    unsigned char nkeys; // keys数组当前实际长度,每个match会占用一个keys元素

    __be16 offmask;
    __u16 off;
    short offoff;

    short hoff;
    __be32 hmask;
    struct tc_u32_key keys[0];
}

命令行参数解析: u32_parse_opt()

struct filter_util u32_filter_util = {
	.id = "u32",
	.parse_fopt = u32_parse_opt, // u32 filter参数解析接口
	.print_fopt = u32_print_opt,
};

static int u32_parse_opt(struct filter_util *qu, char *handle, int argc,
    char **argv, struct nlmsghdr *n)
{
	struct {
		struct tc_u32_sel sel;
		struct tc_u32_key keys[128]; // 每个命令最多128个match条件
	} sel = {}; // 保存所有命令行match参数
	struct tcmsg *t = NLMSG_DATA(n);
	struct rtattr *tail;
	int sel_ok = 0, terminal_ok = 0;
	int sample_ok = 0;
	__u32 htid = 0;
	__u32 order = 0;
	__u32 flags = 0;

    // 由于不同filter对handle字段的解释不同,所以tc filter框架让具体filter自己解释handle参数
	if (handle && get_u32_handle(&t->tcm_handle, handle)) {
		fprintf(stderr, "Illegal filter ID\n");
		return -1;
	}
	if (argc == 0)
		return 0;

	tail = NLMSG_TAIL(n);
	addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0);

	while (argc > 0) {
		if (matches(*argv, "match") == 0) {
			NEXT_ARG();
			if (parse_selector(&argc, &argv, &sel.sel, n)) { // 解析match参数
				fprintf(stderr, "Illegal \"match\"\n");
				return -1;
			}
			sel_ok++;
			continue;
		} else if (matches(*argv, "offset") == 0) {
			NEXT_ARG();
			if (parse_offset(&argc, &argv, &sel.sel)) {
				fprintf(stderr, "Illegal \"offset\"\n");
				return -1;
			}
			continue;
		} else if (matches(*argv, "hashkey") == 0) {
			NEXT_ARG();
			if (parse_hashkey(&argc, &argv, &sel.sel)) {
				fprintf(stderr, "Illegal \"hashkey\"\n");
				return -1;
			}
			continue;
		} else if (matches(*argv, "classid") == 0 || strcmp(*argv, "flowid") == 0) {
		    // 指定该filter应该将数据包分类到哪个class
			unsigned int flowid;
			NEXT_ARG();
			if (get_tc_classid(&flowid, *argv)) {
				fprintf(stderr, "Illegal \"classid\"\n");
				return -1;
			}
			addattr_l(n, MAX_MSG, TCA_U32_CLASSID, &flowid, 4);
			sel.sel.flags |= TC_U32_TERMINAL;
		} else if (matches(*argv, "divisor") == 0) {
			unsigned int divisor;
			NEXT_ARG();
			if (get_unsigned(&divisor, *argv, 0) ||
			    divisor == 0 ||
			    divisor > 0x100 || ((divisor - 1) & divisor)) {
				fprintf(stderr, "Illegal \"divisor\"\n");
				return -1;
			}
			addattr_l(n, MAX_MSG, TCA_U32_DIVISOR, &divisor, 4);
		} else if (matches(*argv, "order") == 0) {
			NEXT_ARG();
			if (get_u32(&order, *argv, 0)) {
				fprintf(stderr, "Illegal \"order\"\n");
				return -1;
			}
		} else if (strcmp(*argv, "link") == 0) {
			unsigned int linkid;

			NEXT_ARG();
			if (get_u32_handle(&linkid, *argv)) {
				fprintf(stderr, "Illegal \"link\"\n");
				return -1;
			}
			if (linkid && TC_U32_NODE(linkid)) {
				fprintf(stderr, "\"link\" must be a hash table.\n");
				return -1;
			}
			addattr_l(n, MAX_MSG, TCA_U32_LINK, &linkid, 4);
		} else if (strcmp(*argv, "ht") == 0) {
			unsigned int ht;

			NEXT_ARG();
			if (get_u32_handle(&ht, *argv)) {
				fprintf(stderr, "Illegal \"ht\"\n");
				return -1;
			}
			if (handle && TC_U32_NODE(ht)) {
				fprintf(stderr, "\"ht\" must be a hash table.\n");
				return -1;
			}
			if (sample_ok)
				htid = (htid & 0xFF000) | (ht & 0xFFF00000);
			else
				htid = (ht & 0xFFFFF000);
		} else if (strcmp(*argv, "sample") == 0) {
			__u32 hash;
			unsigned int divisor = 0x100;
			struct {
				struct tc_u32_sel sel;
				struct tc_u32_key keys[4];
			} sel2 = {};

			NEXT_ARG();
			if (parse_selector(&argc, &argv, &sel2.sel, n)) {
				fprintf(stderr, "Illegal \"sample\"\n");
				return -1;
			}
			if (sel2.sel.nkeys != 1) {
				fprintf(stderr, "\"sample\" must contain exactly ONE key.\n");
				return -1;
			}
			if (*argv != 0 && strcmp(*argv, "divisor") == 0) {
				NEXT_ARG();
				if (get_unsigned(&divisor, *argv, 0) ||
				    divisor == 0 || divisor > 0x100 ||
				    ((divisor - 1) & divisor)) {
					fprintf(stderr, "Illegal sample \"divisor\"\n");
					return -1;
				}
				NEXT_ARG();
			}
			hash = sel2.sel.keys[0].val & sel2.sel.keys[0].mask;
			hash ^= hash >> 16;
			hash ^= hash >> 8;
			htid = ((hash % divisor) << 12) | (htid & 0xFFF00000);
			sample_ok = 1;
			continue;
		} else if (strcmp(*argv, "indev") == 0) {
			char ind[IFNAMSIZ + 1] = {};

			argc--;
			argv++;
			if (argc < 1) {
				fprintf(stderr, "Illegal indev\n");
				return -1;
			}
			strncpy(ind, *argv, sizeof(ind) - 1);
			addattr_l(n, MAX_MSG, TCA_U32_INDEV, ind, strlen(ind) + 1);
		} else if (matches(*argv, "action") == 0) {
			NEXT_ARG();
			if (parse_action(&argc, &argv, TCA_U32_ACT, n)) {
				fprintf(stderr, "Illegal \"action\"\n");
				return -1;
			}
			terminal_ok++;
			continue;
		} else if (matches(*argv, "police") == 0) {
			NEXT_ARG();
			if (parse_police(&argc, &argv, TCA_U32_POLICE, n)) {
				fprintf(stderr, "Illegal \"police\"\n");
				return -1;
			}
			terminal_ok++;
			continue;
		} else if (strcmp(*argv, "skip_hw") == 0) {
			NEXT_ARG();
			flags |= TCA_CLS_FLAGS_SKIP_HW;
			continue;
		} else if (strcmp(*argv, "skip_sw") == 0) {
			NEXT_ARG();
			flags |= TCA_CLS_FLAGS_SKIP_SW;
			continue;
		} else if (strcmp(*argv, "help") == 0) {
			explain();
			return -1;
		} else {
			fprintf(stderr, "What is \"%s\"?\n", *argv);
			explain();
			return -1;
		}
		argc--; argv++;
	}

	/* We dont necessarily need class/flowids */
	if (terminal_ok)
		sel.sel.flags |= TC_U32_TERMINAL;

	if (order) {
		if (TC_U32_NODE(t->tcm_handle) && order != TC_U32_NODE(t->tcm_handle)) {
			fprintf(stderr, "\"order\" contradicts \"handle\"\n");
			return -1;
		}
		t->tcm_handle |= order;
	}

	if (htid)
		addattr_l(n, MAX_MSG, TCA_U32_HASH, &htid, 4);
	if (sel_ok) // 设置match参数
		addattr_l(n, MAX_MSG, TCA_U32_SEL, &sel, sizeof(sel.sel) +
			  sel.sel.nkeys * sizeof(struct tc_u32_key));
	if (flags) {
		if (!(flags ^ (TCA_CLS_FLAGS_SKIP_HW | TCA_CLS_FLAGS_SKIP_SW))) {
			fprintf(stderr, "skip_hw and skip_sw are mutually exclusive\n");
			return -1;
		}
		addattr_l(n, MAX_MSG, TCA_U32_FLAGS, &flags, 4);
	}

	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
	return 0;
}

内核态实现

数据结构

u32会将配置到同一个qdisc以及其内部class的filter统一管理起来。它在qdisc对象中增加了一个专用字段(类型为void *)u32_mode,该字段实际中指向tc_u_common结构。

tc_u_common

通过tc_u_common.hlist,u32将所有配置的filter组织成一个单链表。

struct tc_u_common
{
	struct tc_u_hnode *hlist; // tc_u_hnode链表表头
	struct Qdisc *q; // 指向qdisc
	int	refcnt; // 引用计数,初始为1,链表中每个tc_u_hode也都会持有一个该结构的引用计数
	u32	hgenerator; // 用于自动生成hash table ID
};

filter对象: tc_u_hnode

在Netlink接口中看到,同一个filter链表中,相同优先级的filter命令会对应同一个filter对象(tcf_proto),对于u32,也会相应的关联一个tc_u_hnode对象,二者通过tcf_proto.root字段关联到一起,具体见u32_init()。

struct tc_u_hnode
{
	struct tc_u_hnode *next; // 将tc_u_hnode对象组织到tc_u_common.hlist中
	u32	handle; // 只保存filter的hash ID(最高12位有效,其它位肯定是0)
	u32	prio; // filter的优先级,prio越小,优先级越高,优先级高的filter对象会被放到链表的开头
	struct tc_u_common *tp_c; // 和u32_mode指向同一个tc_u_common对象
	int	refcnt;
	unsigned divisor; // ht数组长度减1
	struct tc_u_knode *ht[1]; // 用filter ID的中间8位(hash ID)索引该数组
};

match信息: tc_u_knode

struct tc_u_knode
{
	struct tc_u_knode *next;
	u32	handle; // 保存完整的32位句柄
	struct tc_u_hnode	*ht_up;
	struct tcf_exts		exts;
#ifdef CONFIG_NET_CLS_IND
	char indev[IFNAMSIZ];
#endif
	u8	fshift;
	struct tcf_result res; // res.classid字段保存了分类结果
	struct tc_u_hnode	*ht_down;
#ifdef CONFIG_CLS_U32_PERF
	struct tc_u32_pcnt	*pf;
#endif
#ifdef CONFIG_CLS_U32_MARK
	struct tc_u32_mark	mark;
#endif
	struct tc_u32_sel sel; // 保存tc命令行中的match信息,可能多条
};

u32 filter操作集: cls_u32_ops

u32模块初始化时,会通过register_tcf_proto_ops()接口向内核流控框架注册该实例,如此内核才可以支持u32 filter。

static struct tcf_proto_ops cls_u32_ops __read_mostly = {
	.kind		=	"u32",
	.classify	=	u32_classify,
	.init		=	u32_init,
	.destroy	=	u32_destroy,
	.get		=	u32_get,
	.put		=	u32_put,
	.change		=	u32_change,
	.delete		=	u32_delete,
	.walk		=	u32_walk,
	.dump		=	u32_dump,
	.owner		=	THIS_MODULE,
};

初始化: u32_init()

用户态通过命令新建u32 filter时,内核态在分配tcf_proto对象后,会调用该回调完成u32特有的初始化。

static int u32_init(struct tcf_proto *tp)
{
	struct tc_u_hnode *root_ht;
	struct tc_u_common *tp_c;

    // u32 filter在qdisc对象中增加了一个专用指针字段u32_mode,用于保存私有信息
	tp_c = tp->q->u32_node;

    // 分配hmode并对其进行初始化
	root_ht = kzalloc(sizeof(*root_ht), GFP_KERNEL);
	if (root_ht == NULL)
		return -ENOBUFS;
	root_ht->divisor = 0;
	root_ht->refcnt++;
	// 初始化filter句柄中的哈希表ID部分(高12位)。
	// 第一个filter的hash table ID会是0x800,后续的filter会在[0x800, 0xFFF]之间选一个未使用的
	root_ht->handle = tp_c ? gen_new_htid(tp_c) : 0x80000000;
	root_ht->prio = tp->prio;

	if (tp_c == NULL) {
	    // qdsic对象中u32_mode尚未分配,则分配它,该流程只走一次
		tp_c = kzalloc(sizeof(*tp_c), GFP_KERNEL);
		if (tp_c == NULL) {
			kfree(root_ht);
			return -ENOBUFS;
		}
		tp_c->q = tp->q;
		tp->q->u32_node = tp_c;
	}
    // 新建的filter私有数据结构tc_u_hnode对象插入tc_u_common.hlist链表的开头
	tp_c->refcnt++;
	root_ht->next = tp_c->hlist;
	tp_c->hlist = root_ht;
	root_ht->tp_c = tp_c;
    // 将私有数据结构对象tc_u_hnode和通用的filter对象tcf_proto关联起来
	tp->root = root_ht;
	tp->data = tp_c;
	return 0;
}

获取filter对象/match对象: u32_get()

根据句柄可以获取hnode对象,或者更加具体的knode对象。

// 提取filter句柄中的hash ID部分,即高12位
#define TC_U32_HTID(h) ((h)&0xFFF00000)
#define TC_U32_ROOT	(0xFFF00000)
#define TC_U32_KEY(h) ((h)&0xFFFFF)

static unsigned long u32_get(struct tcf_proto *tp, u32 handle)
{
	struct tc_u_hnode *ht;
	struct tc_u_common *tp_c = tp->data;

	if (TC_U32_HTID(handle) == TC_U32_ROOT)
		ht = tp->root;
	else
		ht = u32_lookup_ht(tp_c, TC_U32_HTID(handle));
    // 未找到filter对象
	if (!ht)
		return 0;
    // 未指定更详细的match句柄,返回filter对象
	if (TC_U32_KEY(handle) == 0)
		return (unsigned long)ht;
    // 指定了详细的match句柄,返回match对象
	return (unsigned long)u32_lookup_key(ht, handle);
}

// 根据hash table ID在tc_u_common.hlist中找到handle对应的hnode
static __inline__ struct tc_u_hnode *u32_lookup_ht(struct tc_u_common *tp_c, u32 handle)
{
	struct tc_u_hnode *ht;

	for (ht = tp_c->hlist; ht; ht = ht->next)
		if (ht->handle == handle)
			break;
	return ht;
}

#define TC_U32_HASH(h) (((h)>>12)&0xFF)

static __inline__ struct tc_u_knode *u32_lookup_key(struct tc_u_hnode *ht, u32 handle)
{
	unsigned sel;
	struct tc_u_knode *n = NULL;

	sel = TC_U32_HASH(handle);
	if (sel > ht->divisor)
		goto out;
	for (n = ht->ht[sel]; n; n = n->next)
		if (n->handle == handle)
			break;
out:
	return n;
}

修改参数: u32_change()

@tp: filter对象;
@base: 如果filter的父亲为class,则为class操作集的get()返回值;否则为0;
@handle: filter的句柄,如果不指定则为0;
@tca: filter的参数;
@arg: filter操作集get()返回值, arg有效则修改指定的knode,否则修改hnode;
static int u32_change(struct tcf_proto *tp, unsigned long base, u32 handle,
    struct nlattr **tca, unsigned long *arg)
{
	struct tc_u_common *tp_c = tp->data;
	struct tc_u_hnode *ht;
	struct tc_u_knode *n;
	struct tc_u32_sel *s;
	struct nlattr *opt = tca[TCA_OPTIONS];
	struct nlattr *tb[TCA_U32_MAX + 1];
	u32 htid;
	int err;

	if (opt == NULL)
		return handle ? -EINVAL : 0;
    // 解析Netlink参数
	err = nla_parse_nested(tb, TCA_U32_MAX, opt, u32_policy);
	if (err < 0)
		return err;

    // 如果参数已经指定了要修改的knode对象,直接修改内容即可
	if ((n = (struct tc_u_knode*)*arg) != NULL) {
		if (TC_U32_KEY(n->handle) == 0)
			return -EINVAL;
        // 设置参数
		return u32_set_parms(tp, base, n->ht_up, n, tb, tca[TCA_RATE]);
	}

	if (tb[TCA_U32_DIVISOR]) {
		unsigned divisor = nla_get_u32(tb[TCA_U32_DIVISOR]);

		if (--divisor > 0x100) // divisor不能超过256
			return -EINVAL;
		if (TC_U32_KEY(handle))
			return -EINVAL;
		if (handle == 0) {
			handle = gen_new_htid(tp->data);
			if (handle == 0)
				return -ENOMEM;
		}
		ht = kzalloc(sizeof(*ht) + divisor*sizeof(void*), GFP_KERNEL);
		if (ht == NULL)
			return -ENOBUFS;
		ht->tp_c = tp_c;
		ht->refcnt = 1;
		ht->divisor = divisor;
		ht->handle = handle;
		ht->prio = tp->prio;
		ht->next = tp_c->hlist;
		tp_c->hlist = ht;
		*arg = (unsigned long)ht;
		return 0;
	}

	if (tb[TCA_U32_HASH]) {
		htid = nla_get_u32(tb[TCA_U32_HASH]);
		if (TC_U32_HTID(htid) == TC_U32_ROOT) {
			ht = tp->root;
			htid = ht->handle;
		} else {
			ht = u32_lookup_ht(tp->data, TC_U32_HTID(htid));
			if (ht == NULL)
				return -EINVAL;
		}
	} else {
	    // 从filter私有数据中找到hash table ID
		ht = tp->root;
		htid = ht->handle;
	}

	if (ht->divisor < TC_U32_HASH(htid))
		return -EINVAL;

    // 生成knode的句柄
	if (handle) {
		if (TC_U32_HTID(handle) && TC_U32_HTID(handle^htid))
			return -EINVAL;
		handle = htid | TC_U32_NODE(handle);
	} else
	    // 自动生成node ID,和hash table ID组合成完整的handle
		handle = gen_new_kid(ht, htid);

	if (tb[TCA_U32_SEL] == NULL) // 必须提供match信息
		return -EINVAL;
	s = nla_data(tb[TCA_U32_SEL]);

    // 分配knode信息,knode末尾保存的是所有的match信息,见tc_u_sel的定义
	n = kzalloc(sizeof(*n) + s->nkeys*sizeof(struct tc_u32_key), GFP_KERNEL);
	if (n == NULL)
		return -ENOBUFS;

#ifdef CONFIG_CLS_U32_PERF
	n->pf = kzalloc(sizeof(struct tc_u32_pcnt) + s->nkeys*sizeof(u64), GFP_KERNEL);
	if (n->pf == NULL) {
		kfree(n);
		return -ENOBUFS;
	}
#endif
    // 拷贝用户态组织好的match信息
	memcpy(&n->sel, s, sizeof(*s) + s->nkeys*sizeof(struct tc_u32_key));
	n->ht_up = ht;
	n->handle = handle;
	n->fshift = s->hmask ? ffs(ntohl(s->hmask)) - 1 : 0;

#ifdef CONFIG_CLS_U32_MARK
	if (tb[TCA_U32_MARK]) {
		struct tc_u32_mark *mark;

		mark = nla_data(tb[TCA_U32_MARK]);
		memcpy(&n->mark, mark, sizeof(struct tc_u32_mark));
		n->mark.success = 0;
	}
#endif
    // 设置速率信息到新的knode中
	err = u32_set_parms(tp, base, ht, n, tb, tca[TCA_RATE]);
	if (err == 0) {
	    // 遍历哈希表的冲突链,找到新的knode的插入位置,该冲突链中的knode是根据node ID由小到大保存的
		struct tc_u_knode **ins;
		for (ins = &ht->ht[TC_U32_HASH(handle)]; *ins; ins = &(*ins)->next)
			if (TC_U32_NODE(handle) < TC_U32_NODE((*ins)->handle))
				break;
        // 将新的knode插入链表中
		n->next = *ins;
		tcf_tree_lock(tp);
		*ins = n;
		tcf_tree_unlock(tp);
        // 返回新的filter knode对象
		*arg = (unsigned long)n;
		return 0;
	}
#ifdef CONFIG_CLS_U32_PERF
	kfree(n->pf);
#endif
	kfree(n);
	return err;
}

分类: u32_classify()

判定skb是否可以由filter得出分类判定结果res。逻辑没看懂…

static int u32_classify(struct sk_buff *skb, struct tcf_proto *tp, struct tcf_result *res)
{
	struct {
		struct tc_u_knode *knode;
		u8		  *ptr;
	} stack[TC_U32_MAXDEPTH];

	struct tc_u_hnode *ht = (struct tc_u_hnode*)tp->root; // 找到该filter的私有数据结构
	u8 *ptr = skb_network_header(skb);
	struct tc_u_knode *n;
	int sdepth = 0;
	int off2 = 0;
	int sel = 0;
#ifdef CONFIG_CLS_U32_PERF
	int j;
#endif
	int i, r;

next_ht:
	n = ht->ht[sel];

next_knode:
    // 遍历knode链表去匹配
	if (n) {
		struct tc_u32_key *key = n->sel.keys;

#ifdef CONFIG_CLS_U32_PERF
		n->pf->rcnt +=1;
		j = 0;
#endif

#ifdef CONFIG_CLS_U32_MARK
		if ((skb->mark & n->mark.mask) != n->mark.val) {
			n = n->next;
			goto next_knode;
		} else {
			n->mark.success++;
		}
#endif
        // 同一条filter命令中配置的match条件是逻辑与关系,遍历该nkeys数组
		for (i = n->sel.nkeys; i>0; i--, key++) {
			if ((*(__be32*)(ptr+key->off+(off2&key->offmask))^key->val)&key->mask) {
				n = n->next;
				goto next_knode;
			}
#ifdef CONFIG_CLS_U32_PERF
			n->pf->kcnts[j] +=1;
			j++;
#endif
		}
		if (n->ht_down == NULL) {
check_terminal:
			if (n->sel.flags&TC_U32_TERMINAL) {

				*res = n->res;
#ifdef CONFIG_NET_CLS_IND
				if (!tcf_match_indev(skb, n->indev)) {
					n = n->next;
					goto next_knode;
				}
#endif
#ifdef CONFIG_CLS_U32_PERF
				n->pf->rhit +=1;
#endif
				r = tcf_exts_exec(skb, &n->exts, res);
				if (r < 0) {
					n = n->next;
					goto next_knode;
				}
				return r;
			}
			n = n->next;
			goto next_knode;
		}

		/* PUSH */
		if (sdepth >= TC_U32_MAXDEPTH)
			goto deadloop;
		stack[sdepth].knode = n;
		stack[sdepth].ptr = ptr;
		sdepth++;

		ht = n->ht_down;
		sel = 0;
		if (ht->divisor)
			sel = ht->divisor&u32_hash_fold(*(__be32*)(ptr+n->sel.hoff), &n->sel,n->fshift);

		if (!(n->sel.flags&(TC_U32_VAROFFSET|TC_U32_OFFSET|TC_U32_EAT)))
			goto next_ht;

		if (n->sel.flags&(TC_U32_OFFSET|TC_U32_VAROFFSET)) {
			off2 = n->sel.off + 3;
			if (n->sel.flags&TC_U32_VAROFFSET)
				off2 += ntohs(n->sel.offmask & *(__be16*)(ptr+n->sel.offoff)) >>n->sel.offshift;
			off2 &= ~3;
		}
		if (n->sel.flags&TC_U32_EAT) {
			ptr += off2;
			off2 = 0;
		}
		if (ptr < skb_tail_pointer(skb))
			goto next_ht;
	}

	/* POP */
	if (sdepth--) {
		n = stack[sdepth].knode;
		ht = n->ht_up;
		ptr = stack[sdepth].ptr;
		goto check_terminal;
	}
	return -1;

deadloop:
	if (net_ratelimit())
		printk("cls_u32: dead loop\n");
	return -1;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
libpcap是一个用于捕获网络数据包的库,它提供了一组函数和工具,可以用于编写网络数据包捕获程序。设置过滤器是libpcap中的一个重要功能,它允许我们根据特定的条件过滤出我们感兴趣的数据包。 在libpcap中,设置过滤器需要使用BPF(Berkeley Packet Filter)语法。BPF语法允许我们定义一系列规则,用于匹配和过滤数据包。以下是设置过滤器的一般步骤: 1. 打开网络设备或者读取一个保存的数据包文件。 2. 编译过滤器表达式。可以使用`pcap_compile()`函数来编译过滤器表达式,该函数接受过滤器表达式字符串和一个BPF程序结构体作为参数,并将表达式编译为可执行的BPF程序。 3. 设置过滤器。使用`pcap_setfilter()`函数将编译后的BPF程序应用到网络设备或者数据包文件上,以过滤出符合条件的数据包。 4. 开始捕获数据包。使用`pcap_loop()`或者`pcap_next()`等函数开始捕获和处理数据包。 下面是一个简单的示例代码,演示了如何使用libpcap设置过滤器: ```c #include <stdio.h> #include <pcap.h> void packet_handler(u_char *user_data, const struct pcap_pkthdr *pkthdr, const u_char *packet) { // 处理数据包 } int main() { pcap_t *handle; char errbuf[PCAP_ERRBUF_SIZE]; struct bpf_program fp; char filter_exp[] = "port 80"; // 过滤HTTP流量 // 打开网络设备 handle = pcap_open_live("eth0", BUFSIZ, 1, 1000, errbuf); if (handle == NULL) { printf("Error opening device: %s\n", errbuf); return 1; } // 编译过滤器表达式 if (pcap_compile(handle, &fp, filter_exp, 0, PCAP_NETMASK_UNKNOWN) == -1) { printf("Error compiling filter expression: %s\n", pcap_geterr(handle)); return 1; } // 设置过滤器 if (pcap_setfilter(handle, &fp) == -1) { printf("Error setting filter: %s\n", pcap_geterr(handle)); return 1; } // 开始捕获数据包 pcap_loop(handle, 0, packet_handler, NULL); // 关闭网络设备 pcap_close(handle); return 0; } ``` 在上面的示例中,我们使用了过滤器表达式`"port 80"`来过滤出HTTP流量。你可以根据自己的需求修改过滤器表达式,以匹配不同的条件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值