Raw socket with the new std.Io.net interfaces

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)

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:

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, which shows both writing to and reading from raw sockets how I would like to be able to.

conn-message-trace.txt (42.5 KB)

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

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 @kj4tmp used have been removed on master.