# Implementing an interface for Raw Sockets

**URL:** <https://ziggit.dev/t/implementing-an-interface-for-raw-sockets/6577>\
**Category:** Help\
**Tags:** language\
**Created:** [October 29, 2024, 5:33am UTC](https://ziggit.dev/t/implementing-an-interface-for-raw-sockets/6577 "2024-10-29T05:33:14Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![jeffective](https://ziggit.dev/user_avatar/ziggit.dev/jeffective/32/3116_2.png) [@jeffective](https://ziggit.dev/u/jeffective)\
**Post date:** [October 29, 2024, 5:33am UTC](https://ziggit.dev/t/implementing-an-interface-for-raw-sockets/6577/1 "2024-10-29T05:33:14Z")

</div>

I think I need to implement an interface, but I’m not sure what type of interface to implement. These are my requirements:

1. Allow my library code to interact with planned supported network interfaces: linux raw socket, windows raw socket (via npcap), or linux xdp.
2. Allow user to implement their own raw socket interface (for embedded applications)
3. Not use `anytype` because I literally cannot stomach how it degrades readability at the callsite.

My interface will likely have two functions:

`writeAll`: write all the bytes of a slice to the network  
`read`: read bytes from the network

My questions:

1. From these requirements, should I implement an interface like `std.mem.allocator`? Do I need a vtable?
2. Where can I find a detailed and comprehensive overview of my options for interfaces?

---

<div class="post-metadata">

**Author:** ![jeffective](https://ziggit.dev/user_avatar/ziggit.dev/jeffective/32/3116_2.png) [@jeffective](https://ziggit.dev/u/jeffective)\
**Post date:** [October 29, 2024, 6:13am UTC](https://ziggit.dev/t/implementing-an-interface-for-raw-sockets/6577/2 "2024-10-29T06:13:13Z")

</div>

this is what I’ve got so far:

```zig
/// Interface for networking hardware
pub const NetworkAdapter = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    pub const VTable = struct {
        write: *const fn (data: []const u8) anyerror!usize,
        read: *const fn (out: []u8) anyerror!usize,
    };
};

/// Raw socket implementation for NetworkAdapter
pub const RawSocket = struct {
    socket: std.posix.socket_t,

    pub fn init(
        ifname: []const u8,
    ) !RawSocket {
        assert(ifname.len <= std.posix.IFNAMESIZE - 1); // ifname too long
        const socket: std.posix.socket_t = try std.posix.socket(
            std.posix.AF.PACKET,
            std.posix.SOCK.RAW,
            std.mem.nativeToBig(u32, ETH_P_ETHERCAT),
        );
        var timeout_rcv = std.posix.timeval{
            .sec = 0,
            .usec = 1,
        };
        try std.posix.setsockopt(
            socket,
            std.posix.SOL.SOCKET,
            std.posix.SO.RCVTIMEO,
            std.mem.asBytes(&timeout_rcv),
        );

        var timeout_snd = std.posix.timeval{
            .sec = 0,
            .usec = 1,
        };
        try std.posix.setsockopt(
            socket,
            std.posix.SOL.SOCKET,
            std.posix.SO.SNDTIMEO,
            std.mem.asBytes(&timeout_snd),
        );
        const dontroute_enable: c_int = 1;
        try std.posix.setsockopt(
            socket,
            std.posix.SOL.SOCKET,
            std.posix.SO.DONTROUTE,
            std.mem.asBytes(&dontroute_enable),
        );
        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;

        const IFF_PROMISC = 256;
        const IFF_BROADCAST = 2;
        const SIOCGIFFLAGS = 0x8913;
        const SIOCSIFFLAGS = 0x8914;

        var rval = std.posix.errno(std.os.linux.ioctl(socket, SIOCGIFFLAGS, @intFromPtr(&ifr)));
        switch (rval) {
            .SUCCESS => {},
            else => {
                return error.nicError;
            },
        }
        ifr.ifru.flags = ifr.ifru.flags | IFF_BROADCAST | IFF_PROMISC;
        rval = std.posix.errno(std.os.linux.ioctl(socket, 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, ETH_P_ETHERCAT)),
            .halen = 0, //not used
            .addr = .{ 0, 0, 0, 0, 0, 0, 0, 0 }, //not used
            .pkttype = 0, //not used
            .hatype = 0, //not used
        };
        try std.posix.bind(socket, @ptrCast(&sockaddr_ll), @sizeOf(@TypeOf(sockaddr_ll)));
        return RawSocket{
            .socket = socket,
        };
    }

    pub fn deinit(self: *RawSocket) void {
        _ = self;
        // TODO: de init socket
    }

    pub fn write(self: *RawSocket, bytes: []const u8) std.posix.WriteError!usize {
        return try std.posix.write(self.socket, bytes);
    }

    pub fn read(self: *RawSocket, out: []u8) std.posix.ReadError!usize {
        return try std.posix.read(self.socket, out);
    }

    pub fn networkAdapter(self: *RawSocket) NetworkAdapter {
        return NetworkAdapter{
            .ptr = self,
            .vtable = &.{ .write = write, .read = read },
        };
    }
};

```

---

<div class="post-metadata">

**Author:** ![jeffective](https://ziggit.dev/user_avatar/ziggit.dev/jeffective/32/3116_2.png) [@jeffective](https://ziggit.dev/u/jeffective)\
**Post date:** [October 29, 2024, 6:35am UTC](https://ziggit.dev/t/implementing-an-interface-for-raw-sockets/6577/3 "2024-10-29T06:35:06Z")

</div>

ugh Idk what I’m doing, really wish this was easier for a pythonista like me

```zig
/// Interface for networking hardware
pub const NetworkAdapter = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    pub const VTable = struct {
        write: *const fn (ctx: *anyopaque, data: []const u8) anyerror!usize,
        read: *const fn (ctx: *anyopaque, out: []u8) anyerror!usize,
    };

    pub fn write(ctx: *anyopaque, data: []const u8) anyerror!usize {
        const self: NetworkAdapter = @ptrCast(@alignCast(ctx));
        return try self.vtable.write(self.ptr, data);
    }

    pub fn read(ctx: *anyopaque, out: []u8) anyerror!usize {
        const self: NetworkAdapter = @ptrCast(@alignCast(ctx));
        return try self.vtable.read(self.ptr, out);
    }
};

```

```zig
jeff@jeff-debian:~/repos/gatorcat$ zig build
install
└─ install gatorcat
   └─ zig build-exe gatorcat Debug native 2 errors
src/nic.zig:103:37: error: no field or member function named 'write' in 'nic.NetworkAdapter'
            _ = self.network_adapter.write(out) catch return error.LinkError;
                ~~~~~~~~~~~~~~~~~~~~^~~~~~
src/nic.zig:252:28: note: struct declared here
pub const NetworkAdapter = struct {
                           ^ ~~~~~
src/nic.zig:261:9: note: 'write' is not a member function
    pub fn write(ctx: *anyopaque, data: []const u8) anyerror!usize {
    ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/nic.zig:370:28: error: expected type '*const fn (*anyopaque, []const u8) anyerror!usize', found '*const fn (*nic.RawSocket, []const u8) error{DiskQuota,FileTooBig,InputOutput,NoSpaceLeft,DeviceBusy,InvalidArgument,AccessDenied,BrokenPipe,SystemResources,OperationAborted,NotOpenForWriting,LockViolation,WouldBlock,ConnectionResetByPeer,ProcessNotFound,Unexpected}!usize'
            .vtable = &.{ .write = write, .read = read },
                          ~^ ~~~~~~~~~~~~
src/nic.zig:370:28: note: pointer type child 'fn (*nic.RawSocket, []const u8) error{DiskQuota,FileTooBig,InputOutput,NoSpaceLeft,DeviceBusy,InvalidArgument,AccessDenied,BrokenPipe,SystemResources,OperationAborted,NotOpenForWriting,LockViolation,WouldBlock,ConnectionResetByPeer,ProcessNotFound,Unexpected}!usize' cannot cast into pointer type child 'fn (*anyopaque, []const u8) anyerror!usize'
src/nic.zig:370:28: note: parameter 0 '*nic.RawSocket' cannot cast into '*anyopaque'
src/nic.zig:370:28: note: pointer type child 'anyopaque' cannot cast into pointer type child 'nic.RawSocket'

```

---

<div class="post-metadata">

**Author:** ![jeffective](https://ziggit.dev/user_avatar/ziggit.dev/jeffective/32/3116_2.png) [@jeffective](https://ziggit.dev/u/jeffective)\
**Post date:** [October 29, 2024, 6:39am UTC](https://ziggit.dev/t/implementing-an-interface-for-raw-sockets/6577/4 "2024-10-29T06:39:59Z")

</div>

well this built, we shall see if it works

```zig

/// Interface for networking hardware
pub const NetworkAdapter = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    pub const VTable = struct {
        write: *const fn (ctx: *anyopaque, data: []const u8) anyerror!usize,
        read: *const fn (ctx: *anyopaque, out: []u8) anyerror!usize,
    };

    pub fn write(self: NetworkAdapter, data: []const u8) anyerror!usize {
        return try self.vtable.write(self.ptr, data);
    }

    pub fn read(self: NetworkAdapter, out: []u8) anyerror!usize {
        return try self.vtable.read(self.ptr, out);
    }
};

/// Raw socket implementation for NetworkAdapter
pub const RawSocket = struct {
    socket: std.posix.socket_t,

    pub fn init(
        ifname: []const u8,
    ) !RawSocket {
        assert(ifname.len <= std.posix.IFNAMESIZE - 1); // ifname too long
        const socket: std.posix.socket_t = try std.posix.socket(
            std.posix.AF.PACKET,
            std.posix.SOCK.RAW,
            std.mem.nativeToBig(u32, ETH_P_ETHERCAT),
        );
        var timeout_rcv = std.posix.timeval{
            .sec = 0,
            .usec = 1,
        };
        try std.posix.setsockopt(
            socket,
            std.posix.SOL.SOCKET,
            std.posix.SO.RCVTIMEO,
            std.mem.asBytes(&timeout_rcv),
        );

        var timeout_snd = std.posix.timeval{
            .sec = 0,
            .usec = 1,
        };
        try std.posix.setsockopt(
            socket,
            std.posix.SOL.SOCKET,
            std.posix.SO.SNDTIMEO,
            std.mem.asBytes(&timeout_snd),
        );
        const dontroute_enable: c_int = 1;
        try std.posix.setsockopt(
            socket,
            std.posix.SOL.SOCKET,
            std.posix.SO.DONTROUTE,
            std.mem.asBytes(&dontroute_enable),
        );
        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;

        const IFF_PROMISC = 256;
        const IFF_BROADCAST = 2;
        const SIOCGIFFLAGS = 0x8913;
        const SIOCSIFFLAGS = 0x8914;

        var rval = std.posix.errno(std.os.linux.ioctl(socket, SIOCGIFFLAGS, @intFromPtr(&ifr)));
        switch (rval) {
            .SUCCESS => {},
            else => {
                return error.nicError;
            },
        }
        ifr.ifru.flags = ifr.ifru.flags | IFF_BROADCAST | IFF_PROMISC;
        rval = std.posix.errno(std.os.linux.ioctl(socket, 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, ETH_P_ETHERCAT)),
            .halen = 0, //not used
            .addr = .{ 0, 0, 0, 0, 0, 0, 0, 0 }, //not used
            .pkttype = 0, //not used
            .hatype = 0, //not used
        };
        try std.posix.bind(socket, @ptrCast(&sockaddr_ll), @sizeOf(@TypeOf(sockaddr_ll)));
        return RawSocket{
            .socket = socket,
        };
    }

    pub fn deinit(self: *RawSocket) void {
        _ = self;
        // TODO: de init socket
    }

    pub fn write(ctx: *anyopaque, bytes: []const u8) std.posix.WriteError!usize {
        const self: *RawSocket = @ptrCast(@alignCast(ctx));
        return try std.posix.write(self.socket, bytes);
    }

    pub fn read(ctx: *anyopaque, out: []u8) std.posix.ReadError!usize {
        const self: *RawSocket = @ptrCast(@alignCast(ctx));
        return try std.posix.read(self.socket, out);
    }

    pub fn networkAdapter(self: *RawSocket) NetworkAdapter {
        return NetworkAdapter{
            .ptr = self,
            .vtable = &.{ .write = write, .read = read },
        };
    }
};

```

edit: It works!

---

<div class="post-metadata">

**Author:** ![jeffective](https://ziggit.dev/user_avatar/ziggit.dev/jeffective/32/3116_2.png) [@jeffective](https://ziggit.dev/u/jeffective)\
**Post date:** [October 29, 2024, 6:47am UTC](https://ziggit.dev/t/implementing-an-interface-for-raw-sockets/6577/5 "2024-10-29T06:47:49Z")

</div>

I’m not sure if I should be using `anyerror`?

Does this ruin my ability to do exhaustive switching on errors?

---

<div class="post-metadata">

**Author:** ![jmc](https://ziggit.dev/user_avatar/ziggit.dev/jmc/32/38_2.png) [@jmc](https://ziggit.dev/u/jmc)\
**Post date:** [October 29, 2024, 8:42am UTC](https://ziggit.dev/t/implementing-an-interface-for-raw-sockets/6577/6 "2024-10-29T08:42:00Z")

</div>

Yeah, it would be best if you can avoid `anyerror` altogether. My mental model of `anyerror` is “it’s impossible for me to know which errors may arise” or “I’m using recursion and the compiler cannot infer the error union”.

---

<div class="post-metadata">

**Author:** ![dimdin](https://ziggit.dev/user_avatar/ziggit.dev/dimdin/32/1457_2.png) [@dimdin](https://ziggit.dev/u/dimdin)\
**Post date:** [October 29, 2024, 9:35am UTC](https://ziggit.dev/t/implementing-an-interface-for-raw-sockets/6577/7 "2024-10-29T09:35:02Z")

</div>

Adding an error set in `NetworkAdapter` is a good idea.  
You can enumerate error values or use `||` to construct unions of error sets.

> Years into Zig, some advice: use inferred error sets for rapid development but try to convert them to explicit error sets as soon as you can. Likewise, avoid `else =>` in error switches. Its hugely valuable to discover you introduced new error case from the compiler erroring.  
> – Mitchell Hashimoto

---

<div class="post-metadata">

**Author:** ![mark-rushakoff](https://ziggit.dev/user_avatar/ziggit.dev/mark-rushakoff/32/3551_2.png) [@mark-rushakoff](https://ziggit.dev/u/mark-rushakoff)\
**Post date:** [October 29, 2024, 3:44pm UTC](https://ziggit.dev/t/implementing-an-interface-for-raw-sockets/6577/8 "2024-10-29T15:44:54Z")

</div>

> [@jeffective](#):
>
> Where can I find a detailed and comprehensive overview of my options for interfaces?

This Zig News article outlines several options. I haven’t written enough Zig yet to say whether it is outdated (written in Jan 2023) or still totally up to date.

> **[Code study: interface idioms/patterns in zig standard libraries](https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj)**
>
> introduction In Java and Go, behavior based abstractions can be defined using "interface"...
