From 633713180db5830bf20e4b8dcdaf36d76439dbe6 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Thu, 4 Jul 2024 00:58:53 +0200 Subject: [PATCH] Start restructuring --- src/clog.zig | 1 + src/main.zig | 1 + src/proc.zig | 138 ++++++++++++++++++++++++++++++++++++++++++++++ src/procnet.zig | 4 ++ src/protocols.zig | 11 ++++ 5 files changed, 155 insertions(+) create mode 100644 src/clog.zig create mode 100644 src/proc.zig create mode 100644 src/protocols.zig diff --git a/src/clog.zig b/src/clog.zig new file mode 100644 index 0000000..95a0b68 --- /dev/null +++ b/src/clog.zig @@ -0,0 +1 @@ +const std = @import("std"); diff --git a/src/main.zig b/src/main.zig index b9ed3f4..4b11185 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,7 @@ const std = @import("std"); const proc = @import("procnet.zig"); const pid = @import("procpid.zig"); +const clog = @import("proc.zig"); const c = @cImport({ @cInclude("arpa/inet.h"); @cInclude("signal.h"); diff --git a/src/proc.zig b/src/proc.zig new file mode 100644 index 0000000..3c5667e --- /dev/null +++ b/src/proc.zig @@ -0,0 +1,138 @@ +//! Parse some protocol files in /proc/net directory in Linux +//! +//! Understands TCP/IPv4, TCP/IPv6, UDP/IPv4 and UDP/IPv6 which +//! are the only major protocols using ports + +const std = @import("std"); + +// Socket that currently clogs a port with protocol information +pub const ClogSocket = struct { + port: u16, + inode: std.posix.ino_t, + uid: std.posix.uid_t, + protocol_data: ProtocolData, +}; + +// Known protocols +pub const Protocol = enum { + udp_v4, + udp_v6, + tcp_v4, + tcp_v6, +}; + +// Protocol specific data +pub const ProtocolData = union(Protocol) { + udp_v4: struct { addr: u32 }, + udp_v6: struct { addr: u128 }, + tcp_v4: struct { addr: u32 }, + tcp_v6: struct { addr: u128 }, +}; + +// Parses `/proc/net` information into a flat list of ClogSockets. +// +// Memory is owned by caller. []ClogSocket must be freed by caller. +pub fn parse(alloc: std.mem.Allocator, proc_path: ?[]u8) ![]ClogSocket { + var buf = std.ArrayList(ClogSocket).init(alloc); + defer buf.deinit(); // noop we re-own memory to parent at the end + + const base = proc_path orelse "/proc/net"; + + var arena = std.heap.ArenaAllocator.init(alloc); + defer arena.deinit(); + const child_alloc = arena.allocator(); + + const tcp_v4_path = try std.fs.path.join(child_alloc, &[_][]const u8{ base, "tcp" }); + const tcp_v6_path = try std.fs.path.join(child_alloc, &[_][]const u8{ base, "tcp6" }); + const udp_v4_path = try std.fs.path.join(child_alloc, &[_][]const u8{ base, "udp" }); + const udp_v6_path = try std.fs.path.join(child_alloc, &[_][]const u8{ base, "udp6" }); + + try parse_internal(tcp_v4_path, .tcp_v4, &buf); + try parse_internal(tcp_v6_path, .tcp_v6, &buf); + try parse_internal(udp_v4_path, .udp_v4, &buf); + try parse_internal(udp_v6_path, .udp_v6, &buf); + + return buf.toOwnedSlice(); +} + +// Parse a protocol file in `/proc/net`. Write results to buf. +// +// `path` must be absolute +fn parse_internal(path: []const u8, protocol: Protocol, buf: *std.ArrayList(ClogSocket)) !void { + var file = try std.fs.openFileAbsolute(path, .{}); + defer file.close(); + + var buf_reader = std.io.bufferedReader(file.reader()); + var reader = buf_reader.reader(); + + // we could conditionalize from here onwards on protocol, but we don't since + // all known protocols look the same + + // skip header line + try reader.skipUntilDelimiterOrEof('\n'); + + const LineReader = struct { + reader: @TypeOf(reader), + + // now the big question is, how long can a line be? frankly, I dunno, + // one would need to check the linux source (and even then it may not be + // reliable). + // + // however, we do know that lines are 150 chars long on my machine. We + // give it enough buffer so it shouldn't be a problem. If it is, it'll + // just fail. + // + // also note that this buffer is reused for each `read_line`. + var line_buf: [256]u8 = undefined; + fn read_line(self: @This()) !?[]const u8 { + const line = try self.reader.readUntilDelimiterOrEof(&line_buf, '\n') orelse return null; + + return std.mem.trim(u8, line, "\n"); + } + + fn split(_: @This()) std.mem.TokenIterator(u8, .scalar) { + return std.mem.tokenizeScalar(u8, &line_buf, ' '); + } + }; + + var line_reader = LineReader{ .reader = reader }; + + while (try line_reader.read_line()) |_| { + var split = line_reader.split(); + + // see e.g. /proc/net/tcp + _ = split.next().?; // sl, ignore but assert it's there + var local_address = split.next().?; //local address + _ = split.next().?; // remote address + _ = split.next().?; // st (state? might be interesting future extension) + _ = split.next().?; // tx_queue:rx_queue + _ = split.next().?; // tr:tm->when + _ = split.next().?; // retrnsmt + const uid = split.next().?; // uid + _ = split.next().?; // timeout + const inode = split.next().?; // tx_queue:rx_queue + // ignore everything else + + try buf.append(ClogSocket{ + .port = switch (protocol) { + .tcp_v4, .udp_v4 => try std.fmt.parseInt(u16, local_address[9..13], 16), + .tcp_v6, .udp_v6 => try std.fmt.parseInt(u16, local_address[33..37], 16), + }, + .inode = try std.fmt.parseInt(std.posix.ino_t, inode, 10), + .uid = try std.fmt.parseInt(std.posix.uid_t, uid, 10), + .protocol_data = switch (protocol) { + .tcp_v4 => .{ .tcp_v4 = .{ .addr = try std.fmt.parseInt(u32, local_address[0..8], 16) } }, + .tcp_v6 => .{ .tcp_v6 = .{ .addr = try std.fmt.parseInt(u128, local_address[0..32], 16) } }, + + .udp_v4 => .{ .udp_v4 = .{ .addr = try std.fmt.parseInt(u32, local_address[0..8], 16) } }, + .udp_v6 => .{ .udp_v6 = .{ .addr = try std.fmt.parseInt(u128, local_address[0..32], 16) } }, + }, + }); + } +} + +test "parse" { + var alloc = std.testing.allocator; + const clogs = try parse(alloc, null); + alloc.free(clogs); +} diff --git a/src/procnet.zig b/src/procnet.zig index dacc0fd..507ac33 100644 --- a/src/procnet.zig +++ b/src/procnet.zig @@ -33,6 +33,10 @@ pub const ProcNet = union(enum) { } }; +/// Read a protocol file in /proc/net +/// +/// The memory of the resulting []ProcNet is caller owned and must be freed by +/// the caller pub fn read_proc_net(alloc: Allocator, comptime addr_len: enum { V4, V6 }, path: []const u8) ![]ProcNet { var arena = std.heap.ArenaAllocator.init(alloc); defer arena.deinit(); diff --git a/src/protocols.zig b/src/protocols.zig new file mode 100644 index 0000000..315adfe --- /dev/null +++ b/src/protocols.zig @@ -0,0 +1,11 @@ +const std = @import("std"); + +pub const Protocol { + proto: enum { + TCP_V4, + TCP_V6, + UDP_V4, + UDP_V6 + }, + port_map: std.AutoHashMap(u16, Process); +};