# Raw socket with the new std.Io.net interfaces

**URL:** <https://ziggit.dev/t/raw-socket-with-the-new-std-io-net-interfaces/13984>\
**Category:** Help\
**Tags:** standard-library\
**Created:** [January 17, 2026, 11:21pm UTC](https://ziggit.dev/t/raw-socket-with-the-new-std-io-net-interfaces/13984 "2026-01-17T23:21:16Z")\
**Posts on this page:** 1\
**Showing post:** 10

<div class="post-metadata">

**Author:** ![Zambyte](https://ziggit.dev/user_avatar/ziggit.dev/zambyte/32/4977_2.png) [@Zambyte](https://ziggit.dev/u/Zambyte)\
**Post date:** [January 18, 2026, 11:10pm UTC](https://ziggit.dev/t/raw-socket-with-the-new-std-io-net-interfaces/13984/10 "2026-01-18T23:10:51Z")

</div>

Here is a functioning example that lets me write data how I want. This program must either be run as root, or using the `CAP_NET_RAW` capability ([man](https://man7.org/linux/man-pages/man7/capabilities.7.html))

```zig
const std = @import("std");
const Io = std.Io;

const zig_raw_socket = @import("zig_raw_socket");

pub fn main() !void {
    const socket: i32 = @intCast(std.os.linux.socket(
        std.posix.AF.PACKET,
        std.posix.SOCK.RAW,
        0,
    ));

    // hardcoded interface name for test
    const ifname = "enp7s0";

    var ifr: std.posix.ifreq = std.mem.zeroInit(std.posix.ifreq, .{});
    @memcpy(ifr.ifrn.name[0..ifname.len], ifname);
    ifr.ifrn.name[ifname.len] = 0;
    try std.posix.ioctl_SIOCGIFINDEX(socket, &ifr);
    const ifindex: i32 = ifr.ifru.ivalue;

    var rval = std.posix.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCGIFFLAGS, @intFromPtr(&ifr)));
    switch (rval) {
        .SUCCESS => {},
        else => {
            return error.NicError;
        },
    }
    ifr.ifru.flags.BROADCAST = true;
    ifr.ifru.flags.PROMISC = true;
    rval = std.posix.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCSIFFLAGS, @intFromPtr(&ifr)));
    switch (rval) {
        .SUCCESS => {},
        else => {
            return error.NicError;
        },
    }
    const sockaddr_ll = std.posix.sockaddr.ll{
        .family = std.posix.AF.PACKET,
        .ifindex = ifindex,
        .protocol = std.mem.nativeToBig(u16, @as(u16, std.os.linux.ETH.P.IP)),
        .halen = 0, //not used
        .addr = .{ 0, 0, 0, 0, 0, 0, 0, 0 }, //not used
        .pkttype = 0, //not used
        .hatype = 0, //not used
    };
    _ = std.os.linux.bind(socket, @ptrCast(&sockaddr_ll), @sizeOf(@TypeOf(sockaddr_ll)));

    const payload: []const u8 = "raw bytes sent over the wire";
    _ = std.os.linux.sendto(
        socket,
        payload.ptr,
        payload.len,
        0,
        @ptrCast(&sockaddr_ll),
        @sizeOf(@TypeOf(sockaddr_ll)),
    );
}

```

Here is the full strace output:

```zig
execve("./zig-out/bin/zig_raw_socket", ["./zig-out/bin/zig_raw_socket"], 0x7fff87e84380 /* 24 vars */) = 0
mmap(NULL, 262223, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7efee2aa2000
arch_prctl(ARCH_SET_FS, 0x7efee2ae2028) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
prlimit64(0, RLIMIT_STACK, {rlim_cur=16384*1024, rlim_max=RLIM64_INFINITY}, NULL) = 0
sigaltstack({ss_sp=0x7efee2aa200a, ss_flags=0, ss_size=262144}, NULL) = 0
rt_sigaction(SIGSEGV, {sa_handler=0x11a5210, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x10d8780}, NULL, 8) = 0
rt_sigaction(SIGILL, {sa_handler=0x11a5210, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x10d8780}, NULL, 8) = 0
rt_sigaction(SIGBUS, {sa_handler=0x11a5210, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x10d8780}, NULL, 8) = 0
rt_sigaction(SIGFPE, {sa_handler=0x11a5210, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x10d8780}, NULL, 8) = 0
socket(AF_PACKET, SOCK_RAW, htons(0 /* ETH_P_??? */)) = 3
ioctl(3, SIOCGIFINDEX, {ifr_name="enp7s0", ifr_ifindex=2}) = 0
ioctl(3, SIOCGIFFLAGS, {ifr_name="enp7s0", ifr_flags=IFF_UP|IFF_BROADCAST|IFF_RUNNING|IFF_PROMISC|IFF_MULTICAST}) = 0
ioctl(3, SIOCSIFFLAGS, {ifr_name="enp7s0", ifr_flags=IFF_UP|IFF_BROADCAST|IFF_RUNNING|IFF_PROMISC|IFF_MULTICAST}) = 0
bind(3, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_IP), sll_ifindex=if_nametoindex("enp7s0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 0
sendto(3, "raw bytes sent over the wire", 28, 0, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_IP), sll_ifindex=if_nametoindex("enp7s0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 28
exit_group(0) = ?
+++ exited with 0 +++

```

I also captured a more complete strace from running [this Go program](https://gitlab.com/c2-games/red-team/saprus/-/blob/main/clients/bidir-comms/main.go?ref_type=heads), which shows both writing to and reading from raw sockets how I would like to be able to.

[conn-message-trace.txt](https://ziggit.dev/uploads/short-url/vdOihN5psmxPZco7VAWkBHHq58n.txt) (42.5 KB)

Here is a screenshot of Wireshark showing the full packet that is sent:

 ![image](https://ziggit.dev/uploads/default/original/2X/0/01a587d977c14283d421ee8e69cf824ab3a10175.png)

As you can see, the bytes starting from `raw bytes` are interpreted as the ethernet headers.

Edit: and to be clear, I mentioned calling out to libc not because it cannot be done with the standard library right now (as shown), but because I wasn’t sure if the APIs required to do it are expected to be removed, since the POSIX APIs that @jeffective used have been removed on master.

---

_[View the full topic](https://ziggit.dev/t/raw-socket-with-the-new-std-io-net-interfaces/13984)._
