Cybersecurity-Projects/PROJECTS/advanced/zig-stateless-scanner/src/packet.zig

256 lines
8.1 KiB
Zig

// ©AngelaMos | 2026
// packet.zig
const std = @import("std");
const builtin = @import("builtin");
pub const EthHdr = extern struct {
dst: [6]u8,
src: [6]u8,
ethertype: u16,
};
pub const Ipv4Hdr = extern struct {
version_ihl: u8,
tos: u8,
total_len: u16,
id: u16,
flags_frag: u16,
ttl: u8,
protocol: u8,
checksum: u16,
src: u32,
dst: u32,
};
pub const TcpHdr = extern struct {
src_port: u16,
dst_port: u16,
seq: u32,
ack: u32,
data_off_ns: u8,
flags: u8,
window: u16,
checksum: u16,
urgent: u16,
};
pub const UdpHdr = extern struct {
src_port: u16,
dst_port: u16,
length: u16,
checksum: u16,
};
comptime {
std.debug.assert(@sizeOf(EthHdr) == 14);
std.debug.assert(@sizeOf(Ipv4Hdr) == 20);
std.debug.assert(@sizeOf(TcpHdr) == 20);
std.debug.assert(@sizeOf(UdpHdr) == 8);
}
pub fn checksum(bytes: []const u8) u16 {
var sum: u32 = 0;
var i: usize = 0;
while (i + 1 < bytes.len) : (i += 2) {
const word = (@as(u16, bytes[i]) << 8) | @as(u16, bytes[i + 1]);
sum += word;
}
if (i < bytes.len) {
sum += @as(u32, bytes[i]) << 8;
}
while (sum >> 16 != 0) {
sum = (sum & 0xffff) + (sum >> 16);
}
return ~@as(u16, @truncate(sum));
}
pub fn checksumSimd(bytes: []const u8) u16 {
const lanes = comptime (std.simd.suggestVectorLength(u16) orelse 8);
const stride = lanes * 2;
const native_le = builtin.cpu.arch.endian() == .little;
var acc: @Vector(lanes, u32) = @splat(0);
var i: usize = 0;
while (i + stride <= bytes.len) : (i += stride) {
const block: [stride]u8 = bytes[i..][0..stride].*;
var words: @Vector(lanes, u16) = @bitCast(block);
if (native_le) words = @byteSwap(words);
acc += @as(@Vector(lanes, u32), words);
}
var sum: u32 = @reduce(.Add, acc);
while (i + 1 < bytes.len) : (i += 2) {
sum += (@as(u32, bytes[i]) << 8) | @as(u32, bytes[i + 1]);
}
if (i < bytes.len) {
sum += @as(u32, bytes[i]) << 8;
}
while (sum >> 16 != 0) {
sum = (sum & 0xffff) + (sum >> 16);
}
return ~@as(u16, @truncate(sum));
}
pub fn incrementalUpdate(old_check: u16, old_word: u16, new_word: u16) u16 {
var sum: u32 = @as(u32, ~old_check) + @as(u32, ~old_word) + @as(u32, new_word);
while (sum >> 16 != 0) {
sum = (sum & 0xffff) + (sum >> 16);
}
return ~@as(u16, @truncate(sum));
}
pub fn tcpChecksum(src_be: u32, dst_be: u32, segment: []const u8) u16 {
var pseudo: [12]u8 = undefined;
@memcpy(pseudo[0..4], std.mem.asBytes(&src_be));
@memcpy(pseudo[4..8], std.mem.asBytes(&dst_be));
pseudo[8] = 0;
pseudo[9] = 6;
std.mem.writeInt(u16, pseudo[10..12], @intCast(segment.len), .big);
var sum: u32 = 0;
var i: usize = 0;
while (i + 1 < pseudo.len) : (i += 2) {
sum += (@as(u32, pseudo[i]) << 8) | @as(u32, pseudo[i + 1]);
}
i = 0;
while (i + 1 < segment.len) : (i += 2) {
sum += (@as(u32, segment[i]) << 8) | @as(u32, segment[i + 1]);
}
if (i < segment.len) {
sum += @as(u32, segment[i]) << 8;
}
while (sum >> 16 != 0) {
sum = (sum & 0xffff) + (sum >> 16);
}
return ~@as(u16, @truncate(sum));
}
pub fn udpChecksum(src_be: u32, dst_be: u32, segment: []const u8) u16 {
var pseudo: [12]u8 = undefined;
@memcpy(pseudo[0..4], std.mem.asBytes(&src_be));
@memcpy(pseudo[4..8], std.mem.asBytes(&dst_be));
pseudo[8] = 0;
pseudo[9] = 17;
std.mem.writeInt(u16, pseudo[10..12], @intCast(segment.len), .big);
var sum: u32 = 0;
var i: usize = 0;
while (i + 1 < pseudo.len) : (i += 2) {
sum += (@as(u32, pseudo[i]) << 8) | @as(u32, pseudo[i + 1]);
}
i = 0;
while (i + 1 < segment.len) : (i += 2) {
sum += (@as(u32, segment[i]) << 8) | @as(u32, segment[i + 1]);
}
if (i < segment.len) {
sum += @as(u32, segment[i]) << 8;
}
while (sum >> 16 != 0) {
sum = (sum & 0xffff) + (sum >> 16);
}
const folded: u16 = ~@as(u16, @truncate(sum));
return if (folded == 0) 0xffff else folded;
}
test "header sizes are wire-exact" {
try std.testing.expectEqual(@as(usize, 14), @sizeOf(EthHdr));
try std.testing.expectEqual(@as(usize, 20), @sizeOf(Ipv4Hdr));
try std.testing.expectEqual(@as(usize, 20), @sizeOf(TcpHdr));
try std.testing.expectEqual(@as(usize, 8), @sizeOf(UdpHdr));
}
test "RFC 1071 checksum matches the canonical IPv4 KAT (0xb861)" {
const hdr = [_]u8{
0x45, 0x00, 0x00, 0x73, 0x00, 0x00, 0x40, 0x00,
0x40, 0x11, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x01,
0xc0, 0xa8, 0x00, 0xc7,
};
try std.testing.expectEqual(@as(u16, 0xb861), checksum(&hdr));
}
test "SIMD checksum matches the canonical IPv4 KAT (0xb861)" {
const hdr = [_]u8{
0x45, 0x00, 0x00, 0x73, 0x00, 0x00, 0x40, 0x00,
0x40, 0x11, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x01,
0xc0, 0xa8, 0x00, 0xc7,
};
try std.testing.expectEqual(@as(u16, 0xb861), checksumSimd(&hdr));
}
test "SIMD checksum equals scalar checksum for every length 0..256" {
var prng = std.Random.DefaultPrng.init(0xC0FFEE_1624_517A);
const rand = prng.random();
var buf: [256]u8 = undefined;
var len: usize = 0;
while (len <= 256) : (len += 1) {
rand.bytes(buf[0..len]);
try std.testing.expectEqual(checksum(buf[0..len]), checksumSimd(buf[0..len]));
}
}
test "RFC 1624 incremental update matches the RFC section 4 worked example" {
try std.testing.expectEqual(@as(u16, 0x0000), incrementalUpdate(0xDD2F, 0x5555, 0x3285));
}
test "incremental update equals a full recompute for random single-word edits" {
var prng = std.Random.DefaultPrng.init(0x1624_DEAD_BEEF_0001);
const rand = prng.random();
var hdr: [20]u8 = undefined;
var trial: usize = 0;
while (trial < 4096) : (trial += 1) {
rand.bytes(&hdr);
std.mem.writeInt(u16, hdr[10..12], 0, .big);
const old_check = checksum(&hdr);
const word_index = rand.uintLessThan(usize, 9) * 2;
const off = if (word_index >= 10) word_index + 2 else word_index;
const old_word = std.mem.readInt(u16, hdr[off..][0..2], .big);
const new_word = rand.int(u16);
std.mem.writeInt(u16, hdr[off..][0..2], new_word, .big);
const full = checksum(&hdr);
try std.testing.expectEqual(full, incrementalUpdate(old_check, old_word, new_word));
}
}
test "tcpChecksum self-verifies: a segment with its correct checksum folds to 0" {
var tcp = TcpHdr{
.src_port = std.mem.nativeToBig(u16, 54321),
.dst_port = std.mem.nativeToBig(u16, 80),
.seq = std.mem.nativeToBig(u32, 0xdead_beef),
.ack = 0,
.data_off_ns = 0x50,
.flags = 0x02,
.window = std.mem.nativeToBig(u16, 1024),
.checksum = 0,
.urgent = 0,
};
const src = std.mem.nativeToBig(u32, 0x7f000001);
const dst = std.mem.nativeToBig(u32, 0x7f000001);
tcp.checksum = std.mem.nativeToBig(u16, tcpChecksum(src, dst, std.mem.asBytes(&tcp)));
try std.testing.expectEqual(@as(u16, 0), tcpChecksum(src, dst, std.mem.asBytes(&tcp)));
}
test "udpChecksum self-verifies: a correct datagram re-sums to the 0xFFFF all-ones marker" {
const src = std.mem.nativeToBig(u32, 0x7f000001);
const dst = std.mem.nativeToBig(u32, 0x08080808);
var seg: [12]u8 = undefined;
var hdr = UdpHdr{
.src_port = std.mem.nativeToBig(u16, 40000),
.dst_port = std.mem.nativeToBig(u16, 53),
.length = std.mem.nativeToBig(u16, 12),
.checksum = 0,
};
@memcpy(seg[0..8], std.mem.asBytes(&hdr));
@memcpy(seg[8..12], "abcd");
const ck = udpChecksum(src, dst, &seg);
try std.testing.expect(ck != 0);
hdr.checksum = std.mem.nativeToBig(u16, ck);
@memcpy(seg[0..8], std.mem.asBytes(&hdr));
try std.testing.expectEqual(@as(u16, 0xffff), udpChecksum(src, dst, &seg));
}
test "udpChecksum maps a computed 0x0000 to 0xFFFF (IPv4 UDP quirk)" {
try std.testing.expectEqual(@as(u16, 0xffff), udpChecksum(0, 0, &[_]u8{ 0xff, 0xec }));
try std.testing.expect(udpChecksum(0, 0, &[_]u8{ 0xff, 0xec }) != 0);
}