如何将sk_buff模拟为TAP接收的数据包送入Linux网络栈?
如何将sk_buff模拟为TAP接收的数据包送入Linux网络栈?
兄弟,我明白你要做啥了——就是把内核里的一个sk_buff,假装成TAP设备从外部收到的数据包,塞进Linux网络栈里处理对吧?你贴的这段尝试代码已经找对路子了,不过我给你补全下关键细节和避坑点:
我尝试从一个驱动将sk_buff发送到另一个设备(TAP,对应Linux源码里的drivers/net/tun.c)的RX队列,用了这样的代码:
#if RECV_SKB #define NETIF_RX netif_receive_skb #else #define NETIF_RX netif_rx #endif static net_device * dev; /* a pointer to TAP interface */ int sent_to_tap(struct net_device * dev, struct sk_buff * skb, int txq) { struct tun_struct * tun; struct tun_file * tfile; int ret; tun = netdev_priv(dev); if (!tun) goto drop; // 原代码未完成部分
首先你得搞清楚,TAP设备的接收逻辑是用户态和内核态的桥梁,但如果你只是想让上层网络栈认这个包是TAP收到的,其实不用绕TAP的用户态路径,直接修正skb元数据后扔进网络栈就行;如果要走TAP的正规接收流程(比如唤醒用户态监听进程),就得调用TAP驱动的内部接口,给你拆成两种情况说:
快速方案:直接让数据包进入网络栈
这种方式简单直接,只要把skb的“身份”改成TAP设备的,再递给网络栈的接收函数就行:
- 修正skb元数据:必须把
skb->dev设为你的TAP设备指针,同时用skb_reset_mac_header(skb)和skb_reset_network_header(skb)重置头部字段,用eth_type_trans(skb, tap_dev)解析协议类型,确保网络栈能正确识别数据包。 - 选对接收函数:你代码里用到的
netif_rx是老接口了,现在优先用netif_receive_skb;如果是在中断上下文调用,记得用netif_rx_ni避免死锁。 - 处理skb生命周期:调用接收函数后,网络栈会接管skb,只有当返回
NET_RX_DROP时,你才需要用kfree_skb(skb)手动释放,防止内存泄漏。
示例代码:
int send_skb_as_tap_rx(struct net_device *tap_dev, struct sk_buff *skb) { // 确保TAP设备处于运行状态 if (!netif_running(tap_dev)) { kfree_skb(skb); return -ENODEV; } // 修正skb的设备归属和头部信息 skb->dev = tap_dev; skb_reset_mac_header(skb); skb_reset_network_header(skb); skb->protocol = eth_type_trans(skb, tap_dev); skb->pkt_type = PACKET_HOST; // 标记为发给本机的包 // 送入网络栈 int ret = netif_receive_skb(skb); if (ret == NET_RX_DROP) { kfree_skb(skb); return -EINVAL; } return 0; }
正规方案:走TAP驱动的接收流程
如果需要让TAP设备的用户态监听进程(比如OpenVPN)也感知到这个数据包,就得调用TAP驱动的内部接收接口:
- 获取TAP私有数据:用
netdev_priv(tap_dev)拿到tun_struct,这是TAP设备的核心私有结构体。 - 调用TAP内部接收函数:用
tun_rx_skb(不同内核版本可能有差异,比如有些版本需要传入tun_file),这个函数会把数据包同时送入网络栈和通知用户态进程。
示例代码:
int send_skb_via_tap_rx(struct net_device *tap_dev, struct sk_buff *skb) { struct tun_struct *tun; int ret; tun = netdev_priv(tap_dev); if (!tun || !netif_running(tap_dev)) { kfree_skb(skb); return -ENODEV; } // 修正skb元数据,和快速方案一致 skb->dev = tap_dev; skb_reset_mac_header(skb); skb_reset_network_header(skb); skb->protocol = eth_type_trans(skb, tap_dev); skb->pkt_type = PACKET_HOST; // 走TAP正规接收流程 ret = tun_rx_skb(tun, skb); if (ret < 0) { kfree_skb(skb); return ret; } return 0; }
要注意的坑
- 内核版本兼容性:不同内核版本的TAP驱动接口可能有变化,比如
tun_rx_skb的参数、函数名,一定要对照你使用的Linux源码(drivers/net/tun.c)来调整。 - 调用上下文限制:这些内核函数只能在内核模块或驱动里调用,如果是在原子上下文(比如中断处理函数),不能调用可能导致睡眠的接口。
- 数据包合法性:skb里的内容必须符合以太网帧格式,比如要有正确的源/目的MAC、协议类型,不然会被网络栈直接丢弃。
备注:内容来源于stack exchange,提问作者Misha T
相关产品推荐
相关产品推荐

