aboutsummaryrefslogtreecommitdiff
path: root/src/EthIpUdp.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/EthIpUdp.zig')
-rw-r--r--src/EthIpUdp.zig109
1 files changed, 109 insertions, 0 deletions
diff --git a/src/EthIpUdp.zig b/src/EthIpUdp.zig
new file mode 100644
index 0000000..251ed64
--- /dev/null
+++ b/src/EthIpUdp.zig
@@ -0,0 +1,109 @@
+// 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/>.
+
+pub 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 = 1,
+ 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),
+
+ pub 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;
+ }
+
+ pub fn setPayloadLen(self: *@This(), len: usize) void {
+ self.ip.len = @intCast(len + (@bitSizeOf(@TypeOf(self.udp)) / 8) + (@bitSizeOf(@TypeOf(self.ip)) / 8));
+
+ // Zero the checksum field before calculation
+ self.ip.header_checksum = 0;
+
+ // Serialize IP header to big-endian bytes
+ var ip_bytes: [@bitSizeOf(@TypeOf(self.ip)) / 8]u8 = undefined;
+ var w: Writer = .fixed(&ip_bytes);
+ w.writeStruct(self.ip, .big) catch unreachable;
+
+ // Calculate checksum over serialized bytes
+ self.ip.header_checksum = onesComplement16(&ip_bytes);
+
+ self.udp.len = @intCast(len + (@bitSizeOf(@TypeOf(self.udp)) / 8));
+ }
+};
+
+fn onesComplement16(data: []const u8) u16 {
+ var sum: u32 = 0;
+
+ // Process pairs of bytes as 16-bit words
+ var i: usize = 0;
+ while (i + 1 < data.len) : (i += 2) {
+ const word: u16 = (@as(u16, data[i]) << 8) | data[i + 1];
+ sum += word;
+ }
+
+ // Handle odd byte if present
+ if (data.len % 2 == 1) {
+ sum += @as(u32, data[data.len - 1]) << 8;
+ }
+
+ // Fold 32-bit sum to 16 bits
+ while (sum >> 16 != 0) {
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ }
+
+ // Return ones' complement
+ return ~@as(u16, @truncate(sum));
+}
+
+const std = @import("std");
+const Writer = std.Io.Writer;