Files
zaprus/src/Client.zig
Robby Zambito 7077aae9ce fix: convert MacAddr from vector to int
Still expose a vector / slice API with .fromSlice,
2026-04-16 04:56:23 +03:00

183 lines
6.0 KiB
Zig

// 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/>.
//! A client is used to handle interactions with the network.
const base64_enc = std.base64.standard.Encoder;
const base64_dec = std.base64.standard.Decoder;
const Client = @This();
const max_message_size = 2048;
pub const max_payload_len = RawSocket.max_payload_len;
socket: RawSocket,
pub fn init() !Client {
const socket: RawSocket = try .init();
return .{
.socket = socket,
};
}
pub fn deinit(self: *Client) void {
self.socket.deinit();
self.* = undefined;
}
/// Sends a fire and forget message over the network.
/// This function asserts that `payload` fits within a single packet.
pub fn sendRelay(self: *Client, io: Io, payload: []const u8, dest: [4]u8) !void {
const io_source: std.Random.IoSource = .{ .io = io };
const rand = io_source.interface();
var headers: EthIpUdp = .{
.src_mac = .fromSlice(self.socket.mac),
.ip = .{
.id = rand.int(u16),
.src_addr = 0, //rand.int(u32),
.dst_addr = @bitCast([_]u8{ 255, 255, 255, 255 }),
.len = undefined,
},
.udp = .{
.src_port = rand.intRangeAtMost(u16, 1025, std.math.maxInt(u16)),
.dst_port = 8888,
.len = undefined,
},
};
const relay: SaprusMessage = .{
.relay = .{
.dest = .fromBytes(&dest),
.payload = payload,
},
};
var relay_buf: [max_message_size - (@bitSizeOf(EthIpUdp) / 8)]u8 = undefined;
const relay_bytes = relay.toBytes(&relay_buf);
headers.setPayloadLen(relay_bytes.len);
var msg_buf: [max_message_size]u8 = undefined;
var msg_w: Writer = .fixed(&msg_buf);
msg_w.writeAll(&headers.toBytes()) catch unreachable;
msg_w.writeAll(relay_bytes) catch unreachable;
const full_msg = msg_w.buffered();
try self.socket.send(full_msg);
}
/// Attempts to establish a new connection with the sentinel.
pub fn connect(self: Client, io: Io, payload: []const u8) (error{ BpfAttachFailed, Timeout } || SaprusMessage.ParseError)!SaprusConnection {
const io_source: std.Random.IoSource = .{ .io = io };
const rand = io_source.interface();
var headers: EthIpUdp = .{
.src_mac = .fromSlice(self.socket.mac),
.ip = .{
.id = rand.int(u16),
.src_addr = 0, //rand.int(u32),
.dst_addr = @bitCast([_]u8{ 255, 255, 255, 255 }),
.len = undefined,
},
.udp = .{
.src_port = rand.intRangeAtMost(u16, 1025, std.math.maxInt(u16)),
.dst_port = 8888,
.len = undefined,
},
};
// udp dest port should not be 8888 after first
const udp_dest_port = rand.intRangeAtMost(u16, 9000, std.math.maxInt(u16));
var connection: SaprusMessage = .{
.connection = .{
.src = rand.intRangeAtMost(u16, 1025, std.math.maxInt(u16)),
.dest = rand.intRangeAtMost(u16, 1025, std.math.maxInt(u16)), // Ignored, but good noise
.seq = undefined,
.id = undefined,
.payload = payload,
},
};
log.debug("Setting bpf filter to port {}", .{connection.connection.src});
self.socket.attachSaprusPortFilter(null, connection.connection.src) catch |err| {
log.err("Failed to set port filter: {t}", .{err});
return err;
};
log.debug("bpf set", .{});
var connection_buf: [2048]u8 = undefined;
var connection_bytes = connection.toBytes(&connection_buf);
headers.setPayloadLen(connection_bytes.len);
log.debug("Building full message", .{});
var msg_buf: [2048]u8 = undefined;
var msg_w: Writer = .fixed(&msg_buf);
msg_w.writeAll(&headers.toBytes()) catch unreachable;
msg_w.writeAll(connection_bytes) catch unreachable;
var full_msg = msg_w.buffered();
log.debug("Built full message. Sending message", .{});
try self.socket.send(full_msg);
var res_buf: [4096]u8 = undefined;
log.debug("Awaiting handshake response", .{});
// Ignore response from sentinel, just accept that we got one.
const full_handshake_res = try self.socket.receive(&res_buf);
const handshake_res = saprusParse(full_handshake_res[42..]) catch |err| {
log.err("Parse error: {t}", .{err});
return err;
};
self.socket.attachSaprusPortFilter(handshake_res.connection.src, handshake_res.connection.dest) catch |err| {
log.err("Failed to set port filter: {t}", .{err});
return err;
};
connection.connection.dest = handshake_res.connection.src;
connection_bytes = connection.toBytes(&connection_buf);
headers.udp.dst_port = udp_dest_port;
headers.ip.id = rand.int(u16);
headers.setPayloadLen(connection_bytes.len);
log.debug("Building final handshake message", .{});
msg_w.end = 0;
msg_w.writeAll(&headers.toBytes()) catch unreachable;
msg_w.writeAll(connection_bytes) catch unreachable;
full_msg = msg_w.buffered();
try self.socket.send(full_msg);
return .{
.socket = self.socket,
.headers = headers,
.connection = connection,
};
}
const RawSocket = @import("./RawSocket.zig");
const SaprusMessage = @import("message.zig").Message;
const saprusParse = SaprusMessage.parse;
const SaprusConnection = @import("Connection.zig");
const EthIpUdp = @import("./EthIpUdp.zig").EthIpUdp;
const std = @import("std");
const Io = std.Io;
const Writer = std.Io.Writer;
const log = std.log;