1 OS Backends
Watersrc-stack edited this page 2026-10-11 14:31:31 +02:00

OS backends

C has no interfaces, so NetOtter uses a header: includes/common_lib.h declares the functions every backend implements, and the Makefile compiles exactly one backend (libs/linux/ or libs/windows/). Core code includes common_lib.h only, never a backend header, and keeps every OS-specific header (poll.h, sys/socket.h, windows.h...) and every OS-specific field (like st_ino) out of src/. make windows checks that the core still builds for the other system.

The contract

lib_common_t carries the backend's name (lib_name: Linux / Windows, also written in save files), its interface list, and an opaque internal_struct for the backend's own state.

Function Job
init_common_struct() Starts the backend and lists the interfaces, all disabled.
common_lib_terminate() Closes every capture and the resolver socket, frees everything.
tick() Appends the frames waiting on every enabled interface to the queue, without blocking.
toggle_ifname() / get_ifname_state() Starts / stops capture on interface id, or reads its state.
disable_all() Stops every interface; returns how many were running.
stdin_wait() Waits up to n ms for a typed line: 1 ready, 0 timeout, -1 error.
local_addresses_refresh() Appends the machine's current addresses that aren't known yet (never removes one).
resolver_open/send/recv/close() The UDP socket to the system DNS server, for reverse lookups.
random_bytes() Unpredictable bytes, for DNS query ids.
same_file() Whether two open files are the same file (-I vs -O).
my_usleep() Sleeps n µs (usleep is obsolete in POSIX).
cache_file_path() Where a cache file lives, optionally creating the folders.
term_colors() Whether stdout shows ANSI colours.
spool_open/write_at/read_at/close() The temporary payload file.

Some functions take no lib_common_t, because they run before the backend starts or after it stops: same_file, my_usleep, cache_file_path, term_colors and the spool functions.

Interface ids are positions in the backend's current list, so they shift when an interface appears or disappears.

Linux (libs/linux/)

File What
linux_lib.c Interfaces from if_nameindex(), Wi-Fi detection through a wireless-extensions ioctl; same_file (st_dev / st_ino), my_usleep (nanosleep), term_colors (a tty with a TERM other than dumb), cache_file_path ($XDG_CACHE_HOME/netotter/ when absolute, else ~/.cache/netotter/, folders created 0700).
linux_common_functions.c The contract functions.
linux_network.c One AF_PACKET / SOCK_RAW / ETH_P_ALL socket per enabled interface, restricted to it with bind() on its sll_ifindex (SO_BINDTODEVICE doesn't filter packet sockets). nw_tick reads with recvfrom(MSG_DONTWAIT) until EAGAIN; sll_hatype gives the link type.
linux_addresses.c Own addresses from getifaddrs().
linux_input.c stdin_wait with poll().
linux_resolver.c Non-blocking UDP socket connected to the first nameserver of /etc/resolv.conf; random_bytes with getrandom().
linux_spool.c An unnamed file (O_TMPFILE, falling back to mkstemp + unlink), pread / pwrite.

Capturing needs cap_net_raw. Ethernet, Wi-Fi (outside monitor mode) and lo all deliver Ethernet frames. Tunnels (tun, WireGuard) deliver raw IP. On lo, each packet is seen twice (out and in).

Windows (libs/windows/)

File What
windows_pcap.c / .h Loads Npcap's wpcap.dll at runtime and fills a table of function pointers (npcap_t).
windows_adapters.c GetAdaptersAddresses, short names, descriptions.
windows_lib.c The interface list: Npcap's devices, named after their Windows adapters.
windows_common_functions.c The contract functions.
windows_network.c One Npcap capture handle per enabled interface.
windows_addresses.c Own addresses from GetAdaptersAddresses.
windows_input.c stdin_wait for consoles and pipes.
windows_resolver.c Winsock UDP socket to the first DNS server from GetNetworkParams (IPv4 only); random_bytes with BCryptGenRandom.
windows_spool.c FILE_FLAG_DELETE_ON_CLOSE file, offsets through OVERLAPPED.

Npcap, loaded at runtime

The library is loaded from System32\Npcap\wpcap.dll (with LOAD_WITH_ALTERED_SEARCH_PATH), then, for Npcap's WinPcap-compatible mode, wpcap.dll from System32 only. NetOtter never loads it from its own folder or the current directory, so a planted DLL can't be picked up.

Without Npcap, set_interfaces prints a notice and returns an empty list, and NetOtter still works on files. The build needs no Npcap SDK: windows_pcap.h declares the few libpcap types used. Npcap's license forbids redistributing it without an OEM license, so it must never be bundled.

GetProcAddress results are cast through void (*)(void). Under C23 (GCC 16's default), FARPROC's () means (void), and a direct cast breaks -Werror.

Interface names

Windows adapter names are GUIDs. NetOtter uses ConvertInterfaceLuidToNameA names instead (ethernet_32769, wireless_32768, loopback_0, cut to 15 characters). They have no spaces, so they fit me filters and save files. They're matched to Npcap's \Device\NPF_{GUID} by the GUID in AdapterName.

  • A device without an adapter is npf_<position>.
  • -l adds FriendlyName: Description after each name.
  • Own addresses use the same names, so me wireless_32768 works.

Capture

  • Opening: pcap_create with a 65536-byte snapshot length, no promiscuous mode (like Linux), immediate mode and an 8 MiB kernel buffer (it holds frames while a typed line pauses the ticks). Then non-blocking pcap_next_ex, up to 500 frames per tick.
  • Link types:
    • DLT_EN10MB (Ethernet, and Wi-Fi outside monitor mode) is LINK_ETH.
    • DLT_NULL / DLT_LOOP (loopback, 4-byte family header stripped) and the raw-IP DLTs are LINK_RAW_IP.
    • Anything else is LINK_UNK.
  • Loopback: packets show up once.
  • Errors: PCAP_ERROR_PERM_DENIED means Npcap was installed for administrators only.

Console input

A console has no poll(). stdin_wait checks every 20 ms for a key press that types a character (or, for a pipe, PeekNamedPipe). It returns at the first character, then read() takes the rest of the line. The console only echoes while a read() waits, so ticks pause while a line is typed.

Testing under Wine

make windows then wine ./NetOtter.exe -I capture.nto -c | tr -d '\r' gives the same output as Linux (Windows ends lines with \r\n). Limits:

  • Wine's wpcap.dll wraps the host libpcap: interfaces are listed, but opening them fails (Wine has no cap_net_raw).
  • WINEDLLOVERRIDES="wpcap=d" simulates a machine without Npcap.
  • Wine can't PeekNamedPipe a Unix pipe (error 50), so piped commands run no ticks between them.
  • Wine's LOCALAPPDATA is its own (C:\users\<user>\AppData\Local).

Adding a backend function

  1. Declare it in common_lib.h with a comment saying what it returns on each system.
  2. Implement it in both libs/linux/ and libs/windows/. A new file goes in ADDSRCS.
  3. Check both builds: make re && make windows.