编写snull程序

编写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");

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值