mirror of
https://git.robbyzambito.me/zaprus
synced 2026-05-06 14:20:37 +00:00
This is more intuitive than using u32 directly, and follows the same pattern as the new MAC addr handling
183 lines
6.1 KiB
Zig
183 lines
6.1 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 = .fromBytes(self.socket.mac),
|
|
.ip = .{
|
|
.id = rand.int(u16),
|
|
.src_addr = .fromBytes(.{ 0, 0, 0, 0 }), //rand.int(u32),
|
|
.dst_addr = .fromBytes(.{ 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 = .fromBytes(self.socket.mac),
|
|
.ip = .{
|
|
.id = rand.int(u16),
|
|
.src_addr = .fromBytes(.{ 0, 0, 0, 0 }), //rand.int(u32),
|
|
.dst_addr = .fromBytes(.{ 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;
|