24 Commits

Author SHA1 Message Date
56b6b8a386 Use Client as var type instead of singleton 2025-05-11 13:52:42 -04:00
14ed0bc3f3 Fix issue returning stack pointer 2025-05-11 13:40:55 -04:00
c72503fce6 Fix extra bytes in connection message. 2025-05-11 13:40:23 -04:00
373dbebc8c Add broadcast initial interest using raw sockets
Use this from the relay message
2025-05-11 11:40:15 -04:00
cde289d648 Update gatorcat dep and use bytes for broadcast message
The latter is helpful for the lifetime of the message.
2025-05-11 10:12:26 -04:00
716fb466fa Remove allocation for messages 2025-05-10 21:46:53 -04:00
583f9d8b8f Add comments and fix tests
Also added networkBytesAsValue and restored bytesAsValue.
These are useful for treating the bytes from the network directly as a Message.
Otherwise, the init function would overwrite the packet type and length to be correct.
I would like the message handling to fail if the message body is incorrect.
2025-05-10 21:46:53 -04:00
56e72928c6 fix use after free 2025-05-10 21:46:53 -04:00
a80c9abfe7 Attempt to base64 encode the connection payload
For some reason I am still getting this:

2025/05/10 16:37:06 Error decoding message: SGVsbG8gZGFya25lc3MgbXkgb2xkIGZyaWVuZA==::53475673624738675a4746796132356c63334d6762586b676232786b49475a79615756755a413d3daaaa
2025-05-10 21:46:53 -04:00
245dab4909 Use slice for init, and add better error sets.
The slice sets us avoid allocating within the init function.
This means init can't fail, and it also makes it easier to stack allocate messages (slice an array buffer, instead of creating a stack allocator).
2025-05-10 21:46:53 -04:00
cde5c3626c 2025-05-10 21:46:53 -04:00
e84d1a2300 2025-05-10 21:46:53 -04:00
1b7d9bbb1a Remove bytesAsValueUnchecked
Callers can instead use std.mem.bytesAsValue directly.
2025-05-10 21:46:53 -04:00
1512ec1a86 Cleanup asBytes and test it 2025-05-10 21:46:53 -04:00
f1dce257be Simplify init interface 2025-05-10 21:46:53 -04:00
bcab1e4d00 2025-05-10 21:46:53 -04:00
0e8f016978 Align the bytes instead of the struct 2025-05-10 21:46:53 -04:00
fc53e87389 2025-05-10 21:46:53 -04:00
cbf554e853 2025-05-10 21:46:53 -04:00
775212013f 2025-05-10 21:46:53 -04:00
339ac5cfe5 2025-05-10 21:46:53 -04:00
eacfaffb6b 2025-05-10 21:46:53 -04:00
1731b2e643 2025-05-10 21:46:53 -04:00
dae66a0039 Starting real connections 2025-05-10 21:46:53 -04:00
4 changed files with 227 additions and 120 deletions

View File

@@ -45,8 +45,8 @@
.hash = "clap-0.10.0-oBajB434AQBDh-Ei3YtoKIRxZacVPF1iSwp3IX_ZB8f0",
},
.gatorcat = .{
.url = "git+https://github.com/kj4tmp/gatorcat#bb1847f6c95852e7a0ec8c07870a948c171d5f98",
.hash = "gatorcat-0.3.2-WcrpTf1mBwDrmPaIhKCfLJO064v8Sjjn7DBq4CKZSgHH",
.url = "git+https://github.com/kj4tmp/gatorcat.git#0a97b666677501db4939e3e8245f88a19e015893",
.hash = "gatorcat-0.3.4-WcrpTcleBwCta_9TjomuIGb3bdg2Pke_FXI_WkMTEivH",
},
},
.paths = .{

View File

@@ -1,28 +1,86 @@
const base64Enc = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, '=');
const base64Dec = std.base64.Base64Decoder.init(std.base64.standard_alphabet_chars, '=');
var rand: ?Random = null;
rand: Random,
socket: gcat.nic.RawSocket,
pub fn init() !void {
const Self = @This();
const max_message_size = 2048;
pub fn init(interface_name: [:0]const u8) !Self {
var prng = Random.DefaultPrng.init(blk: {
var seed: u64 = undefined;
try posix.getrandom(mem.asBytes(&seed));
break :blk seed;
});
rand = prng.random();
try network.init();
const rand = prng.random();
const socket: gcat.nic.RawSocket = try .init(interface_name);
return .{
.rand = rand,
.socket = socket,
};
}
pub fn deinit() void {
network.deinit();
pub fn deinit(self: *Self) void {
self.socket.deinit();
}
fn broadcastSaprusMessage(msg: *SaprusMessage, udp_port: u16) !void {
/// Used for relay messages and connection handshake.
/// Assumes Client .init has been called.
fn broadcastInitialInterestMessage(self: *Self, msg_bytes: []align(@alignOf(SaprusMessage)) u8) !void {
var packet_bytes: [max_message_size]u8 = comptime blk: {
var b: [max_message_size]u8 = @splat(0);
// Destination MAC addr to FF:FF:FF:FF:FF:FF
for (0..6) |i| {
b[i] = 0xff;
}
// Set Ethernet type to IPv4
b[0x0c] = 0x08;
b[0x0d] = 0x00;
// Set IPv4 version to 4
b[0x0e] = 0x45;
// Destination broadcast
for (0x1e..0x22) |i| {
b[i] = 0xff;
}
// Set TTL
b[0x16] = 0x40;
// Set IPv4 protocol to UDP
b[0x17] = 0x11;
// Set interest filter value to 8888.
b[0x24] = 0x22;
b[0x25] = 0xb8;
break :blk b;
};
var msg: *SaprusMessage = try .bytesAsValue(msg_bytes);
try msg.networkFromNativeEndian();
defer msg.nativeFromNetworkEndian() catch unreachable;
// The byte position within the packet that the saprus message starts at.
const saprus_start_byte = 42;
@memcpy(packet_bytes[saprus_start_byte .. saprus_start_byte + msg_bytes.len], msg_bytes);
try self.socket.linkLayer().send(packet_bytes[0 .. saprus_start_byte + msg_bytes.len]);
}
// fn broadcastSaprusMessage(msg_bytes: []align(@alignOf(SaprusMessage)) u8) !void {}
fn broadcastSaprusMessage(msg_bytes: []align(@alignOf(SaprusMessage)) u8, udp_port: u16) !void {
if (false) {
var foo: gcat.nic.RawSocket = try .init("enp7s0"); // /proc/net/dev
defer foo.deinit();
}
const msg_bytes = msg.asBytes();
const msg: *SaprusMessage = try .bytesAsValue(msg_bytes);
try msg.networkFromNativeEndian();
defer msg.nativeFromNetworkEndian() catch unreachable;
@@ -44,48 +102,41 @@ fn broadcastSaprusMessage(msg: *SaprusMessage, udp_port: u16) !void {
try sock.bind(bind_addr);
std.debug.print("{x}\n", .{msg_bytes});
_ = try sock.sendTo(dest_addr, msg_bytes);
}
pub fn sendRelay(payload: []const u8, dest: [4]u8, allocator: Allocator) !void {
const msg_bytes = try allocator.alignedAlloc(
u8,
@alignOf(SaprusMessage),
try SaprusMessage.lengthForPayloadLength(
.relay,
base64Enc.calcSize(payload.len),
),
);
defer allocator.free(msg_bytes);
pub fn sendRelay(self: *Self, payload: []const u8, dest: [4]u8) !void {
var buf: [max_message_size]u8 align(@alignOf(SaprusMessage)) = undefined;
const msg_bytes = buf[0..try SaprusMessage.calcSize(
.relay,
base64Enc.calcSize(payload.len),
)];
const msg: *SaprusMessage = .init(.relay, msg_bytes);
const relay = (try msg.getSaprusTypePayload()).relay;
relay.dest = dest;
_ = base64Enc.encode(relay.getPayload(), payload);
try broadcastSaprusMessage(msg, 8888);
try self.broadcastInitialInterestMessage(msg_bytes);
}
fn randomPort() u16 {
var p: u16 = 0;
if (rand) |r| {
p = r.intRangeAtMost(u16, 1024, 65000);
} else unreachable;
return p;
fn randomPort(self: Self) u16 {
return self.rand.intRangeAtMost(u16, 1024, 65000);
}
pub fn sendInitialConnection(payload: []const u8, initial_port: u16, allocator: Allocator) !*SaprusMessage {
const dest_port = randomPort();
const msg_bytes = try allocator.alignedAlloc(
u8,
@alignOf(SaprusMessage),
try SaprusMessage.lengthForPayloadLength(
.connection,
base64Enc.calcSize(payload.len),
),
);
pub fn sendInitialConnection(
self: Self,
payload: []const u8,
output_bytes: []align(@alignOf(SaprusMessage)) u8,
initial_port: u16,
) !*SaprusMessage {
const dest_port = self.randomPort();
const msg_bytes = output_bytes[0..try SaprusMessage.calcSize(
.connection,
base64Enc.calcSize(payload.len),
)];
const msg: *SaprusMessage = .init(.connection, msg_bytes);
const connection = (try msg.getSaprusTypePayload()).connection;
@@ -93,16 +144,13 @@ pub fn sendInitialConnection(payload: []const u8, initial_port: u16, allocator:
connection.dest_port = dest_port;
_ = base64Enc.encode(connection.getPayload(), payload);
try broadcastSaprusMessage(msg, 8888);
try broadcastSaprusMessage(msg_bytes, 8888);
return msg;
}
pub fn connect(payload: []const u8, allocator: Allocator) !?SaprusConnection {
var initial_port: u16 = 0;
if (rand) |r| {
initial_port = r.intRangeAtMost(u16, 1024, 65000);
} else unreachable;
pub fn connect(self: Self, payload: []const u8) !?SaprusConnection {
const initial_port = self.randomPort();
var initial_conn_res: ?*SaprusMessage = null;
@@ -119,18 +167,17 @@ pub fn connect(payload: []const u8, allocator: Allocator) !?SaprusConnection {
try sock.setReadTimeout(1 * std.time.us_per_s);
try sock.bind(bind_addr);
const msg = try sendInitialConnection(payload, initial_port, allocator);
defer allocator.free(msg.asBytes());
var sent_msg_bytes: [max_message_size]u8 align(@alignOf(SaprusMessage)) = undefined;
const msg = try self.sendInitialConnection(payload, &sent_msg_bytes, initial_port);
var response_buf: [4096]u8 align(@alignOf(SaprusMessage)) = undefined;
var response_buf: [max_message_size]u8 align(@alignOf(SaprusMessage)) = undefined;
_ = try sock.receive(&response_buf); // Ignore message that I sent.
const len = try sock.receive(&response_buf);
std.debug.print("response bytes: {x}\n", .{response_buf[0..len]});
initial_conn_res = SaprusMessage.init(.connection, response_buf[0..len]);
initial_conn_res = try .networkBytesAsValue(response_buf[0..len]);
// Complete handshake after awaiting response
try broadcastSaprusMessage(msg, randomPort());
try broadcastSaprusMessage(msg.asBytes(), self.randomPort());
if (false) {
return initial_conn_res.?;
@@ -148,5 +195,3 @@ const mem = std.mem;
const network = @import("network");
const gcat = @import("gatorcat");
const Allocator = mem.Allocator;

View File

@@ -42,24 +42,23 @@ pub fn main() !void {
};
defer res.deinit();
try SaprusClient.init();
defer SaprusClient.deinit();
if (res.args.help != 0) {
return clap.help(std.io.getStdErr().writer(), clap.Help, &params, .{});
}
var client = try SaprusClient.init("enp7s0");
defer client.deinit();
if (res.args.relay) |r| {
const dest = parseDest(res.args.dest);
try SaprusClient.sendRelay(
try client.sendRelay(
if (r.len > 0) r else "Hello darkness my old friend",
dest,
gpa,
);
// std.debug.print("Sent: {s}\n", .{r});
return;
} else if (res.args.connect) |c| {
_ = SaprusClient.connect(if (c.len > 0) c else "Hello darkness my old friend", gpa) catch |err| switch (err) {
_ = client.connect(if (c.len > 0) c else "Hello darkness my old friend") catch |err| switch (err) {
error.WouldBlock => null,
else => return err,
};

View File

@@ -38,7 +38,7 @@ pub const Message = packed struct {
pub fn getPayload(self: *align(1) Relay) []u8 {
const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16));
return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @sizeOf(Relay)];
return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @bitSizeOf(Relay) / 8];
}
};
const Connection = packed struct {
@@ -52,7 +52,7 @@ pub const Message = packed struct {
pub fn getPayload(self: *align(1) Connection) []u8 {
const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16));
return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @sizeOf(Connection)];
return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @bitSizeOf(Connection) / 8];
}
fn nativeFromNetworkEndian(self: *align(1) Connection) void {
@@ -79,6 +79,8 @@ pub const Message = packed struct {
/// Takes a byte slice, and returns a Message struct backed by the slice.
/// This properly initializes the top level headers within the slice.
/// This is used for creating new messages. For reading messages from the network,
/// see: networkBytesAsValue.
pub fn init(@"type": PacketType, bytes: []align(@alignOf(Self)) u8) *Self {
std.debug.assert(bytes.len >= @sizeOf(Self));
const res: *Self = @ptrCast(bytes.ptr);
@@ -87,14 +89,14 @@ pub const Message = packed struct {
return res;
}
pub fn lengthForPayloadLength(comptime @"type": PacketType, payload_len: usize) MessageTypeError!u16 {
std.debug.assert(payload_len < std.math.maxInt(u16));
const header_size = @sizeOf(switch (@"type") {
/// Compute the number of bytes required to store a given payload size for a given message type.
pub fn calcSize(comptime @"type": PacketType, payload_len: usize) MessageTypeError!u16 {
const header_size = @bitSizeOf(switch (@"type") {
.relay => Relay,
.connection => Connection,
.file_transfer => return MessageTypeError.NotImplementedSaprusType,
else => return MessageTypeError.UnknownSaprusType,
});
}) / 8;
return @intCast(payload_len + @sizeOf(Self) + header_size);
}
@@ -105,6 +107,7 @@ pub const Message = packed struct {
return std.mem.bytesAsValue(Connection, &self.bytes);
}
/// Access the message Saprus payload.
pub fn getSaprusTypePayload(self: *Self) MessageTypeError!(union(PacketType) {
relay: *align(1) Relay,
file_transfer: void,
@@ -118,6 +121,7 @@ pub const Message = packed struct {
};
}
/// Convert the message to native endianness from network endianness in-place.
pub fn nativeFromNetworkEndian(self: *Self) MessageTypeError!void {
self.type = @enumFromInt(bigToNative(
@typeInfo(@TypeOf(self.type)).@"enum".tag_type,
@@ -141,6 +145,7 @@ pub const Message = packed struct {
}
}
/// Convert the message to network endianness from native endianness in-place.
pub fn networkFromNativeEndian(self: *Self) MessageTypeError!void {
try switch (try self.getSaprusTypePayload()) {
.relay => {},
@@ -155,6 +160,27 @@ pub const Message = packed struct {
self.length = nativeToBig(@TypeOf(self.length), self.length);
}
/// Convert network endian bytes to a native endian value in-place.
pub fn networkBytesAsValue(bytes: SelfBytes) MessageParseError!*Self {
const res = std.mem.bytesAsValue(Self, bytes);
try res.nativeFromNetworkEndian();
return .bytesAsValue(bytes);
}
/// Create a structured view of the bytes without initializing the length or type,
/// and without converting the endianness.
pub fn bytesAsValue(bytes: SelfBytes) MessageParseError!*Self {
const res = std.mem.bytesAsValue(Self, bytes);
return switch (res.type) {
.relay, .connection => if (bytes.len == res.length + @sizeOf(Self))
res
else
MessageParseError.InvalidMessage,
.file_transfer => MessageParseError.NotImplementedSaprusType,
else => MessageParseError.UnknownSaprusType,
};
}
/// Deprecated.
/// If I need the bytes, I should just pass around the slice that is backing this to begin with.
pub fn asBytes(self: *Self) SelfBytes {
@@ -164,43 +190,74 @@ pub const Message = packed struct {
};
test "testing variable length zero copy struct" {
const gpa = std.testing.allocator;
const payload = "Hello darkness my old friend";
// Create a view of the byte slice as a Message
const msg: *Message = try .init(gpa, .relay, payload.len);
defer msg.deinit(gpa);
{
// Set the message values
// Relay test
const payload = "Hello darkness my old friend";
var msg_bytes: [try Message.calcSize(.relay, payload.len)]u8 align(@alignOf(Message)) = undefined;
// Create a view of the byte slice as a Message
const msg: *Message = .init(.relay, &msg_bytes);
{
// These are both set by the init call.
// msg.type = .relay;
// msg.length = payload_len;
// Set the message values
{
// These are both set by the init call.
// msg.type = .relay;
// msg.length = payload_len;
}
const relay = (try msg.getSaprusTypePayload()).relay;
relay.dest = .{ 1, 2, 3, 4 };
@memcpy(relay.getPayload(), payload);
}
const relay = (try msg.getSaprusTypePayload()).relay;
relay.dest = .{ 1, 2, 3, 4 };
@memcpy(relay.getPayload(), payload);
{
// Print the message as hex using the network byte order
try msg.networkFromNativeEndian();
// We know the error from nativeFromNetworkEndian is unreachable because
// it would have returned an error from networkFromNativeEndian.
defer msg.nativeFromNetworkEndian() catch unreachable;
std.debug.print("relay network bytes: {x}\n", .{msg_bytes});
std.debug.print("bytes len: {d}\n", .{msg_bytes.len});
}
if (false) {
// Illegal behavior
std.debug.print("{any}\n", .{(try msg.getSaprusTypePayload()).connection});
}
try std.testing.expectEqualDeep(msg, try Message.bytesAsValue(msg.asBytes()));
}
{
const bytes = msg.asBytes();
// Connection test
const payload = "Hello darkness my old friend";
var msg_bytes: [try Message.calcSize(.connection, payload.len)]u8 align(@alignOf(Message)) = undefined;
// Print the message as hex using the network byte order
try msg.networkFromNativeEndian();
// We know the error from nativeFromNetworkEndian is unreachable because
// it would have returned an error from networkFromNativeEndian.
defer msg.nativeFromNetworkEndian() catch unreachable;
std.debug.print("network bytes: {x}\n", .{bytes});
std.debug.print("bytes len: {d}\n", .{bytes.len});
// Create a view of the byte slice as a Message
const msg: *Message = .init(.connection, &msg_bytes);
{
// Initializing connection header values
const connection = (try msg.getSaprusTypePayload()).connection;
connection.src_port = 1;
connection.dest_port = 2;
connection.seq_num = 3;
connection.msg_id = 4;
connection.reserved = 5;
connection.options = @bitCast(@as(u8, 6));
@memcpy(connection.getPayload(), payload);
}
{
// Print the message as hex using the network byte order
try msg.networkFromNativeEndian();
// We know the error from nativeFromNetworkEndian is unreachable because
// it would have returned an error from networkFromNativeEndian.
defer msg.nativeFromNetworkEndian() catch unreachable;
std.debug.print("connection network bytes: {x}\n", .{msg_bytes});
std.debug.print("bytes len: {d}\n", .{msg_bytes.len});
}
}
if (false) {
// Illegal behavior
std.debug.print("{any}\n", .{(try msg.getSaprusTypePayload()).connection});
}
try std.testing.expectEqualDeep(msg, try Message.bytesAsValue(msg.asBytes()));
}
const std = @import("std");
@@ -211,42 +268,48 @@ const nativeToBig = std.mem.nativeToBig;
const bigToNative = std.mem.bigToNative;
test "Round trip Relay toBytes and fromBytes" {
const gpa = std.testing.allocator;
const msg = Message{
.relay = .{
.header = .{ .dest = .{ 255, 255, 255, 255 } },
.payload = "Hello darkness my old friend",
},
};
if (false) {
const gpa = std.testing.allocator;
const msg = Message{
.relay = .{
.header = .{ .dest = .{ 255, 255, 255, 255 } },
.payload = "Hello darkness my old friend",
},
};
const to_bytes = try msg.toBytes(gpa);
defer gpa.free(to_bytes);
const to_bytes = try msg.toBytes(gpa);
defer gpa.free(to_bytes);
const from_bytes = try Message.fromBytes(to_bytes, gpa);
defer from_bytes.deinit(gpa);
const from_bytes = try Message.fromBytes(to_bytes, gpa);
defer from_bytes.deinit(gpa);
try std.testing.expectEqualDeep(msg, from_bytes);
try std.testing.expectEqualDeep(msg, from_bytes);
}
return error.SkipZigTest;
}
test "Round trip Connection toBytes and fromBytes" {
const gpa = std.testing.allocator;
const msg = Message{
.connection = .{
.header = .{
.src_port = 0,
.dest_port = 0,
if (false) {
const gpa = std.testing.allocator;
const msg = Message{
.connection = .{
.header = .{
.src_port = 0,
.dest_port = 0,
},
.payload = "Hello darkness my old friend",
},
.payload = "Hello darkness my old friend",
},
};
};
const to_bytes = try msg.toBytes(gpa);
defer gpa.free(to_bytes);
const to_bytes = try msg.toBytes(gpa);
defer gpa.free(to_bytes);
const from_bytes = try Message.fromBytes(to_bytes, gpa);
defer from_bytes.deinit(gpa);
const from_bytes = try Message.fromBytes(to_bytes, gpa);
defer from_bytes.deinit(gpa);
try std.testing.expectEqualDeep(msg, from_bytes);
try std.testing.expectEqualDeep(msg, from_bytes);
}
return error.SkipZigTest;
}
test {