上一节描述了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;
}
在实现了这些设备方法之后,基本上驱动程序就完成了。
本文详细介绍了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通效果。

3118

被折叠的 条评论
为什么被折叠?



