Linux网络驱动示例之snull网络接口(二):设备方法的实现

本文详细介绍了Linux网络驱动中的snull网络接口设备方法,包括snull_open、snull_release、snull_tx、snull_interrupt、snull_ioctl、snull_stats、snull_tx_timeout、snull_header和snull_rebuild_header的实现。snull_open用于启动接口传输队列,snull_release停止队列,snull_tx处理数据包发送,snull_interrupt模拟硬件中断,snull_ioctl简单返回,snull_stats提供设备状态,snull_tx_timeout处理发送超时,snull_header和snull_rebuild_header构建硬件头部。通过这些方法,snull驱动实现了网络接口间的ping通效果。

        上一节描述了snull网络接口的入口和出口函数,在入口函数分配一个net_device,用snull_init函数初始化net_device的成员,之后注册这个网络设备。初始化net_device成员主要是实现一些设备操作方法,如下所示:

static const struct net_device_ops snull_dev_ops = {
    .ndo_open = snull_open,
    .ndo_stop = snull_release,
    .ndo_start_xmit = snull_tx,
    .ndo_do_ioctl = snull_ioctl,
    .ndo_get_stats = snull_stats,
    .ndo_tx_timeout = snull_tx_timeout,
};
 
static const struct header_ops snull_header_ops= {
    .create    = snull_header,
    .rebuild = snull_rebuild_header,
    .cache = NULL, 
};

下面就具体说明这些函数如何实现:

1. snull_open:打开设备

int snull_open(struct net_device *dev)
{ 
    memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);
    if(dev == snull_devs[1])
        dev->dev_addr[ETH_ALEN-1] ++; 
    netif_start_queue(dev);   
 
    return 0;
}

open函数赋予snull_devs[0]和snull_devs[1]地址,并启动接口的传输队列,关于传输队列操作包括:

netif_start_queue(struct net_device *dev);    //启动传输队列
netif_stop_queue(struct net_device *dev);    //停止传输队列
netif_wake_queue(struct net_device *dev);    //重启传输队列

2.    snull_release: 在接口被关闭时调用,只是简单的停止传输队列:

int snull_release(struct net_device *dev)
{
    netif_stop_queue(dev);    
 
    return 0;
}

3. snull_tx: 传输数据包

int snull_tx(struct sk_buff *skb, struct net_device *dev)
{
	int len;
	char *data, shortpkt[ETH_ZLEN];
	struct snull_priv *priv = netdev_priv(dev);
	
	data = skb->data;
	len = skb->len;
	if (len < ETH_ZLEN) {
		memset(shortpkt, 0, ETH_ZLEN);
		memcpy(shortpkt, skb->data, skb->len);
		len = ETH_ZLEN;
		data = shortpkt;
	}
	dev->trans_start = jiffies; /* save the timestamp */

	/* Remember the skb, so we can free it at interrupt time */
	priv->skb = skb;

	/* actual deliver of data is device-specific, and not shown here */
	snull_hw_tx(data, len, dev);

	return 0; /* Our simple device can not fail */
}

skb是指向sk_buff的指针,从skb中获取要发送的数据及数据长度,再调用snull_hw_tx函数发送,snull_hw_tx代码如下:

void snull_hw_tx(char *buf, int len, struct net_device *dev)
{
    struct iphdr *ih;
    struct net_device *dest;
    struct snull_priv *priv = netdev_priv(dev);
    u32 *saddr, *daddr;
	struct snull_packet *tx_buffer;
 
    if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) 
    {
        printk("snull: Hmm... packet too short (%i octets)\n",len);
        return;
    }
 
    ih = (struct iphdr *)(buf+sizeof(struct ethhdr));
    saddr = &ih->saddr;
    daddr = &ih->daddr;
	
	if (dev == snull_devs[0])
        printk(KERN_INFO"before change: %08x:%05i --> %08x:%05i\n",
               ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source),
               ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest));
    else
        printk(KERN_INFO"before change: %08x:%05i <-- %08x:%05i\n",
               ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest),
               ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source));
			   
    ((u8 *)saddr)[2] ^= 1;
    ((u8 *)daddr)[2] ^= 1;
	

    ih->check = 0; 
    ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
 
    if (dev == snull_devs[0])
        printk(KERN_INFO"after change: %08x:%05i --> %08x:%05i\n",
               ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source),
               ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest));
    else
        printk(KERN_INFO"after change: %08x:%05i <-- %08x:%05i\n",
               ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest),
               ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source));
	
	dest = snull_devs[dev == snull_devs[0] ? 1 : 0];
	priv = netdev_priv(dest);
	tx_buffer = snull_get_tx_buffer(dev);
	tx_buffer->datalen = len;
	memcpy(tx_buffer->data, buf, len);
	snull_enqueue_buf(dest, tx_buffer);
	if (priv->rx_int_enabled) {
		priv->status |= SNULL_RX_INTR;
		snull_interrupt(0, dest, NULL);
	}
	priv = netdev_priv(dev);
    priv->status = SNULL_TX_INTR;
    priv->tx_packetlen = len;
    priv->tx_packetdata = buf;
    if (lockup && ((priv->stats.tx_packets + 1) % lockup) == 0) 
    {
        netif_stop_queue(dev);
        printk(KERN_INFO"Simulate lockup at %ld, txp %ld\n", jiffies,(unsigned long) priv->stats.tx_packets);
    }
    else
        snull_interrupt(0, dev, NULL);
 
    return;
}

该函数修改了数据包的源地址和目的地址,产生ping通两个网络接口的效果,比如设置几个IP地址如下:
192.168.0.1: local0
192.168.0.2: remote0
192.168.1.2: local1
192.168.1.1: remote1
当发送 ping -c 1 remote0时,发送源地址为local0,目的地址为remote0,发送函数将其修改为remote1和local1,之后local1收到数据,向remote1发送回应,发送函数又将源地址和目的地址修改为remote0和local0,最终local0收到数据,造成ping通的假象。这里将数据包添加到接收队列,调用snull_interrupt模仿一个硬件的中断,并设置两个网络接口的状态。snull_interrupt函数为:

void snull_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    int statusword;
    struct snull_priv *priv;
	struct snull_packet *pkt = NULL;
	
	struct net_device *dev = (struct net_device *)dev_id;
   
    if (!dev) 
        return;
 
    /* Lock the device */
	priv = netdev_priv(dev);
	spin_lock(&priv->lock);
	
    statusword = priv->status;
    priv->status = 0;
    if (statusword & SNULL_RX_INTR) 
    {
		if (dev_id == snull_devs[0])
			printk(KERN_INFO"sn0 rx\n");
		if (dev_id == snull_devs[1])
			printk(KERN_INFO"sn1 rx\n");
        /* send it to snull_rx for handling */
		pkt = priv->rx_queue;
		if (pkt) {
			priv->rx_queue = pkt->next;
			snull_rx(dev, pkt);
		}
    }
    if (statusword & SNULL_TX_INTR) 
    {
		if (dev_id == snull_devs[0])
			printk(KERN_INFO"sn0 tx\n");
		if (dev_id == snull_devs[1])
			printk(KERN_INFO"sn1 tx\n");
        priv->stats.tx_packets++;
        priv->stats.tx_bytes += priv->tx_packetlen;
        dev_kfree_skb(priv->skb);
    }
    spin_unlock(&priv->lock);
	if (pkt) 
		snull_release_buffer(pkt); /* Do this outside the lock! */
	
    return;
}

如果为接收中断,则调用snull_rx函数:主要流程为分配一个sk_bull套接字缓冲区,写入数据和设置一些成员后用netif_rx函数提交给上层软件处理。具体描述参考书中的描述。

void snull_rx(struct net_device *dev, struct snull_packet *pkt)
{
    struct sk_buff *skb;
    struct snull_priv *priv = netdev_priv(dev);
 
    skb = dev_alloc_skb(pkt->datalen + 2);
    if (!skb) 
    {
        printk("snull rx: low on mem - packet dropped\n");
        priv->stats.rx_dropped++;
        return;
    }
    skb_reserve(skb, 2);
	memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
 
    /* Write metadata, and then pass to the receive level */
	skb->dev = dev;
	skb->protocol = eth_type_trans(skb, dev);
	skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
	priv->stats.rx_packets++;
	priv->stats.rx_bytes += pkt->datalen;
	netif_rx(skb);
 
    return;
}

关于接收有两种方式,基于中断或轮询,这里只保留了使用中断接收,基于轮询的方法参考书中的描述。
如果是发送中断,则设置相关的状态后释放套接字缓冲区。

4. snull_ioctl:在使用ifconfig命令时会调用,但这里为了简化程序,只简单的返回0,什么都不做:

int snull_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
    return 0;
}

5.    snull_stats: 返回设备当前状态

struct net_device_stats *snull_stats(struct net_device *dev)
{
    struct snull_priv *priv = netdev_priv(dev);
 
    return &priv->stats;
}

6.    snull_tx_timeout: 发送超时处理,只是再次模拟发送中断并重启传输队列

void snull_tx_timeout (struct net_device *dev)
{
    struct snull_priv *priv = netdev_priv(dev);
    printk(KERN_INFO"Transmit timeout at %ld, latency %ld\n", jiffies,jiffies - dev->trans_start);
 
    priv->status = SNULL_TX_INTR;
    snull_interrupt(0, dev, NULL);
    priv->stats.tx_errors++;
    netif_wake_queue(dev);
 
    return;
}

7.    snull_header:在hard_start_xmit前被调用,用于根据检索到的源和目标硬件地址建立硬件头。

int snull_header(struct sk_buff *skb, struct net_device *dev,
                unsigned short type, const void *daddr, const void *saddr,
                unsigned int len)
{
    struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN);
 
    eth->h_proto = htons(type);
    memcpy(eth->h_source, saddr ? saddr : dev->dev_addr, dev->addr_len);
    memcpy(eth->h_dest, daddr ? daddr : dev->dev_addr, dev->addr_len);
    eth->h_dest[ETH_ALEN-1] ^= 0x01;
 
    return (dev->hard_header_len);
}

8.    snull_rebuild_header:用来在传输数据包之前,完成ARP解析之后,重新建立硬件头

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;
 
    return 0;
}

在实现了这些设备方法之后,基本上驱动程序就完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值