diff options
Diffstat (limited to 'src/RawSocket.zig')
| -rw-r--r-- | src/RawSocket.zig | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/src/RawSocket.zig b/src/RawSocket.zig new file mode 100644 index 0000000..9561dcf --- /dev/null +++ b/src/RawSocket.zig @@ -0,0 +1,203 @@ +// Copyright 2026 Robby Zambito +// +// This file is part of zaprus. +// +// Zaprus is free software: you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later +// version. +// +// Zaprus is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +// A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with +// Zaprus. If not, see <https://www.gnu.org/licenses/>. + +const RawSocket = @This(); + +const is_debug = builtin.mode == .Debug; + +fd: i32, +sockaddr_ll: std.posix.sockaddr.ll, +mac: [6]u8, + +pub const max_payload_len = 1000; + +const Ifconf = extern struct { + ifc_len: i32, + ifc_ifcu: extern union { + ifcu_buf: ?[*]u8, + ifcu_req: ?[*]std.os.linux.ifreq, + }, +}; + +pub fn init() error{ + SocketError, + NicError, + NoInterfaceFound, + BindError, +}!RawSocket { + const socket: i32 = std.math.cast(i32, std.os.linux.socket(std.os.linux.AF.PACKET, std.os.linux.SOCK.RAW, 0)) orelse return error.SocketError; + 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; + + return .{ + .fd = socket, + .sockaddr_ll = sockaddr_ll, + .mac = mac, + }; +} + +pub fn setTimeout(self: *RawSocket, sec: isize, usec: i64) !void { + const timeout: std.os.linux.timeval = .{ .sec = sec, .usec = usec }; + const timeout_ret = std.os.linux.setsockopt(self.fd, std.os.linux.SOL.SOCKET, std.os.linux.SO.RCVTIMEO, @ptrCast(&timeout), @sizeOf(@TypeOf(timeout))); + if (timeout_ret != 0) return error.SetTimeoutError; +} + +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, incoming_src_port: ?u16, incoming_dest_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 = if (incoming_src_port) |inc_src| &[_]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 1 if true, skip 0 if false) + .{ .code = BPF.JMP | BPF.JEQ | BPF.K, .jt = 1, .jf = 0, .k = @as(u32, inc_src) }, + // Return 0x0 (fail) + .{ .code = BPF.RET | BPF.K, .jt = 0, .jf = 0, .k = 0x0 }, + // Load 2 bytes at offset 48 (absolute) + .{ .code = BPF.LD | BPF.H | BPF.ABS, .jt = 0, .jf = 0, .k = 48 }, + // 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, incoming_dest_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 }, + } else &[_]SockFilter{ + // Load 2 bytes at offset 48 (absolute) + .{ .code = BPF.LD | BPF.H | BPF.ABS, .jt = 0, .jf = 0, .k = 48 }, + // 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, incoming_dest_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 = @intCast(filter.len), + .filter = filter.ptr, + }; + + // 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"); |
