const RawSocket = @This(); fd: i32, sockaddr_ll: std.posix.sockaddr.ll, mac: [6]u8, const Ifconf = extern struct { ifc_len: i32, ifc_ifcu: extern union { ifcu_buf: ?[*]u8, ifcu_req: ?[*]std.os.linux.ifreq, }, }; pub fn init() !RawSocket { const socket: i32 = @intCast(std.os.linux.socket(std.os.linux.AF.PACKET, std.os.linux.SOCK.RAW, 0)); if (socket < 0) return error.SocketError; var ifreq_storage: [16]std.os.linux.ifreq = undefined; var ifc = Ifconf{ .ifc_len = @sizeOf(@TypeOf(ifreq_storage)), .ifc_ifcu = .{ .ifcu_req = &ifreq_storage }, }; if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFCONF, @intFromPtr(&ifc)) != 0) { return error.NicError; } const count = @divExact(ifc.ifc_len, @sizeOf(std.os.linux.ifreq)); // Get the first non loopback interface var ifr = for (ifreq_storage[0..@intCast(count)]) |*ifr| { if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFFLAGS, @intFromPtr(ifr)) == 0) { if (ifr.ifru.flags.LOOPBACK) continue; break ifr; } } else return error.NoInterfaceFound; // 2. Get Interface Index if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFINDEX, @intFromPtr(ifr)) != 0) { return error.NicError; } const ifindex: i32 = ifr.ifru.ivalue; // 3. Get Real MAC Address if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFHWADDR, @intFromPtr(ifr)) != 0) { return error.NicError; } var mac: [6]u8 = ifr.ifru.hwaddr.data[0..6].*; if (builtin.cpu.arch.endian() == .little) std.mem.reverse(u8, &mac); // 4. Set Flags (Promiscuous/Broadcast) if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFFLAGS, @intFromPtr(ifr)) != 0) { return error.NicError; } ifr.ifru.flags.BROADCAST = true; ifr.ifru.flags.PROMISC = true; if (std.os.linux.ioctl(socket, std.os.linux.SIOCSIFFLAGS, @intFromPtr(ifr)) != 0) { return error.NicError; } const sockaddr_ll = std.mem.zeroInit(std.posix.sockaddr.ll, .{ .family = std.posix.AF.PACKET, .ifindex = ifindex, .protocol = std.mem.nativeToBig(u16, @as(u16, std.os.linux.ETH.P.IP)), }); const bind_ret = std.os.linux.bind(socket, @ptrCast(&sockaddr_ll), @sizeOf(@TypeOf(sockaddr_ll))); if (bind_ret != 0) return error.BindError; const timeout: std.os.linux.timeval = .{ .sec = 600, .usec = 0 }; const timeout_ret = std.os.linux.setsockopt(socket, std.os.linux.SOL.SOCKET, std.os.linux.SO.RCVTIMEO, @ptrCast(&timeout), @sizeOf(@TypeOf(timeout))); if (timeout_ret != 0) return error.SetTimeoutError; return .{ .fd = socket, .sockaddr_ll = sockaddr_ll, .mac = mac, }; } pub fn deinit(self: *RawSocket) void { _ = std.os.linux.close(self.fd); self.* = undefined; } pub fn send(self: RawSocket, payload: []const u8) !void { const sent_bytes = std.os.linux.sendto( self.fd, payload.ptr, payload.len, 0, @ptrCast(&self.sockaddr_ll), @sizeOf(@TypeOf(self.sockaddr_ll)), ); _ = sent_bytes; } pub fn receive(self: RawSocket, buf: []u8) ![]u8 { const len = std.os.linux.recvfrom( self.fd, buf.ptr, buf.len, 0, null, null, ); if (std.os.linux.errno(len) != .SUCCESS) { return error.Timeout; // TODO: get the real error, assume timeout for now. } return buf[0..len]; } pub fn attachSaprusPortFilter(self: RawSocket, port: u16) !void { const BPF = std.os.linux.BPF; // BPF instruction structure for classic BPF const SockFilter = extern struct { code: u16, jt: u8, jf: u8, k: u32, }; const SockFprog = extern struct { len: u16, filter: [*]const SockFilter, }; // Build the filter program const filter = [_]SockFilter{ // Load 2 bytes at offset 46 (absolute) .{ .code = BPF.LD | BPF.H | BPF.ABS, .jt = 0, .jf = 0, .k = 46 }, // Jump if equal to port (skip 0 if true, skip 1 if false) .{ .code = BPF.JMP | BPF.JEQ | BPF.K, .jt = 0, .jf = 1, .k = @as(u32, port) }, // Return 0xffff (pass) .{ .code = BPF.RET | BPF.K, .jt = 0, .jf = 0, .k = 0xffff }, // Return 0x0 (fail) .{ .code = BPF.RET | BPF.K, .jt = 0, .jf = 0, .k = 0x0 }, }; const fprog = SockFprog{ .len = filter.len, .filter = &filter, }; // Attach filter to socket using setsockopt const rc = std.os.linux.setsockopt( self.fd, std.os.linux.SOL.SOCKET, std.os.linux.SO.ATTACH_FILTER, @ptrCast(&fprog), @sizeOf(SockFprog), ); if (rc != 0) { return error.BpfAttachFailed; } } const std = @import("std"); const builtin = @import("builtin");