aboutsummaryrefslogtreecommitdiff
path: root/src/RawSocket.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/RawSocket.zig')
-rw-r--r--src/RawSocket.zig203
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");