[Linux driver development nine] Simple virtual network card production

Directory

1. Introduction to network card equipment

2. Write the virtual network card driver

2.1 Writing the init initial function ( vir_dev_register() )

2.2 Write the contract sending function ( vir_dev_xmit() )

2.3 Write the packet receiving function ( vir_dev_rcpacket() )

3. Run the test

3.1 Compile and load

3.2 Network card startup and ip address configuration

3.3 ping test

Four. Summary


1. Network card device introduction

The network card device is mainly responsible for sending and receiving network data packets. The network card has two main functions: receiving data and sending data. It passes the data packets passed by the upper layer protocol to the Linux kernel, and sends the data of the Linux kernel.

Different from character devices, network card devices have no corresponding files in the /dev directory, but you can view the network card in the /sys/class/net directory. Next, let’s briefly introduce the process of sending and receiving packets of the network card.

(1) Network card receiving packets

Step1: The network card chip obtains the physical frame on the network cable, and checks the CRC of the physical frame to ensure the integrity;

Step2: The network card chip removes the physical frame header and obtains the MAC data packet;

Step3: The network card chip checks the destination MAC address in the MAC packet, and discards it if it is inconsistent with the MAC address of the network card (except promiscuous mode);

Step4: The network card chip copies the MAC frame to the internal buffer and triggers a hardware interrupt;

Step5: The network card driver constructs sk_buff through the interrupt processing function, copies it to the memory, and delivers it to the kernel for processing.

(2) Network card sending packets

step1: The network card driver constructs the data packet (IP data packet) passed down by the upper layer protocol into a MAC packet;

Step2: The network card driver copies the MAC packet to the internal buffer of the network card chip;

Step3: The network card chip encapsulates the MAC packet into a physical frame, adds synchronization information and CRC check, and sends it out through the network cable (all network cards on the network can receive the frame).

2. Write virtual network card driver

This chapter writes a virtual network card driver, uses the ping command to send packets, and then constructs a packet sending function to forge a received ping packet function, so that the virtual network card can ping any ip address.

2.1 init initial function writing ( vir_dev_register() )

(1) Use the alloc_netdev() function to allocate a net_device structure;

(2) Set the net_device structure members;

(3) Use register_netdev() to register the net_device structure;

static const struct net_device_ops vnet_ops = {
.ndo_open = vnet_open,
.ndo_stop = vnet_stop,
.ndo_start_xmit = vnet_tx,
};

static int vir_dev_register(void)
{
    int ret = 0;

    vir_dev = alloc_netdev(sizeof(struct net_device), "eth_xzx", NET_NAME_UNKNOWN, ether_setup);
    if (IS_ERR(vir_dev)) {
        return -ENOMEM;
    }

    /* Initialize the MAC address */
    vir_dev->dev_addr[0] = 0x00;
    vir_dev->dev_addr[1] = 0x01;
    vir_dev->dev_addr[2] = 0x02;
    vir_dev->dev_addr[3] = 0x03;
    vir_dev->dev_addr[4] = 0x04;
    vir_dev->dev_addr[5] = 0x05;

    /* set operation function */
    vir_dev->netdev_ops = &vir_dev_ops;
    vir_dev->flags |= IFF_NOARP;
    vir_dev->features |= NETIF_F_HW_CSUM;

    /* Register net_device structure */
    ret = register_netdev(vir_dev);
    if (ret) {
        free_netdev(vir_dev);
        return ret;
    }

    return ret;
}

2.2 Writing the contract sending function ( vir_dev_xmit() )

(1) Use netif_stop_queue() to prevent the upper layer from sending packets to the network device driver layer;

(2) Use the packet receiving function ( vir_dev_rcpacket() ) to forge the received ping function;

(3) Use the dev_kfree_skb() function to release the sent sk_buff buffer;

(4) Update sending statistics;

(5) Use netif_wake_queue() to wake up the blocked upper layer;

static int vir_dev_xmit(struct sk_buff *skb, struct net_device *dev)
{
    printk("Running vir_dev_xmit\
");

    /* Call netif_stop_queue() to prevent the upper layer from sending packets to the network device driver layer */
    netif_stop_queue(dev);

    /* Call the packet receiving function to forge receiving ping packets */
    vir_dev_rcpacket(skb, dev);

    /* Call the dev_kfree_skb() function to release the sent sk_buff */
    dev_kfree_skb(skb);

    /* Update sent statistics */
    dev->stats.tx_packets++;
    dev->stats.tx_bytes + = skb->len;
    dev->trans_start = jiffies;

    /* Call netif_wake_queue() to wake up the blocked upper layer */
    netif_wake_queue(dev);

    return 0;
}

2.3 Write packet receiving function ( vir_dev_rcpacket() )

(1) Exchange the “source/destination” MAC address of the ethhdr structure;

(2) Exchange the “source/destination” IP address of the iphdr structure;

(3) Use ip_fast_csum() to reacquire the check code of the iphdr structure;

(4) Modify the data type (0x08 for sending ping packets and 0x00 for receiving ping packets);

(5) Use dev_alloc_skb() to construct a new sk_buff;

(6) Call skb_reserve() to reserve 2 bytes of header space, 16 bytes aligned, (the protocol header length of Ethernet is 14 bytes);

(7) Copy skb_buff->data to the new sk_buff and use skb_put to dynamically expand the data area to avoid overflow;

(8) Set other members of the new sk_buff;

(9) Call eth_type_trans() to obtain the upper layer protocol;

(10) Update the received statistical information and call netif_rx() to deliver the sk_buff packet.

static int vir_dev_rcpacket(struct sk_buff *skb, struct net_device *dev)
{
    struct ethhdr *ethhdr;
    struct iphdr *ih;
    unsigned char *type;
    unsigned char tmp_dev_addr[ETH_ALEN];
    __be32 *saddr, *daddr, tmp;
    struct sk_buff *rx_skb;
    
    /* 1. Swap source/destination MAC address */
    ethhdr = (struct ethhdr *)skb->data;
    memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
    memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
    memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);

    /* 2. Swap source/destination IP address */
    ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
    saddr = &ih->saddr;
    daddr = &ih->daddr;
    tmp = *saddr;
    *saddr = *daddr;
    *daddr = tmp;

    /* 3. Call ip_fast_csum() to get the check code of the iphr structure */
    ih->check = 0;
    ih->check = ip_fast_csum((unsigned char *)ih, ih->ihl);

    /* 4. Set the data type to 0, which means receiving ping packets */
    type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
    *type = 0;

    /* 5. Call dev_alloc_skb() to construct a new receiving sk_buff */
    rx_skb = dev_alloc_skb(skb->len + 2);

    /*
    * skb_reserve() increases header space
    * skb_put() increases the length of the data area
    */

    /* 6. Call skb_reserve() to reserve 2 bytes of header space, 16 bytes aligned, (Ethernet protocol header length is 14 bytes) */
    skb_reserve(rx_skb, 2);

    /* 7. Copy skb_buff->data to the new sk_buff Use skb_put to dynamically expand the data area to avoid overflow */
    memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);

    /* 8. Set other members of the new sk_buff */
    rx_skb->dev = dev;
    rx_skb->ip_summed = CHECKSUM_UNNECESSARY;

    /* 9. Call eth_type_trans() to get the upper layer protocol */
    rx_skb->protocol = eth_type_trans(rx_skb, dev);

    /* 10. Update received statistical information, call netif_rx() to pass sk_buff packet */
    dev->stats.rx_packets++;
    dev->stats.rx_bytes + = skb->len;
    dev->last_rx = jiffies;

    netif_rx(rx_skb);

    return 0;
}

[Complete source code and Makefile download link]

3. Run the test

3.1 Compile and load

3.2 Network card startup and ip address configuration

3.3 ping test

Four. Summary

This chapter writes a simple network card driver, which only implements the ndo_start_xmit operation method. The vir_dev_rcpacket() function in the driver exchanges the source address and destination address of the data to be sent from the upper layer, and then returns it to the upper layer of the system, realizing the function similar to the loopback interface.