Network Drivers ‐ What, How and Why?! - cu-ecen-aeld/final-project-AbhishekKoppaCU GitHub Wiki

Understanding Network Drivers

What is a Network Driver?

A Network Driver is a software component, typically part of the Linux Kernel, that enables communication between the operating system and a Network Interface Card (NIC).

  • Layer: Network drivers operate at Layer 2 (Data Link Layer) of the OSI networking model.
  • Kernel Space: These drivers reside in kernel space and interact directly with hardware.
  • Interaction with NIC: They receive raw packets from NIC hardware, either through interrupts (NIC signals the CPU when data is ready) or via polling (driver periodically checks NIC for data).
  • Packet Handling: Upon receiving data, the driver creates a sk_buff (socket buffer or skb), fills in relevant fields (e.g., packet length, protocol type, device pointers), and sends the packet up to the Linux network stack using netif_rx().
  • User-Space Visibility: Once processed by the network stack, the packet becomes available to user-space applications through sockets APIs like recvfrom().

Packet Reception - Block Diagram

image

Character Driver vs Network Driver

Feature Character Driver Network Driver
Purpose Simple byte-stream I/O (read, write, ioctl) Handling packets over a network interface
Example Serial ports, sensors Ethernet driver, Wi-Fi driver
Interface File abstraction (/dev/char_device) Network device abstraction (struct net_device)
Packet Concept No concept of "packets", just raw data Packets (sk_buffs) are fundamental
Interaction read(), write() netif_rx(), ndo_start_xmit()
APIs ioctl() net_device_ops, netif_rx()

Although character drivers and network drivers operate in different domains (simple data vs packets), both follow a similar kernel development pattern. In both drivers, a private structure (priv) is used extensively to hold driver-specific state and configuration. In character drivers, this private structure is often attached using the file->private_data field, while in network drivers, it is accessed via the netdev_priv(struct net_device *dev) function. This "priv" structure is fundamental because it cleanly separates the generic kernel interfaces (file_operations or net_device_ops) from the driver's internal logic. In both types of drivers, this design enables modular, clean, and maintainable code. Whether handling bytes or complex network packets, the driver code always refers back to this private structure to maintain context, manage state, and perform specific operations tied to the instance of the device.

My Project: Simple Network Driver Using Character Driver Simulation

Motivation:

  • Understand how to build a Kernel Module from scratch.
  • Learn the mechanics of a Network Driver.
  • Face and solve real-world challenges like:
  • Packet Construction and Parsing
  • Reception and Transmission handling
  • Byte Ordering Issues (endianness)
  • User Space <-> Kernel Space Communication

Project Design Overview

Problem: I had no real NIC attached.
Solution: I simulated NIC behavior using a Character Device Driver alongside the Network Driver.

Project Block Diagram:

image

Transmission:

  • User space sends data via ioctl() to Character Driver.
  • Character Driver hands it over to Network Driver.
  • Network Driver constructs full UDP/IP packet and queues it for transmission.

Reception

  • Receiver socket on another RPi fetches the packet.
  • Receiver socket writes received data to Character Driver.
  • Character Driver forwards it to Network Driver's RX path.
  • Network Driver injects it into the network stack (like real NIC).
  • Data is available to other components (e.g., actuator driver via /tmp/proxpipe).

Detailed Steps

1. Transmitting a Packet (Simulated TX):

  • Data from user space arrives via ioctl().
  • The Character Driver passes it to a helper function.
  • This function constructs an Ethernet frame with IP and UDP headers:
  • IP header fields: version, header length, total length, source/destination IP.
  • UDP header fields: source port, destination port, payload length.
  • Packet is placed into an sk_buff (socket buffer).
  • pinet_tx() queues the skb for transmission.

2. Receiving a Packet (Simulated RX)

  • Data from network arrives at the receiver socket.
  • Receiver app writes the data to the Character Driver.
  • Character Driver calls the Network Driver's receive function.
  • netif_rx() is used to push the packet into the Linux network stack.

Challenges Faced and Solutions:

1. Communication Between Two Devices

  • Problem: Establishing a UDP link between two RPis (or RPi + VM).
  • Learning: Devices must be on the same IP subnet, and in bridged or local networking mode.
  • Extra: VM networking needed to be configured to "Bridged Adapter".

2. No NIC Present

  • Solution: Used a Character Driver to simulate NIC behavior.

3. Kernel/User Space Communication

  • Problem: Efficiently moving data between user space and kernel.
  • Solution: Used IOCTL for sending data and read() for receiving.

4. Packet Construction Problem: Proper layout of IP and UDP headers. Solution:

  • Careful memory layout: [payload start] -> [IP Header] [UDP Header] [Actual Data]
  • Used kernel structs (struct iphdr, struct udphdr).

5. Byte Ordering Issues Problem: Different endianess between Host CPU and Network. Solution:

  • Used htons(), htonl() while writing headers.
  • Used ntohs(), ntohl() while parsing headers.

Testing and Debugging Strategies

1. Initial Loopback Mechanism

  • To understand the internal working of a network driver before actual communication was established, I initially created a loopback mechanism.
  • In this setup, the TX (Transmit) function directly triggered the RX (Receive) function inside the driver.
  • This mimicked the behavior of a NIC: a packet was "transmitted" and immediately "received".
  • This allowed me to verify that packet construction, sk_buff management, and reception processing inside the network driver were working correctly without requiring a second device.

2. Kernel Debugging Tools

  • dmesg: Used extensively to monitor kernel logs. Important for seeing debug prints (printk()) from both character and network drivers.
  • tcpdump: Used on network interfaces (e.g., pinet0) to capture and verify outgoing and incoming packets at the Ethernet/IP/UDP layers.
  • Wireshark: Captured live traffic to visually inspect headers and payload. Very helpful in verifying if IP addresses, ports, and checksums were set correctly.
  • strace: Helped in diagnosing issues between user-space applications and character devices by tracing system calls.

3. User Space Debugging

  • Monitored ioctl() and read() operations to ensure proper handoff between user space and kernel space.
  • Developed small test scripts to send and receive packets automatically in a loop.

4. Common Problems Encountered and Debugging Approach

  • Incorrect Packet Format: Used Wireshark to verify headers and identified byte alignment issues.
  • Incorrect Byte Order: Added debug logs to print raw packet fields after using htons/ntohs/htonl/ntohl conversions.
  • Interface Binding Issues: Used ip addr and ifconfig to verify that the pinet0 interface was correctly up and assigned IPs.

5. Best Practices Learned

  • Always validate packet headers before sending them into the network stack.
  • Use logging at every critical stage: after ioctl(), before skb allocation, after packet build, before netif_rx().
  • Testing loopback first saves hours of debugging real network communication issues.

Final Achievements

  • Built a custom simulated network driver.
  • Understood IP/UDP packetization in kernel.
  • Learned how to move data safely between user space and kernel space.
  • Built a communication link between two Raspberry Pi devices over UDP.
  • Gained insights into challenges real network drivers face (alignment, performance, correctness).
  • Set up actuator control based on real-time sensor data transmission.

Additional Improvements (Future Scope)

  • Implement real checksum computation for IP and UDP headers.
  • Use Netfilter hooks to inspect live traffic.
  • Replace character driver simulation with virtual NIC devices (e.g., TUN/TAP).
  • Build an interrupt-simulated mechanism instead of polling the char driver.

References

  • Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman, Linux Device Drivers, 3rd Edition, O'Reilly Media, 2005. [ISBN 978-0-596-00590-0].
  • Linux Kernel Documentation - sk_buff struct documentation.
  • James F. Kurose and Keith W. Ross, Computer Networking: A Top-Down Approach, 7th Edition, Pearson, 2016. [ISBN 978-0-13-359414-0].