Creating a socket using linux syscall

I need to create a socket using lower level linux syscalls now after std.posix.socket was removed and the new std.Io does not (yet) support non-ip networking. So I looked up the 0.15.2 std.posix.socket and do something similar:

const ETH_P_ETHERCAT = @intFromEnum(telegram.EtherType.ETHERCAT);
const socket_rc = std.os.linux.socket(
    std.os.linux.AF.PACKET,
    std.os.linux.SOCK.RAW,
    std.mem.nativeToBig(u32, ETH_P_ETHERCAT),
);
std.log.err("socket rc: {}", .{socket_rc});
switch (std.posix.errno(socket_rc)) {
    .SUCCESS => {},
    else => |err| {
        logger.err("failed to open raw socket: {}", .{err});
        return error.NicError;
    },
}
const socket: std.posix.socket_t = @intCast(socket_rc);
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),
);

But when I attempt this (unprivileged, expecting an error), I get integer out of bounds panic:

error: socket rc: 18446744073709551615
thread 279069 panic: integer does not fit in destination type
/home/jeff/repos/gatorcat/src/module/nic.zig:92:44: 0x1818a03 in init (root.zig)
        const socket: std.posix.socket_t = @intCast(socket_rc);

What am I doing wrong? Am I not interpreting the return type correctly?

2 Likes

Use std.os.linux.errno instead of std.posix.errno (std.os.linux.E.init on 0.15.2)
Reason is that std.posix.errno depends on libc errno if you link against libc.

2 Likes

std.posix.socket_t is void, use std.os.linux.socket_t (i32).

thanks

const socket_rc: usize = std.os.linux.socket(
            std.os.linux.AF.PACKET,
            std.os.linux.SOCK.RAW,
            std.mem.nativeToBig(u32, ETH_P_ETHERCAT),
        );

switch (std.os.linux.errno(socket_rc)) {
    .SUCCESS => {},
    else => |err| {
        logger.err("failed to open raw socket: {}", .{err});
        return error.NicError;
    },
}

Much better now (error expected)

error(gatorcat): failed to open raw socket: .PERM
1 Like

std.posix.socket_t is still i32 on linux and still works for the other stuff (std.posix.setsockopt), but will try getting rid of all my std.posix usage entirely momentarily

congratulations to me:

jeff@jeff-debian:~/repos/gatorcat$ git grep -r "posix"
jeff@jeff-debian:~/repos/gatorcat$ 

implementation that at least compiles:

pub const LinuxRawSocket = struct {
    send_mutex: std.Io.Mutex = .init,
    recv_mutex: std.Io.Mutex = .init,
    socket: std.os.linux.socket_t,

    pub const InitError = error{
        /// The ifname argument provided is too long.
        InterfaceNameTooLong,
        /// Non-descriptive error. OS returned an error.
        NicError,
    };
    pub fn init(
        ifname: [:0]const u8,
    ) InitError!LinuxRawSocket {
        if (ifname.len > std.os.linux.IFNAMESIZE - 1) return error.InterfaceNameTooLong;
        assert(ifname.len <= std.os.linux.IFNAMESIZE - 1); // ifname too long
        const ETH_P_ETHERCAT = @intFromEnum(telegram.EtherType.ETHERCAT);
        const socket_rc: usize = std.os.linux.socket(
            std.os.linux.AF.PACKET,
            std.os.linux.SOCK.RAW,
            std.mem.nativeToBig(u32, ETH_P_ETHERCAT),
        );

        switch (std.os.linux.errno(socket_rc)) {
            .SUCCESS => {},
            else => |err| {
                logger.err("failed to open raw socket: {}", .{err});
                return error.NicError;
            },
        }
        const socket: std.os.linux.socket_t = @intCast(socket_rc);
        var timeout_rcv = std.os.linux.timeval{
            .sec = 0,
            .usec = 1,
        };
        const rcvtime0_setsockopt_rc = std.os.linux.setsockopt(
            socket,
            std.os.linux.SOL.SOCKET,
            std.os.linux.SO.RCVTIMEO,
            std.mem.asBytes(&timeout_rcv),
            std.mem.asBytes(&timeout_rcv).len,
        );
        switch (std.os.linux.errno(rcvtime0_setsockopt_rc)) {
            .SUCCESS => {},
            else => |err| {
                logger.err("failed to setsockopt RCVTIME0: {}", .{err});
                return error.NicError;
            },
        }

        var timeout_snd = std.os.linux.timeval{
            .sec = 0,
            .usec = 1,
        };
        const sndtime0_setsockopt_rc = std.os.linux.setsockopt(
            socket,
            std.os.linux.SOL.SOCKET,
            std.os.linux.SO.SNDTIMEO,
            std.mem.asBytes(&timeout_snd).ptr,
            std.mem.asBytes(&timeout_snd).len,
        );
        switch (std.os.linux.errno(sndtime0_setsockopt_rc)) {
            .SUCCESS => {},
            else => |err| {
                logger.err("failed to setsockopt SNDTIME0: {}", .{err});
                return error.NicError;
            },
        }
        const dontroute_enable: c_int = 1;
        const dontroute_setsockopt_rc = std.os.linux.setsockopt(
            socket,
            std.os.linux.SOL.SOCKET,
            std.os.linux.SO.DONTROUTE,
            std.mem.asBytes(&dontroute_enable).ptr,
            std.mem.asBytes(&dontroute_enable).len,
        );
        switch (std.os.linux.errno(dontroute_setsockopt_rc)) {
            .SUCCESS => {},
            else => |err| {
                logger.err("failed to setsockopt DONTROUTE: {}", .{err});
                return error.NicError;
            },
        }
        var ifr = std.mem.zeroes(std.os.linux.ifreq);
        @memcpy(ifr.ifrn.name[0..ifname.len], ifname);
        ifr.ifrn.name[ifname.len] = 0;
        switch (std.os.linux.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCGIFINDEX, @intFromPtr(&ifr)))) {
            .SUCCESS => {},
            else => |err| {
                logger.err("failed to find interface with name: {s}, error: {}", .{ ifname, err });
                return error.NicError;
            },
        }
        const ifindex: i32 = ifr.ifru.ivalue;

        var rval = std.os.linux.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.os.linux.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCSIFFLAGS, @intFromPtr(&ifr)));
        switch (rval) {
            .SUCCESS => {},
            else => {
                return error.NicError;
            },
        }
        const sockaddr_ll = std.os.linux.sockaddr.ll{
            .family = std.os.linux.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
        };
        switch (std.os.linux.errno(std.os.linux.bind(socket, @ptrCast(&sockaddr_ll), @sizeOf(@TypeOf(sockaddr_ll))))) {
            .SUCCESS => {},
            else => |err| {
                logger.err("failed to bind to interface: {}", .{err});
                return error.NicError;
            },
        }
        return LinuxRawSocket{
            .socket = socket,
        };
    }

    pub fn deinit(self: *LinuxRawSocket) void {
        switch (std.os.linux.errno(std.os.linux.close(self.socket))) {
            .BADF => unreachable, // Always a race condition.
            .INTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425
            else => return,
        }
    }

    pub fn send(ctx: *anyopaque, io: std.Io, bytes: []const u8) error{NicError}!void {
        const self: *LinuxRawSocket = @ptrCast(@alignCast(ctx));
        self.send_mutex.lockUncancelable(io);
        defer self.send_mutex.unlock(io);
        switch (std.os.linux.errno(std.os.linux.sendto(self.socket, bytes.ptr, bytes.len, 0, null, 0))) {
            .SUCCESS => {},
            else => |err| {
                logger.err("failed to send packet: {}", .{err});
                return error.NicError;
            },
        }
        // TODO: look at return length?
    }

    pub fn recv(ctx: *anyopaque, io: std.Io, out: []u8) error{NicError}!usize {
        const self: *LinuxRawSocket = @ptrCast(@alignCast(ctx));
        self.recv_mutex.lockUncancelable(io);
        defer self.recv_mutex.unlock(io);

        while (true) {
            const rc = std.os.linux.recvfrom(self.socket, out.ptr, out.len, std.os.linux.MSG.TRUNC, null, null);
            switch (std.os.linux.errno(rc)) {
                .SUCCESS => return @intCast(rc),
                .INTR => continue,
                else => |err| {
                    logger.err("raw socket recv error: {}", .{err});
                    return error.NicError;
                },
            }
        }
    }

    pub fn linkLayer(self: *LinuxRawSocket) LinkLayer {
        return LinkLayer{
            .ptr = self,
            .vtable = &.{ .send = send, .recv = recv },
        };
    }
};
2 Likes