From 067a11ab232e03ad8d368beccb58b1e64ec96829 Mon Sep 17 00:00:00 2001 From: Robby Zambito Date: Tue, 20 Jan 2026 22:41:53 -0500 Subject: Move RawSocket and clean it up --- src/RawSocket.zig | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 src/RawSocket.zig (limited to 'src/RawSocket.zig') diff --git a/src/RawSocket.zig b/src/RawSocket.zig new file mode 100644 index 0000000..b559ea5 --- /dev/null +++ b/src/RawSocket.zig @@ -0,0 +1,161 @@ +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"); -- cgit