diff --git a/build.zig b/build.zig index 60560c7..533793d 100644 --- a/build.zig +++ b/build.zig @@ -20,8 +20,8 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, + .link_libc = true, }); - exe.addIncludePath(.{ .path = "lib" }); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default @@ -58,7 +58,6 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); - sockets_unit_tests.addIncludePath(.{ .path = "lib" }); const run_sockets_unit_tests = b.addRunArtifact(sockets_unit_tests); run_sockets_unit_tests.has_side_effects = true; // needed so tests aren't cached @@ -70,7 +69,6 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); - process_unit_tests.addIncludePath(.{ .path = "lib" }); const run_process_unit_tests = b.addRunArtifact(process_unit_tests); run_process_unit_tests.has_side_effects = true; // needed so tests aren't cached @@ -79,8 +77,8 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, + .link_libc = true, }); - exe_unit_tests.addIncludePath(.{ .path = "lib" }); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); diff --git a/src/main.zig b/src/main.zig index a9d5877..c3d027e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,11 @@ const std = @import("std"); const sockets = @import("sockets.zig"); const process = @import("process.zig"); +const c = @cImport({ + @cInclude("pwd.h"); + @cInclude("arpa/inet.h"); + @cInclude("signal.h"); +}); pub const std_options = .{ .log_level = .info }; @@ -14,19 +19,124 @@ pub fn main() !void { const alloc = gpa.allocator(); - const clog_sockets = try sockets.parse(alloc, null); - defer alloc.free(clog_sockets); + const pids = try print_clogs(alloc, port); + defer alloc.free(pids); - for (clog_sockets) |clog_socket| { - if (clog_socket.port == port) { - const p = try process.find_by_inode(alloc, clog_socket.inode, null); - defer p.deinit(); - switch (clog_socket.protocol_data) { - .tcp_v4 => |v4| std.log.info("Found process {any} clogging address {any}", .{ p, v4.addr }), - .udp_v4 => |v4| std.log.info("Found process {any} clogging address {any}", .{ p, v4.addr }), - .tcp_v6 => |v6| std.log.info("Found process {any} clogging address {any}", .{ p, v6.addr }), - .udp_v6 => |v6| std.log.info("Found process {any} clogging address {any}", .{ p, v6.addr }), + if (pids.len == 0) { + return; + } + + const kill_pids = try choose_kill(); + + kill(&[_]std.posix.pid_t{ + pids[kill_pids[0]], + }); +} + +fn kill(pids: []const std.posix.pid_t) void { + for (pids) |pid| { + if (c.kill(pid, c.SIGTERM) == 0) { + // Wait briefly for process to exit + std.time.sleep(100000000); // 100ms + + // Check if process still exists + if (c.kill(pid, 0) == 0) { + _ = c.kill(pid, c.SIGKILL); // now try with force } } } } + +fn choose_kill() ![]usize { + var stdin = std.io.getStdIn().reader(); + var stdout = std.io.getStdOut().writer(); + + try stdout.writeAll("Kill? "); + + var buf: [2]u8 = undefined; + _ = try stdin.readUntilDelimiter(&buf, '\n'); + + var kills = [_]usize{ + (try std.fmt.parseInt(usize, buf[0..1], 10)) - 1, + }; + + return &kills; +} + +fn print_clogs(alloc: std.mem.Allocator, port: u16) ![]std.posix.pid_t { + var writer = std.io.getStdOut().writer(); + var pids = std.ArrayList(std.posix.pid_t).init(alloc); + defer pids.deinit(); // noop after re-owning memory at end + + const clog_sockets = try sockets.parse(alloc, null); + defer alloc.free(clog_sockets); + + var any: bool = false; + for (clog_sockets) |cs| { + if (cs.port == port) any = true; + } + if (!any) { + try writer.print("Port {d} looks unclogged\n", .{port}); + return &[_]std.posix.pid_t{}; + } + + try writer.print("{s: <3}{s: <10} {s: <30} {s: <12} {s: <15} {s: <10} {s: <6} {s: <5}\n", .{ "#", "Command", "Path", "Protocol", "Address", "User", "Inode", "Port" }); + var idx: usize = 1; + + for (clog_sockets) |cs| { + if (cs.port == port) { + const clogs = try process.find_by_inode(alloc, cs.inode, null); + defer clogs.deinit(); + + const user = c.getpwuid(cs.uid); + + for (clogs.items) |clog| { + switch (cs.protocol_data) { + // zig fmt: off + .tcp_v4 => |proto| { + var addr: [c.INET_ADDRSTRLEN:0]u8 = undefined; + _ = c.inet_ntop(c.AF_INET, &proto.addr, &addr, c.INET_ADDRSTRLEN); + try writer.print("{d: <3}{s: <10} {s: <30} {s: <12} {s: <15} {s: <10} {d: <6} {d: <5}\n", .{ + idx, + clog.comm[0..@min(10, clog.comm.len)], clog.exe[0..@min(30, clog.exe.len)], + "TCP/IPv4", std.mem.sliceTo(&addr, 0), + user.*.pw_name, cs.inode, cs.port }); + }, + .tcp_v6 => |proto| { + var addr: [c.INET6_ADDRSTRLEN:0]u8 = undefined; + _ = c.inet_ntop(c.AF_INET6, &proto.addr, &addr, c.INET6_ADDRSTRLEN); + try writer.print("{d: <3}{s: <10} {s: <30} {s: <12} {s: <15} {s: <10} {d: <6} {d: <5}\n", .{ + idx, + clog.comm[0..@min(10, clog.comm.len)], clog.exe[0..@min(30, clog.exe.len)], + "TCP/IPv6", std.mem.sliceTo(&addr, 0), + user.*.pw_name, cs.inode, cs.port }); + }, + .udp_v4 => |proto| { + var addr: [c.INET_ADDRSTRLEN:0]u8 = undefined; + _ = c.inet_ntop(c.AF_INET, &proto.addr, &addr, c.INET_ADDRSTRLEN); + try writer.print("{d: <3}{s: <10} {s: <30} {s: <12} {s: <15} {s: <10} {d: <6} {d: <5}\n", .{ + idx, + clog.comm[0..@min(10, clog.comm.len)], clog.exe[0..@min(30, clog.exe.len)], + "UDP/IPv4", std.mem.sliceTo(&addr, 0), + user.*.pw_name, cs.inode, cs.port }); + }, + .udp_v6 => |proto| { + var addr: [c.INET6_ADDRSTRLEN:0]u8 = undefined; + _ = c.inet_ntop(c.AF_INET6, &proto.addr, &addr, c.INET6_ADDRSTRLEN); + try writer.print("{d: <3}{s: <10} {s: <30} {s: <12} {s: <15} {s: <10} {d: <6} {d: <5}\n", .{ + idx, + clog.comm[0..@min(10, clog.comm.len)], clog.exe[0..@min(30, clog.exe.len)], + "UDP/IPv6", std.mem.sliceTo(&addr, 0), + user.*.pw_name, cs.inode, cs.port }); + }, + // zig fmt: on + } + + try pids.append(clog.pid); + idx += 1; + } + } + } + + return pids.toOwnedSlice(); +} diff --git a/src/sockets.zig b/src/sockets.zig index d3765b3..31f3adc 100644 --- a/src/sockets.zig +++ b/src/sockets.zig @@ -124,15 +124,38 @@ fn parse_internal(path: []const u8, protocol: Protocol, buf: *std.ArrayList(Clog .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) } }, + .tcp_v6 => .{ .tcp_v6 = .{ .addr = try parse_v6_address(local_address[0..32]) } }, .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) } }, + .udp_v6 => .{ .udp_v6 = .{ .addr = try parse_v6_address(local_address[0..32]) } }, }, }); } } +// v6 addresses in /proc/net are not actually one big number, but an array of 4 +// 32-bit (4 byte) numbers, each in base-16 little-endian (or just host byte order +// probably) +fn parse_v6_address(data: []const u8) !u128 { + var buf: [4]u32 = undefined; + + buf[0] = try std.fmt.parseInt(u32, data[0..8], 16); + buf[1] = try std.fmt.parseInt(u32, data[8..16], 16); + buf[2] = try std.fmt.parseInt(u32, data[16..24], 16); + buf[3] = try std.fmt.parseInt(u32, data[24..32], 16); + + // now do the fun stuff, just re-interpret the array as u128 + return std.mem.bytesToValue(u128, &buf); +} + +test "parse_v6" { + std.testing.log_level = .info; + + const data = "00000000000000000000000001000000"; + const r = try parse_v6_address(data); + std.log.info("Got {d}", .{r}); +} + test "parse" { var alloc = std.testing.allocator;