编写snull程序
内容简介
这是一篇手把手简易实现LDD3第17章:网络驱动程序的记录过程(其实我是把程序调试完了再来写的总结文章)。
确认内核版本
$uname -r
4.15.0-88-generic
模块程序的框架
这是内核的模块驱动(module driver),那先就把模块的框架和对应的Makefile写好。
构成模块的文件为snull.c snull.h Makefile(为什么要多加一个snull.h文件呢?可以不要的)
Makefile文件如下
obj-m += snull.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
模块驱动框架程序如下:
#include <linux/module.h>
int snull_module_init(void)
{
int ret = 0;
return ret;
}
void snull_module_exit(void)
{
return;
}
module_init(snull_module_init);
module_exit(snull_module_exit);
网络驱动编写的指导思想
最简单的网络驱动程序做以下三件事情:
- 从网卡上收取数据包发给协议栈
- 从协议栈解析数据包,通过网卡发出去
- 统计状态:发了多少个包,收了多少个包,丢了多少包等状态的统计。
这里涉及到网卡,数据包,协议栈,收取,收发。都要用软件来表示,如果让你自己来写,你怎么用软件来表示:
内核中用net_device表示一个网络设备。
使用skb表示一个数据包。
数据包的接收
网卡驱动程序不涉及协议栈,但是数据包从网卡收了要发到协议栈,要从协议栈取数据包通过网卡发出去,那协议栈至少要给个接口吧。
数据包先要到达网卡的接收寄存器,触发中断,这时候接收中断就产生了,这时候数据是放在网卡上的,我首先要把数据弄到内存吧,那我就要申请内存,内核提供的skb的内存申请接口dev_alloc_skb,有内存了,我就可以用memcpy把数据包从网卡拷贝到内存,完成pkt到skb的组装。
调用协议栈的收取接口netif_rx,数据包传给协议栈
数据包的发送
发送的数据包是来自协议栈的,应用程序的内容,什么时候发送,由协议栈决定,就是把数据包skb给驱动程序,驱动程序解析出其中的数据,这时候也要通过memcpy把处于内存的skb拷贝到网卡的寄存器。在把pkt写好之后,就通知硬件可以发送了。硬件就执行发送。这个发送函数集成在net_dev中,是个函数指针,.ndo_start_xmit,只需要挂载发送函数即可。
协议栈控制函数
为了协调协议栈和网卡之间的数据交流,应该能想到,应用程序到到协议栈,肯定比协议栈到网卡的速度快。处于速度慢的一方,肯定要有机制来通知快的一方,以我的"速度"为准.当我准备好了,我就通知你给我发送,但是当我处理不过来了,我就得通知你停止发送。
使用netif_start_queue通知协议栈说可以发送。使用netif_stop_queue通知协议栈停止发送。还有一种情况,就是发到一半,停止了,我要使用netif_wake_queue再次通知协议栈发送。
网卡的状态统计
为了明确知道网卡发送和接收的状态,需要在发送和接收的过程中统计。应该在什么地方统计呢?
- 一个skb送给协议栈了,这个包才算完全的接收,这时候接收统计就增加(RX packets)。
- 一个skb通过网卡发送出去了,发送完成,这时候发送统计就增加(TX packets)。
- 当一个包从协议栈下来了,但是没有发出去,都应该统计到丢包(dropped)。
- 当我传输超时后,就应该把这个包统计在错误包(errors)。
- 等等
为网络设备申请内存
#include <linux/netdevice.h>
申请函数:
#define alloc_netdev(sizeof_priv, name, name_assign_type, setup) \
alloc_netdev_mqs(sizeof_priv, name, name_assign_type, setup, 1, 1)
继续看这个宏定义的调用函数
struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
unsigned char name_assign_type,
void (*setup)(struct net_device *),
unsigned int txqs, unsigned int rxqs);
释放函数:
void free_netdev(struct net_device *dev);
按照内核提供的函数填写如下
首先函数返回的是一个net_devices的指针,那就先定义2个指针数组用于存储返回值
struct net_device *snull_devs[2];
第一个参数是这个网络驱动的私有变量,这个私有变量,不知道有什么内容,但至少应该有一个net_device的结构,因为这是个网络设备,后面用到什么的时候再逐次添加
struct snull_priv = {
struct net_device *dev;
};
第二个参数是网络设备的名字,如果只是一个的话,直接写出,比如sn0,但是有多个的话,就需要写成sn%d。后续申请的可以安好编号递增。
第三个参数NET_NAME_UNKNOWN,直接翻译成中文就是,网络名字不可知,查看内核中的定义写到,不要暴露给用户空间,那这里有个疑问了,怎么才能暴露给用户空间,暴露了有什么用,作为往后提高的部分再深入研究。
/* interface name assignment types (sysfs name_assign_type attribute) */
#define NET_NAME_UNKNOWN 0 /* unknown origin (not exposed to userspace) */
最后一个是网络设备初始化函数,一个网络设备要初始化的内容是什么呢?不论怎样,先写上。
void snull_init(struct net_device *dev)
{
return;
}
填好这些基本的数据结构之后就调用alloc_netdev函数,在这里申请两个网络设备
snull_devs[0] = alloc_netdev(sizeof (struct snull_priv), "sn%d", NET_NAME_UNKNOWN, snull_init);
snull_devs[1] = alloc_netdev(sizeof (struct snull_priv), "sn%d", NET_NAME_UNKNOWN, snull_init);
对应的在模块退出的时候,要对该内存块进行清除
for (i = 0; i < 2; i++) {
free_netdev(snull_devs[i])
}
这里也可以看出,为何要把snull_devs设置成全局,因为在init和exit函数中都要用到该指针。
注册网络设备到内核
内核提供的注册函数和注销函数
#include <linux/netdevice.h>
int register_netdev(struct net_device *dev);
void unregister_netdev(struct net_device *dev);
/net/core/dev.c
/**
* register_netdev - register a network device
* @dev: device to register
*
* Take a completed network device structure and add it to the kernel
* interfaces. A %NETDEV_REGISTER message is sent to the netdev notifier
* chain. 0 is returned on success. A negative errno code is returned
* on a failure to set up the device, or if the name is a duplicate.
*
* This is a wrapper around register_netdevice that takes the rtnl semaphore
* and expands the device name if you passed a format string to
* alloc_netdev.
*/
int register_netdev(struct net_device *dev)
{
int err;
rtnl_lock();
err = register_netdevice(dev);
rtnl_unlock();
return err;
}
EXPORT_SYMBOL(register_netdev);
调用了register_netdevice(dev),注册函数是有返回值的,所以在写代码的时候要检测该返回值,在这里
for (i = 0; i < 2; i++) {
if ((result = register_netdev(snull_devs[i]))) { //这里要写两个括号,避免编译器警告
printk(KERN_INFO "snull: erro:%d register.\n", result);
} else {
printk(KERN_INFO "snull register success.\n")
}
}
只写这两步的时候,运行make和sudo insmod snull.ko,内核显示空指针处理错误,为什么?
对于一个设备,必须要在初始化函数中写上对他的操作函数,即使操作函数为空。需要修改snull_init函数
struct net_device_ops snull_netdev_ops = {};
void snull_init(struct net_device *dev)
{
dev->netdev_ops = &snull_netdev_ops;
return;
}
到此,我们就写好了网络驱动的框架代码,全部代码如下
#include <linux/module.h>
#include <linux/netdevice.h>
void snull_module_exit(void);
struct snull_priv {
struct net_device dev;
};
struct net_device *snull_devs[2];
struct net_device_ops snull_netdev_ops = {
};
void snull_init(struct net_device *dev)
{
dev->netdev_ops = &snull_netdev_ops;
}
int snull_module_init(void)
{
int ret = -ENOMEM;
int i = 0;
int result = 0;
snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d", NET_NAME_UNKNOWN, snull_init);
snull_devs[1] = alloc_netdev(sizeof(struct snull_priv), "sn%d", NET_NAME_UNKNOWN, snull_init);
if (snull_devs[0] == NULL || snull_devs[1] == NULL)
goto out;
ret = -ENODEV;
for (i = 0; i < 2; i++) {
if ((result = register_netdev(snull_devs[i]))) {
printk(KERN_INFO "snull: error :%d register device:%s\n", result, snull_devs[i]->name);
} else {
ret = 0;
}
}
out:
if (ret)
snull_module_exit();
return 0;
}
void snull_module_exit(void)
{
int i;
for (i = 0; i < 2; i++) {
if (snull_devs[i]) {
unregister_netdev(snull_devs[i]);
free_netdev(snull_devs[i]);
}
}
return;
}
module_init(snull_module_init);
module_exit(snull_module_exit);
MODULE_LICENSE("Dual BSD/GPL");
对代码进行编译(make),将模块加载(sudo insmod snull.ko)到内核,可以从内核的sysfs文件夹下看到我们注册的网络模块
$ ls /sys/class/net
sn0 sn1
用ifconfig -a也可以看到我们的网卡已经注册上去。
sn0 Link encap:AMPR NET/ROM HWaddr
[NO FLAGS] MTU:0 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
sn1 Link encap:AMPR NET/ROM HWaddr
[NO FLAGS] MTU:0 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
为网络设备增加操作函数
我们刚才添加的网络设备,什么都不能做,只是注册到内核,接下来我们就要为网络设备添加操作。网络设备是net_device,那net_device的本质是decive.
但是网络设备有他的特殊性,他是用来收发数据包的。查看内核中对该操作都定义了哪些函数
都是一些函数指针,这体现了面向对象设计的思想,相当于虚函数,每个具体的网卡可以具体的实现对应的函数
没错,这个结构体非常大,做内核的人好耐心。因为net_device表示了一切的网络设备,比如网卡,网桥等,所以要兼容所有设备的操作。针对以太网,我们需要,打开设备,关闭设备,发送数据包,超时处理,更改MAC地址,更改MTU长度等,用到哪个就添加哪个。
设备的打开
首先就是open函数。在该函数中,会调用netif_start_queue()
int snull_open(struct net_device *dev)
{
netif_start_queue(dev);
return 0;
}
这是什么意思?看内核代码:允许上层调用设备的发送例程
#include <linux/netdevice.h>
static __always_inline void netif_tx_start_queue(struct netdev_queue *dev_queue)
{
clear_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state);
}
/**
* netif_start_queue - allow transmit
* @dev: network device
*
* Allow upper layers to call the device hard_start_xmit routine.
*/
static inline void netif_start_queue(struct net_device *dev)
{
netif_tx_start_queue(netdev_get_tx_queue(dev, 0));
}
设备的关闭
与打开设备对应的是关闭设备
int snull_release(struct net_device *dev)
{
netif_stop_queue(dev);
return 0;
}
内核代码:
#include <linux/netdevice.h>
static __always_inline void netif_tx_stop_queue(struct netdev_queue *dev_queue)
{
set_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state);
}
/**
* netif_stop_queue - stop transmitted packets
* @dev: network device
*
* Stop upper layers calling the device hard_start_xmit routine.
* Used for flow control when transmit resources are unavailable.
*/
static inline void netif_stop_queue(struct net_device *dev)
{
netif_tx_stop_queue(netdev_get_tx_queue(dev, 0));
}
设备的收包流程(rx)
网络设备的主要功能是收发数据包,当数据包到达网卡时,硬件是最先知道的,硬件知道了,当然要通过中断的形式告知内核,数据包到来,收数据包。
想想数据到达硬件,他是放在哪儿的,只能放在硬件,硬件哪里可以放数据?只有寄存器。当我们得知有数据包到来时,我们首先做的事情就是把数据从硬件的寄存器拷贝到内存,要拷贝内存,首先你要有内存吧,没有的话,那就申请吧。
内核申请skb的函数dev_alloc_skb
#include <linux/skbbuf.h>
/* legacy helper around netdev_alloc_skb() */
static inline struct sk_buff *dev_alloc_skb(unsigned int length)
{
return netdev_alloc_skb(NULL, length);
}
/**
* netdev_alloc_skb - allocate an skbuff for rx on a specific device
* @dev: network device to receive on
* @length: length to allocate
*
* Allocate a new &sk_buff and assign it a usage count of one. The
* buffer has unspecified headroom built in. Users should allocate
* the headroom they think they need without accounting for the
* built in space. The built in space is used for optimisations.
*
* %NULL is returned if there is no free memory. Although this function
* allocates memory it can be called from an interrupt.
*/
static inline struct sk_buff *netdev_alloc_skb(struct net_device *dev,
unsigned int length)
{
return __netdev_alloc_skb(dev, length, GFP_ATOMIC);
}
申请完的下一步就是将物理层的pkt拷贝到skb中,用memcpy函数,需要知道源地址,目的地址,长度
memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
然后填上协议类型,统计一些变量,再调用netif_rx送到协议栈,到此收据的收取流程就结束了。
int netif_rx(struct sk_buff *skb);
设备的发包流程(tx)
首先确定发的包是从哪儿来的?数据包是从协议栈来的。要通过网卡发出去,网卡这个硬件只认识硬件寄存器,所以要包协议栈到来的skb想把法写到硬件的寄存器。
我在写的这个过程中,我是不能再次接收的,为什么?因为我只有有限的寄存器,还没有写完,等我发完了你再写吧,那怎么知道我发完了呢?
当我写完了,我就改变我的状态,我可以写了,在每次中断例程中都去查询这个状态,如果查到我是发完的状态,就执行下一次数据协议,然后发送。
协议栈下来的是skb,首先,要把skb中的数据部分解析出来data = skb->data,这时候还要把这个skb记录在设备的私有变量中priv->skb = skb,以便我们的发送完后,把这个skb对应的内存释放掉dev_kfree_skb(priv->skb)。
当把数据解析出来后,就通过**memcpy(tx_buffer->data, buf, len);**把数据拷贝到网卡的寄存器中。通常情况下是写网卡的是能发送寄存器。网卡就把数据包发送出去了。
snull的设计
|-----------|
| |
|-->| remote0 |
| |192.168.0.2|
| |
| |-----------|
|
|-----------| |
| sn0|<------|
|192.168.0.1|
| |
| sn1|<------|
|192.168.1.2| |
|-----------| |
|
|
|
| |-----------|
| | |
|-->| remote1 |
|192.168.1.1|
| |
|-----------|
sull网络拓扑如上所示:
当执行ping remote0 -I sn0时,数据包的发送流程是192.168.0.1—>192.168.0.2,此时会调用snull的发送函数。在发送函数中直接修改IP地址的第3个oct。并重新构建检验和。
/* 提取本地和目标的IP地址 */
ih = (struct iphdr *)(buf + sizeof(struct ethhdr));
saddr = &ih->saddr;
daddr = &ih->daddr;
printk(KERN_INFO "ih->protocol = %d is buf[23]\n", ih->protocol);
printk(KERN_INFO "txbe %s:saddr:%d.%d.%d.%d -->daddr:%d.%d.%d.%d\n", dev->name,
((u8 *)saddr)[0], ((u8 *)saddr)[1], ((u8 *)saddr)[2],((u8 *)saddr)[3],
((u8 *)daddr)[0], ((u8 *)daddr)[1], ((u8 *)daddr)[2],((u8 *)daddr)[3]);
/* 修改原地址,目的地址 */
((u8 *)saddr)[2] ^= 1;
((u8 *)daddr)[2] ^= 1;
printk(KERN_INFO "txaf %s:saddr:%d.%d.%d.%d -->daddr:%d.%d.%d.%d\n",dev->name,
((u8 *)saddr)[0], ((u8 *)saddr)[1], ((u8 *)saddr)[2],((u8 *)saddr)[3],
((u8 *)daddr)[0], ((u8 *)daddr)[1], ((u8 *)daddr)[2],((u8 *)daddr)[3]);
/* IP改变,重新构建校验和 */
ih->check = 0;
ih->check = ip_fast_csum((unsigned char *)ih, ih->ihl);
改变之后,数据流量相当于192.168.1.1—>192.168.1.2sn1就接收到了sn0发过来的数据包。
sn0收到数据包之后,要回复该数据包,发送的数据流是192.168.1.2—>192.168.1.1
此时也调用了snull_tx函数,修改了IP地址,变为192.168.0.2—>192.168.0.1
sn0就能和sn1互ping 成功:
snull完整代码
/*
* snull.c -- the Simple Network Utility
*
* Copyright (C) 2020.3.24 liweijie<ee.liweijie@gmail.com>
*
* The source code in this file can be freely used, adapted,
* and redistributed in source or binary form, so long as an
* acknowledgment appears in derived source files. The citation
* should list that the code comes from the book "Linux Device
* Drivers" by Alessandro Rubini and Jonathan Corbet, published
* by O'Reilly & Associates. No warranty is attached;
* we cannot take responsibility for errors or fitness for use.
*
*/
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/etherdevice.h>
#include <linux/tcp.h>
#define SNULL_RX_INTR 0x0001
#define SNULL_TX_INTR 0x0002
int pool_size = 8;
module_param(pool_size, int, 0);
void snull_module_exit(void);
static void (*snull_interrupt)(int, void *, struct pt_regs *);
struct net_device *snull_devs[2];
struct snull_packet {
struct snull_packet *next;
struct net_device *dev;
int datalen;
u8 data[ETH_DATA_LEN];
};
struct snull_priv {
struct net_device *dev;
struct sk_buff *skb;
struct snull_packet *ppool;
struct snull_packet *rx_queue;
spinlock_t lock;
int status;
int rx_int_enabled;
int tx_packetlen;
u8 *tx_packetdata;
struct net_device_stats stats;
struct napi_struct napi;
};
struct snull_packet *snull_get_tx_buffer(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
unsigned long flags;
struct snull_packet *pkt;
spin_lock_irqsave(&priv->lock, flags);
/* 让pkt指向下一个pkt,如果数据包被取完了,通知内核,要求停止发送 */
pkt = priv->ppool;
priv->ppool = pkt->next;
if (priv->ppool == NULL) {
printk (KERN_INFO "Pool empty\n");
netif_stop_queue(dev);
}
spin_unlock_irqrestore(&priv->lock, flags);
return pkt;
}
void snull_release_buffer(struct snull_packet *pkt)
{
unsigned long flags;
struct snull_priv *priv = netdev_priv(pkt->dev);
spin_lock_irqsave(&priv->lock, flags);
pkt->next = priv->ppool;
priv->ppool = pkt;
spin_unlock_irqrestore(&priv->lock, flags);
if (netif_queue_stopped(pkt->dev) && pkt->next == NULL)
netif_wake_queue(pkt->dev);
}
void snull_setup_pool(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
int i;
struct snull_packet *pkt;
priv->ppool = NULL;
for (i = 0; i < pool_size; i++) {
pkt = kmalloc (sizeof (struct snull_packet), GFP_KERNEL);
if (pkt == NULL) {
printk (KERN_NOTICE "Ran out of memory allocating packet pool\n");
return;
}
pkt->dev = dev;
pkt->next = priv->ppool;
priv->ppool = pkt;
printk(KERN_INFO "%d name:%s, pkt:0x%lx, priv:0x%lx,priv->ppool:0x%lx\n",
i, dev->name, (unsigned long)pkt, (unsigned long)priv, (unsigned long)priv->ppool);
}
printk(KERN_INFO "create snull pool\n");
}
void snull_teardown_pool(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
struct snull_packet *pkt;
while ((pkt = priv->ppool)) {
priv->ppool = pkt->next;
printk(KERN_INFO "name:%s, pkt:0x%lx, priv:0x%lx,priv->ppool:0x%lx\n",
dev->name, (unsigned long)pkt, (unsigned long)priv, (unsigned long)priv->ppool);
kfree (pkt);
}
}
void snull_enqueue_buf(struct net_device *dev, struct snull_packet *pkt)
{
unsigned long flags;
struct snull_priv *priv = netdev_priv(dev);
spin_lock_irqsave(&priv->lock, flags);
pkt->next = priv->rx_queue;
priv->rx_queue = pkt;
spin_unlock_irqrestore(&priv->lock, flags);
}
struct snull_packet *snull_dequeue_buf(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
struct snull_packet *pkt;
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
pkt = priv->rx_queue;
if (pkt != NULL)
priv->rx_queue = pkt->next;
spin_unlock_irqrestore(&priv->lock, flags);
return pkt;
}
static void snull_rx_ints(struct net_device *dev, int enable)
{
struct snull_priv *priv = netdev_priv(dev);
priv->rx_int_enabled = enable;
}
int snull_open(struct net_device *dev)
{
if (dev == snull_devs[0])
memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);
else
memcpy(dev->dev_addr, "\0SNUL1", ETH_ALEN);
netif_start_queue(dev);
printk(KERN_INFO "snull open\n");
return 0;
}
int snull_release(struct net_device *dev)
{
netif_stop_queue(dev);
printk(KERN_INFO "snull release\n");
return 0;
}
/*
* 接收数据包:检索,封装并传递到更高层
*/
void snull_rx(struct net_device *dev, struct snull_packet *pkt)
{
struct sk_buff *skb;
struct snull_priv *priv;
priv = netdev_priv(dev);
/* 为接收包分配一个skb,+2是为了下面的skb_reserve使用 */
skb = dev_alloc_skb(pkt->datalen + 2);
if (!skb) {
if (printk_ratelimit())
printk(KERN_NOTICE "snull rx: low on mem - packet dropped\n");
priv->stats.rx_dropped++;
goto out;
}
/* 16字节对齐,即IP首部前是网卡硬件地址首部,
* 占用14个字节(原地址:6,目的地址:6,类型:2)
* 需要将其增加2 */
skb_reserve(skb, 2);
/* 开辟一个缓冲区用于存放接收数据 */
memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
/* Write metadata, and then pass to the receive level */
skb->dev = dev;
if (skb->dev == snull_devs[0])
printk(KERN_INFO "skb->dev is snull_devs[0]\n");
else
printk(KERN_INFO "skb->dev is snull_devs[1]\n");
/* 确定包的协议 */
skb->protocol = eth_type_trans(skb, dev);
printk(KERN_INFO "skb->protocol:%d\n", skb->protocol);
skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
/* 统计接收包数和字节数 */
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt->datalen;
/* 上报应用层 */
netif_rx(skb);
out:
return;
}
/*
* 数据的收发都要依靠中断,在中断中要处理rx tx中断
*/
static void snull_regular_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
int statusword;
struct snull_priv *priv;
struct snull_packet *pkt = NULL;
struct net_device *dev = (struct net_device *)dev_id;
/* paranoid */
if (!dev)
return;
/* Lock the device */
priv = netdev_priv(dev);
spin_lock(&priv->lock);
statusword = priv->status;
priv->status = 0;
/*
* 数据包到来,产生接收中断 调用接收函数
* 在接收函数中申请skb,将收到的pkt,拷贝到skb中
* 调用netif_rx,传递给协议栈
*/
if (statusword & SNULL_RX_INTR) {
printk(KERN_INFO "---start %s rx process---\n", dev->name);
printk(KERN_INFO "name:%s enter the rx interrupt\n", dev->name);
pkt = priv->rx_queue;
if (pkt) {
priv->rx_queue = pkt->next;
/* 网卡接收到数据,上报给应用层 */
snull_rx(dev, pkt);
}
printk(KERN_INFO "--- stop %s rx process---\n", dev->name);
}
/* 数据包传输完成,产生传输中断
* 统计发送的包数和字节数,并释放这个包的内存 */
if (statusword & SNULL_TX_INTR) {
printk(KERN_INFO "name:%s enter the tx interrupt\n", dev->name);
priv->stats.tx_packets++;
priv->stats.tx_bytes += priv->tx_packetlen;
dev_kfree_skb(priv->skb);
}
spin_unlock(&priv->lock);
if (pkt)
snull_release_buffer(pkt); /* Do this outside the lock! */
printk(KERN_INFO "snull regular interrupt\n");
return;
}
static void snull_hw_tx(char *buf, int len, struct net_device *dev)
{
struct iphdr *ih;
struct net_device *dest;
struct snull_priv *priv;
u32 *saddr, *daddr;
struct snull_packet *tx_buffer;
/* 以太网头部14字节,IP头部20个字节,*/
if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {
printk("snull: Hmm... packet too short (%i octets)\n", len);
return;
}
/*
* 打印上层应用层要发的包的内容
* 14字节以太网首都 + 20字节IP地址首都 + 20字节TCP地址首部 + n字节数据
*/
if (0) {
int i = 0;
printk(KERN_INFO "ethernet header:\n");
for (i = 0; i < 14; i++)
printk("%3d:%02x", i, buf[i] & 0xff);
printk(KERN_INFO "IP header:\n");
for (i = 14; i < 34; i++)
printk("%3d:%02x", i, buf[i] & 0xff);
printk(KERN_INFO "TCP header:\n");
for (i = 34; i < 54; i++)
printk("%3d:%02x", i, buf[i] & 0xff);
printk(KERN_INFO "data:\n");
for (i = 54; i < len; i++)
printk("%3d, %02x", i, buf[i] & 0xff);
}
/*
* Ethhdr is 14 bytes, but the kernel arranges for iphdr
* to be aligned (i.e., ethhdr is unaligned)
*/
/* 提取本地和目标的IP地址 */
ih = (struct iphdr *)(buf + sizeof(struct ethhdr));
saddr = &ih->saddr;
daddr = &ih->daddr;
printk(KERN_INFO "ih->protocol = %d is buf[23]\n", ih->protocol);
printk(KERN_INFO "txbe %s:saddr:%d.%d.%d.%d -->daddr:%d.%d.%d.%d\n", dev->name,
((u8 *)saddr)[0], ((u8 *)saddr)[1], ((u8 *)saddr)[2],((u8 *)saddr)[3],
((u8 *)daddr)[0], ((u8 *)daddr)[1], ((u8 *)daddr)[2],((u8 *)daddr)[3]);
/* 修改原地址,目的地址 */
((u8 *)saddr)[2] ^= 1;
((u8 *)daddr)[2] ^= 1;
printk(KERN_INFO "txaf %s:saddr:%d.%d.%d.%d -->daddr:%d.%d.%d.%d\n",dev->name,
((u8 *)saddr)[0], ((u8 *)saddr)[1], ((u8 *)saddr)[2],((u8 *)saddr)[3],
((u8 *)daddr)[0], ((u8 *)daddr)[1], ((u8 *)daddr)[2],((u8 *)daddr)[3]);
/* IP改变,重新构建校验和 */
ih->check = 0;
ih->check = ip_fast_csum((unsigned char *)ih, ih->ihl);
/* 打印变更后的地址和TCP地址 */
if (dev == snull_devs[0])
printk(KERN_INFO "name:%s, %08x:%05i --> %08x:%05i\n",
dev->name,
ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source),
ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest));
else
printk(KERN_INFO "name:%s,%08x:%05i <-- %08x:%05i\n",
dev->name,
ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest),
ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source));
/*
* 数据包准备好了
* 要模拟两个中断:一个是在接收端模拟接收中断,另一个实在发送端模拟发送完成中断
* 通过设置私有变量的状态来模拟priv->status
*/
//dest = snull_devs[dev == snull_devs[0] ? 1 : 0]; /* 如果源是snull_devs[0],目的则是snull_devs[1] */
/* 获取目的网卡地址 */
if (dev == snull_devs[0]) {
dev = snull_devs[0];
dest = snull_devs[1];
printk(KERN_INFO "snull_devs[0]\n");
} else {
dev = snull_devs[1];
dest = snull_devs[0];
printk(KERN_INFO "snull_dev[1]\n");
}
/* 处理目的端:接收 */
priv = netdev_priv(dest);
/* 取出一块内存,分配给本地网卡 */
tx_buffer = snull_get_tx_buffer(dev);
/* 设置数据包大小 */
tx_buffer->datalen = len;
printk(KERN_INFO "tx_buffer->datalen = %d\n", tx_buffer->datalen);
/* 填充发送网卡的数据 */
memcpy(tx_buffer->data, buf, len);
/* 把发送的数据直接加入到接收队列
* 这里相当于本地网卡要发送的数据已经给目标网卡直接接收到了
*/
snull_enqueue_buf(dest, tx_buffer);
if (priv->rx_int_enabled) {
priv->status |= SNULL_RX_INTR; /* 目的端收到数据包之后,模拟触发接收中断 */
snull_interrupt(0, dest, NULL);
printk(KERN_INFO "priv->status = %d\n", priv->status);
}
/* 处理源端:发送 */
priv = netdev_priv(dev);
/* 把本地网卡要发送的数据存到私有数据缓冲区 */
priv->tx_packetlen = len;
priv->tx_packetdata = buf;
/* 模拟产生一个发送中断 */
priv->status |= SNULL_TX_INTR; /* 源端发送完了,触发发送中断 */
snull_interrupt(0, dev, NULL);
printk(KERN_INFO "snull_interrupt(0, dev, NULL)\n");
}
/*
* tx函数是协议栈决定何时调用
* 在初始化网络设备时,挂接.ndo_start_xmit = snull_tx
*/
int snull_tx(struct sk_buff *skb, struct net_device *dev)
{
int len;
char *data, shortpkt[ETH_ZLEN];
struct snull_priv *priv = netdev_priv(dev);
/* 获取上层要发送的数据和长度 */
data = skb->data;
len = skb->len;
printk(KERN_INFO "skb->len = %d\n", skb->len);
printk(KERN_INFO "***start %s tx process***\n", dev->name);
printk(KERN_INFO "name:%s data_len:%d\n", dev->name, len);
/* 如果小于60字节,用0填充,最终修改了data,len*/
if (len < ETH_ZLEN) {
memset(shortpkt, 0, ETH_ZLEN);
memcpy(shortpkt, skb->data, skb->len);
len = ETH_ZLEN;
data = shortpkt;
}
/*
* 用私有变量记录skb,以便在发送完成
* 调用中断的时候,释放skb
*/
priv->skb = skb;
/* 模拟把数据写入硬件,通过硬件发送出去,实际不是 */
snull_hw_tx(data, len, dev);
printk(KERN_INFO "****stop %s tx process***\n", dev->name);
return 0; /* Our simple device can not fail */
}
struct net_device_stats *snull_stats(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
return &priv->stats;
}
int snull_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type, const void *daddr, const void *saddr,
unsigned len)
{
struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN);
printk(KERN_INFO "---begin create the %s header---\n", dev->name);
printk(KERN_INFO "len = %d\n", len);
printk(KERN_INFO "type = 0x%04x\n", type); //ETH_P_IP 0x0800 /* Internet Protocol packet */
/*
* 将整形变量从主机字节序转变为网络字节序
* 就是整数在地址空间的存储方式变为:高位字节存放在内存的低地址处
*/
eth->h_proto = htons(type);
printk(KERN_INFO "h_proto = 0x%04x\n", eth->h_proto);
printk(KERN_INFO "addr_len = %d\n", dev->addr_len);
printk(KERN_INFO "dev_addr = %02x.%02x.%02x.%02x.%02x.%02x\n",
dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
if (saddr) {
printk("saddr = %02x.%02x.%02x.%02x.%02x.%02x\n",
*((unsigned char *)saddr + 0),
*((unsigned char *)saddr + 1),
*((unsigned char *)saddr + 2),
*((unsigned char *)saddr + 3),
*((unsigned char *)saddr + 4),
*((unsigned char *)saddr + 5));
}
if (daddr) {
printk("daddr = %02x.%02x.%02x.%02x.%02x.%02x\n",
*((unsigned char *)daddr + 0),
*((unsigned char *)daddr + 1),
*((unsigned char *)daddr + 2),
*((unsigned char *)daddr + 3),
*((unsigned char *)daddr + 4),
*((unsigned char *)daddr + 5));
}
/* 上层应用数据,通过下层添加硬件地址,才能决定发送到目标网卡 */
memcpy(eth->h_source, saddr ? saddr : dev->dev_addr, dev->addr_len);
memcpy(eth->h_dest, daddr ? daddr : dev->dev_addr, dev->addr_len);
printk(KERN_INFO "h_source = %02x.%02x.%02x.%02x.%02x.%02x\n",
eth->h_source[0], eth->h_source[1], eth->h_source[2],
eth->h_source[3], eth->h_source[4], eth->h_source[5]);
printk(KERN_INFO " h_dest = %02x.%02x.%02x.%02x.%02x.%02x\n",
eth->h_dest[0], eth->h_dest[1], eth->h_dest[2],
eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]);
/*
* 设置目标网卡硬件地址,即本地网卡和目标网卡硬件地址最后一个字节的低有效位
* 是相反关系,即本地是\0snull0的话,目标就是\0snull1
* 或者本地是\0snull1,目标就是\0snull0
*/
eth->h_dest[ETH_ALEN-1] ^= 0x01; /* dest is us xor 1 */
printk(KERN_INFO "h_dest[ETH_ALEN-1] ^ 0x01 = %02x\n", eth->h_dest[ETH_ALEN-1]);
printk(KERN_INFO "hard_header_len = %d\n", dev->hard_header_len);
printk(KERN_INFO "---end of create the %s header---\n", dev->name);
return (dev->hard_header_len);
}
/*
* The "change_mtu" method is usually not needed.
* If you need it, it must be like this.
*/
int snull_change_mtu(struct net_device *dev, int new_mtu)
{
unsigned long flags;
struct snull_priv *priv = netdev_priv(dev);
spinlock_t *lock = &priv->lock;
/* check ranges */
if ((new_mtu < 68) || (new_mtu > 1500))
return -EINVAL;
/*
* Do anything you need, and the accept the value
*/
spin_lock_irqsave(lock, flags);
dev->mtu = new_mtu;
spin_unlock_irqrestore(lock, flags);
return 0; /* success */
}
static const struct header_ops snull_header_ops = {
.create = snull_header,
};
struct net_device_ops snull_netdev_ops = {
.ndo_open = snull_open,
.ndo_stop = snull_release,
.ndo_start_xmit = snull_tx,
.ndo_get_stats = snull_stats,
};
void snull_init(struct net_device *dev)
{
struct snull_priv *priv;
ether_setup(dev); /* assign some of the fields */
dev->header_ops = &snull_header_ops;
dev->netdev_ops = &snull_netdev_ops;
dev->flags |= IFF_NOARP;
dev->features |= NETIF_F_HW_CSUM;
priv = netdev_priv(dev);
memset(priv, 0, sizeof(struct snull_priv));
spin_lock_init(&priv->lock);
snull_rx_ints(dev, 1);
snull_setup_pool(dev);
printk(KERN_INFO "snull init\n");
}
void snull_module_exit(void)
{
int i;
for (i = 0; i < 2; i++) {
if (snull_devs[i]) {
unregister_netdev(snull_devs[i]);
snull_teardown_pool(snull_devs[i]);
free_netdev(snull_devs[i]);
}
}
return;
}
int snull_module_init(void)
{
int ret = -ENOMEM;
int i = 0;
int result = 0;
snull_interrupt = snull_regular_interrupt;
snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d", NET_NAME_UNKNOWN, snull_init);
snull_devs[1] = alloc_netdev(sizeof(struct snull_priv), "sn%d", NET_NAME_UNKNOWN, snull_init);
if (snull_devs[0] == NULL || snull_devs[1] == NULL)
goto out;
ret = -ENODEV;
for (i = 0; i < 2; i++) {
if ((result = register_netdev(snull_devs[i]))) {
printk(KERN_INFO "snull: error :%d register device:%s\n", result, snull_devs[i]->name);
} else {
ret = 0;
}
}
printk(KERN_INFO "snull init module\n");
out:
if (ret)
snull_module_exit();
return 0;
}
module_init(snull_module_init);
module_exit(snull_module_exit);
MODULE_LICENSE("Dual BSD/GPL");