Linux内核中流量控制
linux内核中提供了流量控制的相关处理功能,相关代码在net/sched目录下;而应用层上的控制是通过iproute2软件包中的tc来实现, tc和sched的关系就好象iptables和netfilter的关系一样,一个是用户层接口,一个是具体实现. 流控包括几个部分: 流控算法, 通常在net/sched/sch_*.c中实现, 缺省的是FIFO, 是比较典型的黑盒模式, 对外只看到入队和出对两个操作; 流控结构的操作处理; 和用户空间的控制接口, 是通过rtnetlink实现的。 以下内核代码版本为2.6.24. [数据结构] 流控处理对外表现是一个黑盒,外部只能看到数据入队和出队,但内部队列是如何操作和管理外面是不知道的; 另外处理队列处理外,流控还有一个调度器,该调度器将数据进行分类,然后对不同类型的数据采取不同的流控处理, 所分的类型可能是多级的,形成一个树型的分类树。 流控的基本数据结构 struct Qdisc { int (*enqueue)(struct sk_buff *skb, struct Qdisc *dev);//入队操作 struct sk_buff * (*dequeue)(struct Qdisc *dev);//出队操作 unsigned flags;//标志 #define TCQ_F_BUILTIN 1 #define TCQ_F_THROTTLED 2 #define TCQ_F_INGRESS 4 int padded; struct Qdisc_ops *ops;//Qdisc的基本操作结构 u32 handle;//句柄,用于查找 u32 parent; atomic_t refcnt; struct sk_buff_head q;//数据包链表头 struct net_device *dev;//网卡设备 struct list_head list; //统计信息 struct gnet_stats_basic bstats; struct gnet_stats_queue qstats; //速率估计 struct gnet_stats_rate_est rate_est; //流控锁 spinlock_t *stats_lock; struct rcu_head q_rcu; int (*reshape_fail)(struct sk_buff *skb, struct Qdisc *q); /* This field is deprecated, but it is still used by CBQ * and it will live until better solution will be invented. */ struct Qdisc *__parent;//父节点, 但基本已经被淘汰了 }; 流控队列的基本操作结构 struct Qdisc_ops { struct Qdisc_ops *next;//链表中的下一个 struct Qdisc_class_ops *cl_ops;//类别操作结构 char id[IFNAMSIZ];//Qdisc的名称, 从数组大小看应该就是网卡名称 int priv_size;//私有数据大小 int (*enqueue)(struct sk_buff *, struct Qdisc *);//入队 struct sk_buff * (*dequeue)(struct Qdisc *);//出队 int (*requeue)(struct sk_buff *, struct Qdisc *);//将数据包重新排队 unsigned int (*drop)(struct Qdisc *);//丢弃 int (*init)(struct Qdisc *, struct rtattr *arg);//初始化 void (*reset)(struct Qdisc *);//复位为初始状态,释放缓冲,删除定时器,清空计数器 void (*destroy)(struct Qdisc *);//释放 int (*change)(struct Qdisc *, struct rtattr *arg);//更改Qdisc参数 //输出 int (*dump)(struct Qdisc *, struct sk_buff *); int (*dump_stats)(struct Qdisc *, struct gnet_dump *); struct module *owner; }; 流控队列类别操作结构 struct Qdisc_class_ops { /* Child qdisc manipulation */ int (*graft)(struct Qdisc *, unsigned long cl, struct Qdisc *, struct Qdisc **);//减子节点 struct Qdisc * (*leaf)(struct Qdisc *, unsigned long cl);//增加子节点 void (*qlen_notify)(struct Qdisc *, unsigned long); /* Class manipulation routines */ unsigned long (*get)(struct Qdisc *, u32 classid);//获取, 增加使用计数 void (*put)(struct Qdisc *, unsigned long);//释放, 减少使用计数 int (*change)(struct Qdisc *, u32, u32, struct rtattr **, unsigned long *);//改变 int (*delete)(struct Qdisc *, unsigned long);//删除 void (*walk)(struct Qdisc *, struct qdisc_walker * arg);//遍历 /* Filter manipulation */ struct tcf_proto ** (*tcf_chain)(struct Qdisc *, unsigned long); unsigned long (*bind_tcf)(struct Qdisc *, unsigned long, u32 classid);//tc捆绑 void (*unbind_tcf)(struct Qdisc *, unsigned long);//tc解除 /* rtnetlink specific */ //输出 int (*dump)(struct Qdisc *, unsigned long, struct sk_buff *skb, struct tcmsg*); int (*dump_stats)(struct Qdisc *, unsigned long, struct gnet_dump *); }; 流控速率控制表结构 struct qdisc_rate_table { struct tc_ratespec rate; u32 data[256]; struct qdisc_rate_table *next; int refcnt; }; 在这结构中有一些相关的流控制数据结构 struct net_device { ...... /* * Cache line mostly used on queue transmit path (qdisc) */ /* device queue lock */ spinlock_t queue_lock ____cacheline_aligned_in_smp; struct Qdisc *qdisc;//发送数据时的队列处理 struct Qdisc *qdisc_sleeping;//网卡停止时保存网卡活动时的队列处理方法 struct list_head qdisc_list;//网卡处理的数据队列链表 unsigned long tx_queue_len; //最大队列长度 /* Max frames per queue allowed */ /* Partially transmitted GSO packet. */ struct sk_buff *gso_skb; /* ingress path synchronizer */ spinlock_t ingress_lock;//输入流控锁 struct Qdisc *qdisc_ingress;//这是对于接收数据时的队列处理 ...... }; [/数据结构] [初始化] 在网卡设备的初始化函数register_netdevice()函数中调用dev_init_scheduler()函数对网卡设备的流控队列处理进行了初始化, 也就是说每个网络网卡设备的qdisc指针都不会是空的: int register_netdevice(struct net_device *dev) { ...... dev_init_scheduler(dev); ...... } void dev_init_scheduler(struct net_device *dev) { qdisc_lock_tree(dev); //处理发出数据的qdisc是必须的,而处理输入数据的qdisc_ingress则不是必须的 //缺省情况下的qdisc dev->qdisc = &noop_qdisc; dev->qdisc_sleeping = &noop_qdisc; INIT_LIST_HEAD(&dev->qdisc_list); qdisc_unlock_tree(dev); dev_watchdog_init(dev); } watchdog初始化 static void dev_watchdog_init(struct net_device *dev) { init_timer(&dev->watchdog_timer); dev->watchdog_timer.data = (unsigned long)dev; dev->watchdog_timer.function = dev_watchdog; } 当我们用命令ifconfig ethX up时进入到系统的ioctl调用路径是 sock_ioctl->dev_ioctl->dev_ifsioc->dev_change_flags->dev_open 顺便说一下dev_open会调用具体设备的open函数dev->open(dev); ->dev_activate(dev); 当然noop_qdisc中的调度是不可用的, 只进行丢包处理;网卡在激活(dev_open)时会调用 dev_activate()函数重新对qdisc指针赋值,但未对qdisc_ingress赋值. void dev_activate(struct net_device *dev) { /* No queueing discipline is attached to device; create default one i.e. pfifo_fast for devices, which need queueing and noqueue_qdisc for virtual interfaces */ if (dev->qdisc_sleeping == &noop_qdisc) {//如果当前的qdisc_sleeping是noop_qdisc,重新找一个流控操作指针 struct Qdisc *qdisc; if (dev->tx_queue_len) {//前提条件是发送队列长度非0, 这正常情况肯定满足的 //对dev设备建立fifo处理, 只是缺省的网卡发送策略: FIFO, 先入先出 qdisc = qdisc_create_dflt(dev, &pfifo_fast_ops, TC_H_ROOT); if (qdisc == NULL) { printk(KERN_INFO "%s: activation failed\n", dev->name); return; } list_add_tail(&qdisc->list, &dev->qdisc_list); } else { qdisc = &noqueue_qdisc;//为0,不要入队出队处理 } dev->qdisc_sleeping = qdisc;//对qdisc_sleeping赋值 } if (!netif_carrier_ok(dev))//如果现在网线没插, 返回 /* Delay activation until next carrier-on event */ return; spin_lock_bh(&dev->queue_lock); rcu_assign_pointer(dev->qdisc, dev->qdisc_sleeping);//将网卡当前的qdisc赋值为qdisc_sleeping所指的qdisc if (dev->qdisc != &noqueue_qdisc) {//启动watchdog dev->trans_start = jiffies; dev_watchdog_up(dev); } spin_unlock_bh(&dev->queue_lock); } struct Qdisc * qdisc_create_dflt(struct net_device *dev, struct Qdisc_ops *ops, unsigned int parentid) { struct Qdisc *sch; sch = qdisc_alloc(dev, ops);//分配Qdisc结构 if (IS_ERR(sch)) goto errout; sch->stats_lock = &dev->queue_lock; sch->parent = parentid; if (!ops->init || ops->init(sch, NULL) == 0)//参考下面默认操作结构实现 return sch; qdisc_destroy(sch);//初始化失败, 释放Qdisc errout: return NULL; } 分配新的Qdisc结构, Qdisc的操作结构由函数参数指定 struct Qdisc *qdisc_alloc(struct net_device *dev, struct Qdisc_ops *ops) { void *p; struct Qdisc *sch; unsigned int size; int err = -ENOBUFS; /* ensure that the Qdisc and the private data are 32-byte aligned */ //#define QDISC_ALIGNTO 32 //#define QDISC_ALIGN(len) (((len) + QDISC_ALIGNTO-1) & ~(QDISC_ALIGNTO-1)) //32字节对齐 size = QDISC_ALIGN(sizeof(*sch)); size += ops->priv_size + (QDISC_ALIGNTO - 1);//增加私有数据空间 p = kzalloc(size, GFP_KERNEL); if (!p) goto errout; sch = (struct Qdisc *) QDISC_ALIGN((unsigned long) p);//确保sch地址是32字节对齐的 sch->padded = (char *) sch - (char *) p;//地址移动后的偏移量 INIT_LIST_HEAD(&sch->list); skb_queue_head_init(&sch->q); //Qdisc结构参数 sch->ops = ops; sch->enqueue = ops->enqueue; sch->dequeue = ops->dequeue; sch->dev = dev; dev_hold(dev); atomic_set(&sch->refcnt, 1); return sch; errout: return ERR_PTR(-err); } [/初始化] [输入流程] 输入流控好象不是必须的,目前内核需要配置CONFIG_NET_CLS_ACT选项才起作用. int netif_receive_skb(struct sk_buff *skb) { ...... #ifdef CONFIG_NET_CLS_ACT if (skb->tc_verd & TC_NCLS) { skb->tc_verd = CLR_TC_NCLS(skb->tc_verd); goto ncls; } #endif ...... #ifdef CONFIG_NET_CLS_ACT skb = handle_ing(skb, &pt_prev, &ret, orig_dev); if (!skb) goto out; ncls: #endif ...... } static inline struct sk_buff *handle_ing(struct sk_buff *skb, struct packet_type **pt_prev, int *ret, struct net_device *orig_dev) { if (!skb->dev->qdisc_ingress)//如果网卡设备没有输入流控处理,参考下面输入流量控制实现 goto out; if (*pt_prev) { *ret = deliver_skb(skb, *pt_prev, orig_dev); *pt_prev = NULL; } else { /* Huh? Why does turning on AF_PACKET affect this? */ skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd); } switch (ing_filter(skb)) { //输入处理 case TC_ACT_SHOT: case TC_ACT_STOLEN: kfree_skb(skb); return NULL; } out: skb->tc_verd = 0; return skb; } static int ing_filter(struct sk_buff *skb) { struct Qdisc *q; struct net_device *dev = skb->dev; int result = TC_ACT_OK; u32 ttl = G_TC_RTTL(skb->tc_verd); if (MAX_RED_LOOP < ttl++) { printk(KERN_WARNING "Redir loop detected Dropping packet (%d->%d)\n", skb->iif, dev->ifindex); return TC_ACT_SHOT; } //设置数据包的TC参数 skb->tc_verd = SET_TC_RTTL(skb->tc_verd, ttl); skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_INGRESS); spin_lock(&dev->ingress_lock); if ((q = dev->qdisc_ingress) != NULL) result = q->enqueue(skb, q);//数据入队,参考下面输入流控制实现 spin_unlock(&dev->ingress_lock); return result; } [/输入流程] [输出流程] 数据发出流控处理时,上层的所有处理已经完成,数据包已经交到网卡设备进行发送, 在数据发送时进行相关的流控处理网络数据的出口函数为dev_queue_xmit(); int dev_queue_xmit(struct sk_buff *skb) { ...... q = rcu_dereference(dev->qdisc);//获取网卡的qdisc指针 #ifdef CONFIG_NET_CLS_ACT skb->tc_verd = SET_TC_AT(skb->tc_verd,AT_EGRESS); #endif //如果队列输入函数非空, 将数据包入队 //对于物理网卡设备, 缺省使用的是FIFO qdisc, 该成员函数非空, 只有虚拟网卡才可能为空 if (q->enqueue) { /* Grab device queue */ spin_lock(&dev->queue_lock); q = dev->qdisc;//可以直接访问dev->qdisc了 if (q->enqueue) { /* reset queue_mapping to zero */ skb_set_queue_mapping(skb, 0); //实现 skb->queue_mapping = 0 rc = q->enqueue(skb, q);//入队处理 qdisc_run(dev);//运行流控, 出队操作,发送数据 spin_unlock(&dev->queue_lock); rc = rc == NET_XMIT_BYPASS ? NET_XMIT_SUCCESS : rc; goto out; } spin_unlock(&dev->queue_lock); } ...... } 出队操作 static inline void qdisc_run(struct net_device *dev) { if (!netif_queue_stopped(dev) && !test_and_set_bit(__LINK_STATE_QDISC_RUNNING, &dev->state)) __qdisc_run(dev); } void __qdisc_run(struct net_device *dev) { do { if (!qdisc_restart(dev)) //发送数据 break; } while (!netif_queue_stopped(dev));//设备没有停止发送 clear_bit(__LINK_STATE_QDISC_RUNNING, &dev->state); } static inline int qdisc_restart(struct net_device *dev) { struct Qdisc *q = dev->qdisc; struct sk_buff *skb; int ret = NETDEV_TX_BUSY; /* Dequeue packet */ if (unlikely((skb = dev_dequeue_skb(dev, q)) == NULL))//数据包出队,主要是skb = q->dequeue(q) return 0; /* And release queue */ spin_unlock(&dev->queue_lock); HARD_TX_LOCK(dev, smp_processor_id()); //根据设备的特性 dev->features & NETIF_F_LLTX,可以在发送时不上锁 if (!netif_subqueue_stopped(dev, skb)) //设备子队列没有停止 ret = dev_hard_start_xmit(skb, dev);//发送数据 HARD_TX_UNLOCK(dev); spin_lock(&dev->queue_lock); q = dev->qdisc; switch (ret) { case NETDEV_TX_OK: /* Driver sent out skb successfully */ ret = qdisc_qlen(q); //返回队列中还存在的skb的个数 break; case NETDEV_TX_LOCKED: /* Driver try lock failed */ ret = handle_dev_cpu_collision(skb, dev, q); //发送锁已经被cpu获取导致一个冲突,处理这个冲突 break; default: /* Driver returned NETDEV_TX_BUSY - requeue skb */ if (unlikely (ret != NETDEV_TX_BUSY && net_ratelimit())) printk(KERN_WARNING "BUG %s code %d qlen %d\n", dev->name, ret, q->q.qlen); //设备忙,重新入队 q->ops->requeue(skb, q); 重新调度设备,然后返回0 ret = dev_requeue_skb(skb, dev, q); break; } return ret; } [/输出流程] [默认操作结构实现] PFIFO_FAST是缺省的流控算法,网卡初始化时就是设置该算法为网卡的流控算法, 算法比较简单,就在net/sched/sch_generic.c中定义了,没在单独文件中定义. static struct Qdisc_ops pfifo_fast_ops = { .id = "pfifo_fast", .priv_size = PFIFO_FAST_BANDS * sizeof(struct sk_buff_head), .enqueue = pfifo_fast_enqueue, .dequeue = pfifo_fast_dequeue, .requeue = pfifo_fast_requeue, .init = pfifo_fast_init, .reset = pfifo_fast_reset, .dump = pfifo_fast_dump, .owner = THIS_MODULE, }; #define PFIFO_FAST_BANDS 3 初始化 static int pfifo_fast_init(struct Qdisc *qdisc, struct rtattr *opt) { int prio; struct sk_buff_head *list = qdisc_priv(qdisc);//qdisc私有数据指针, 数据包链表头 //初始化3个链表头 for (prio = 0; prio < PFIFO_FAST_BANDS; prio++) skb_queue_head_init(list + prio); return 0; } 入队 static int pfifo_fast_enqueue(struct sk_buff *skb, struct Qdisc* qdisc) { //根据数据包的优先级参数挑一个队列头准备将数据包插入该队列 struct sk_buff_head *list = prio2list(skb, qdisc); //如果当前队列中的数据包数量小于网卡设备允许的输出队列的数量则将该数据包插入该队列 if (skb_queue_len(list) < qdisc->dev->tx_queue_len) { qdisc->q.qlen++; return __qdisc_enqueue_tail(skb, qdisc, list); } return qdisc_drop(skb, qdisc);//否则的话丢弃该数据包 } //优先权值到队列号的变换数组, 该数组体现算法内容, 通过修改该数组可以调整算法效果 //该数组定义中, 优先值(低4位)为1,2,3,5时使用2号队列, 优先值(低4位)为6,7时使用0号队列, 其他值为1号队列 //在普通情况下skb->priority都是0, 所以应该只使用了1号队列 //这个数组实际是根据RFC1349中定义的TOS类型值定义的, 在该RFC中TOS就是只有4位有效 static const u8 prio2band[TC_PRIO_MAX+1] = { 1, 2, 2, 2, 1, 2, 0, 0 , 1, 1, 1, 1, 1, 1, 1, 1 }; 选队列处理 static inline struct sk_buff_head *prio2list(struct sk_buff *skb, struct Qdisc *qdisc) { struct sk_buff_head *list = qdisc_priv(qdisc);//qdisc私有数据指针, 数据包链表头 //根据数据包的优先权值确定队列头偏移值 //skb->priority是个32位整数, 只使用最后4位 return list + prio2band[skb->priority & TC_PRIO_MAX]; } 出队 static struct sk_buff *pfifo_fast_dequeue(struct Qdisc* qdisc) { int prio; struct sk_buff_head *list = qdisc_priv(qdisc); //循环3个队列 for (prio = 0; prio < PFIFO_FAST_BANDS; prio++) { if (!skb_queue_empty(list + prio)) {//如果队列非空, 返回队列头的那个数据包 qdisc->q.qlen--; return __qdisc_dequeue_head(qdisc, list + prio); } } return NULL; } 重入队 static int pfifo_fast_requeue(struct sk_buff *skb, struct Qdisc* qdisc) { qdisc->q.qlen++;//队列长度递增 return __qdisc_requeue(skb, qdisc, prio2list(skb, qdisc));//使用标准重入队函数将数据插回队列链表 } 复位 static void pfifo_fast_reset(struct Qdisc* qdisc) { int prio; struct sk_buff_head *list = qdisc_priv(qdisc); //释放三个队列链表中的所有数据包 for (prio = 0; prio < PFIFO_FAST_BANDS; prio++) __qdisc_reset_queue(qdisc, list + prio); //skb_queue_purge(list); //计数清零 qdisc->qstats.backlog = 0; qdisc->q.qlen = 0; } 输出当前算法的内容信息, 由于PFIFO_FAST算法核心就是prio2band数组, 因此就是将该数组内容输出到数据包供用户空间获取 static int pfifo_fast_dump(struct Qdisc *qdisc, struct sk_buff *skb) { struct tc_prio_qopt opt = { .bands = PFIFO_FAST_BANDS };//TC优先权数组结构 memcpy(&opt.priomap, prio2band, TC_PRIO_MAX+1);//将当前prio2band数组内容拷贝到选项数据中 RTA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt);//将结构作为路由属性复制到数据包中供返回 return skb->len; rtattr_failure: return -1; } [/默认操作结构实现] [用户内核交互实现] 在net/sched/sch_api.c中有初始化函数 subsys_initcall(pktsched_init);启动时自动初始化 static int __init pktsched_init(void) { //登记FIFO流控处理, 这是网卡设备基本流控方法, 缺省必有的 register_qdisc(&pfifo_qdisc_ops); register_qdisc(&bfifo_qdisc_ops); proc_net_fops_create(&init_net, "psched", 0, &psched_fops); //在注册了一些流量控制操作后如果要使用,就要在用户空间使用命令行工具配置 //然后和内核交互,告诉内核使用新的或改变一些流量控制操作(也就是改变了流量控制算法) //下面就是为通过rtnetlink和用户交互而注册的函数和交互类型 rtnl_register(PF_UNSPEC, RTM_NEWQDISC, tc_modify_qdisc, NULL); //Qdisc操作, 也就是对应tc qdisc add/modify等操作 rtnl_register(PF_UNSPEC, RTM_DELQDISC, tc_get_qdisc, NULL); //删除/获取Qdisc操作 rtnl_register(PF_UNSPEC, RTM_GETQDISC, tc_get_qdisc, tc_dump_qdisc);//获取Qdisc信息, 也就是对应tc qdisc show //类别操作是通过tc class命令来完成的, 当网卡使用的流控算法是可分类的(如HTB, CBQ等)时候使用, 功能是对Qdisc根节点进行划分, //定义出分类树, 同时可定义每个类别的流量限制参数,但具体那些数据属于哪一类则是通过tc filter命令来实现。 //class操作, 也就是对应tc class add/delete/modify/get等操作 rtnl_register(PF_UNSPEC, RTM_NEWTCLASS, tc_ctl_tclass, NULL); rtnl_register(PF_UNSPEC, RTM_DELTCLASS, tc_ctl_tclass, NULL); rtnl_register(PF_UNSPEC, RTM_GETTCLASS, tc_ctl_tclass, tc_dump_tclass); return 0; } 创建/修改qdisc static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg) { struct tcmsg *tcm; struct rtattr **tca; struct net_device *dev; u32 clid; struct Qdisc *q, *p; int err; replay: /* Reinit, just in case something touches this. */ tcm = NLMSG_DATA(n); //从netlink中取出数据,tc消息指针 tca = arg; clid = tcm->tcm_parent;//父类别id q = p = NULL; if ((dev = __dev_get_by_index(&init_net, tcm->tcm_ifindex)) == NULL) //通过索引找设备 return -ENODEV; if (clid) {//指定了父类别ID的情况 if (clid != TC_H_ROOT) {//如果不是根节点 if (clid != TC_H_INGRESS) { //非ingress节点时, 根据类别ID的高16位查找Qdisc节点 if ((p = qdisc_lookup(dev, TC_H_MAJ(clid))) == NULL) return -ENOENT; q = qdisc_leaf(p, clid);//获取p节点的叶子节点, 将调用p->ops->cl_ops->leaf()函数 } else { /*ingress */ q = dev->qdisc_ingress;//使用设备ingress流控 } } else { q = dev->qdisc_sleeping;//根节点情况下流控用的是设备的qdisc_sleeping } /* It may be default qdisc, ignore it */ //如果找到的Qdisc的句柄为0, 放弃q if (q && q->handle == 0) q = NULL; //没找到Qdisc节点, 或没在tc消息中指定句柄值, 或者找到的Qdisc句柄和tc消息中的句柄不同 if (!q || !tcm->tcm_handle || q->handle != tcm->tcm_handle) { if (tcm->tcm_handle) {//TC指定了句柄 //如果Qdisc存在但不是更新命令, 返回对象存在错误 if (q && !(n->nlmsg_flags & NLM_F_REPLACE)) return -EEXIST; if (TC_H_MIN(tcm->tcm_handle))//TC句柄低16位检测 return -EINVAL; //根据TC句柄查找该设备上的Qdisc, 找不到的话跳转到创建新节点操作 if ((q = qdisc_lookup(dev, tcm->tcm_handle)) == NULL) goto create_n_graft; if (n->nlmsg_flags & NLM_F_EXCL)//找到但设置了NLM_F_EXCL排斥标志, 返回对象存在错误 return -EEXIST; //比较TC命令中的算法名称和Qdisc名称算法相同 if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id)) return -EINVAL; //检查算法出现回环情况, p是用clid找到的Qdisc if (q == p || (p && check_loop(q, p, 0))) return -ELOOP; //新找到的Qdisc有效, 转到嫁接操作 atomic_inc(&q->refcnt); goto graft; } else { if (q == NULL)//没指定TC句柄, 如果没找到Qdisc, 跳转到创建新节点 goto create_n_graft; //检查各种标志是否冲突, Qdisc名称是否正确 if ((n->nlmsg_flags&NLM_F_CREATE) && (n->nlmsg_flags&NLM_F_REPLACE) && ((n->nlmsg_flags&NLM_F_EXCL) || (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id)))) goto create_n_graft; } } } else {//如果没指定父类别ID, 从tc消息的句柄来查找Qdisc if (!tcm->tcm_handle) return -EINVAL; q = qdisc_lookup(dev, tcm->tcm_handle); } /* Change qdisc parameters */ //到这里是属于Qdisc修改操作 if (q == NULL)//没找到Qdisc节点, 返回错误 return -ENOENT; if (n->nlmsg_flags&NLM_F_EXCL)//找到Qdisc节点, 但设置了NLM_F_EXCL(排斥)标志, 返回对象存在错误 return -EEXIST; //检查找到的Qdisc节点的名称和tc中指定的是否匹配 if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id)) return -EINVAL; err = qdisc_change(q, tca);//修改Qdisc参数 if (err == 0) qdisc_notify(skb, n, clid, NULL, q); return err; create_n_graft: if (!(n->nlmsg_flags&NLM_F_CREATE))//如果TC命令中没有创建标志, 返回错误 return -ENOENT; if (clid == TC_H_INGRESS) //创建输入流量控制 q = qdisc_create(dev, tcm->tcm_parent, tcm->tcm_parent, tca, &err); else //创建输出流量控制 q = qdisc_create(dev, tcm->tcm_parent, tcm->tcm_handle, tca, &err); if (q == NULL) {//创建失败, 如果不是EAGAIN(重来一次), 返回失败 if (err == -EAGAIN) goto replay; return err; } graft://嫁接操作 if (1) { struct Qdisc *old_q = NULL; err = qdisc_graft(dev, p, clid, q, &old_q);//新的Qdisc节点添加到父节点作为叶节点 if (err) { if (q) { qdisc_lock_tree(dev); qdisc_destroy(q); qdisc_unlock_tree(dev); } return err; } qdisc_notify(skb, n, clid, old_q, q);// Qdisc通告 if (old_q) { qdisc_lock_tree(dev); qdisc_destroy(old_q); qdisc_unlock_tree(dev); } } return 0; } 在指定的网卡设备上创建新的Qdisc结构 static struct Qdisc * qdisc_create(struct net_device *dev, u32 parent, u32 handle, struct rtattr **tca, int *errp) { int err; struct rtattr *kind = tca[TCA_KIND-1]; struct Qdisc *sch; struct Qdisc_ops *ops; ops = qdisc_lookup_ops(kind);//查找相关的Qdisc操作结构,根据id #ifdef CONFIG_KMOD if (ops == NULL && kind != NULL) {//如果没在当前内核中找到,加载相关名称的Qdisc操作内核模块 char name[IFNAMSIZ]; if (rtattr_strlcpy(name, kind, IFNAMSIZ) < IFNAMSIZ) { /* We dropped the RTNL semaphore in order to perform the module load. So, even if we * succeeded in loading the module we have to tell the caller to replay the request. We * indicate this using -EAGAIN. We replay the request because the device may * go away in the mean time. */ rtnl_unlock(); request_module("sch_%s", name);//加载模块 rtnl_lock(); ops = qdisc_lookup_ops(kind);//重新查找 if (ops != NULL) { /* We will try again qdisc_lookup_ops, so don't keep a reference. */ module_put(ops->owner); err = -EAGAIN; goto err_out; } } } #endif err = -ENOENT; if (ops == NULL) goto err_out; //我们假定找到的操作是输入流量控制操作,实现看下面 sch = qdisc_alloc(dev, ops);//分配新的Qdisc结构,我们已经看到过 if (IS_ERR(sch)) { err = PTR_ERR(sch); goto err_out2; } sch->parent = parent; if (handle == TC_H_INGRESS) {//是针对输入进行流控 sch->flags |= TCQ_F_INGRESS; sch->stats_lock = &dev->ingress_lock; handle = TC_H_MAKE(TC_H_INGRESS, 0); } else { sch->stats_lock = &dev->queue_lock; if (handle == 0) { handle = qdisc_alloc_handle(dev); //分配一个唯一的据柄 err = -ENOMEM; if (handle == 0) goto err_out3; } } sch->handle = handle; //调用Qdisc操作结构的初始化函数进行初始化,参考下面输入流量控制实现 if (!ops->init || (err = ops->init(sch, tca[TCA_OPTIONS-1])) == 0) { if (tca[TCA_RATE-1]) { //用户传来了速率属性 //创建预估器 err = gen_new_estimator(&sch->bstats, &sch->rate_est, sch->stats_lock, tca[TCA_RATE-1]); if (err) { /* Any broken qdiscs that would require a ops->reset() here? The qdisc was never * in action so it shouldn't be necessary. */ if (ops->destroy) ops->destroy(sch); goto err_out3; } } qdisc_lock_tree(dev); list_add_tail(&sch->list, &dev->qdisc_list);//将Qdisc结构添加到网卡设备的流控链表 qdisc_unlock_tree(dev); return sch; } err_out3: dev_put(dev); kfree((char *) sch - sch->padded); err_out2: module_put(ops->owner); err_out: *errp = err; return NULL; } "嫁接"Qdisc, 将新的Qdisc节点添加到父节点作为叶节点 static int qdisc_graft(struct net_device *dev, struct Qdisc *parent, u32 classid, struct Qdisc *new, struct Qdisc **old) { int err = 0; struct Qdisc *q = *old; if (parent == NULL) {//父qdisc节点为空, 将新节点作为dev的基本qdisc, 返回dev原来的老的qdisc if (q && q->flags & TCQ_F_INGRESS) { *old = dev_graft_qdisc(dev, q); } else { *old = dev_graft_qdisc(dev, new); } } else {//父qdisc非空情况 //将使用Qdisc类操作结构中的相关成员函数来完成操作 struct Qdisc_class_ops *cops = parent->ops->cl_ops; err = -EINVAL; if (cops) { unsigned long cl = cops->get(parent, classid);//获取类别句柄值 if (cl) { //类别有效, 调用graft成员函数将新节点插入qdisc树中 err = cops->graft(parent, cl, new, old); cops->put(parent, cl); } } } return err; } 将qdisc作为顶层Qdisc节点附着于dev设备 static struct Qdisc * dev_graft_qdisc(struct net_device *dev, struct Qdisc *qdisc) { struct Qdisc *oqdisc; if (dev->flags & IFF_UP)//如果网卡设备是启动的, 先停掉 dev_deactivate(dev); qdisc_lock_tree(dev);//加树锁 if (qdisc && qdisc->flags&TCQ_F_INGRESS) {//是处理输入的流控节点 oqdisc = dev->qdisc_ingress; /* Prune old scheduler */ if (oqdisc && atomic_read(&oqdisc->refcnt) <= 1) { /* delete */ qdisc_reset(oqdisc); dev->qdisc_ingress = NULL; } else { /* new */ dev->qdisc_ingress = qdisc; //第一次安装 } } else {//处理输出 oqdisc = dev->qdisc_sleeping; /* Prune old scheduler */ if (oqdisc && atomic_read(&oqdisc->refcnt) <= 1) qdisc_reset(oqdisc); /* ... and graft new one */ if (qdisc == NULL) qdisc = &noop_qdisc; dev->qdisc_sleeping = qdisc;//将睡眠qdisc(dev启动时将使用的qdisc)赋值为新的qdisc dev->qdisc = &noop_qdisc; } qdisc_unlock_tree(dev); if (dev->flags & IFF_UP) dev_activate(dev);//激活网卡 return oqdisc;//返回dev设备的老的qdisc } 获取/删除qdisc static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg) { struct tcmsg *tcm = NLMSG_DATA(n); struct rtattr **tca = arg; struct net_device *dev; u32 clid = tcm->tcm_parent; struct Qdisc *q = NULL; struct Qdisc *p = NULL; int err; //根据TC参数中的网卡索引号查找网卡设备 if ((dev = __dev_get_by_index(&init_net, tcm->tcm_ifindex)) == NULL) return -ENODEV; if (clid) {//根据类别ID或TC句柄查找Qdisc, 和上面函数类似 if (clid != TC_H_ROOT) { if (TC_H_MAJ(clid) != TC_H_MAJ(TC_H_INGRESS)) { if ((p = qdisc_lookup(dev, TC_H_MAJ(clid))) == NULL) return -ENOENT; q = qdisc_leaf(p, clid); } else { q = dev->qdisc_ingress; } } else { q = dev->qdisc_sleeping; } if (!q) return -ENOENT; if (tcm->tcm_handle && q->handle != tcm->tcm_handle) return -EINVAL; } else { if ((q = qdisc_lookup(dev, tcm->tcm_handle)) == NULL) return -ENOENT; } //检查找到的Qdisc名称和TC命令中指定的是否一致 if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id)) return -EINVAL; if (n->nlmsg_type == RTM_DELQDISC) {//删除Qdisc操作 if (!clid)//必须指定类别ID return -EINVAL; if (q->handle == 0)//如果找到的Qdisc句柄为0, 返回错误 return -ENOENT; //进行Qdisc嫁接操作, 新节点是NULL, 即将叶子节点替换为NULL, 即删除了原叶子节点 //原叶子节点返回到q if ((err = qdisc_graft(dev, p, clid, NULL, &q)) != 0) return err; if (q) {//释放原叶子节点 qdisc_notify(skb, n, clid, q, NULL); qdisc_lock_tree(dev); qdisc_destroy(q); qdisc_unlock_tree(dev); } } else {//非删除操作, 通告一下, q作为获得的Qdisc参数返回 qdisc_notify(skb, n, clid, NULL, q); } return 0; } 输出网卡qdisc参数 static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb) { int idx, q_idx; int s_idx, s_q_idx; struct net_device *dev; struct Qdisc *q; s_idx = cb->args[0];//起始网卡索引 s_q_idx = q_idx = cb->args[1];//起始Qdisc索引 read_lock(&dev_base_lock); idx = 0; //遍历所有网卡 for_each_netdev(&init_net, dev) { //索引值小于所提供的起始索引值, 跳过这个索引和网卡的索引号应该没啥关系 if (idx < s_idx) goto cont; if (idx > s_idx)//索引值大于所提供的起始索引值, 将起始Qdisc索引清零 s_q_idx = 0; q_idx = 0; list_for_each_entry(q, &dev->qdisc_list, list) {//遍历该网卡设备的所有Qdisc //当前Qdisc索引小于起始Qdisc索引, 跳过 if (q_idx < s_q_idx) { q_idx++; continue; } //填充Qdisc信息到数据包 if (tc_fill_qdisc(skb, q, q->parent, NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWQDISC) <= 0) goto done; q_idx++; } cont: idx++; } done: read_unlock(&dev_base_lock); //返回处理的所有网卡数和Qdisc数 cb->args[0] = idx; cb->args[1] = q_idx; return skb->len; } 类别控制操作 static int tc_ctl_tclass(struct sk_buff *skb, struct nlmsghdr *n, void *arg) { struct tcmsg *tcm = NLMSG_DATA(n); struct rtattr **tca = arg; struct net_device *dev; struct Qdisc *q = NULL; struct Qdisc_class_ops *cops; unsigned long cl = 0; unsigned long new_cl; u32 pid = tcm->tcm_parent; u32 clid = tcm->tcm_handle; u32 qid = TC_H_MAJ(clid);//qdisc id: 初始化位类别id的高16位 int err; //根据TC信息中的网卡索引值查找网卡 if ((dev = __dev_get_by_index(&init_net, tcm->tcm_ifindex)) == NULL) return -ENODEV; /* 以下是tc class的parent参数取值的说明 parent == TC_H_UNSPEC - unspecified parent. parent == TC_H_ROOT - class is root, which has no parent. parent == X:0 - parent is root class. parent == X:Y - parent is a node in hierarchy. parent == 0:Y - parent is X:Y, where X:0 is qdisc. 以下是tc class的classid参数取值的说明 handle == 0:0 - generate handle from kernel pool. handle == 0:Y - class is X:Y, where X:0 is qdisc. handle == X:Y - clear. handle == X:0 - root class. */ /* Step 1. Determine qdisc handle X:0 */ if (pid != TC_H_ROOT) {//parent id非根节点的情况 u32 qid1 = TC_H_MAJ(pid); if (qid && qid1) { /* If both majors are known, they must be identical. */ if (qid != qid1) return -EINVAL; } else if (qid1) { qid = qid1; } else if (qid == 0) qid = dev->qdisc_sleeping->handle; /* Now qid is genuine qdisc handle consistent both with parent and child. TC_H_MAJ(pid) still may be unspecified, complete it now. */ if (pid) pid = TC_H_MAKE(qid, pid); } else {//为根节点, 如果当前qid为0, 更新为设备的qdisc_sleeping的handle if (qid == 0) qid = dev->qdisc_sleeping->handle; } /* OK. Locate qdisc */ //根据qid查找该dev上的Qdisc指针, 找不到的话返回失败 if ((q = qdisc_lookup(dev, qid)) == NULL) return -ENOENT; /* An check that it supports classes */ //获取Qdisc的类别操作指针 cops = q->ops->cl_ops; if (cops == NULL)//如果Qdisc是非分类的, 类别操作结构指针位空, 返回失败 return -EINVAL; /* Now try to get class */ //生成合法的类别ID if (clid == 0) { if (pid == TC_H_ROOT) clid = qid; } else clid = TC_H_MAKE(qid, clid); //如果clid非0, 调用get函数获取该类别, 增加类别的引用计数 //cl虽然定义是unsigned long, 但实际是个指针的数值 if (clid) cl = cops->get(q, clid); if (cl == 0) {//类别为空 err = -ENOENT; //如果netlink命令不是新建类别的话, 返回错误 if (n->nlmsg_type != RTM_NEWTCLASS || !(n->nlmsg_flags&NLM_F_CREATE)) goto out; } else { switch (n->nlmsg_type) {//获取类别成功, 根据netlink命令类型进行相关操作 case RTM_NEWTCLASS://新建class err = -EEXIST; if (n->nlmsg_flags&NLM_F_EXCL)//如果设置了互斥标志, 返回错误, 因为现在该class已经存在 goto out; break; case RTM_DELTCLASS://删除class err = cops->delete(q, cl); if (err == 0) tclass_notify(skb, n, q, cl, RTM_DELTCLASS); goto out; case RTM_GETTCLASS://获取class信息, 进行class通知操作 err = tclass_notify(skb, n, q, cl, RTM_NEWTCLASS); goto out; default: err = -EINVAL; goto out; } } new_cl = cl; //不论是新建还是修改class参数, 都是调用类别操作结构的change函数 err = cops->change(q, clid, pid, tca, &new_cl); if (err == 0) tclass_notify(skb, n, q, new_cl, RTM_NEWTCLASS); out: if (cl) cops->put(q, cl); return err; } 类别输出 static int tc_dump_tclass(struct sk_buff *skb, struct netlink_callback *cb) { int t; int s_t; struct net_device *dev; struct Qdisc *q; struct tcmsg *tcm = (struct tcmsg*)NLMSG_DATA(cb->nlh); struct qdisc_dump_args arg; //输入数据长度检查 if (cb->nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*tcm))) return 0; if ((dev = dev_get_by_index(&init_net, tcm->tcm_ifindex)) == NULL)//查找网卡设备 return 0; s_t = cb->args[0];//起始class索引 t = 0; list_for_each_entry(q, &dev->qdisc_list, list) { //当前索引号小于起始索引号, 或者当前Qdisc是非分类的, 或者句柄handle不匹配, 跳过 if (t < s_t || !q->ops->cl_ops || (tcm->tcm_parent && TC_H_MAJ(tcm->tcm_parent) != q->handle)) { t++; continue; } if (t > s_t)//索引号超过了起始索引号, 将从数组1号开始的数据缓冲区清零 memset(&cb->args[1], 0, sizeof(cb->args) - sizeof(cb->args[0])); //填写arg结构参数,输出单个class函数 arg.w.fn = qdisc_class_dump; arg.skb = skb;//数据包指针 arg.cb = cb;//控制块指针 //遍历结构walker参数 arg.w.stop = 0; arg.w.skip = cb->args[1]; arg.w.count = 0; q->ops->cl_ops->walk(q, &arg.w);//调用Qdisc类别操作结构的walk函数遍历该Qdisc所有类别 cb->args[1] = arg.w.count;//记录处理的类别数 if (arg.w.stop)//如果设置了停止标志, 退出循环 break; t++; } cb->args[0] = t;//跳过的Qdisc数, 有的Qdisc可能是跳过没处理的 dev_put(dev); return skb->len; } [/用户内核交互实现] [输入流量控制实现] 现在我们看一下加入我们使用输入流量控制的情况. 在net/sched/sch_ingress.c中定义了输入流量控制实现. 模块初始化 static int __init ingress_module_init(void) { int ret = 0; if ((ret = register_qdisc(&ingress_qdisc_ops)) < 0) { //注册这个操作 printk("Unable to register Ingress qdisc\n"); return ret; } return ret; } static struct Qdisc_ops *qdisc_base; int register_qdisc(struct Qdisc_ops *qops) { struct Qdisc_ops *q, **qp; int rc = -EEXIST; write_lock(&qdisc_mod_lock); //查找看是否有重复 for (qp = &qdisc_base; (q = *qp) != NULL; qp = &q->next) if (!strcmp(qops->id, q->id)) goto out; //三个主要函数如果有空就设置为伪操作(函数什么也不做) if (qops->enqueue == NULL) qops->enqueue = noop_qdisc_ops.enqueue; if (qops->requeue == NULL) qops->requeue = noop_qdisc_ops.requeue; if (qops->dequeue == NULL) qops->dequeue = noop_qdisc_ops.dequeue; qops->next = NULL; *qp = qops; //添加到最后一项 rc = 0; out: write_unlock(&qdisc_mod_lock); return rc; } ingress私有数据结构 struct ingress_qdisc_data { struct Qdisc *q;//内部流控 struct tcf_proto *filter_list;//过滤规则 }; ingress流控操作结构 static struct Qdisc_ops ingress_qdisc_ops = { .next = NULL, .cl_ops = &ingress_class_ops, //类别操作 .id = "ingress", //查找使用 .priv_size = sizeof(struct ingress_qdisc_data), .enqueue = ingress_enqueue, .dequeue = ingress_dequeue, .requeue = ingress_requeue, .drop = ingress_drop, .init = ingress_init, .reset = ingress_reset, .destroy = ingress_destroy, .change = NULL, .dump = ingress_dump, .owner = THIS_MODULE, }; 初始化,在创建Qdisc时被调用 static int ingress_init(struct Qdisc *sch, struct rtattr *opt) { struct ingress_qdisc_data *p = PRIV(sch); /* Make sure either netfilter or preferably CLS_ACT is compiled in */ //输入接收流控必须在内核选项中定义分类动作NET_CLS_ACT或NETFILTER #ifndef CONFIG_NET_CLS_ACT #ifndef CONFIG_NETFILTER printk("You MUST compile classifier actions into the kernel\n"); return -EINVAL; #else//使用NET_CLS_ACT优于使用NETFILTER printk("Ingress scheduler: Classifier actions prefered over netfilter\n"); #endif #endif //没定义NET_CLS_ACT, 而定义了NETFILTER的情况,登记输入流控的netfilter钩子节点函数 //定义了NET_CLS_ACT就不实用netfilter的hook函数了,在netif_receive_skb中就会处理了,看上面输入流程. #ifndef CONFIG_NET_CLS_ACT #ifdef CONFIG_NETFILTER if (!nf_registered) { if (nf_register_hook(&ing_ops) < 0) { printk("ingress qdisc registration error \n"); return -EINVAL; } nf_registered++;//非0表示已经登记了 if (nf_register_hook(&ing6_ops) < 0) { printk("IPv6 ingress qdisc registration error, disabling IPv6 support.\n"); } else nf_registered++; } #endif #endif DPRINTK("ingress_init(sch %p,[qdisc %p],opt %p)\n",sch,p,opt); p->q = &noop_qdisc;//初始内部流控初始化为noop, 丢包使用的 return 0; } hook函数 static unsigned int ing_hook(unsigned int hook, struct sk_buff *skb, const struct net_device *indev, const struct net_device *outdev, int (*okfn)(struct sk_buff *)) { struct Qdisc *q; struct net_device *dev = skb->dev; int fwres = NF_ACCEPT; DPRINTK("ing_hook: skb %s dev=%s len=%u\n", skb->sk ? "(owned)" : "(unowned)", skb->dev ? skb->dev->name : "(no dev)", skb->len); if (dev->qdisc_ingress) { //有输入控制 spin_lock(&dev->ingress_lock); if ((q = dev->qdisc_ingress) != NULL) fwres = q->enqueue(skb, q); //入队 spin_unlock(&dev->ingress_lock); } return fwres; } static int ingress_enqueue(struct sk_buff *skb,struct Qdisc *sch) { struct ingress_qdisc_data *p = PRIV(sch);//接收流控私有数据 struct tcf_result res;//分类结果 int result; result = tc_classify(skb, p->filter_list, &res);//对数据进行分类,参考过滤操作实现 #ifdef CONFIG_NET_CLS_ACT //在定义了NET_CLS_ACT的情况, 这时不会返回NF_*, 而是返回TC_ACT_* sch->bstats.packets++; sch->bstats.bytes += skb->len; switch (result) { case TC_ACT_SHOT: result = TC_ACT_SHOT; sch->qstats.drops++; break; case TC_ACT_STOLEN: case TC_ACT_QUEUED: result = TC_ACT_STOLEN; break; case TC_ACT_RECLASSIFY: case TC_ACT_OK: skb->tc_index = TC_H_MIN(res.classid);//接受, 计算tc_index default: result = TC_ACT_OK; break; } #else result = NF_ACCEPT; sch->bstats.packets++; sch->bstats.bytes += skb->len; #endif return result; } int tc_classify(struct sk_buff *skb, struct tcf_proto *tp, struct tcf_result *res) { int err = 0; __be16 protocol; #ifdef CONFIG_NET_CLS_ACT struct tcf_proto *otp = tp; reclassify: #endif protocol = skb->protocol; err = tc_classify_compat(skb, tp, res); //分类比较 #ifdef CONFIG_NET_CLS_ACT if (err == TC_ACT_RECLASSIFY) { u32 verd = G_TC_VERD(skb->tc_verd); tp = otp; if (verd++ >= MAX_REC_LOOP) { printk("rule prio %u protocol %02x reclassify loop, packet dropped\n", tp->prio&0xffff, ntohs(tp->protocol)); return TC_ACT_SHOT; } skb->tc_verd = SET_TC_VERD(skb->tc_verd, verd); goto reclassify; } #endif return err; } int tc_classify_compat(struct sk_buff *skb, struct tcf_proto *tp, struct tcf_result *res) { __be16 protocol = skb->protocol; int err = 0; for (; tp; tp = tp->next) { //循环所有过滤操作,调用相关分类操作 if ((tp->protocol == protocol || tp->protocol == htons(ETH_P_ALL)) && (err = tp->classify(skb, tp, res)) >= 0) { #ifdef CONFIG_NET_CLS_ACT if (err != TC_ACT_RECLASSIFY && skb->tc_verd) skb->tc_verd = SET_TC_VERD(skb->tc_verd, 0); #endif return err; } } return -1; } 重入队,什么也不做 static int ingress_requeue(struct sk_buff *skb,struct Qdisc *sch) { return 0; } 出队,空函数,因为不会有真正的dequeue操作 static struct sk_buff *ingress_dequeue(struct Qdisc *sch) { return 0; } 丢包 static unsigned int ingress_drop(struct Qdisc *sch) { return 0; } 复位 static void ingress_reset(struct Qdisc *sch) { struct ingress_qdisc_data *p = PRIV(sch); qdisc_reset(p->q);//复位内部流控节点 } 释放 static void ingress_destroy(struct Qdisc *sch) { struct ingress_qdisc_data *p = PRIV(sch); tcf_destroy_chain(p->filter_list);//释放TC过滤规则 } 输出参数 static int ingress_dump(struct Qdisc *sch, struct sk_buff *skb) { unsigned char *b = skb_tail_pointer(skb); struct rtattr *rta; rta = (struct rtattr *) b; RTA_PUT(skb, TCA_OPTIONS, 0, NULL);//起始什么数据也没有 rta->rta_len = skb_tail_pointer(skb) - b; return skb->len; rtattr_failure: nlmsg_trim(skb, b); return -1; } ingress类别操作结构 static struct Qdisc_class_ops ingress_class_ops = { .graft = ingress_graft, //嫁接, 增加叶子节点,恒返回1 .leaf = ingress_leaf, //直接返回 NULL .get = ingress_get, //计数增加, 使用类别ID计算 return TC_H_MIN(classid) + 1; .put = ingress_put, //什么也没有 .change = ingress_change, //直接返回0 .delete = NULL, .walk = ingress_walk, //遍历,什么也没有 .tcf_chain = ingress_find_tcf, .bind_tcf = ingress_bind_filter, .unbind_tcf = ingress_put, .dump = NULL, }; 获取TC过滤规则表 static struct tcf_proto **ingress_find_tcf(struct Qdisc *sch,unsigned long cl) { struct ingress_qdisc_data *p = PRIV(sch); return &p->filter_list; } 绑定TC过滤规则表 static unsigned long ingress_bind_filter(struct Qdisc *sch, unsigned long parent, u32 classid) { return ingress_get(sch, classid); } [/输入流量控制实现] [filter操作实现] tc filter命令是用来定义数据包进行分类的命令, 中间就要用到各种匹配条件, 其功能就象netfilter的match一样, filter的处理和class的处理是紧密联系在一起的,用于完成对数据包的分类。 tc过滤协议结构 struct tcf_proto { /* Fast access part */ struct tcf_proto *next;//链表中的下一项 void *root;//根节点 //分类操作函数, 通常是tcf_proto_ops的classify函数, 就象Qdisc结构中的enqueue就是 //Qdisc_class_ops中的enqueue一样, 目的是向上层隐藏tcf_proto_ops结构 int (*classify)(struct sk_buff*, struct tcf_proto*, struct tcf_result *); __be16 protocol;//协议 /* All the rest */ u32 prio;//优先权 u32 classid;//类别ID struct Qdisc *q;//流控节点 void *data;//私有数据 struct tcf_proto_ops *ops;//filter操作结构 }; filter操作结构, 实际就是定义匹配操作, 通常每个匹配操作都由一个静态tcf_proto_ops结构定义, 作为一个内核模块, 初始化事登记系统的链表 struct tcf_proto_ops { struct tcf_proto_ops *next;//链表中的下一项 char kind[IFNAMSIZ];//名称 int (*classify)(struct sk_buff*, struct tcf_proto*, struct tcf_result *);//分类操作 int (*init)(struct tcf_proto*);//初始化 void (*destroy)(struct tcf_proto*);//释放 unsigned long (*get)(struct tcf_proto*, u32 handle);//获取, 增加引用 void (*put)(struct tcf_proto*, unsigned long);//减少引用 int (*change)(struct tcf_proto*, unsigned long, u32 handle, struct rtattr **, unsigned long *);//参数修改 int (*delete)(struct tcf_proto*, unsigned long);//删除 void (*walk)(struct tcf_proto*, struct tcf_walker *arg);//遍历 /* rtnetlink specific */ // 输出 int (*dump)(struct tcf_proto*, unsigned long, struct sk_buff *skb, struct tcmsg*); struct module *owner; }; filter操作结果, 返回分类结果: 类别和类别ID struct tcf_result { unsigned long class; u32 classid; }; 初始化 static int __init tc_filter_init(void) //net/sched/cls_api.c { //定义filter操作处理函数,增加/删除/获取等操作 rtnl_register(PF_UNSPEC, RTM_NEWTFILTER, tc_ctl_tfilter, NULL); rtnl_register(PF_UNSPEC, RTM_DELTFILTER, tc_ctl_tfilter, NULL); rtnl_register(PF_UNSPEC, RTM_GETTFILTER, tc_ctl_tfilter, tc_dump_tfilter); return 0; } 用于增加, 修改, 删除, 获取过滤结构 static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n, void *arg) { struct rtattr **tca; struct tcmsg *t; u32 protocol; u32 prio; u32 nprio; u32 parent; struct net_device *dev; struct Qdisc *q; struct tcf_proto **back, **chain; struct tcf_proto *tp; struct tcf_proto_ops *tp_ops; struct Qdisc_class_ops *cops; unsigned long cl; unsigned long fh; int err; replay: tca = arg; t = NLMSG_DATA(n); //TC信息的低16位是协议, 高16位是优先权 protocol = TC_H_MIN(t->tcm_info); prio = TC_H_MAJ(t->tcm_info); nprio = prio;//备份优先权参数 parent = t->tcm_parent; cl = 0; if (prio == 0) {//如果没指定优先权值, 在新建filter情况下构造一个缺省值, 其他情况则是错误 /* If no priority is given, user wants we allocated it. */ if (n->nlmsg_type != RTM_NEWTFILTER || !(n->nlmsg_flags&NLM_F_CREATE)) return -ENOENT; prio = TC_H_MAKE(0x80000000U,0U); } /* Find head of filter chain. */ /* Find link */ if ((dev = __dev_get_by_index(&init_net, t->tcm_ifindex)) == NULL)//查找网卡设备 return -ENODEV; /* Find qdisc */ //查找网卡所用的Qdisc if (!parent) { q = dev->qdisc_sleeping;//根节点的情况, 使用qdisc_sleeping parent = q->handle; } else if ((q = qdisc_lookup(dev, TC_H_MAJ(t->tcm_parent))) == NULL)//非根节点的话根据handle查找 return -EINVAL; /* Is it classful? */ if ((cops = q->ops->cl_ops) == NULL)//如果该流控不支持分类操作, 返回失败 return -EINVAL; /* Do we search for filter, attached to class? */ if (TC_H_MIN(parent)) {//低16位是子类别值 cl = cops->get(q, parent);//获取类别结构, cl实际就是结构指针转的unsigned long值 if (cl == 0) return -ENOENT; } /* And the last stroke */ chain = cops->tcf_chain(q, cl);//获取过滤规则链表头地址, 因为是地址的地址, 所以这个值基本不应该是空的 err = -EINVAL; if (chain == NULL) goto errout; /* Check the chain for existence of proto-tcf with this priority */ //遍历规则链表, 这个链表是有序表, 由小到大 for (back = chain; (tp=*back) != NULL; back = &tp->next) { if (tp->prio >= prio) {//如果某过滤规则的优先权值大于指定的prio if (tp->prio == prio) {//如果优先权相同 if (!nprio || (tp->protocol != protocol && protocol)) goto errout; } else//否则优先权不同, 没有相同的优先权的节点, tp置为空 tp = NULL; break; } } //退出循环时, *back指向要链表中插入的位置后面那个的节点 if (tp == NULL) {//tp为空, 当前规则中不存在指定优先权的节点 /* Proto-tcf does not exist, create new one */ if (tca[TCA_KIND-1] == NULL || !protocol)//如果参数不全, 返回失败 goto errout; err = -ENOENT; if (n->nlmsg_type != RTM_NEWTFILTER || !(n->nlmsg_flags&NLM_F_CREATE))//如果不是新建命令, 返回失败 goto errout; /* Create new proto tcf */ err = -ENOBUFS; if ((tp = kzalloc(sizeof(*tp), GFP_KERNEL)) == NULL)//分配新的tcf_proto结构节点 goto errout; err = -EINVAL; tp_ops = tcf_proto_lookup_ops(tca[TCA_KIND-1]);//根据名称查找tp操作结构, 比如rsvp, u32, fw(参看下面)等 if (tp_ops == NULL) { #ifdef CONFIG_KMOD //如果当前内核中没找到的话, 使用模块方式加载后重新查找 struct rtattr *kind = tca[TCA_KIND-1]; char name[IFNAMSIZ]; //检查一下名称算法合法 if (kind != NULL && rtattr_strlcpy(name, kind, IFNAMSIZ) < IFNAMSIZ) { rtnl_unlock(); request_module("cls_%s", name); rtnl_lock(); tp_ops = tcf_proto_lookup_ops(kind); if (tp_ops != NULL) { module_put(tp_ops->owner); err = -EAGAIN; } } #endif //释放tcf_proto空间, 返回失败值 kfree(tp); goto errout; } //设置结构各参数 tp->ops = tp_ops; tp->protocol = protocol; tp->prio = nprio ? : tcf_auto_prio(*back); tp->q = q; //classify函数赋值 tp->classify = tp_ops->classify; tp->classid = parent; if ((err = tp_ops->init(tp)) != 0) {//调用tp_ops的初始化函数初始化 module_put(tp_ops->owner); kfree(tp); goto errout; } qdisc_lock_tree(dev); //将tp插入*back节点前面 tp->next = *back; *back = tp; qdisc_unlock_tree(dev); //找到了节点, 比较一下名称, 不同的话返回错误 } else if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], tp->ops->kind)) goto errout; //获取与t->tcm_handle对应的filte fh = tp->ops->get(tp, t->tcm_handle); if (fh == 0) {//获取filter失败 if (n->nlmsg_type == RTM_DELTFILTER && t->tcm_handle == 0) {//如果是删除命令, 而且TC信息的句柄为0, 则可认为删除操作是成功的 qdisc_lock_tree(dev); *back = tp->next;//将找到的tp从链表中断开 qdisc_unlock_tree(dev); tfilter_notify(skb, n, tp, fh, RTM_DELTFILTER);//删除通告, 释放tp tcf_destroy(tp); err = 0;//命令成功 goto errout; } //如果不是新建filter的话, 没找到filter就表示失败 err = -ENOENT; if (n->nlmsg_type != RTM_NEWTFILTER || !(n->nlmsg_flags&NLM_F_CREATE)) goto errout; } else {//找到filter, 根据命令类型进行操作 switch (n->nlmsg_type) { case RTM_NEWTFILTER://新建filter, 如果定义了互斥标志, 返回错误, 因为filter已经存在了 err = -EEXIST; if (n->nlmsg_flags&NLM_F_EXCL) goto errout; break; case RTM_DELTFILTER://删除filter命令, 运行tcf_proto_ops的delete函数 err = tp->ops->delete(tp, fh); if (err == 0)//如果操作成功, 发送通告消息 tfilter_notify(skb, n, tp, fh, RTM_DELTFILTER); goto errout; case RTM_GETTFILTER://获取filter命令, 发送通告信息, 其中包含了filter的参数 err = tfilter_notify(skb, n, tp, fh, RTM_NEWTFILTER); goto errout; default: err = -EINVAL; goto errout; } } //新建,修改操作都通过tcf_proto_ops的change函数完成 err = tp->ops->change(tp, cl, t->tcm_handle, tca, &fh); if (err == 0)//如果操作成功, 发送通告消息 tfilter_notify(skb, n, tp, fh, RTM_NEWTFILTER); errout: if (cl) cops->put(q, cl); if (err == -EAGAIN) /* Replay the request. */ goto replay; return err; } fw分类方法主要是根据skb中mark参数来进行数据分类, 而该参数是由netfilter定义的, 如果内核里没有定义netfilter, 该分类方法意义不大,该分类方法在 net/sched/cls_fw.c 中定义 static struct tcf_proto_ops cls_fw_ops = { .next = NULL, .kind = "fw", .classify = fw_classify, .init = fw_init, //空函数,直接返回0 .destroy = fw_destroy, .get = fw_get, .put = fw_put, //空函数 .change = fw_change, .delete = fw_delete, .walk = fw_walk, .dump = fw_dump, .owner = THIS_MODULE, }; 初始化,可作为模块 static int __init init_fw(void) { return register_tcf_proto_ops(&cls_fw_ops);//注册这个操作 } 分类方法, 返回负数表示分类失败, 返回0表示分类成功,分类结果在res中返回 static int fw_classify(struct sk_buff *skb, struct tcf_proto *tp, struct tcf_result *res) { struct fw_head *head = (struct fw_head*)tp->root;//HASH链表头 struct fw_filter *f; int r; u32 id = skb->mark; if (head != NULL) { id &= head->mask; //根据id进行hash, 遍历合适的链表 for (f=head->ht[fw_hash(id)]; f; f=f->next) { if (f->id == id) {//如果ID相同, 合适的话可以返回 *res = f->res; #ifdef CONFIG_NET_CLS_IND if (!tcf_match_indev(skb, f->indev))//网卡设备匹配 continue; #endif /* CONFIG_NET_CLS_IND */ r = tcf_exts_exec(skb, &f->exts, res);//如果没有定义CONFIG_NET_CLS_ACT话就是个空函数, 返回0 if (r < 0) continue; return r; } } } else { /* old method */ //老分类方法, id非0, id高16为0或和Qdisc的handle的高16位相同时, 分类成功 if (id && (TC_H_MAJ(id) == 0 || !(TC_H_MAJ(id ^ tp->q->handle)))) { res->classid = id; res->class = 0; return 0; } } } static inline int tcf_exts_exec(struct sk_buff *skb, struct tcf_exts *exts, struct tcf_result *res) { #ifdef CONFIG_NET_CLS_ACT if (exts->action) return tcf_action_exec(skb, exts->action, res); //执行具体动作 #endif return 0; } 执行动作 int tcf_action_exec(struct sk_buff *skb, struct tc_action *act, struct tcf_result *res) { struct tc_action *a; int ret = -1; if (skb->tc_verd & TC_NCLS) { skb->tc_verd = CLR_TC_NCLS(skb->tc_verd); ret = TC_ACT_OK; goto exec_done; } while ((a = act) != NULL) { //循环调用具体动作 repeat: if (a->ops && a->ops->act) { ret = a->ops->act(skb, a, res); //具体动作函数,参看下面动作操作实现 if (TC_MUNGED & skb->tc_verd) { /* copied already, allow trampling */ skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd); skb->tc_verd = CLR_TC_MUNGED(skb->tc_verd); } if (ret == TC_ACT_REPEAT) goto repeat; /* we need a ttl - JHS */ if (ret != TC_ACT_PIPE) goto exec_done; } act = a->next; } exec_done: return ret; } 获取过滤器 static unsigned long fw_get(struct tcf_proto *tp, u32 handle) { struct fw_head *head = (struct fw_head*)tp->root; struct fw_filter *f; if (head == NULL) return 0; // 用handle进行哈希, 遍历指定的hash表 for (f=head->ht[fw_hash(handle)]; f; f=f->next) { if (f->id == handle)//如果有filter的id和handle相同, 返回 return (unsigned long)f; } return 0; } 新建, 修改都通过该函数完成 static int fw_change(struct tcf_proto *tp, unsigned long base, u32 handle, struct rtattr **tca, unsigned long *arg) { struct fw_head *head = (struct fw_head*)tp->root;//根哈希节点 struct fw_filter *f = (struct fw_filter *) *arg;//fw过滤器指针 struct rtattr *opt = tca[TCA_OPTIONS-1];//选项参数 struct rtattr *tb[TCA_FW_MAX]; int err; if (!opt)//如果没提供选项, 在提供了handle的情况下错误, 否则返回成功 return handle ? -EINVAL : 0; if (rtattr_parse_nested(tb, TCA_FW_MAX, opt) < 0)//解析选项参数是否合法 return -EINVAL; if (f != NULL) {//fw过滤器非空, 修改操作 if (f->id != handle && handle)//修改的情况下, 如果handle值非0, 而且和fw过滤器的id不同的话, 返回参数错误 return -EINVAL; return fw_change_attrs(tp, f, tb, tca, base);//进行参数修改操作 } if (!handle)//新建fw过滤器的情况, 如果handle为0, 返回参数错误 return -EINVAL; if (head == NULL) {//链表头为空, 第一次操作 u32 mask = 0xFFFFFFFF;//缺省掩码 if (tb[TCA_FW_MASK-1]) {//如果在命令参数中定义了掩码, 获取之 if (RTA_PAYLOAD(tb[TCA_FW_MASK-1]) != sizeof(u32)) return -EINVAL; mask = *(u32*)RTA_DATA(tb[TCA_FW_MASK-1]); } //分配链表头空间 head = kzalloc(sizeof(struct fw_head), GFP_KERNEL); if (head == NULL) return -ENOBUFS; head->mask = mask;//掩码 tcf_tree_lock(tp); tp->root = head;//作为系统的根哈希链表头 tcf_tree_unlock(tp); } //分配新的fw过滤器结构指针 f = kzalloc(sizeof(struct fw_filter), GFP_KERNEL); if (f == NULL) return -ENOBUFS; //使用handle值作为fw过滤器的ID f->id = handle; //调用修改函数进行赋值操作 err = fw_change_attrs(tp, f, tb, tca, base); if (err < 0) goto errout; //添加到合适的hash链表的头 f->next = head->ht[fw_hash(handle)]; tcf_tree_lock(tp); head->ht[fw_hash(handle)] = f; tcf_tree_unlock(tp); *arg = (unsigned long)f;//将fw过滤器作为参数返回 return 0; errout: kfree(f); return err; } 参数修改处理 static int fw_change_attrs(struct tcf_proto *tp, struct fw_filter *f, struct rtattr **tb, struct rtattr **tca, unsigned long base) { struct fw_head *head = (struct fw_head *)tp->root; struct tcf_exts e; u32 mask; int err; //tcf扩展验证操作 err = tcf_exts_validate(tp, tb, tca[TCA_RATE-1], &e, &fw_ext_map); if (err < 0) return err; err = -EINVAL; if (tb[TCA_FW_CLASSID-1]) {//命令参数中提供了类别ID if (RTA_PAYLOAD(tb[TCA_FW_CLASSID-1]) != sizeof(u32)) goto errout; //类别ID赋值 f->res.classid = *(u32*)RTA_DATA(tb[TCA_FW_CLASSID-1]); tcf_bind_filter(tp, &f->res, base); } #ifdef CONFIG_NET_CLS_IND if (tb[TCA_FW_INDEV-1]) {//网卡设备 err = tcf_change_indev(tp, f->indev, tb[TCA_FW_INDEV-1]); if (err < 0) goto errout; } #endif /* CONFIG_NET_CLS_IND */ if (tb[TCA_FW_MASK-1]) {//FW掩码 if (RTA_PAYLOAD(tb[TCA_FW_MASK-1]) != sizeof(u32)) goto errout; mask = *(u32*)RTA_DATA(tb[TCA_FW_MASK-1]); if (mask != head->mask) goto errout; } else if (head->mask != 0xFFFFFFFF) goto errout; tcf_exts_change(tp, &f->exts, &e);//将e中的数据赋值到f->exts中 return 0; errout: tcf_exts_destroy(tp, &e); return err; } [/filter操作实现] [动作操作实现] tc action命令是用来定义数据包进行最终处理方法的命令, 其功能就象netfilter的target一样, 需要内核定义NET_CLS_ACT选项。 动作处理的基本api在net/sched/act_api.c中定义, 而各种动作方法在net/sched/act_*.c中定义。 struct tc_action { void *priv;//私有数据 struct tc_action_ops *ops;//操作结构 __u32 type; /* for backward compat(TCA_OLD_COMPAT) */ __u32 order;//阶数 struct tc_action *next; }; action操作结构, 实际就是定义目标操作, 通常每个匹配操作都由一个静态tcf_action_ops结构定义, 作为一个内核模块, 初始化登记到系统的链表. struct tc_action_ops { struct tc_action_ops *next; struct tcf_hashinfo *hinfo; char kind[IFNAMSIZ];//名称 __u32 type; /* TBD to match kind */ __u32 capab; /* capabilities includes 4 bit version */ struct module *owner; int (*act)(struct sk_buff *, struct tc_action *, struct tcf_result *);//动作 int (*get_stats)(struct sk_buff *, struct tc_action *);//获取统计参数 int (*dump)(struct sk_buff *, struct tc_action *, int, int);//输出 int (*cleanup)(struct tc_action *, int bind);//清除 int (*lookup)(struct tc_action *, u32);//查找 int (*init)(struct rtattr *, struct rtattr *, struct tc_action *, int , int);//初始化 int (*walk)(struct sk_buff *, struct netlink_callback *, int, struct tc_action *);//遍历 }; 初始化 static int __init tc_action_init(void) //net/sched/act_api.c { //定义action操作处理函数,增加/删除/获取等操作 rtnl_register(PF_UNSPEC, RTM_NEWACTION, tc_ctl_action, NULL); rtnl_register(PF_UNSPEC, RTM_DELACTION, tc_ctl_action, NULL); rtnl_register(PF_UNSPEC, RTM_GETACTION, tc_ctl_action, tc_dump_action); return 0; } subsys_initcall(tc_action_init); 这些内容和上面我们看到的都大同小议,所以这就不在看了,我们还是看一个具体的活动实现。 packet mirroring and redirect actions mirred动作是对数据进行镜像和重定向操作, 将数据包从指定网卡发出, 在net/sched/act_mirred.c中定义. static struct tc_action_ops act_mirred_ops = { .kind = "mirred",//名称 .hinfo = &mirred_hash_info, .type = TCA_ACT_MIRRED,//类型 .capab = TCA_CAP_NONE, .owner = THIS_MODULE, .act = tcf_mirred, .dump = tcf_mirred_dump, .cleanup = tcf_mirred_cleanup, .lookup = tcf_hash_search,//查找, 通用函数 .init = tcf_mirred_init, .walk = tcf_generic_walker//遍历, 通用函数 }; 初始化 static int tcf_mirred_init(struct rtattr *rta, struct rtattr *est, struct tc_action *a, int ovr, int bind) { struct rtattr *tb[TCA_MIRRED_MAX]; struct tc_mirred *parm; struct tcf_mirred *m; struct tcf_common *pc; struct net_device *dev = NULL; int ret = 0; int ok_push = 0; if (rta == NULL || rtattr_parse_nested(tb, TCA_MIRRED_MAX, rta) < 0)//解析参数, 保存于tb数组, 失败返回 return -EINVAL; //必须要有MIRRED参数 if (tb[TCA_MIRRED_PARMS-1] == NULL || RTA_PAYLOAD(tb[TCA_MIRRED_PARMS-1]) < sizeof(*parm)) return -EINVAL; parm = RTA_DATA(tb[TCA_MIRRED_PARMS-1]); if (parm->ifindex) {//如果定义了网卡索引号 dev = __dev_get_by_index(&init_net, parm->ifindex);//查找相应的网卡设备结构 if (dev == NULL) return -ENODEV; switch (dev->type) {//以下类型的网卡扩展硬件头, 这些通常是虚拟网卡 case ARPHRD_TUNNEL: case ARPHRD_TUNNEL6: case ARPHRD_SIT: case ARPHRD_IPGRE: case ARPHRD_VOID: case ARPHRD_NONE: ok_push = 0; break; default://其他类型网卡需要扩展硬件头 ok_push = 1; break; } } //根据索引号查找common节点, 绑定到a节点(priv) pc = tcf_hash_check(parm->index, a, bind, &mirred_hash_info); if (!pc) {//如果节点为空 //必须要有网卡参数 if (!parm->ifindex) return -EINVAL; //创建新的common节点 pc = tcf_hash_create(parm->index, est, a, sizeof(*m), bind, &mirred_idx_gen, &mirred_hash_info); if (unlikely(!pc)) return -ENOMEM; ret = ACT_P_CREATED;//新建标志 } else { if (!ovr) {//ovr是替代标志, 如果不是替代操作, 对象已经存在, 操作失败 tcf_mirred_release(to_mirred(pc), bind); return -EEXIST; } } m = to_mirred(pc);//转换为mirred动作结构 spin_lock_bh(&m->tcf_lock); m->tcf_action = parm->action;//动作 m->tcfm_eaction = parm->eaction;//实际动作 if (parm->ifindex) {//填充网卡参数 m->tcfm_ifindex = parm->ifindex; if (ret != ACT_P_CREATED)//如果不是新建操作, 减少网卡计数, 因为已经引用过了 dev_put(m->tcfm_dev); m->tcfm_dev = dev;//网卡 dev_hold(dev); m->tcfm_ok_push = ok_push;//硬件头扩展标志 } spin_unlock_bh(&m->tcf_lock); if (ret == ACT_P_CREATED)//如果是新建节点, 插入哈希表 tcf_hash_insert(pc, &mirred_hash_info); return ret; } 动作,将数据包从指定网卡发出 static int tcf_mirred(struct sk_buff *skb, struct tc_action *a, struct tcf_result *res) { struct tcf_mirred *m = a->priv;//mirred动作结构 struct net_device *dev; struct sk_buff *skb2 = NULL; u32 at = G_TC_AT(skb->tc_verd);//数据包自身的动作信息 spin_lock(&m->tcf_lock); dev = m->tcfm_dev;//网卡 m->tcf_tm.lastuse = jiffies;//最后使用时间 if (!(dev->flags&IFF_UP) ) {//如果该网卡没运行, 丢包 if (net_ratelimit()) printk("mirred to Houston: device %s is gone!\n", dev->name); bad_mirred: if (skb2 != NULL) kfree_skb(skb2); m->tcf_qstats.overlimits++; m->tcf_bstats.bytes += skb->len; m->tcf_bstats.packets++; spin_unlock(&m->tcf_lock); /* should we be asking for packet to be dropped? * may make sense for redirect case only */ return TC_ACT_SHOT; } skb2 = skb_act_clone(skb, GFP_ATOMIC);//克隆数据包用于镜像或重定向 if (skb2 == NULL) goto bad_mirred; //如果实际动作既不是镜像也不是重定向, 出错返回 if (m->tcfm_eaction != TCA_EGRESS_MIRROR && m->tcfm_eaction != TCA_EGRESS_REDIR) { if (net_ratelimit()) printk("tcf_mirred unknown action %d\n", m->tcfm_eaction); goto bad_mirred; } //统计数更新 m->tcf_bstats.bytes += skb2->len; m->tcf_bstats.packets++; if (!(at & AT_EGRESS))//如果不是发出的, 根据需要扩展硬件头 if (m->tcfm_ok_push) skb_push(skb2, skb2->dev->hard_header_len); /* mirror is always swallowed */ if (m->tcfm_eaction != TCA_EGRESS_MIRROR)//实际动作不是镜像, 重新设置TC判定值 skb2->tc_verd = SET_TC_FROM(skb2->tc_verd, at); skb2->dev = dev;//将克隆的数据包从指定网卡发出 skb2->iif = skb->dev->ifindex; //记录原始数据包设备索引 dev_queue_xmit(skb2); spin_unlock(&m->tcf_lock); return m->tcf_action; } [/动作操作实现]
posted on 2013-08-30 10:28 SuperKing 阅读(5341) 评论(0) 编辑 收藏 举报