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 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 185 +----------------------------------------------------- 2 files changed, 164 insertions(+), 182 deletions(-) create mode 100644 src/RawSocket.zig (limited to 'src') 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"); diff --git a/src/main.zig b/src/main.zig index 2175cf9..3b7ed80 100644 --- a/src/main.zig +++ b/src/main.zig @@ -229,6 +229,7 @@ pub fn main(init: std.process.Init) !void { var res_buf: [4096]u8 = undefined; var res = socket.receive(&res_buf) catch continue; + try init.io.sleep(.fromMilliseconds(40), .real); headers.udp.dst_port = udp_dest_port; headers.ip.id = rand.int(u16); @@ -244,6 +245,7 @@ pub fn main(init: std.process.Init) !void { while (true) { res = socket.receive(&res_buf) catch continue :reconnect; + try init.io.sleep(.fromMilliseconds(40), .real); const connection_res = blk: { const msg: SaprusMessage = try .parse(res[42..]); break :blk msg.connection; @@ -320,187 +322,6 @@ const RawSocketWriter = zaprus.RawSocketWriter; const AF = std.os.linux.AF; const SOCK = std.os.linux.SOCK; -const RawSocket = struct { - fd: i32, - sockaddr_ll: std.posix.sockaddr.ll, - mac: [6]u8, - - const IFF_LOOPBACK = 0x8; - - const ifconf = extern struct { - ifc_len: i32, - ifc_ifcu: extern union { - ifcu_buf: ?[*]u8, - ifcu_req: ?[*]std.os.linux.ifreq, - }, - }; - - fn init() !RawSocket { - const socket: i32 = @intCast(std.os.linux.socket(std.posix.AF.PACKET, std.posix.SOCK.RAW, 0)); - if (socket < 0) return error.SocketError; - - // 1. Enumerate interfaces - 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)); - var target_ifr: ?std.os.linux.ifreq = null; - - for (ifreq_storage[0..@intCast(count)]) |ifr| { - var temp_ifr = ifr; - if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFFLAGS, @intFromPtr(&temp_ifr)) == 0) { - // Cast the packed flags to u16 to match the kernel's ifr_flags size - const flags: u16 = @bitCast(temp_ifr.ifru.flags); - if (flags & IFF_LOOPBACK != 0) continue; - - target_ifr = ifr; - break; - } - } - - var ifr = target_ifr orelse 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].*; - 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.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, - .addr = .{ 0, 0, 0, 0, 0, 0, 0, 0 }, - .pkttype = 0, - .hatype = 0, - }; - - 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, - }; - } - - fn deinit(self: *RawSocket) void { - _ = std.os.linux.close(self.fd); - self.* = undefined; - } - - 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; - } - - 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]; - } - - fn attachSaprusPortFilter(self: RawSocket, port: u16) !void { - const linux = std.os.linux; - // BPF instruction structure for classic BPF - const sock_filter = extern struct { - code: u16, - jt: u8, - jf: u8, - k: u32, - }; - - const sock_fprog = extern struct { - len: u16, - filter: [*]const sock_filter, - }; - - // BPF instruction opcodes - const BPF_LD = 0x00; - const BPF_H = 0x08; // Half-word (2 bytes) - const BPF_ABS = 0x20; - const BPF_JMP = 0x05; - const BPF_JEQ = 0x10; - const BPF_K = 0x00; - const BPF_RET = 0x06; - - // Build the filter program - const filter = [_]sock_filter{ - // 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 = sock_fprog{ - .len = filter.len, - .filter = &filter, - }; - - // Attach filter to socket using setsockopt - const SO_ATTACH_FILTER = 26; - const rc = linux.setsockopt( - self.fd, - linux.SOL.SOCKET, - SO_ATTACH_FILTER, - @ptrCast(&fprog), - @sizeOf(sock_fprog), - ); - - if (rc != 0) { - return error.BpfAttachFailed; - } - } -}; +const RawSocket = @import("./RawSocket.zig"); const Writer = std.Io.Writer; -- cgit