netr (Network Raw)

July 14, 2026 · View on GitHub

netr is a modular, zero-dependency raw socket and network packet crafting library written in V. It provides low-level abstractions over POSIX and Linux-specific C socket APIs, enabling the construction and transmission of custom network packets from Layer 2 (Data Link) up to Layer 4 (Transport).


Features

  • Zero Third-Party Dependencies: Built entirely using native V wrappers over POSIX C socket headers.
  • Unified Packet Construction: Linear layer stacking model utilizing V's interface system.
  • Automatic IP and TCP/UDP Checksums: Built-in network byte-order checksum calculation algorithms.
  • L2 Interface Auto-Binding: Automated resolution of active network interfaces (wlan0, eth0, etc.) for Link Layer frame injection.
  • Dynamic Local IP Resolution: Utility function to query local interface routing tables without sending network packets.

Installation

You can install the library directly from the GitHub repository using V's package manager:

v install --git https://github.com/tailsmails/netr

Method 2: Manual Installation

Alternatively, you can clone or move the netr directory into your global V modules path manually:

  • Linux / macOS: ~/.vmodules/netr/
  • Android (Termux): /data/data/com.termux/files/home/.vmodules/netr/
  • Windows: C:\Users\<Your_Username>\.vmodules\netr\

Core Architecture

The design revolves around the Layer interface. Any custom protocol can be implemented by satisfying this contract:

pub interface Layer {
	name() string
	register(mut ctx PackContext) !
	pack(payload []u8, mut ctx PackContext) ![]u8
}
  • PackContext: Holds state information (like IP addresses and protocol types) shared across layers during compilation.
  • Packet: Manages the stacked layers and packs them in reverse order (L4 -> L3 -> L2) to correctly encapsulate payloads.

Code Examples

1. IPv4 ICMP Echo Request (Kernel-Managed IP Header)

import netr
import netr.layers

fn main() {
	mut sock := netr.new_socket(.layer3_v4) or { panic(err) }
	defer { sock.close() }

	mut packet := netr.new_packet()
	packet.add(layers.ICMPv4{
		@type: 8
		identifier: 4321
	})
	packet.add(layers.Payload{
		data: 'netr-ping'.bytes()
	})

	packet_data := packet.build() or { panic(err) }
	sent := sock.send(packet_data, '1.1.1.1') or { panic(err) }
	println("Sent ${sent} bytes")

	mut recv_buf := []u8{len: 1024}
	received := sock.recv(mut recv_buf) or { panic(err) }
	println("Received ${received} bytes")
}

2. IPv4 ICMP Echo Request (Custom IPv4 Header)

import netr
import netr.layers

fn main() {
	local_ip := netr.get_local_ip() or { panic(err) }

	mut sock := netr.new_socket(.layer3_v4) or { panic(err) }
	defer { sock.close() }
	sock.set_ip_header_included(true) or { panic(err) }

	mut packet := netr.new_packet()
	packet.add(layers.IPv4{
		src_ip: local_ip
		dst_ip: [u8(1), 1, 1, 1]!
		protocol: 1
	})
	packet.add(layers.ICMPv4{
		@type: 8
		identifier: 4321
	})
	packet.add(layers.Payload{
		data: 'netr-ping'.bytes()
	})

	packet_data := packet.build() or { panic(err) }
	sent := sock.send(packet_data, '1.1.1.1') or { panic(err) }
	println("Sent ${sent} bytes")
}

3. Layer 2 ARP Request (Linux/Android/Termux Only)

import netr
import netr.layers

fn main() {
	$if linux || android || termux {
		mut sock := netr.new_socket(.layer2) or { panic(err) }
		defer { sock.close() }
		sock.auto_bind() or { panic(err) }

		mut packet := netr.new_packet()
		packet.add(layers.Ethernet{
			dst_mac: [u8(0xff), 0xff, 0xff, 0xff, 0xff, 0xff]!
			src_mac: [u8(0x00), 0x0c, 0x29, 0xab, 0xcd, 0xef]!
			ether_type: 0x0806
		})
		packet.add(layers.ARP{
			opcode: 1
			sender_mac: [u8(0x00), 0x0c, 0x29, 0xab, 0xcd, 0xef]!
			sender_ip: [u8(192), 168, 1, 5]!
			target_mac: [u8(0x00), 0x00, 0x00, 0x00, 0x00, 0x00]!
			target_ip: [u8(192), 168, 1, 1]!
		})

		packet_data := packet.build() or { panic(err) }
		sent := sock.send(packet_data, '') or { panic(err) }
		println("ARP Frame Sent: ${sent} bytes")
	}
}

Operating System and Permission Constraints

Low-level socket access (SOCK_RAW and AF_PACKET) is strictly restricted by operating systems to prevent raw packet injection by unprivileged users.

Root Privileges

Execution of compiled binaries requires administrative/root privileges:

v example.v
sudo ./example

Android and Termux Targets

Android systems impose strict sandbox restrictions that can interfere with raw socket operations even under a root user:

  1. SELinux Policies: Android’s default SELinux policies often block raw socket reading (rawip_socket recv). If packets are sent but responses are never received, temporarily set SELinux to permissive mode:
    sudo setenforce 0
    
  2. Android Paranoid Network: The Android kernel restricts internet and raw socket syscalls to specific Unix group IDs (GID 3003 for inet and GID 3004 for net_raw). Ensure your execution environment preserves these auxiliary system groups when escalating privileges.