const is_debug = builtin.mode == .Debug; const help = \\-h, --help Display this help and exit. \\-r, --relay A relay message to send. \\-d, --dest An IPv4 or <= 4 ASCII byte string. \\-c, --connect A connection message to send. \\ ; const Option = enum { help, relay, dest, connect }; const to_option: StaticStringMap(Option) = .initComptime(.{ .{ "-h", .help }, .{ "--help", .help }, .{ "-r", .relay }, .{ "--relay", .relay }, .{ "-d", .dest }, .{ "--dest", .dest }, .{ "-c", .connect }, .{ "--connect", .connect }, }); pub fn main(init: std.process.Init) !void { // CLI parsing adapted from the example here // https://codeberg.org/ziglang/zig/pulls/30644 const args = try init.minimal.args.toSlice(init.arena.allocator()); if (args.len == 1) { std.debug.print("{s}", .{help}); return; } var flags: struct { relay: ?[]const u8 = null, dest: ?[]const u8 = null, connect: ?[]const u8 = null, } = .{}; { var i: usize = 1; while (i < args.len) : (i += 1) { if (to_option.get(args[i])) |opt| { switch (opt) { .help => { std.debug.print("{s}\n", .{help}); return; }, .relay => { i += 1; if (i < args.len) { flags.relay = args[i]; } else { std.debug.print("-r/--relay requires a string\n", .{}); return error.InvalidArguments; } }, .dest => { i += 1; if (i < args.len) { flags.dest = args[i]; } else { std.debug.print("-d/--dest requires a string\n", .{}); return error.InvalidArguments; } }, .connect => { i += 1; if (i < args.len) { flags.connect = args[i]; } else { std.debug.print("-c/--connect requires a string\n", .{}); return error.InvalidArguments; } }, } } else { std.debug.print("Unknown argument: {s}\n", .{args[i]}); return error.InvalidArguments; } } } if (flags.connect != null and (flags.relay != null or flags.dest != null)) { std.debug.print("Incompatible arguments.\nCannot use --connect/-c with dest or relay.\n", .{}); return error.InvalidArguments; } std.debug.print("relay: {s}\n", .{flags.relay orelse ""}); std.debug.print("dest: {s}\n", .{flags.dest orelse ""}); std.debug.print("connect: {s}\n", .{flags.connect orelse ""}); // const rand = blk: { // const io_source: std.Random.IoSource = .{ .io = init.io }; // break :blk io_source.interface(); // }; // const net_interface: std.Io.net.Interface = .{ .index = 1 }; // std.debug.print("Interface: {s}\n", .{(try net_interface.name(init.io)).toSlice()}); const EthIpUdp = packed struct(u336) { // 42 bytes * 8 bits = 336 // --- UDP (Last in memory, defined first for LSB->MSB) --- udp: packed struct { checksum: u16 = 0, len: u16, dst_port: u16, src_port: u16, }, // --- IP --- ip: packed struct { dst_addr: u32, src_addr: u32, header_checksum: u16 = 0, protocol: u8 = 17, // udp ttl: u8 = 0x40, // fragment_offset (13 bits) + flags (3 bits) = 16 bits // In Big Endian, flags are the high bits of the first byte. // To have flags appear first in the stream, define them last here. fragment_offset: u13 = 0, flags: packed struct(u3) { reserved: u1 = 0, dont_fragment: u1 = 0, more_fragments: u1 = 0, } = .{}, id: u16, len: u16, tos: u8 = undefined, // ip_version (4 bits) + ihl (4 bits) = 8 bits // To have version appear first (high nibble), define it last. ihl: u4 = 5, ip_version: u4 = 4, }, // --- Ethernet --- eth_type: u16 = std.os.linux.ETH.P.IP, src_mac: @Vector(6, u8), dst_mac: @Vector(6, u8) = @splat(0xff), fn toBytes(self: @This()) [336 / 8]u8 { var res: [336 / 8]u8 = undefined; var w: Writer = .fixed(&res); w.writeStruct(self, .big) catch unreachable; return res; } fn setPayloadLen(self: *@This(), len: usize) void { self.ip.len = @intCast(len + @sizeOf(@TypeOf(self.udp)) + @sizeOf(@TypeOf(self.ip))); self.udp.len = @intCast(len + @sizeOf(@TypeOf(self.udp))); } }; var headers: EthIpUdp = .{ .src_mac = @splat(0x0e), .ip = .{ .id = 0, .src_addr = 0, .dst_addr = @bitCast([_]u8{ 255, 255, 255, 255 }), .len = undefined, }, .udp = .{ .src_port = undefined, // TODO: change this? .dst_port = 8888, .len = undefined, }, }; std.debug.print("headers: {any}\n", .{&headers.toBytes()}); const relay: SaprusMessage = .{ .relay = .{ .dest = .fromBytes(&parseDest(flags.dest)), .payload = flags.relay.?, }, }; var relay_buf: [2048]u8 = undefined; const relay_bytes = relay.toBytes(&relay_buf); std.debug.print("payload: {any}\n", .{relay_bytes}); headers.setPayloadLen(relay_bytes.len); const full_msg = blk: { var msg_buf: [2048]u8 = undefined; var msg_w: Writer = .fixed(&msg_buf); msg_w.writeAll(&headers.toBytes()) catch unreachable; msg_w.writeAll(relay_bytes) catch unreachable; break :blk msg_w.buffered(); }; std.debug.print("full message = {any}\n", .{full_msg}); } fn parseDest(in: ?[]const u8) [4]u8 { if (in) |dest| { if (dest.len <= 4) { var res: [4]u8 = @splat(0); @memcpy(res[0..dest.len], dest); return res; } const addr = std.Io.net.Ip4Address.parse(dest, 0) catch return "FAIL".*; return addr.bytes; } return "zap\x00".*; } const builtin = @import("builtin"); const std = @import("std"); const ArrayList = std.ArrayList; const StaticStringMap = std.StaticStringMap; const zaprus = @import("zaprus"); const SaprusClient = zaprus.Client; const SaprusMessage = zaprus.Message; 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, fn init(ifname: []const u8) RawSocket { const socket: i32 = @intCast(std.os.linux.socket(AF.PACKET, SOCK.RAW, 0)); var ifr: std.posix.ifreq = std.mem.zeroInit(std.posix.ifreq, .{}); @memcpy(ifr.ifrn.name[0..ifname.len], ifname); ifr.ifrn.name[ifname.len] = 0; try std.posix.ioctl_SIOCGIFINDEX(socket, &ifr); const ifindex: i32 = ifr.ifru.ivalue; var rval = std.posix.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCGIFFLAGS, @intFromPtr(&ifr))); switch (rval) { .SUCCESS => {}, else => { return error.NicError; }, } ifr.ifru.flags.BROADCAST = true; ifr.ifru.flags.PROMISC = true; rval = std.posix.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCSIFFLAGS, @intFromPtr(&ifr))); switch (rval) { .SUCCESS => {}, else => { return error.NicError; }, } std.debug.print("ifindex: {}\n", .{ifindex}); 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, //not used .addr = .{ 0, 0, 0, 0, 0, 0, 0, 0 }, //not used .pkttype = 0, //not used .hatype = 0, //not used }; _ = std.os.linux.bind(socket, @ptrCast(&sockaddr_ll), @sizeOf(@TypeOf(sockaddr_ll))); return .{ .fd = socket, .sockaddr_ll = sockaddr_ll, }; } fn deinit() void {} 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)), ); std.debug.assert(sent_bytes == payload.len); } fn receive(self: RawSocket, buf: []u8) ![]u8 { const len = std.os.linux.recvfrom( self.fd, buf.ptr, buf.len, 0, // flags null, null, ); return buf[0..len]; } }; const Writer = std.Io.Writer;