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
Method 1: V Package Manager (Recommended)
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:
- 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 - Android Paranoid Network: The Android kernel restricts internet and raw socket syscalls to specific Unix group IDs (GID
3003forinetand GID3004fornet_raw). Ensure your execution environment preserves these auxiliary system groups when escalating privileges.