Virtual Wireless Network Driver

31 May 2023

OVERVIEW


This project provides a simple, virtual network driver for the Linux kernel. It is primarily intended for educational purposes and demonstrates various concepts such as kernel module programming, network device operations, and ethtool operations.

The virtual network driver can be loaded and unloaded using standard Linux kernel module utilities, such as insmod and rmmod. It also provides basic networking functionality, allowing users to interact with the virtual network device using standard network commands like ifconfig and iwconfig.

The Team

Table of Contents

Features

  • Basic virtual network device implementation
  • Network device open, close, and transmit operations
  • IOCTL command handling
  • Timer-based callbacks
  • Virtual FIFO buffer for packet transmission

Prerequisites

To compile a Loadable Kernel Module, you need the kernel headers matching the kernel version you are working with. To resolve this issue, install the kernel headers and development tools for your Linux distribution. For Fedora, you can use the following command:

sudo dnf install kernel-devel kernel-headers gcc make iw hostapd

Building

To build the virtual network driver module, update the Makefile to use the correct path for the kernel build directory. The build directory is typically found under /lib/modules/$(uname -r)/build, where $(uname -r) returns the currently running kernel version. Finally, run the make command from the project directory.

make

This will generate a kernel module file called virt_net_driver.ko.

Usage

Load the cfg80211 kernel module:

sudo modprobe cfg80211

Load the virtual network driver module using insmod:

sudo insmod virt_net_driver.ko

To check if the module has been loaded successfully, run:

lsmod | grep virt_net_driver

Check if the virtual interfaces were created:

ifconfig

You should see three new interfaces like the example below:

vif0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether 00:76:69:66:30:00  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

vif1: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether 00:76:69:66:31:00  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

vif2: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether 00:76:69:66:32:00  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Check if the wifi devices have been properly configured. You should see the same interface names as the ifonfig output.

sudo iw dev
sudo iw list

Check if the get_station function works:

sudo iw dev vif0 station get 00:76:69:66:30:00

The output should look something like:

Station 00:76:69:66:30:00 (on vif0)
	rx bytes:	1
	rx packets:	1
	tx bytes:	1
	tx packets:	1
	tx failed:	1
	current time:	1682978276569 ms

To test if the scan and connect functions work properly, we must put each interface in their own network namespaces.

Add network namespaces:

sudo ip netns add v0
sudo ip netns add v1
sudo ip netns add v2

run sudo iw dev again and look and note the physical device numbers (phy#{NUMBER}). There should be three consecutive physical numbers (e.g. phy#0 phy#1 phy#2). Use these physical device numbers to add each device to its respective namespace:

sudo iw phy phy0 set netns name v0
sudo iw phy phy1 set netns name v1
sudo iw phy phy2 set netns name v2

Ensure all the interfaces are UP:

sudo ip netns exec v0 ip link set vif0 up
sudo ip netns exec v1 ip link set vif1 up
sudo ip netns exec v2 ip link set vif2 up

Configure vif0 to be an access point:

sudo ip netns exec v0 hostapd -B hostapd.conf

Check if vif0 has been successfully configured as an AP:

sudo ip netns exec v0 iw dev

The output should say ssid VAP and type AP:

phy#0
	Interface vif0
		ifindex 3
		wdev 0x1
		addr 00:76:69:66:30:00
		ssid VAP
		type AP

Give each interface an IP:

sudo ip netns exec v0 ip addr add 10.10.10.1/24 dev vif0
sudo ip netns exec v1 ip addr add 10.10.10.2/24 dev vif1
sudo ip netns exec v2 ip addr add 10.10.10.3/24 dev vif2

Perform a scan from vif1:

sudo ip netns exec v1 iw dev vif1 scan

Connect to vif0 from vif1:

sudo ip netns exec v1 iw dev vif1 connect VAP

Check vif1’s link status:

sudo ip netns exec v1 iw dev vif1 link

Remove the virtual network driver module:

sudo rmmod virt_net_driver

Test packet transfer functionality. We did not implement packet transfers between APs and STAs, but packet transfer between two STAs on the same namespace do work.

Re-insert the module:

sudo insmod virt_net_driver.ko

Give each interface each IP

sudo ip addr add 10.10.10.1/24 dev vif0
sudo ip addr add 10.10.10.2/24 dev vif1
sudo ip addr add 10.10.10.3/24 dev vif2

Ping v2 from v1:

ping -I 10.10.10.2 10.10.10.1

Developer Information

This project was developed and tested using Visual Studio Code on Fedora Core 38 with Linux Kernel 6.2.11. To set up your Visual Studio Code environment for C/C++ development, debugging, and linting, follow these steps:

  1. Install Visual Studio Code from https://code.visualstudio.com/.
  2. Open Visual Studio Code and go to the Extensions view by clicking the square icon on the Activity Bar on the side of the window.
  3. Search for and install the following extensions:
    1. C/C++ (by Microsoft)
    2. C/C++ Advanced Lint (by JBenden)
    3. Native Debug (by WebFreak)
  4. Configure the C/C++ extension by creating a c_cpp_properties.json file in the .vscode directory of your workspace. Add the following content to the file, adjusting the compilerPath and includePath as needed:
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/usr/lib/gcc/x86_64-linux-gnu/9/include"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "gcc-x64"
        }
    ],
    "version": 4
}
  1. Configure debugging for your project by creating a launch.json file in the .vscode directory. Add the following content to the file, adjusting the program and miDebuggerPath as needed:
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug Kernel Module",
            "type": "cppdbg",
            "request": "launch",
            "program": "/usr/src/linux-headers-$(uname -r)/scripts/gdb/vmlinux-gdb.py",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "/usr/bin/gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "loadModule"
        }
    ]
}
  1. Configure the build and load module tasks by creating a tasks.json file in the .vscode directory. Add the following content to the file:
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "type": "shell",
            "command": "make",
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": []
        },
        {
            "label": "loadModule",
            "type": "shell",
            "command": "sudo insmod virt_net_driver.ko",
            "dependsOn": "build",
            "problemMatcher": []
        }
    ]
}
  1. To build and load the kernel module, press F5 in Visual Studio Code.

Troubleshooting

If you encounter issues with the virtual network driver, follow these basic debugging steps:

  1. Check the output of the dmesg command for any error messages or warnings related to the virtual network driver.
  2. Use the printk function in the virtual network driver source code to output debug information to the kernel log. Make sure to recompile and reload the module after adding or modifying any printk statements.
  3. Use the modinfo command to inspect the virtual network driver module and ensure that it was built with the correct kernel version:
modinfo virt_net_driver.ko