这篇笔记使用vcard虚拟网卡来演示网络驱动程序应该如何和协议栈配合完成数据包的发送,以便加深对发送过程的理解。
核心思想
- vcard虚拟网卡使用默认的流量控制和协议栈配合完成数据包发送;
- 用户态使用AF_PACKET套接字编程向vcard虚拟网卡的写数据;
内核态核心代码
在网络设备初始化时,完成相关字段的赋值:
// 提供了open回调和start_xmit发送回调,分别用于设备打开和数据包发送
static struct net_device_ops vcard_device_ops = {
.ndo_open = &vcard_open,
.ndo_start_xmit = &vcard_start_xmit,
};
// 初始化网卡
static void vcard_setup(struct net_device *dev)
{
// 必须要对netdev_ops进行赋值,否则在注册时会空指针异常
dev->netdev_ops = &vcard_device_ops;
// 指定tx_queue_len,表示我们使用流量控制机制发送
dev->tx_queue_len = 5;
// 正确的做法应该是实现ndo_change_mtu()回调进行MTU的配置,
// 这里这么做仅仅是为了方便。这里必须要设定,否则IP层会认为
// 数据包长度过大,导致无法发送数据
dev->mtu = 1500;
}
提供的两个回调的实现如下:
// 生成的数据包携带5个字节数据,"AAAAA"~"ZZZZZ"
#define CONTENT_LEN 5
// 打开设备回调
static int vcard_open(struct net_device *dev)
{
// 清除__LINK_STATE_XOFF标记,否则无法进行发送过程
netif_start_queue(dev);
return 0;
}
// 发送数据包回调
static netdev_tx_t vcard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
char data[6] = {'\0'};
if (skb == NULL) {
printk(TAG "%s: skb is null\n", __func__);
return NETDEV_TX_OK;
}
if (skb->len != CONTENT_LEN) {
printk(TAG "%s: wrong skb length(%d)\n", __func__, skb->len);
goto done;
}
memcpy(data, skb->data, CONTENT_LEN);
printk(TAG "%s: recv %s\n", __func__, data);
done:
kfree_skb(skb);
return NETDEV_TX_OK;
}
用户态代码实现
int main(int argc, char *argv[])
{
// RAW类型的IP报文
int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));
if (fd < 0) {
printf("socket fail\n");
return -1;
}
int ifindex = get_if_index("vcard");
if (ifindex <= 0) {
printf("get_if_index fail\n");
return -1;
}
printf("vcard ifindex: %d\n", ifindex);
// 绑定到指定的网络设备,这样只会接收到来自该数据的包
struct sockaddr_ll addr = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_IP),
.sll_ifindex = ifindex,
};
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
printf("bind fail\n");
return -1;
}
// 发送"AAAAA" ~ "ZZZZZ"共26个数据包
char buf[6] = {'\0'};
int i;
for (i = 0; i < 26; ++i) {
memset(buf, 'A' + i, 5);
int ret = send(fd, buf, 5, 0);
if (ret < 0) {
printf("send fail: %s\n", strerror(errno));
return -1;
} else if (ret == 0) {
continue;
} else {
printf("send %s done(%d)\n", buf, ret);
}
}
return 0;
}
实验结果
内核驱动的输出如下图所示:
注意:
- 要先将vcard插入内核中;
- 执行"ip link set dev vcard up"打开设备,否则用户态无法bind到该设备;
- 用户态程序一定要以root权限执行;
完整的实验代码在这里。