17 Commits

Author SHA1 Message Date
5b362590b7 fix use after free 2025-05-10 12:48:40 -04:00
c1b96370ba 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 12:37:39 -04:00
76d2c09c7d 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 12:35:32 -04:00
9280fd095f 2025-04-30 17:00:12 -04:00
b575ad9094 2025-04-30 14:43:19 -04:00
73d0a80851 Remove bytesAsValueUnchecked
Callers can instead use std.mem.bytesAsValue directly.
2025-04-30 13:46:55 -04:00
fdd3f29fba Cleanup asBytes and test it 2025-04-30 13:46:55 -04:00
d0d0b83b57 Simplify init interface 2025-04-30 13:46:55 -04:00
bc3926bcca 2025-04-30 13:46:55 -04:00
6d4880fa6a Align the bytes instead of the struct 2025-04-30 13:46:54 -04:00
5e22c2b2ef 2025-04-30 13:46:54 -04:00
5530ed3d77 2025-04-30 13:46:54 -04:00
66ea478617 2025-04-30 13:46:54 -04:00
08954b9f3d 2025-04-30 13:46:54 -04:00
8d404a7c8d 2025-04-30 13:46:54 -04:00
18b04364df 2025-04-27 18:03:06 -04:00
cc8438448d Staring real connections 2025-04-27 18:03:06 -04:00
4 changed files with 120 additions and 227 deletions

View File

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

View File

@@ -1,86 +1,28 @@
const base64Enc = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, '='); const base64Enc = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, '=');
const base64Dec = std.base64.Base64Decoder.init(std.base64.standard_alphabet_chars, '='); const base64Dec = std.base64.Base64Decoder.init(std.base64.standard_alphabet_chars, '=');
rand: Random, var rand: ?Random = null;
socket: gcat.nic.RawSocket,
const Self = @This(); pub fn init() !void {
const max_message_size = 2048;
pub fn init(interface_name: [:0]const u8) !Self {
var prng = Random.DefaultPrng.init(blk: { var prng = Random.DefaultPrng.init(blk: {
var seed: u64 = undefined; var seed: u64 = undefined;
try posix.getrandom(mem.asBytes(&seed)); try posix.getrandom(mem.asBytes(&seed));
break :blk seed; break :blk seed;
}); });
const rand = prng.random(); rand = prng.random();
try network.init();
const socket: gcat.nic.RawSocket = try .init(interface_name);
return .{
.rand = rand,
.socket = socket,
};
} }
pub fn deinit(self: *Self) void { pub fn deinit() void {
self.socket.deinit(); network.deinit();
} }
/// Used for relay messages and connection handshake. fn broadcastSaprusMessage(msg: *SaprusMessage, udp_port: u16) !void {
/// 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) { if (false) {
var foo: gcat.nic.RawSocket = try .init("enp7s0"); // /proc/net/dev var foo: gcat.nic.RawSocket = try .init("enp7s0"); // /proc/net/dev
defer foo.deinit(); defer foo.deinit();
} }
const msg: *SaprusMessage = try .bytesAsValue(msg_bytes); const msg_bytes = msg.asBytes();
try msg.networkFromNativeEndian(); try msg.networkFromNativeEndian();
defer msg.nativeFromNetworkEndian() catch unreachable; defer msg.nativeFromNetworkEndian() catch unreachable;
@@ -102,41 +44,48 @@ fn broadcastSaprusMessage(msg_bytes: []align(@alignOf(SaprusMessage)) u8, udp_po
try sock.bind(bind_addr); try sock.bind(bind_addr);
std.debug.print("{x}\n", .{msg_bytes});
_ = try sock.sendTo(dest_addr, msg_bytes); _ = try sock.sendTo(dest_addr, msg_bytes);
} }
pub fn sendRelay(self: *Self, payload: []const u8, dest: [4]u8) !void { pub fn sendRelay(payload: []const u8, dest: [4]u8, allocator: Allocator) !void {
var buf: [max_message_size]u8 align(@alignOf(SaprusMessage)) = undefined; const msg_bytes = try allocator.alignedAlloc(
const msg_bytes = buf[0..try SaprusMessage.calcSize( u8,
@alignOf(SaprusMessage),
try SaprusMessage.lengthForPayloadLength(
.relay, .relay,
base64Enc.calcSize(payload.len), base64Enc.calcSize(payload.len),
)]; ),
);
defer allocator.free(msg_bytes);
const msg: *SaprusMessage = .init(.relay, msg_bytes); const msg: *SaprusMessage = .init(.relay, msg_bytes);
const relay = (try msg.getSaprusTypePayload()).relay; const relay = (try msg.getSaprusTypePayload()).relay;
relay.dest = dest; relay.dest = dest;
_ = base64Enc.encode(relay.getPayload(), payload); _ = base64Enc.encode(relay.getPayload(), payload);
try self.broadcastInitialInterestMessage(msg_bytes); try broadcastSaprusMessage(msg, 8888);
} }
fn randomPort(self: Self) u16 { fn randomPort() u16 {
return self.rand.intRangeAtMost(u16, 1024, 65000); var p: u16 = 0;
if (rand) |r| {
p = r.intRangeAtMost(u16, 1024, 65000);
} else unreachable;
return p;
} }
pub fn sendInitialConnection( pub fn sendInitialConnection(payload: []const u8, initial_port: u16, allocator: Allocator) !*SaprusMessage {
self: Self, const dest_port = randomPort();
payload: []const u8, const msg_bytes = try allocator.alignedAlloc(
output_bytes: []align(@alignOf(SaprusMessage)) u8, u8,
initial_port: u16, @alignOf(SaprusMessage),
) !*SaprusMessage { try SaprusMessage.lengthForPayloadLength(
const dest_port = self.randomPort();
const msg_bytes = output_bytes[0..try SaprusMessage.calcSize(
.connection, .connection,
base64Enc.calcSize(payload.len), base64Enc.calcSize(payload.len),
)]; ),
);
const msg: *SaprusMessage = .init(.connection, msg_bytes); const msg: *SaprusMessage = .init(.connection, msg_bytes);
const connection = (try msg.getSaprusTypePayload()).connection; const connection = (try msg.getSaprusTypePayload()).connection;
@@ -144,13 +93,16 @@ pub fn sendInitialConnection(
connection.dest_port = dest_port; connection.dest_port = dest_port;
_ = base64Enc.encode(connection.getPayload(), payload); _ = base64Enc.encode(connection.getPayload(), payload);
try broadcastSaprusMessage(msg_bytes, 8888); try broadcastSaprusMessage(msg, 8888);
return msg; return msg;
} }
pub fn connect(self: Self, payload: []const u8) !?SaprusConnection { pub fn connect(payload: []const u8, allocator: Allocator) !?SaprusConnection {
const initial_port = self.randomPort(); var initial_port: u16 = 0;
if (rand) |r| {
initial_port = r.intRangeAtMost(u16, 1024, 65000);
} else unreachable;
var initial_conn_res: ?*SaprusMessage = null; var initial_conn_res: ?*SaprusMessage = null;
@@ -167,17 +119,18 @@ pub fn connect(self: Self, payload: []const u8) !?SaprusConnection {
try sock.setReadTimeout(1 * std.time.us_per_s); try sock.setReadTimeout(1 * std.time.us_per_s);
try sock.bind(bind_addr); try sock.bind(bind_addr);
var sent_msg_bytes: [max_message_size]u8 align(@alignOf(SaprusMessage)) = undefined; const msg = try sendInitialConnection(payload, initial_port, allocator);
const msg = try self.sendInitialConnection(payload, &sent_msg_bytes, initial_port); defer allocator.free(msg.asBytes());
var response_buf: [max_message_size]u8 align(@alignOf(SaprusMessage)) = undefined; var response_buf: [4096]u8 align(@alignOf(SaprusMessage)) = undefined;
_ = try sock.receive(&response_buf); // Ignore message that I sent. _ = try sock.receive(&response_buf); // Ignore message that I sent.
const len = try sock.receive(&response_buf); const len = try sock.receive(&response_buf);
initial_conn_res = try .networkBytesAsValue(response_buf[0..len]); std.debug.print("response bytes: {x}\n", .{response_buf[0..len]});
initial_conn_res = SaprusMessage.init(.connection, response_buf[0..len]);
// Complete handshake after awaiting response // Complete handshake after awaiting response
try broadcastSaprusMessage(msg.asBytes(), self.randomPort()); try broadcastSaprusMessage(msg, randomPort());
if (false) { if (false) {
return initial_conn_res.?; return initial_conn_res.?;
@@ -195,3 +148,5 @@ const mem = std.mem;
const network = @import("network"); const network = @import("network");
const gcat = @import("gatorcat"); const gcat = @import("gatorcat");
const Allocator = mem.Allocator;

View File

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

View File

@@ -38,7 +38,7 @@ pub const Message = packed struct {
pub fn getPayload(self: *align(1) Relay) []u8 { pub fn getPayload(self: *align(1) Relay) []u8 {
const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16)); const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16));
return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @bitSizeOf(Relay) / 8]; return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @sizeOf(Relay)];
} }
}; };
const Connection = packed struct { const Connection = packed struct {
@@ -52,7 +52,7 @@ pub const Message = packed struct {
pub fn getPayload(self: *align(1) Connection) []u8 { pub fn getPayload(self: *align(1) Connection) []u8 {
const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16)); const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16));
return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @bitSizeOf(Connection) / 8]; return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @sizeOf(Connection)];
} }
fn nativeFromNetworkEndian(self: *align(1) Connection) void { fn nativeFromNetworkEndian(self: *align(1) Connection) void {
@@ -79,8 +79,6 @@ pub const Message = packed struct {
/// Takes a byte slice, and returns a Message struct backed by the slice. /// Takes a byte slice, and returns a Message struct backed by the slice.
/// This properly initializes the top level headers within 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 { pub fn init(@"type": PacketType, bytes: []align(@alignOf(Self)) u8) *Self {
std.debug.assert(bytes.len >= @sizeOf(Self)); std.debug.assert(bytes.len >= @sizeOf(Self));
const res: *Self = @ptrCast(bytes.ptr); const res: *Self = @ptrCast(bytes.ptr);
@@ -89,14 +87,14 @@ pub const Message = packed struct {
return res; return res;
} }
/// Compute the number of bytes required to store a given payload size for a given message type. pub fn lengthForPayloadLength(comptime @"type": PacketType, payload_len: usize) MessageTypeError!u16 {
pub fn calcSize(comptime @"type": PacketType, payload_len: usize) MessageTypeError!u16 { std.debug.assert(payload_len < std.math.maxInt(u16));
const header_size = @bitSizeOf(switch (@"type") { const header_size = @sizeOf(switch (@"type") {
.relay => Relay, .relay => Relay,
.connection => Connection, .connection => Connection,
.file_transfer => return MessageTypeError.NotImplementedSaprusType, .file_transfer => return MessageTypeError.NotImplementedSaprusType,
else => return MessageTypeError.UnknownSaprusType, else => return MessageTypeError.UnknownSaprusType,
}) / 8; });
return @intCast(payload_len + @sizeOf(Self) + header_size); return @intCast(payload_len + @sizeOf(Self) + header_size);
} }
@@ -107,7 +105,6 @@ pub const Message = packed struct {
return std.mem.bytesAsValue(Connection, &self.bytes); return std.mem.bytesAsValue(Connection, &self.bytes);
} }
/// Access the message Saprus payload.
pub fn getSaprusTypePayload(self: *Self) MessageTypeError!(union(PacketType) { pub fn getSaprusTypePayload(self: *Self) MessageTypeError!(union(PacketType) {
relay: *align(1) Relay, relay: *align(1) Relay,
file_transfer: void, file_transfer: void,
@@ -121,7 +118,6 @@ pub const Message = packed struct {
}; };
} }
/// Convert the message to native endianness from network endianness in-place.
pub fn nativeFromNetworkEndian(self: *Self) MessageTypeError!void { pub fn nativeFromNetworkEndian(self: *Self) MessageTypeError!void {
self.type = @enumFromInt(bigToNative( self.type = @enumFromInt(bigToNative(
@typeInfo(@TypeOf(self.type)).@"enum".tag_type, @typeInfo(@TypeOf(self.type)).@"enum".tag_type,
@@ -145,7 +141,6 @@ pub const Message = packed struct {
} }
} }
/// Convert the message to network endianness from native endianness in-place.
pub fn networkFromNativeEndian(self: *Self) MessageTypeError!void { pub fn networkFromNativeEndian(self: *Self) MessageTypeError!void {
try switch (try self.getSaprusTypePayload()) { try switch (try self.getSaprusTypePayload()) {
.relay => {}, .relay => {},
@@ -160,27 +155,6 @@ pub const Message = packed struct {
self.length = nativeToBig(@TypeOf(self.length), self.length); 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. /// Deprecated.
/// If I need the bytes, I should just pass around the slice that is backing this to begin with. /// 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 { pub fn asBytes(self: *Self) SelfBytes {
@@ -190,13 +164,12 @@ pub const Message = packed struct {
}; };
test "testing variable length zero copy struct" { test "testing variable length zero copy struct" {
{ const gpa = std.testing.allocator;
// Relay test
const payload = "Hello darkness my old friend"; 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 // Create a view of the byte slice as a Message
const msg: *Message = .init(.relay, &msg_bytes); const msg: *Message = try .init(gpa, .relay, payload.len);
defer msg.deinit(gpa);
{ {
// Set the message values // Set the message values
@@ -211,13 +184,15 @@ test "testing variable length zero copy struct" {
} }
{ {
const bytes = msg.asBytes();
// Print the message as hex using the network byte order // Print the message as hex using the network byte order
try msg.networkFromNativeEndian(); try msg.networkFromNativeEndian();
// We know the error from nativeFromNetworkEndian is unreachable because // We know the error from nativeFromNetworkEndian is unreachable because
// it would have returned an error from networkFromNativeEndian. // it would have returned an error from networkFromNativeEndian.
defer msg.nativeFromNetworkEndian() catch unreachable; defer msg.nativeFromNetworkEndian() catch unreachable;
std.debug.print("relay network bytes: {x}\n", .{msg_bytes}); std.debug.print("network bytes: {x}\n", .{bytes});
std.debug.print("bytes len: {d}\n", .{msg_bytes.len}); std.debug.print("bytes len: {d}\n", .{bytes.len});
} }
if (false) { if (false) {
@@ -228,38 +203,6 @@ test "testing variable length zero copy struct" {
try std.testing.expectEqualDeep(msg, try Message.bytesAsValue(msg.asBytes())); try std.testing.expectEqualDeep(msg, try Message.bytesAsValue(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;
// 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});
}
}
}
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
@@ -268,7 +211,6 @@ const nativeToBig = std.mem.nativeToBig;
const bigToNative = std.mem.bigToNative; const bigToNative = std.mem.bigToNative;
test "Round trip Relay toBytes and fromBytes" { test "Round trip Relay toBytes and fromBytes" {
if (false) {
const gpa = std.testing.allocator; const gpa = std.testing.allocator;
const msg = Message{ const msg = Message{
.relay = .{ .relay = .{
@@ -285,11 +227,8 @@ test "Round trip Relay toBytes and fromBytes" {
try std.testing.expectEqualDeep(msg, from_bytes); try std.testing.expectEqualDeep(msg, from_bytes);
} }
return error.SkipZigTest;
}
test "Round trip Connection toBytes and fromBytes" { test "Round trip Connection toBytes and fromBytes" {
if (false) {
const gpa = std.testing.allocator; const gpa = std.testing.allocator;
const msg = Message{ const msg = Message{
.connection = .{ .connection = .{
@@ -309,8 +248,6 @@ test "Round trip Connection toBytes and fromBytes" {
try std.testing.expectEqualDeep(msg, from_bytes); try std.testing.expectEqualDeep(msg, from_bytes);
} }
return error.SkipZigTest;
}
test { test {
std.testing.refAllDeclsRecursive(@This()); std.testing.refAllDeclsRecursive(@This());