mirror of
https://git.robbyzambito.me/zaprus
synced 2026-02-04 00:14:52 +00:00
302 lines
9.4 KiB
Zig
302 lines
9.4 KiB
Zig
const is_debug = builtin.mode == .Debug;
|
|
|
|
const help =
|
|
\\-h, --help Display this help and exit.
|
|
\\-r, --relay <str> A relay message to send.
|
|
\\-d, --dest <str> An IPv4 or <= 4 ASCII byte string.
|
|
\\-c, --connect <str> 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 "<null>"});
|
|
std.debug.print("dest: {s}\n", .{flags.dest orelse "<null>"});
|
|
std.debug.print("connect: {s}\n", .{flags.connect orelse "<null>"});
|
|
|
|
// 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});
|
|
|
|
var socket: RawSocket = try .init("enp7s0");
|
|
defer socket.deinit();
|
|
try socket.send(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 "disc".*;
|
|
}
|
|
|
|
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(self: *RawSocket) void {
|
|
_ = self;
|
|
}
|
|
|
|
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;
|