良许Linux教程网 干货合集 Linux网络驱动开发:以snull为例

Linux网络驱动开发:以snull为例

 

网络驱动是Linux系统中一种重要的设备驱动,它用来实现对网络设备的控制和数据传输,如网卡,交换机,路由器等。网络驱动的开发涉及到网络协议栈,套接字接口,网络设备结构体,网络数据包等概念。在本文中,我们将介绍Linux网络驱动的原理和方法,以snull为例,snull是一种虚拟的网络设备,它可以在本地模拟网络通信。我们将介绍snull的实现细节,包括初始化和注销,打开和关闭,发送和接收,配置和控制等,并举例说明它们的使用方法和注意事项。

snull是《Linux Device Drivers》中的一个网络驱动的例子。这里引用这个例子学习Linux网络驱动。

估计不少网友看到《Linux Device Drivers》的网络驱动部分,一脸懵逼,包括我自己,不理解作者设计这个例子的真正目的,尽管有配图,仍然懵懂,甚至不知道为什么会用到6个IP地址。如图:

img
img

其实作者的本意是想通过虚拟网卡来模拟实际的网卡和外部的网络设备的通信来讨论网络驱动。通过其中任何一个网络接口(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 
\#include 
\#include 

\#include 
\#include  /* printk() */
\#include  /* kmalloc() */
\#include  /* error codes */
\#include  /* size_t */
\#include  /* mark_bh */

\#include 
\#include  /* struct device, and other headers */
\#include  /* eth_type_trans */
\#include      /* struct iphdr */
\#include     /* struct tcphdr */
\#include 

\#include "snull.h"

\#include 
\#include 

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 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 rx_queue) {
  while (npackets 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 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 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 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  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 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 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" %02x",buf[i]&0xff);
    printk("\n");

    /* [cgw]: 20字节IP地址首部 */
for (i=14 ; i" %02x",buf[i]&0xff);
    printk("\n");

    /* [cgw]: 20字节TCP地址首部 */
for (i=34 ; i" %02x",buf[i]&0xff);
    printk("\n");

    /* [cgw]: n字节数据 */
for (i=54 ; i" %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 "
\#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  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) {
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);
    }
  }

改为

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轮询接收数据,并上报给应用层。

通过本文,我们了解了Linux网络驱动的原理和方法,以snull为例,我们学习了如何实现一个虚拟的网络设备,并进行网络通信。我们应该根据实际需求选择合适的方法,并遵循一些基本原则,如使用正确的数据结构和函数,处理好中断和锁,遵守网络协议规范等。网络驱动是Linux系统中最复杂的设备驱动之一,它可以实现对网络设备的控制和数据传输,也可以提升系统的性能和可靠性。希望本文能够对你有所帮助和启发。

以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

137e00002230ad9f26e78-265x300
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部