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 = 0;
module_param(lockup, int, 0);
static int timeout = SNULL_TIMEOUT;
module_param(timeout, int, 0);
/*
* Do we run in NAPI mode?
*/
static int use_napi = 0;
module_param(use_napi, int, 0);
/*
* 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 = 8;
module_param(pool_size, int, 0);
/*
* 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 = 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;
}
}
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[1])
dev->dev_addr[ETH_ALEN-1]++; /* \0SNUL1 */
/* [cgw]: 啟動發送隊列 */
netif_start_queue(dev);
printk("snull_open\n");
return 0;
}
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 0;
}
/*
* 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 0;
}
/*
* 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 + 2);
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, 2); /* 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[0]) {
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 = 0, 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 + 2);
if (! skb) {
if (printk_ratelimit())
printk(KERN_NOTICE "snull: packet dropped\n");
priv->stats.rx_dropped++;
snull_release_buffer(pkt);
continue;
}
skb_reserve(skb, 2); /* 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, 1);
return 0;
}
/* We couldn't process everything. */
//return npackets;
return 1;
}
/*
* 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 = 0;
/* [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 = 0;
if (statusword & SNULL_RX_INTR) {
snull_rx_ints(dev, 0); /* 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 (1) { /* enable this conditional to look at the data */
int i;
PDEBUG("len is %i\n" KERN_DEBUG "data:",len);
/* [cgw]: 14字節以太網首部 */
for (i=0 ; i<14; i++)
printk(" %02x",buf[i]&0xff);
printk("\n");
/* [cgw]: 20字節IP地址首部 */
for (i=14 ; i<34; i++)
printk(" %02x",buf[i]&0xff);
printk("\n");
/* [cgw]: 20字節TCP地址首部 */
for (i=34 ; i<54; i++)
printk(" %02x",buf[i]&0xff);
printk("\n");
/* [cgw]: n字節數據 */
for (i=54 ; 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 + 0), *((u8 *)saddr + 1), *((u8 *)saddr + 2), *((u8 *)saddr + 3));
printk("daddr = %d.%d.%d.%d\n", *((u8 *)daddr + 0), *((u8 *)daddr + 1), *((u8 *)daddr + 2), *((u8 *)daddr + 3));
/* [cgw]: 改變本地和目標IP地址的第三個字節的最低位,即原來是0,則改為1,原來是1,則改為0
*/
((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
((u8 *)daddr)[2] ^= 1;
/* [cgw]: 從新計算校驗,因為IP已改變 */
ih->check = 0; /* and rebuild the checksum (ip needs it) */
ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
/* [cgw]: 打印更改後的IP地址,和TCP地址,
*/
if (dev == snull_devs[0])
//PDEBUGG("%08x:%05i --> %08x:%05i\n",
printk("%08x:%05i --> %08x:%05i\n",
ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source),
ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest));
else
//PDEBUGG("%08x:%05i <-- %08x:%05i\n",
printk("%08x:%05i <-- %08x:%05i\n",
ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest),
ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->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[0] ? 1 : 0];
if (dev == snull_devs[0]) {
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(0, 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 + 1) % lockup) == 0) {
/* 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(0, 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, 0, 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 0; /* 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(0, 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 0;
}
/*
* 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-1] ^= 0x01; /* dest is us xor 1 */
printk("snull_rebuild_header\n");
return 0;
}
//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[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));
}
/* [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[0], eth->h_source[1], eth->h_source[2],eth->h_source[3], eth->h_source[4], eth->h_source[5]);
printk("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]);
/* [cgw]: 設置目標網卡硬件地址,即本地網卡和目標網卡硬件地址的最後一個字節的最低有效位
* 是相反關系,即本地是\0SNUL0的話,目標就是\0SNUL1,或者本地是\0SNUL1,目標就是\0SNUL0
*/
eth->h_dest[ETH_ALEN-1] ^= 0x01; /* dest is us xor 1 */
printk("h_dest[ETH_ALEN-1] ^ 0x01 = %02x\n", eth->h_dest[ETH_ALEN-1]);
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 < 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 */
}
#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,2);
}
#else
if (use_napi) {
dev->poll = snull_poll;
dev->weight = 2;
}
#endif
memset(priv, 0, sizeof(struct snull_priv));
spin_lock_init(&priv->lock);
snull_rx_ints(dev, 1); /* enable receive interrupts */
snull_setup_pool(dev);
printk("snull_init\n");
}
/*
* The devices
*/
struct net_device *snull_devs[2];
/*
* Finally, the module stuff
*/
void snull_cleanup(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_init_module(void)
{
int result, i, ret = -ENOMEM;
snull_interrupt = use_napi ? snull_napi_interrupt : snull_regular_interrupt;
/* Allocate the devices */
snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
snull_init);
snull_devs[1] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
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("snull: error %i registering device \"%s\"\n",
result, snull_devs[i]->name);
else
ret = 0;
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 1 remote0
PING remote0 (192.168.2.9): 56 data bytes
len = 84
type = 800
h_proto = 8
addr_len = 6
dev_addr = 00.53.4e.55.4c.30
daddr = 00.53.4e.55.4c.30
h_source = 00.53.4e.55.4c.30
h_dest = 00.53.4e.55.4c.30
h_dest[ETH_ALEN-1] ^ 0x01 = 31
hard_header_len = 14
skb->len = 98
snull_tx
00 53 4e 55 4c 31 00 53 4e 55 4c 30 08 00
45 00 00 54 00 00 40 00 40 01 b5 47 c0 a8 02 08 c0 a8 02 09
08 00 d0 0e 09 03 00 00 bc e8 62 05 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ih->protocol = 1 is buf[23]
saddr = 192.168.2.8
daddr = 192.168.2.9
c0a80308:02048 --> c0a80309:53262
snull_devs[0]
tx_buffer->datalen = 98
priv->status = 1
priv->status = 1
skb->dev is snull_devs[1]
skb->protocol = 8
snull_rx
snull_release_buffer
snull_regular_interrupt
snull_interrupt(0, dest, NULL);
priv->status = 2
snull_regular_interrupt
snull_interrupt(0, dev, NULL);
len = 84
type = 800
h_proto = 8
addr_len = 6
dev_addr = 00.53.4e.55.4c.31
daddr = 00.53.4e.55.4c.31
h_source = 00.53.4e.55.4c.31
h_dest = 00.53.4e.55.4c.31
h_dest[ETH_ALEN-1] ^ 0x01 = 30
hard_header_len = 14
skb->len = 98
snull_tx
00 53 4e 55 4c 30 00 53 4e 55 4c 31 08 00
45 00 00 54 a0 17 00 00 40 01 53 30 c0 a8 03 09 c0 a8 03 08
00 00 d8 0e 09 03 00 00 bc e8 62 05 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ih->protocol = 1 is buf[23]
saddr = 192.168.3.9
daddr = 192.168.3.8
c0a80208:55310 <-- c0a80209:00000
snull_devs[1]
tx_buffer->datalen = 98
priv->status = 1
priv->status = 1
skb->dev is snull_devs[0]
skb->protocol = 8
snull_rx
snull_release_buffer
snull_regular_interrupt
snull_interrupt(0, dest, NULL);
priv->status = 2
snull_regular_interrupt
snull_interrupt(0, dev, NULL);
64 bytes from 192.168.2.9: seq=0 ttl=64 time=159.673 ms
--- remote0 ping statistics ---
1 packets transmitted, 1 packets received, 0% 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[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));
}
/* [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[0], eth->h_source[1], eth->h_source[2],eth->h_source[3], eth->h_source[4], eth->h_source[5]);
printk("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]);
/* [cgw]: 設置目標網卡硬件地址,即本地網卡和目標網卡硬件地址的最後一個字節的最低有效位
* 是相反關系,即本地是\0SNUL0的話,目標就是\0SNUL1,或者本地是\0SNUL1,目標就是\0SNUL0
*/
eth->h_dest[ETH_ALEN-1] ^= 0x01; /* dest is us xor 1 */
printk("h_dest[ETH_ALEN-1] ^ 0x01 = %02x\n", eth->h_dest[ETH_ALEN-1]);
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=0 ; i<14; i++)
printk(" %02x",buf[i]&0xff);
printk("\n");
/* [cgw]: 20字節IP地址首部 */
for (i=14 ; i<34; i++)
printk(" %02x",buf[i]&0xff);
printk("\n");
/* [cgw]: 20字節TCP地址首部 */
for (i=34 ; i<54; i++)
printk(" %02x",buf[i]&0xff);
printk("\n");
/* [cgw]: n字節數據 */
for (i=54 ; i<len; i++)
printk(" %02x",buf[i]&0xff);
printk("\n");
打印結果:
00 53 4e 55 4c 30 00 53 4e 55 4c 31 08 00 //14字節以太網首部 45 00 00 54 a0 17 00 00 40 01 53 30 c0 a8 03 09 c0 a8 03 08 //20字節IP地址首部 00 00 d8 0e 09 03 00 00 bc e8 62 05 00 00 00 00 00 00 00 00 //20字節TCP地址首部 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 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 的結構體是:
1 struct ethhdr { 2 unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ 3 unsigned char h_source[ETH_ALEN]; /* source ether addr */ 4 __be16 h_proto; /* packet type ID field */ 5 } __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:4,
version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8 version:4,
ihl:4;
#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:4,
doff:4,
fin:1,
syn:1,
rst:1,
psh:1,
ack:1,
urg:1,
ece:1,
cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4,
res1:4,
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#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函數
1 if (use_napi) { 2 dev->poll = snull_poll; 3 dev->weight = 2; 4 }
在接收中斷中
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);
}
}
改為
1 if (statusword & SNULL_RX_INTR) { 2 snull_rx_ints(dev, 0); /* Disable further interrupts */ 3 //napi_schedule(&priv->napi); 4 netif_rx_schedule(dev); 5 }
在中斷中,直接通知內核調用snull_poll即可,snull_poll輪詢接收數據,並上報給應用層。