欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

入口流量控制

程序员文章站 2022-06-28 22:32:31
...


在驱动将数据包递交给协议栈的最后一步netif_receive_skb()函数中,有这个宏CONFIG_NET_CLS_ACT控制的相关逻辑是入口流量控制。

handle_ing()

static inline struct sk_buff *handle_ing(struct sk_buff *skb, struct packet_type **pt_prev,
	int *ret, struct net_device *orig_dev)
{
    // 网络设备接收队列默认的qdisc就是noop_qdisc,即使网络设备打开也是如此,此时入口流控不生效
	if (skb->dev->rx_queue.qdisc == &noop_qdisc)
		goto out;

	if (*pt_prev) {
	    // 前一次递交,ptype_all链表的处理,主要是优化写法导致的
		*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);
	}

    // 让ing_filter()决定数据包的去向
	switch (ing_filter(skb)) {
	case TC_ACT_SHOT: // 这两个值会导致数据包被释放,其它值会将数据包正常递交
	case TC_ACT_STOLEN:
		kfree_skb(skb);
		return NULL;
	}
out:
	skb->tc_verd = 0;
	return skb;
}

可以看出,流程上仅仅支持将接收数据包丢弃。

入口数据包过滤: ing_filter()

static int ing_filter(struct sk_buff *skb)
{
	struct net_device *dev = skb->dev;
	u32 ttl = G_TC_RTTL(skb->tc_verd);
	struct netdev_queue *rxq;
	int result = TC_ACT_OK;
	struct Qdisc *q;

	if (MAX_RED_LOOP < ttl++) {
		printk(KERN_WARNING "Redir loop detected Dropping packet (%d->%d)\n",
		       skb->iif, dev->ifindex);
		return TC_ACT_SHOT;
	}

	skb->tc_verd = SET_TC_RTTL(skb->tc_verd, ttl);
	skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_INGRESS);

    // 将数据包放入接收队列的qdisc,注意这里仅仅有入队列
	rxq = &dev->rx_queue;
	q = rxq->qdisc;
	if (q != &noop_qdisc) {
		spin_lock(qdisc_lock(q));
		if (likely(!test_bit(__QDISC_STATE_DEACTIVATED, &q->state)))
			result = qdisc_enqueue_root(skb, q);
		spin_unlock(qdisc_lock(q));
	}
	return result;
}

可以看出,在入口方向,流控框架会对根据数据包的入队操作返回结果决定是否终止递交数据包到高层协议栈,并没有调用出队操作,这就限制了我们在入口方向上能够对数据包执行的操作。

接收方向qdisc: ingress_qdisc

接收方向唯一可以设置的qdisc就是ingress_qdisc。

static const struct Qdisc_class_ops ingress_class_ops = {
	.graft		=	ingress_graft,
	.leaf		=	ingress_leaf,
	.get		=	ingress_get,
	.put		=	ingress_put,
	.change		=	ingress_change,
	.walk		=	ingress_walk,
	.tcf_chain	=	ingress_find_tcf,
	.bind_tcf	=	ingress_bind_filter,
	.unbind_tcf	=	ingress_put,
};

// 注意,这里没有dequeue()实现,因为不需要,框架也不会调用
static struct Qdisc_ops ingress_qdisc_ops __read_mostly = {
	.cl_ops		=	&ingress_class_ops, // 实际上是一个有类qdisc
	.id		=	"ingress",
	.priv_size	=	sizeof(struct ingress_qdisc_data), // 私有数据结构
	.enqueue	=	ingress_enqueue,
	.destroy	=	ingress_destroy,
	.dump		=	ingress_dump,
	.owner		=	THIS_MODULE,
};

ingress_qdisc私有数据结构: ingress_qdisc_data

struct ingress_qdisc_data {
	struct tcf_proto	*filter_list; // filter链表
};

入队: ingress_enqueue()

static int ingress_enqueue(struct sk_buff *skb, struct Qdisc *sch)
{
	struct ingress_qdisc_data *p = qdisc_priv(sch);
	struct tcf_result res;
	int result;

    // 根据filter过滤数据包
	result = tc_classify(skb, p->filter_list, &res);

	sch->bstats.packets++;
	sch->bstats.bytes += qdisc_pkt_len(skb);
	// 根据过滤结果设置返回值,特别注意,这里并没有将数据包放入某个实际的队列中
	switch (result) {
	case TC_ACT_SHOT: // 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);
	default:
		result = TC_ACT_OK;
		break;
	}
	return result;
}