snull是《Linux Device Drivers》中的一个网络驱动的例子。这里引用这个例子学习Linux网络驱动。
因为snull的源码,网上已经更新到适合最新内核,而我自己用的还是2.6.22.6比较旧的内核。而网上好像找不到旧版的snull。因此结合《Linux Device Drivers》把最新的snull例子移植到2.6.22.6内核中。移植也相对简单,这里也提供移植好的代码。
估计不少网友看到《Linux Device Drivers》的网络驱动部分,一脸懵逼,包括我自己,不理解作者设计这个例子的真正目的,尽管有配图,仍然懵懂,甚至不知道为什么会用到6个IP地址。如图:
其实作者的本意是想通过虚拟网卡来模拟实际的网卡和外部的网络设备的通信来讨论网络驱动。通过其中任何一个网络接口(sn0或sn1)发送数据,都在另一个网络接口(sn0或sn1)接收到。
因为sn0和sn1都不在同一个网段,所以sn0和sn1之间直接互ping是不行的,这中间必须必须做点转换。
例子:
理论上local0和remote0只能互ping,因为他们都在同一个网段:192.168.0.0,但事实上,local0在发出数据之后,local0的第3个字节最低有效位改取反,就变成了remote1,remote1的数据才能到达local1,因为他们在同一段IP。相反,local1在发出数据之后,local1的第3个字节最低有效位改取反,就变成了remote0,remote0的数据才能到达local0.
因此,在实验之前,需要添加一些配置:
在/etc/networks文件中添加如下网段IP:
snullnet0 192.168.2.0
snullnet1 192.168.3.0
在/etc/hosts文件中添加如下IP地址
192.168.2.8 local0
192.168.2.9 remote0
192.168.3.9 local1
192.168.3.8 remote1
注意: 1. 网段IP和IP地址的第三个字节的最低有效位是相反的
2. local0和remote1第四个字节必须一样,remote0和local1第四个字节必须一样
3. 如果开发板上的真正网卡用了的网段IP,就不能再用于本实验。如:我的开发板的DM9000网卡使用网段是192.168.1.0, 因此本实验不能再使用192.168.1.0作为网段,否则有冲突。
代码: snull.c, 其中snull.h没改动,因此不贴出来
/*
* snull.c -- the Simple Network Utility
*
* Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
* Copyright (C) 2001 O'Reilly & Associates
*
* 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.
*
* $Id: snull.c,v 1.21 2004/11/05 02:36:03 rubini Exp $
*/ #include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h> #include <linux/sched.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/interrupt.h> /* mark_bh */ #include <linux/in.h>
#include <linux/netdevice.h> /* struct device, and other headers */
#include <linux/etherdevice.h> /* eth_type_trans */
#include <linux/ip.h> /* struct iphdr */
#include <linux/tcp.h> /* struct tcphdr */
#include <linux/skbuff.h> #include "snull.h" #include <linux/in6.h>
#include <asm/checksum.h> MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet");
MODULE_LICENSE("Dual BSD/GPL"); /*
* Transmitter lockup simulation, normally disabled.
*/
static int lockup = ;
module_param(lockup, int, ); static int timeout = SNULL_TIMEOUT;
module_param(timeout, int, ); /*
* Do we run in NAPI mode?
*/
static int use_napi = ;
module_param(use_napi, int, ); /*
* A structure representing an in-flight packet.
*/
struct snull_packet {
struct snull_packet *next;
struct net_device *dev;
int datalen;
u8 data[ETH_DATA_LEN];
}; int pool_size = ;
module_param(pool_size, int, ); /*
* This structure is private to each device. It is used to pass
* packets in and out, so there is place for a packet
*/ struct snull_priv {
struct net_device_stats stats;
int status;
struct snull_packet *ppool;
struct snull_packet *rx_queue; /* List of incoming packets */
int rx_int_enabled;
int tx_packetlen;
u8 *tx_packetdata;
struct sk_buff *skb;
spinlock_t lock;
struct net_device *dev;
//struct napi_struct napi;
}; static void snull_tx_timeout(struct net_device *dev);
static void (*snull_interrupt)(int, void *, struct pt_regs *); /*
* Set up a device's packet pool.
*/
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 = ; 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;
}
} 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;
kfree (pkt);
/* FIXME - in-flight packets ? */
}
} /*
* Buffer/pool management.
*/
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 = 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); printk("snull_release_buffer\n");
} 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; /* FIXME - misorders packets */
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;
} /*
* Enable and disable receive interrupts.
*/
static void snull_rx_ints(struct net_device *dev, int enable)
{
struct snull_priv *priv = netdev_priv(dev);
priv->rx_int_enabled = enable;
} /*
* Open and close
*/ int snull_open(struct net_device *dev)
{
/* request_region(), request_irq(), .... (like fops->open) */ /*
* Assign the hardware address of the board: use "\0SNULx", where
* x is 0 or 1. The first byte is '\0' to avoid being a multicast
* address (the first byte of multicast addrs is odd).
*/
/* [cgw]: 分配一个假的硬件地址,真正的网卡的时候,这个地址是从网卡读出来的 */
memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);
/* [cgw]: 因为注册了两个虚拟网卡,第二个虚拟网卡的地址跟第一个的地址必须不一样
* 即这两个网卡地址分别为\0SNUL0和\0SNUL1
*/
if (dev == snull_devs[])
dev->dev_addr[ETH_ALEN-]++; /* \0SNUL1 */
/* [cgw]: 启动发送队列 */
netif_start_queue(dev); printk("snull_open\n"); return ;
} int snull_release(struct net_device *dev)
{
/* release ports, irq and such -- like fops->close */ netif_stop_queue(dev); /* can't transmit any more */ printk("snull_release\n"); return ;
} /*
* Configuration changes (passed on by ifconfig)
*/
int snull_config(struct net_device *dev, struct ifmap *map)
{
if (dev->flags & IFF_UP) /* can't act on a running interface */
return -EBUSY; /* Don't allow changing the I/O address */
if (map->base_addr != dev->base_addr) {
printk(KERN_WARNING "snull: Can't change I/O address\n");
return -EOPNOTSUPP;
} /* Allow changing the IRQ */
if (map->irq != dev->irq) {
dev->irq = map->irq;
/* request_irq() is delayed to open-time */
} printk("snull_config\n"); /* ignore other fields */
return ;
} /*
* Receive a packet: retrieve, encapsulate and pass over to upper levels
*/
void snull_rx(struct net_device *dev, struct snull_packet *pkt)
{
struct sk_buff *skb;
struct snull_priv *priv = netdev_priv(dev); /*
* The packet has been retrieved from the transmission
* medium. Build an skb around it, so upper layers can handle it
*/
/* [cgw]: 为接收包分配一个skb */
skb = dev_alloc_skb(pkt->datalen + );
if (!skb) {
if (printk_ratelimit())
printk(KERN_NOTICE "snull rx: low on mem - packet dropped\n");
priv->stats.rx_dropped++;
goto out;
}
/* [cgw]: 16字节对齐,即IP首部前是网卡硬件地址首部,其占14字节,需要为其增加2
* 个字节
*/
skb_reserve(skb, ); /* align IP on 16B boundary */
/* [cgw]: 开辟一个数据缓冲区用于存放接收数据 */
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[]) {
printk("skb->dev is snull_devs[0]\n");
} else {
printk("skb->dev is snull_devs[1]\n");
}
/* [cgw]: 确定包的协议ID */
skb->protocol = eth_type_trans(skb, dev); printk("skb->protocol = %d\n", skb->protocol); skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
/* [cgw]: 统计接收包数和字节数 */
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt->datalen;
/* [cgw]: 上报应用层 */
netif_rx(skb); printk("snull_rx\n"); out:
return;
} /*
* The poll implementation.
*/
//static int snull_poll(struct napi_struct *napi, int budget)
static int snull_poll(struct net_device *dev, int *budget)
{
//int npackets = 0;
//struct sk_buff *skb;
//struct snull_priv *priv = container_of(napi, struct snull_priv, napi);
//struct net_device *dev = priv->dev;
//struct snull_packet *pkt; int npackets = , quota = min(dev->quota, *budget);
struct sk_buff *skb;
struct snull_priv *priv = netdev_priv(dev);
struct snull_packet *pkt; printk("snull_poll\n"); //while (npackets < budget && priv->rx_queue) {
while (npackets < quota && priv->rx_queue) {
pkt = snull_dequeue_buf(dev);
skb = dev_alloc_skb(pkt->datalen + );
if (! skb) {
if (printk_ratelimit())
printk(KERN_NOTICE "snull: packet dropped\n");
priv->stats.rx_dropped++;
snull_release_buffer(pkt);
continue;
}
skb_reserve(skb, ); /* align IP on 16B boundary */
memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
netif_receive_skb(skb); /* Maintain stats */
npackets++;
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt->datalen;
snull_release_buffer(pkt);
}
/* If we processed all packets, we're done; tell the kernel and reenable ints */
*budget -= npackets;
dev->quota -= npackets;
if (! priv->rx_queue) {
//napi_complete(napi);
netif_rx_complete(dev);
snull_rx_ints(dev, );
return ;
}
/* We couldn't process everything. */
//return npackets;
return ;
} /*
* The typical interrupt entry point
*/
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;
/*
* As usual, check the "device" pointer to be sure it is
* really interrupting.
* Then assign "struct device *dev"
*/
struct net_device *dev = (struct net_device *)dev_id;
/* ... and check with hw if it's really ours */ /* paranoid */
if (!dev)
return; /* Lock the device */
priv = netdev_priv(dev);
spin_lock(&priv->lock); /* [cgw]: 判断产生的是什么类型的中断,接收还是中断 */
/* retrieve statusword: real netdevices use I/O instructions */
statusword = priv->status; printk("priv->status = %d\n", priv->status); priv->status = ;
/* [cgw]: 接收完成中断 */
if (statusword & SNULL_RX_INTR) {
/* send it to snull_rx for handling */
pkt = priv->rx_queue;
if (pkt) {
priv->rx_queue = pkt->next;
/* [cgw]: 网卡接收到数据,上报给应用层 */
snull_rx(dev, pkt);
}
}
/* [cgw]: 发送完成中断 */
if (statusword & SNULL_TX_INTR) {
/* [cgw]: 统计已发送的包数和总字节数,并释放这个包的内存 */
/* a transmission is over: free the skb */
priv->stats.tx_packets++;
priv->stats.tx_bytes += priv->tx_packetlen;
dev_kfree_skb(priv->skb);
} /* Unlock the device and we are done */
spin_unlock(&priv->lock);
if (pkt) snull_release_buffer(pkt); /* Do this outside the lock! */ printk("snull_regular_interrupt\n"); return;
} /*
* A NAPI interrupt handler.
*/
static void snull_napi_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
int statusword;
struct snull_priv *priv; /*
* As usual, check the "device" pointer for shared handlers.
* Then assign "struct device *dev"
*/
struct net_device *dev = (struct net_device *)dev_id;
/* ... and check with hw if it's really ours */ printk("snull_napi_interrupt\n"); /* paranoid */
if (!dev)
return; /* Lock the device */
priv = netdev_priv(dev);
spin_lock(&priv->lock); /* retrieve statusword: real netdevices use I/O instructions */
statusword = priv->status;
priv->status = ;
if (statusword & SNULL_RX_INTR) {
snull_rx_ints(dev, ); /* Disable further interrupts */
//napi_schedule(&priv->napi);
netif_rx_schedule(dev);
}
if (statusword & SNULL_TX_INTR) {
/* a transmission is over: free the skb */
priv->stats.tx_packets++;
priv->stats.tx_bytes += priv->tx_packetlen;
dev_kfree_skb(priv->skb);
} /* Unlock the device and we are done */
spin_unlock(&priv->lock);
return;
} /*
* Transmit a packet (low level interface)
*/
static void snull_hw_tx(char *buf, int len, struct net_device *dev)
{
/*
* This function deals with hw details. This interface loops
* back the packet to the other snull interface (if any).
* In other words, this function implements the snull behaviour,
* while all other procedures are rather device-independent
*/
struct iphdr *ih;
struct net_device *dest;
struct snull_priv *priv;
u32 *saddr, *daddr;
struct snull_packet *tx_buffer; /* I am paranoid. Ain't I? */
if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {
printk("snull: Hmm... packet too short (%i octets)\n",
len);
return;
} /* [cgw]: 打印上层应用(即ping)要发的这个包的内容
* 这个包的格式为:
* 14字节以太网首部+20字节IP地址首部+20字节TCP地址首部+n字节数据
*/ if () { /* enable this conditional to look at the data */
int i;
PDEBUG("len is %i\n" KERN_DEBUG "data:",len);
/* [cgw]: 14字节以太网首部 */
for (i= ; i<; i++)
printk(" %02x",buf[i]&0xff);
printk("\n"); /* [cgw]: 20字节IP地址首部 */
for (i= ; i<; i++)
printk(" %02x",buf[i]&0xff);
printk("\n"); /* [cgw]: 20字节TCP地址首部 */
for (i= ; i<; i++)
printk(" %02x",buf[i]&0xff);
printk("\n"); /* [cgw]: n字节数据 */
for (i= ; i<len; i++)
printk(" %02x",buf[i]&0xff);
printk("\n");
}
/*
* Ethhdr is 14 bytes, but the kernel arranges for iphdr
* to be aligned (i.e., ethhdr is unaligned)
*/
/* [cgw]: 提取本地和目标IP地址 */
ih = (struct iphdr *)(buf+sizeof(struct ethhdr));
saddr = &ih->saddr;
daddr = &ih->daddr; printk("ih->protocol = %d is buf[23]\n", ih->protocol);
printk("saddr = %d.%d.%d.%d\n", *((u8 *)saddr + ), *((u8 *)saddr + ), *((u8 *)saddr + ), *((u8 *)saddr + ));
printk("daddr = %d.%d.%d.%d\n", *((u8 *)daddr + ), *((u8 *)daddr + ), *((u8 *)daddr + ), *((u8 *)daddr + )); /* [cgw]: 改变本地和目标IP地址的第三个字节的最低位,即原来是0,则改为1,原来是1,则改为0
*/
((u8 *)saddr)[] ^= ; /* change the third octet (class C) */
((u8 *)daddr)[] ^= ; /* [cgw]: 从新计算校验,因为IP已改变 */
ih->check = ; /* and rebuild the checksum (ip needs it) */
ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl); /* [cgw]: 打印更改后的IP地址,和TCP地址,
*/
if (dev == snull_devs[])
//PDEBUGG("%08x:%05i --> %08x:%05i\n",
printk("%08x:%05i --> %08x:%05i\n",
ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+))->source),
ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+))->dest));
else
//PDEBUGG("%08x:%05i <-- %08x:%05i\n",
printk("%08x:%05i <-- %08x:%05i\n",
ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+))->dest),
ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+))->source)); /*
* Ok, now the packet is ready for transmission: first simulate a
* receive interrupt on the twin device, then a
* transmission-done on the transmitting device
*/
/* [cgw]: 获得目的网卡设备 */
dest = snull_devs[dev == snull_devs[] ? : ]; if (dev == snull_devs[]) {
printk("snull_devs[0]\n");
} else {
printk("snull_devs[1]\n");
} priv = netdev_priv(dest);
/* [cgw]: 取出一块内存分配给本地网卡 */
tx_buffer = snull_get_tx_buffer(dev);
/* [cgw]: 设置数据包大小 */
tx_buffer->datalen = len; printk("tx_buffer->datalen = %d\n", tx_buffer->datalen); /* [cgw]: 填充发送网卡的数据 */
memcpy(tx_buffer->data, buf, len);
/* [cgw]: 把发送的数据直接加入到接收队列,这里相当于本地网卡要发送的数据
* 已经给目标网卡直接接收到了
*/
snull_enqueue_buf(dest, tx_buffer);
/* [cgw]: 如果接收中断使能,这个也是模拟的接收中断,因为上面已经模拟接收
* 到数据,所以立刻产生一个中断
*/
if (priv->rx_int_enabled) {
priv->status |= SNULL_RX_INTR;
printk("priv->status = %d\n", priv->status);
/* [cgw]: 执行接收中断 */
snull_interrupt(, dest, NULL);
printk("snull_interrupt(0, dest, NULL);\n");
} /* [cgw]: 获得本地网卡的私有数据指针 */
priv = netdev_priv(dev);
/* [cgw]: 把本地网卡要发送的数据存到私有数据缓冲区,接着产生一个发送中断
*/
priv->tx_packetlen = len;
priv->tx_packetdata = buf;
priv->status |= SNULL_TX_INTR;
if (lockup && ((priv->stats.tx_packets + ) % lockup) == ) {
/* Simulate a dropped transmit interrupt */
netif_stop_queue(dev);
PDEBUG("Simulate lockup at %ld, txp %ld\n", jiffies,
(unsigned long) priv->stats.tx_packets);
}
else {
/* [cgw]: 产生一个发送中断 */
snull_interrupt(, dev, NULL);
printk("snull_interrupt(0, dev, NULL);\n");
}
} /*
* Transmit a packet (called by the kernel)
*/
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); /* [cgw]: 获取上层需要发送的数据和长度 */
data = skb->data;
len = skb->len; printk("skb->len = %d\n", skb->len); if (len < ETH_ZLEN) {
memset(shortpkt, , ETH_ZLEN);
memcpy(shortpkt, skb->data, skb->len);
len = ETH_ZLEN;
data = shortpkt;
}
/* [cgw]: 开始计算时间截,用于处理发送超时 */
dev->trans_start = jiffies; /* save the timestamp */ /* Remember the skb, so we can free it at interrupt time */
priv->skb = skb; printk("snull_tx\n"); /* actual deliver of data is device-specific, and not shown here */
/* [cgw]: 模拟把数据包写入硬件,通过硬件发送出去,但实际上不是 */
snull_hw_tx(data, len, dev); //printk("snull_tx\n"); return ; /* Our simple device can not fail */
} /*
* Deal with a transmit timeout.
*/
void snull_tx_timeout (struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev); PDEBUG("Transmit timeout at %ld, latency %ld\n", jiffies,
jiffies - dev->trans_start);
/* Simulate a transmission interrupt to get things moving */
priv->status = SNULL_TX_INTR;
snull_interrupt(, dev, NULL);
priv->stats.tx_errors++;
netif_wake_queue(dev); printk("snull_tx_timeout\n"); return;
} /*
* Ioctl commands
*/
int snull_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
PDEBUG("ioctl\n");
printk("ioctl\n");
return ;
} /*
* Return statistics to the caller
*/
struct net_device_stats *snull_stats(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev); printk("snull_stats\n"); return &priv->stats;
} /*
* This function is called to fill up an eth header, since arp is not
* available on the interface
*/
int snull_rebuild_header(struct sk_buff *skb)
{
struct ethhdr *eth = (struct ethhdr *) skb->data;
struct net_device *dev = skb->dev; memcpy(eth->h_source, dev->dev_addr, dev->addr_len);
memcpy(eth->h_dest, dev->dev_addr, dev->addr_len);
eth->h_dest[ETH_ALEN-] ^= 0x01; /* dest is us xor 1 */ printk("snull_rebuild_header\n"); return ;
} //int snull_header(struct sk_buff *skb, struct net_device *dev,
// unsigned short type, const void *daddr, const void *saddr,
// unsigned len) int snull_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type, void *daddr, void *saddr,
unsigned len)
{
struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN); printk("len = %d\n", len); printk("type = %02x\n", type); //ETH_P_IP 0x0800 /* Internet Protocol packet */ /* htons是将整型变量从主机字节顺序转变成网络字节顺序,
* 就是整数在地址空间存储方式变为:高位字节存放在内存的低地址处
*/
eth->h_proto = htons(type);
printk("h_proto = %d\n", eth->h_proto); printk("addr_len = %d\n", dev->addr_len);
printk("dev_addr = %02x.%02x.%02x.%02x.%02x.%02x\n", dev->dev_addr[], dev->dev_addr[], dev->dev_addr[], dev->dev_addr[], dev->dev_addr[], dev->dev_addr[]); if (saddr) {
printk("saddr = %02x.%02x.%02x.%02x.%02x.%02x\n", *((unsigned char *)saddr + ), *((unsigned char *)saddr + ), *((unsigned char *)saddr + ), *((unsigned char *)saddr + ), *((unsigned char *)saddr + ), *((unsigned char *)saddr + ));
} if (daddr) {
printk("daddr = %02x.%02x.%02x.%02x.%02x.%02x\n", *((unsigned char *)daddr + ), *((unsigned char *)daddr + ), *((unsigned char *)daddr + ), *((unsigned char *)daddr + ), *((unsigned char *)daddr + ), *((unsigned char *)daddr + ));
} /* [cgw]: 上层应用要发送数据时,通过下层添加硬件地址,才能决定发送到那个目标网卡
*/
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("h_source = %02x.%02x.%02x.%02x.%02x.%02x\n", eth->h_source[], eth->h_source[], eth->h_source[],eth->h_source[], eth->h_source[], eth->h_source[]);
printk("h_dest = %02x.%02x.%02x.%02x.%02x.%02x\n", eth->h_dest[], eth->h_dest[], eth->h_dest[], eth->h_dest[], eth->h_dest[], eth->h_dest[]); /* [cgw]: 设置目标网卡硬件地址,即本地网卡和目标网卡硬件地址的最后一个字节的最低有效位
* 是相反关系,即本地是\0SNUL0的话,目标就是\0SNUL1,或者本地是\0SNUL1,目标就是\0SNUL0
*/
eth->h_dest[ETH_ALEN-] ^= 0x01; /* dest is us xor 1 */
printk("h_dest[ETH_ALEN-1] ^ 0x01 = %02x\n", eth->h_dest[ETH_ALEN-]); printk("hard_header_len = %d\n", dev->hard_header_len); 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 < ) || (new_mtu > ))
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 ; /* success */
} #if 0
static const struct header_ops snull_header_ops = {
.create = snull_header,
.rebuild = snull_rebuild_header
}; static const struct net_device_ops snull_netdev_ops = {
.ndo_open = snull_open,
.ndo_stop = snull_release,
.ndo_start_xmit = snull_tx,
.ndo_do_ioctl = snull_ioctl,
.ndo_set_config = snull_config,
.ndo_get_stats = snull_stats,
.ndo_change_mtu = snull_change_mtu,
.ndo_tx_timeout = snull_tx_timeout
};
#endif /*
* The init function (sometimes called probe).
* It is invoked by register_netdev()
*/
void snull_init(struct net_device *dev)
{
struct snull_priv *priv;
#if 0
/*
* Make the usual checks: check_region(), probe irq, ... -ENODEV
* should be returned if no device found. No resource should be
* grabbed: this is done on open().
*/
#endif /*
* Then, assign other fields in dev, using ether_setup() and some
* hand assignments
*/
ether_setup(dev); /* assign some of the fields */
dev->watchdog_timeo = timeout; //dev->netdev_ops = &snull_netdev_ops;
//dev->header_ops = &snull_header_ops; dev->hard_header = snull_header;
dev->rebuild_header = snull_rebuild_header; dev->open = snull_open;
dev->stop = snull_release;
dev->hard_start_xmit = snull_tx;
dev->do_ioctl = snull_ioctl;
dev->set_config = snull_config;
dev->get_stats = snull_stats;
dev->change_mtu = snull_change_mtu;
dev->tx_timeout = snull_tx_timeout; /* keep the default flags, just add NOARP */
dev->flags |= IFF_NOARP;
dev->features |= NETIF_F_HW_CSUM; dev->hard_header_cache = NULL; /*
* Then, initialize the priv field. This encloses the statistics
* and a few private fields.
*/
priv = netdev_priv(dev);
#if 0
if (use_napi) {
netif_napi_add(dev, &priv->napi, snull_poll,);
}
#else
if (use_napi) {
dev->poll = snull_poll;
dev->weight = ;
}
#endif
memset(priv, , sizeof(struct snull_priv));
spin_lock_init(&priv->lock);
snull_rx_ints(dev, ); /* enable receive interrupts */
snull_setup_pool(dev); printk("snull_init\n");
} /*
* The devices
*/ struct net_device *snull_devs[]; /*
* Finally, the module stuff
*/ void snull_cleanup(void)
{
int i; for (i = ; i < ; i++) {
if (snull_devs[i]) {
unregister_netdev(snull_devs[i]);
snull_teardown_pool(snull_devs[i]);
free_netdev(snull_devs[i]);
}
}
return;
} int snull_init_module(void)
{
int result, i, ret = -ENOMEM; snull_interrupt = use_napi ? snull_napi_interrupt : snull_regular_interrupt; /* Allocate the devices */
snull_devs[] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
snull_init);
snull_devs[] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
snull_init);
if (snull_devs[] == NULL || snull_devs[] == NULL)
goto out; ret = -ENODEV;
for (i = ; i < ; i++)
if ((result = register_netdev(snull_devs[i])))
printk("snull: error %i registering device \"%s\"\n",
result, snull_devs[i]->name);
else
ret = ; printk("snull_init_module\n"); out:
if (ret)
snull_cleanup();
return ret;
} module_init(snull_init_module);
module_exit(snull_cleanup);
makefile:
# Comment/uncomment the following line to disable/enable debugging
#DEBUG = y # Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
DEBFLAGS = -O -g -DSBULL_DEBUG # "-O" is needed to expand inlines
else
DEBFLAGS = -O2
endif EXTRA_CFLAGS += $(DEBFLAGS)
EXTRA_CFLAGS += -I.. ifneq ($(KERNELRELEASE),)
# call from kernel build system obj-m := snull.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd) default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif
运行:
# insmod snull.ko
snull_init
snull_init
snull_stats
snull_stats
snull_init_module
# ifconfig sn0 local0
snull_open
snull_stats
# ifconfig sn1 local1
snull_open
snull_stats
# ping -c remote0
PING remote0 (192.168.2.9): data bytes
len =
type =
h_proto =
addr_len =
dev_addr = 00.53.4e..4c.
daddr = 00.53.4e..4c.
h_source = 00.53.4e..4c.
h_dest = 00.53.4e..4c.
h_dest[ETH_ALEN-] ^ 0x01 =
hard_header_len =
skb->len =
snull_tx
4e 4c 4e 4c
b5 c0 a8 c0 a8
d0 0e bc e8 ih->protocol = is buf[]
saddr = 192.168.2.8
daddr = 192.168.2.9
c0a80308: --> c0a80309:
snull_devs[]
tx_buffer->datalen =
priv->status =
priv->status =
skb->dev is snull_devs[]
skb->protocol =
snull_rx
snull_release_buffer
snull_regular_interrupt
snull_interrupt(, dest, NULL);
priv->status =
snull_regular_interrupt
snull_interrupt(, dev, NULL);
len =
type =
h_proto =
addr_len =
dev_addr = 00.53.4e..4c.
daddr = 00.53.4e..4c.
h_source = 00.53.4e..4c.
h_dest = 00.53.4e..4c.
h_dest[ETH_ALEN-] ^ 0x01 =
hard_header_len =
skb->len =
snull_tx
4e 4c 4e 4c
a0 c0 a8 c0 a8
d8 0e bc e8 ih->protocol = is buf[]
saddr = 192.168.3.9
daddr = 192.168.3.8
c0a80208: <-- c0a80209:
snull_devs[]
tx_buffer->datalen =
priv->status =
priv->status =
skb->dev is snull_devs[]
skb->protocol =
snull_rx
snull_release_buffer
snull_regular_interrupt
snull_interrupt(, dest, NULL);
priv->status =
snull_regular_interrupt
snull_interrupt(, dev, NULL);
bytes from 192.168.2.9: seq= ttl= time=159.673 ms --- remote0 ping statistics ---
packets transmitted, packets received, % packet loss
round-trip min/avg/max = 159.673/159.673/159.673 ms
分析现象:
1.当执行ping命令后,驱动首先会调用snull_header
int snull_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type, void *daddr, void *saddr,
unsigned len)
{
struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN); printk("len = %d\n", len); printk("type = %02x\n", type); //ETH_P_IP 0x0800 /* Internet Protocol packet */ /* htons是将整型变量从主机字节顺序转变成网络字节顺序,
* 就是整数在地址空间存储方式变为:高位字节存放在内存的低地址处
*/
eth->h_proto = htons(type);
printk("h_proto = %d\n", eth->h_proto); printk("addr_len = %d\n", dev->addr_len);
printk("dev_addr = %02x.%02x.%02x.%02x.%02x.%02x\n", dev->dev_addr[], dev->dev_addr[], dev->dev_addr[], dev->dev_addr[], dev->dev_addr[], dev->dev_addr[]); if (saddr) {
printk("saddr = %02x.%02x.%02x.%02x.%02x.%02x\n", *((unsigned char *)saddr + ), *((unsigned char *)saddr + ), *((unsigned char *)saddr + ), *((unsigned char *)saddr + ), *((unsigned char *)saddr + ), *((unsigned char *)saddr + ));
} if (daddr) {
printk("daddr = %02x.%02x.%02x.%02x.%02x.%02x\n", *((unsigned char *)daddr + ), *((unsigned char *)daddr + ), *((unsigned char *)daddr + ), *((unsigned char *)daddr + ), *((unsigned char *)daddr + ), *((unsigned char *)daddr + ));
} /* [cgw]: 上层应用要发送数据时,通过下层添加硬件地址,才能决定发送到那个目标网卡
*/
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("h_source = %02x.%02x.%02x.%02x.%02x.%02x\n", eth->h_source[], eth->h_source[], eth->h_source[],eth->h_source[], eth->h_source[], eth->h_source[]);
printk("h_dest = %02x.%02x.%02x.%02x.%02x.%02x\n", eth->h_dest[], eth->h_dest[], eth->h_dest[], eth->h_dest[], eth->h_dest[], eth->h_dest[]); /* [cgw]: 设置目标网卡硬件地址,即本地网卡和目标网卡硬件地址的最后一个字节的最低有效位
* 是相反关系,即本地是\0SNUL0的话,目标就是\0SNUL1,或者本地是\0SNUL1,目标就是\0SNUL0
*/
eth->h_dest[ETH_ALEN-] ^= 0x01; /* dest is us xor 1 */
printk("h_dest[ETH_ALEN-1] ^ 0x01 = %02x\n", eth->h_dest[ETH_ALEN-]); printk("hard_header_len = %d\n", dev->hard_header_len); return (dev->hard_header_len);
}
因为应用层要发送数据包了,所以要为这个数据包添加硬件地址,即以太网地址首部,才能通过网卡发送出去。
2. 然后内核会通过调用snull_tx发送数据包,snull_tx调用了snull_hw_tx,在这里更改本地IP为目标IP,并把本地要发的数据直接拷贝给目标网卡,代表目标网卡以接收到数据,并触发接收完成中断,向应用层上报数据,接着触发发送完成中断,表示数据已经发送到目标网卡。
3. 数据包分析:
static void snull_hw_tx(char *buf, int len, struct net_device *dev)
这里的buf为应用层要发送的数据包,数据包格式为:14字节以太网首部+20字节IP地址首部+20字节TCP地址首部+n字节数据
/* [cgw]: 14字节以太网首部 */
for (i= ; i<; i++)
printk(" %02x",buf[i]&0xff);
printk("\n"); /* [cgw]: 20字节IP地址首部 */
for (i= ; i<; i++)
printk(" %02x",buf[i]&0xff);
printk("\n"); /* [cgw]: 20字节TCP地址首部 */
for (i= ; i<; i++)
printk(" %02x",buf[i]&0xff);
printk("\n"); /* [cgw]: n字节数据 */
for (i= ; i<len; i++)
printk(" %02x",buf[i]&0xff);
printk("\n");
打印结果:
4e 4c 4e 4c 00 //14字节以太网首部
a0 c0 a8 c0 a8 08 //20字节IP地址首部
d8 0e bc e8 00 //20字节TCP地址首部
00 //n字节数据
其中:00 53 4e 55 4c 30 就硬件地址\0SNUL0的ASCII码,00 53 4e 55 4c 31 就硬件地址\0SNUL1的ASCII码。
c0 a8 02 08表示本地IP地址local0:192.168.2.8, c0 a8 02 09表示本地IP地址remote0:192.168.2.9。
代表 00 53 4e 55 4c 31 00 53 4e 55 4c 30 08 00 的结构体是:
struct ethhdr {
unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
unsigned char h_source[ETH_ALEN]; /* source ether addr */
__be16 h_proto; /* packet type ID field */
} __attribute__((packed));
即h_ptoto = 0x08 (0x0800,经过htons转换为0x08)
代表45 00 00 54 00 00 40 00 40 01 b5 47 c0 a8 02 08 c0 a8 02 09的结构体是:
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:,
version:;
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8 version:,
ihl:;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__u8 tos;
__be16 tot_len;
__be16 id;
__be16 frag_off;
__u8 ttl;
__u8 protocol;
__sum16 check;
__be32 saddr;
__be32 daddr;
/*The options start here. */
};
代表 08 00 d0 0e 09 03 00 00 bc e8 62 05 00 00 00 00 00 00 00 00 的结构体是:
struct tcphdr {
__be16 source;
__be16 dest;
__be32 seq;
__be32 ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u16 res1:,
doff:,
fin:,
syn:,
rst:,
psh:,
ack:,
urg:,
ece:,
cwr:;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:,
res1:,
cwr:,
ece:,
urg:,
ack:,
psh:,
rst:,
syn:,
fin:;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
__be16 window;
__sum16 check;
__be16 urg_ptr;
};
NAPI
NAPI的全称是“NEW API”。
要使用NAPI功能,只要在加载snull.ko的添加一句use_napi=1就行了
如:#insmod snull.ko use_napi=1
NAPI有什么作用?
NAPI是一种使用轮询(poll)的方式去接收数据。如当系统需要接收一大坨数据时,数据量比较大时,这个时候数据的接收就不应该在中断中进行。即产生接收完成中断后,立即禁止中断,通知内核调用poll,轮询接收数据,接收完成后,再使能接收中断。这样大大提高系统的性能。
在驱动初始化时:分配好poll函数
if (use_napi) {
dev->poll = snull_poll;
dev->weight = ;
}
在接收中断中
if (statusword & SNULL_RX_INTR) {
/* send it to snull_rx for handling */
pkt = priv->rx_queue;
if (pkt) {
priv->rx_queue = pkt->next;
/* [cgw]: 网卡接收到数据,上报给应用层 */
snull_rx(dev, pkt);
}
}
改为
if (statusword & SNULL_RX_INTR) {
snull_rx_ints(dev, ); /* Disable further interrupts */
//napi_schedule(&priv->napi);
netif_rx_schedule(dev);
}
在中断中,直接通知内核调用snull_poll即可,snull_poll轮询接收数据,并上报给应用层。
下一篇博客介绍DM9000网卡驱动。