Compare commits
13 commits
Author | SHA1 | Date | |
---|---|---|---|
39e9527dcd | |||
2e29b73b84 | |||
37f51c6002 | |||
85f097eb3b | |||
642179da59 | |||
b4c252480c | |||
98e43e1ffd | |||
1657a643ff | |||
056da26cc0 | |||
72ba68744e | |||
633713180d | |||
1b271ea203 | |||
60ecd10e67 |
17 changed files with 767 additions and 433 deletions
16
README.md
Normal file
16
README.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Unclog: The Ultimate Drain Liberator
|
||||||
|
|
||||||
|
- Blasts through stubborn blockages
|
||||||
|
- Safe for all sockets
|
||||||
|
- Eco-friendly formula
|
||||||
|
- Works on tcp, udp, and more
|
||||||
|
- No harsh chemicals
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
Don't let blocked ports slow you down.
|
||||||
|
|
||||||
|
| ![Unclog your ports](res/unclog_small.png) |
|
||||||
|
|:------------------------------------:|
|
||||||
|
| *Clear pipes, clear mind.* |
|
||||||
|
|
||||||
|
</div>
|
38
build.zig
38
build.zig
|
@ -15,17 +15,6 @@ pub fn build(b: *std.Build) void {
|
||||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
const relib = b.addStaticLibrary(.{
|
|
||||||
.name = "regex_slim",
|
|
||||||
.optimize = .Debug,
|
|
||||||
.target = target,
|
|
||||||
});
|
|
||||||
relib.addIncludePath(.{ .path = "lib" });
|
|
||||||
relib.addCSourceFile(.{
|
|
||||||
.file = b.path("lib/regex_slim.c")
|
|
||||||
});
|
|
||||||
relib.linkLibC();
|
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "unclog",
|
.name = "unclog",
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
@ -33,9 +22,6 @@ pub fn build(b: *std.Build) void {
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.link_libc = true,
|
.link_libc = true,
|
||||||
});
|
});
|
||||||
exe.addIncludePath(.{ .path = "lib" });
|
|
||||||
exe.linkLibC();
|
|
||||||
exe.linkLibrary(relib);
|
|
||||||
|
|
||||||
// This declares intent for the executable to be installed into the
|
// This declares intent for the executable to be installed into the
|
||||||
// standard location when the user invokes the "install" step (the default
|
// standard location when the user invokes the "install" step (the default
|
||||||
|
@ -65,21 +51,27 @@ pub fn build(b: *std.Build) void {
|
||||||
const run_step = b.step("run", "Run the app");
|
const run_step = b.step("run", "Run the app");
|
||||||
run_step.dependOn(&run_cmd.step);
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
|
||||||
|
// Creates a step for unit testing. This only builds the test executable
|
||||||
|
// but does not run it.
|
||||||
|
const sockets_unit_tests = b.addTest(.{
|
||||||
|
.root_source_file = b.path("src/sockets.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_sockets_unit_tests = b.addRunArtifact(sockets_unit_tests);
|
||||||
|
run_sockets_unit_tests.has_side_effects = true; // needed so tests aren't cached
|
||||||
|
|
||||||
// Creates a step for unit testing. This only builds the test executable
|
// Creates a step for unit testing. This only builds the test executable
|
||||||
// but does not run it.
|
// but does not run it.
|
||||||
const procnet_unit_tests = b.addTest(.{
|
const process_unit_tests = b.addTest(.{
|
||||||
.root_source_file = b.path("src/procnet.zig"),
|
.root_source_file = b.path("src/process.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.link_libc = true,
|
|
||||||
});
|
});
|
||||||
procnet_unit_tests.addIncludePath(.{ .path = "lib" });
|
|
||||||
procnet_unit_tests.linkLibrary(relib);
|
|
||||||
|
|
||||||
const run_procnet_unit_tests = b.addRunArtifact(procnet_unit_tests);
|
const run_process_unit_tests = b.addRunArtifact(process_unit_tests);
|
||||||
run_procnet_unit_tests.has_side_effects = true; // needed so tests aren't cached
|
run_process_unit_tests.has_side_effects = true; // needed so tests aren't cached
|
||||||
|
|
||||||
const exe_unit_tests = b.addTest(.{
|
const exe_unit_tests = b.addTest(.{
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
@ -87,7 +79,6 @@ pub fn build(b: *std.Build) void {
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.link_libc = true,
|
.link_libc = true,
|
||||||
});
|
});
|
||||||
exe_unit_tests.addIncludePath(.{ .path = "lib" });
|
|
||||||
|
|
||||||
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
||||||
|
|
||||||
|
@ -95,6 +86,7 @@ pub fn build(b: *std.Build) void {
|
||||||
// the `zig build --help` menu, providing a way for the user to request
|
// the `zig build --help` menu, providing a way for the user to request
|
||||||
// running the unit tests.
|
// running the unit tests.
|
||||||
const test_step = b.step("test", "Run unit tests");
|
const test_step = b.step("test", "Run unit tests");
|
||||||
test_step.dependOn(&run_procnet_unit_tests.step);
|
test_step.dependOn(&run_sockets_unit_tests.step);
|
||||||
|
test_step.dependOn(&run_process_unit_tests.step);
|
||||||
test_step.dependOn(&run_exe_unit_tests.step);
|
test_step.dependOn(&run_exe_unit_tests.step);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
// This is a [Semantic Version](https://semver.org/).
|
// This is a [Semantic Version](https://semver.org/).
|
||||||
// In a future version of Zig it will be used for package deduplication.
|
// In a future version of Zig it will be used for package deduplication.
|
||||||
.version = "0.0.0",
|
.version = "0.0.2",
|
||||||
|
|
||||||
// This field is optional.
|
// This field is optional.
|
||||||
// This is currently advisory only; Zig does not yet do anything
|
// This is currently advisory only; Zig does not yet do anything
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
#include "regex_slim.h"
|
|
||||||
|
|
||||||
regex_t* alloc_regex_t(void) {
|
|
||||||
return (regex_t*)malloc(sizeof(regex_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
void free_regex_t(regex_t* ptr) {
|
|
||||||
free(ptr);
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// Workaround for using regex in Zig.
|
|
||||||
// https://www.openmymind.net/Regular-Expressions-in-Zig/
|
|
||||||
// https://stackoverflow.com/questions/73086494/how-to-allocate-a-struct-of-incomplete-type-in-zig
|
|
||||||
#include <regex.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
regex_t* alloc_regex_t(void);
|
|
||||||
void free_regex_t(regex_t* ptr);
|
|
BIN
res/unclog.jpg
Normal file
BIN
res/unclog.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 146 KiB |
BIN
res/unclog_small.png
Normal file
BIN
res/unclog_small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
295
src/main.zig
295
src/main.zig
|
@ -1,110 +1,221 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const proc = @import("procnet.zig");
|
const builtin = @import("builtin");
|
||||||
const pid = @import("procpid.zig");
|
const sockets = @import("sockets.zig");
|
||||||
|
const process = @import("process.zig");
|
||||||
|
const match = @import("match.zig");
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
|
@cInclude("pwd.h");
|
||||||
@cInclude("arpa/inet.h");
|
@cInclude("arpa/inet.h");
|
||||||
|
@cInclude("signal.h");
|
||||||
});
|
});
|
||||||
|
|
||||||
pub const std_options = .{
|
pub const std_options = .{ .log_level = .info };
|
||||||
.log_level = .err
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn main() !void {
|
|
||||||
var argsit = std.process.args();
|
|
||||||
_ = argsit.next() orelse return error.Args;
|
|
||||||
const port = try std.fmt.parseInt(u16, argsit.next().?, 10);
|
|
||||||
|
|
||||||
|
pub fn main() !u8 {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
defer _ = gpa.deinit();
|
defer _ = gpa.deinit();
|
||||||
|
|
||||||
const alloc = gpa.allocator();
|
const alloc = gpa.allocator();
|
||||||
|
|
||||||
var clogged = false;
|
const ports = try_parse_args(alloc) catch |err| {
|
||||||
|
try usage();
|
||||||
var procs = std.ArrayList(pid.Process).init(alloc);
|
if (builtin.mode == std.builtin.Mode.Debug) {
|
||||||
defer procs.deinit();
|
return err;
|
||||||
|
|
||||||
const tcp = try proc.read_proc_net(alloc, .V4, "/proc/net/tcp");
|
|
||||||
defer alloc.free(tcp);
|
|
||||||
for(tcp) |pn| {
|
|
||||||
if(pn.V4.port == port) {
|
|
||||||
clogged = true;
|
|
||||||
try print_proc_net(&pn, "TCP/IPv4");
|
|
||||||
try append_processes(alloc, pn.V4.inode, &procs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
defer alloc.free(ports);
|
||||||
|
|
||||||
|
const clogs = try match.match(alloc, ports);
|
||||||
|
defer clogs.deinit();
|
||||||
|
|
||||||
|
if (clogs.items.len == 0) {
|
||||||
|
try std.io.getStdOut().writeAll("Ports look unclogged\n");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tcp6 = try proc.read_proc_net(alloc, .V6, "/proc/net/tcp6");
|
try print_clogs(clogs);
|
||||||
defer alloc.free(tcp6);
|
|
||||||
for(tcp6) |pn| {
|
|
||||||
if(pn.V6.port == port) {
|
|
||||||
clogged = true;
|
|
||||||
try print_proc_net(&pn, "TCP/IPv6");
|
|
||||||
try append_processes(alloc, pn.V6.inode, &procs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const udp = try proc.read_proc_net(alloc, .V4, "/proc/net/udp");
|
var kills: []u16 = undefined;
|
||||||
defer alloc.free(udp);
|
while(true) {
|
||||||
for(udp) |pn| {
|
kills = choose_kill(alloc, clogs.items.len) catch |err| {
|
||||||
if(pn.V4.port == port) {
|
if (builtin.mode == std.builtin.Mode.Debug) {
|
||||||
clogged = true;
|
try kill_usage();
|
||||||
try print_proc_net(&pn, "UDP/IPv4");
|
return err;
|
||||||
try append_processes(alloc, pn.V4.inode, &procs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const udp6 = try proc.read_proc_net(alloc, .V6, "/proc/net/udp6");
|
|
||||||
defer alloc.free(udp6);
|
|
||||||
for(udp6) |pn| {
|
|
||||||
if(pn.V6.port == port) {
|
|
||||||
clogged = true;
|
|
||||||
try print_proc_net(&pn, "UDP/IPv6");
|
|
||||||
try append_processes(alloc, pn.V6.inode, &procs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!clogged){
|
|
||||||
try std.io.getStdOut().writer().print("Port {d} looks unclogged already\n", .{port});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn append_processes(alloc: std.mem.Allocator, inode: u32, buf: *std.ArrayList(pid.Process)) !void {
|
|
||||||
const pids = try pid.find_proc(alloc, inode);
|
|
||||||
defer alloc.free(pids);
|
|
||||||
try std.io.getStdOut().writer().print("\t{s: <10}{s: <20}{s}\n", .{"PID", "CMD", "ARGS"});
|
|
||||||
|
|
||||||
for(pids) |p| {
|
|
||||||
const process = try pid.resolve_process(alloc, p);
|
|
||||||
defer process.deinit();
|
|
||||||
try buf.append(process);
|
|
||||||
|
|
||||||
const cmdline = try std.mem.join(alloc, " ", process.cmdline[1..]);
|
|
||||||
defer alloc.free(cmdline);
|
|
||||||
try std.io.getStdOut().writer().print("\t{d: <10}{s: <20}{s}\n", .{process.proc_pid.pid, process.comm[0..process.comm.len-1], cmdline});
|
|
||||||
}
|
|
||||||
_ = try std.io.getStdOut().writer().write("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_proc_net(entry: *const proc.ProcNet, addr_type: []const u8) !void {
|
|
||||||
var stdio = std.io.getStdOut();
|
|
||||||
|
|
||||||
switch(entry.*) {
|
|
||||||
.V4 => |v4| {
|
|
||||||
const src_addr_pp = c.inet_ntoa(.{.s_addr = v4.addr}); // allocates static global buffer, don't free
|
|
||||||
try stdio.writer().print("Port {d} clogged on {s} Address {s} with socket inode {d}\n",
|
|
||||||
.{v4.port, addr_type, src_addr_pp, v4.inode});
|
|
||||||
},
|
|
||||||
.V6 => |v6| {
|
|
||||||
try stdio.writer().print("Port {d} clogged on {s} Address ", .{v6.port, addr_type});
|
|
||||||
for(@as(*const [16]u8, @ptrCast(&v6.addr)), 0..) |h, idx| {
|
|
||||||
if(idx % 2 == 0 and idx != 0) {
|
|
||||||
_ = try stdio.writer().write(":");
|
|
||||||
}
|
|
||||||
|
|
||||||
try stdio.writer().print("{X:0<2}", .{h});
|
|
||||||
}
|
}
|
||||||
try stdio.writer().print(" with socket inode {d}\n", .{v6.inode});
|
|
||||||
},
|
try kill_usage();
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
defer alloc.free(kills);
|
||||||
|
|
||||||
|
for(kills) |k| {
|
||||||
|
// go find the kill
|
||||||
|
var procnum: usize = 0;
|
||||||
|
for(clogs.items) |clog| {
|
||||||
|
if(clog.procs.items.len < (k-procnum)) { // kill is in a process in the next clog
|
||||||
|
procnum += clog.procs.items.len;
|
||||||
|
} else { // kill is in this clog - the # of clog process we passed
|
||||||
|
kill(clog.procs.items[k-procnum-1].pid);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_parse_args(alloc: std.mem.Allocator) ![]u16 {
|
||||||
|
var argsit = std.process.args();
|
||||||
|
_ = argsit.next() orelse return error.Args; // program name
|
||||||
|
|
||||||
|
return try_parse_num(alloc, &argsit);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_parse_num(alloc: std.mem.Allocator, iterator: anytype) ![]u16 {
|
||||||
|
var buf = std.ArrayList(u16).init(alloc);
|
||||||
|
defer buf.deinit(); // noop after memory re-owned to caller
|
||||||
|
|
||||||
|
argloop: while (iterator.next()) |arg| {
|
||||||
|
if (std.mem.indexOfScalar(u8, arg, '-')) |i| {
|
||||||
|
const port_start = try std.fmt.parseInt(u16, arg[0..i], 10);
|
||||||
|
const port_end = try std.fmt.parseInt(u16, arg[i + 1 ..], 10);
|
||||||
|
|
||||||
|
if (port_start > port_end+1) return error.InvalidPortRange;
|
||||||
|
|
||||||
|
for (port_start..port_end+1) |p| {
|
||||||
|
try buf.append(@intCast(p));
|
||||||
|
}
|
||||||
|
continue :argloop;
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = try std.fmt.parseInt(u16, arg, 10);
|
||||||
|
try buf.append(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
return try buf.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kill(pid: std.posix.pid_t) void {
|
||||||
|
if (c.kill(pid, c.SIGTERM) == 0) {
|
||||||
|
for(0..10) |_| { // wait up to 10 sec
|
||||||
|
// Wait briefly for process to exit
|
||||||
|
std.time.sleep(100000000); // ns = 0.1s
|
||||||
|
|
||||||
|
// Check if process already dead
|
||||||
|
if (c.kill(pid, 0) == -1) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.kill(pid, 0) == 0) { // Check if process still exists
|
||||||
|
_ = c.kill(pid, c.SIGKILL); // ...and try with force
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn choose_kill(alloc: std.mem.Allocator, choices: usize) ![]u16 {
|
||||||
|
var stdin = std.io.getStdIn().reader();
|
||||||
|
var stdout = std.io.getStdOut().writer();
|
||||||
|
|
||||||
|
try stdout.writeAll("Kill? ");
|
||||||
|
|
||||||
|
const buf = try stdin.readUntilDelimiterAlloc(alloc, '\n', 1024);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var iterator = std.mem.splitScalar(u8, buf, ' ');
|
||||||
|
|
||||||
|
const kills = try try_parse_num(alloc, &iterator);
|
||||||
|
errdefer alloc.free(kills);
|
||||||
|
|
||||||
|
for(kills) |k| {
|
||||||
|
if(k > choices or k < 1) {
|
||||||
|
return error.InvalidKill;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return kills;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_clogs(clogs: match.Clogs) !void {
|
||||||
|
var writer = std.io.getStdOut().writer();
|
||||||
|
|
||||||
|
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 (clogs.items) |clog| {
|
||||||
|
const user = c.getpwuid(clog.sock.uid);
|
||||||
|
|
||||||
|
for (clog.procs.items) |proc| {
|
||||||
|
switch (clog.sock.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,
|
||||||
|
proc.comm[0..@min(10, proc.comm.len)], proc.exe[0..@min(30, proc.exe.len)],
|
||||||
|
"TCP/IPv4", std.mem.sliceTo(&addr, 0),
|
||||||
|
user.*.pw_name, clog.sock.inode, clog.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,
|
||||||
|
proc.comm[0..@min(10, proc.comm.len)], proc.exe[0..@min(30, proc.exe.len)],
|
||||||
|
"TCP/IPv6", std.mem.sliceTo(&addr, 0),
|
||||||
|
user.*.pw_name, clog.sock.inode, clog.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,
|
||||||
|
proc.comm[0..@min(10, proc.comm.len)], proc.exe[0..@min(30, proc.exe.len)],
|
||||||
|
"UDP/IPv4", std.mem.sliceTo(&addr, 0),
|
||||||
|
user.*.pw_name, clog.sock.inode, clog.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,
|
||||||
|
proc.comm[0..@min(10, proc.comm.len)], proc.exe[0..@min(30, proc.exe.len)],
|
||||||
|
"UDP/IPv6", std.mem.sliceTo(&addr, 0),
|
||||||
|
user.*.pw_name, clog.sock.inode, clog.port });
|
||||||
|
},
|
||||||
|
// zig fmt: on
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage() !void {
|
||||||
|
var stdout = std.io.getStdOut().writer();
|
||||||
|
|
||||||
|
try stdout.writeAll(
|
||||||
|
\\USAGE: unclog <port(s)>
|
||||||
|
\\
|
||||||
|
\\EXAMPLE: unclog 8080
|
||||||
|
\\ unclog 8080-9090
|
||||||
|
\\ unclog 8080 8081 9000-9090
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kill_usage() !void {
|
||||||
|
var stdout = std.io.getStdOut().writer();
|
||||||
|
|
||||||
|
try stdout.writeAll(
|
||||||
|
\\Invalid kill choice
|
||||||
|
\\
|
||||||
|
\\EXAMPLES: 1
|
||||||
|
\\ 1-3
|
||||||
|
\\ 1 3 7-9 5
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
55
src/match.zig
Normal file
55
src/match.zig
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const process = @import("process.zig");
|
||||||
|
const sockets = @import("sockets.zig");
|
||||||
|
|
||||||
|
pub const Clogs = struct {
|
||||||
|
items: []Clog,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn deinit(self: @This()) void {
|
||||||
|
for(self.items) |clog| {
|
||||||
|
clog.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.alloc.free(self.items);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Clog = struct {
|
||||||
|
port: u16,
|
||||||
|
sock: sockets.ClogSocket,
|
||||||
|
procs: process.ClogProcesses,
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn deinit(self: @This()) void {
|
||||||
|
self.procs.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn match(alloc: std.mem.Allocator, ports: []u16) !Clogs {
|
||||||
|
var buf = std.ArrayList(Clog).init(alloc);
|
||||||
|
defer(buf.deinit()); // noop after re-owned at end
|
||||||
|
|
||||||
|
const socks = try sockets.parse(alloc, null);
|
||||||
|
defer alloc.free(socks);
|
||||||
|
|
||||||
|
for (socks) |sock| {
|
||||||
|
if (std.mem.indexOfScalar(u16, ports, sock.port) == null) continue;
|
||||||
|
|
||||||
|
const procs = try process.find_by_inode(alloc, sock.inode, null);
|
||||||
|
defer procs.deinit();
|
||||||
|
|
||||||
|
try buf.append(Clog {
|
||||||
|
.port = sock.port,
|
||||||
|
.sock = sock.clone(),
|
||||||
|
.procs = try procs.clone(alloc),
|
||||||
|
.alloc = alloc,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Clogs{
|
||||||
|
.items = try buf.toOwnedSlice(),
|
||||||
|
.alloc = alloc,
|
||||||
|
};
|
||||||
|
}
|
240
src/process.zig
Normal file
240
src/process.zig
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
//! Find and get information about a processes
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// A simple array of clogging processes. Mainly a memory-owning wrapper for
|
||||||
|
/// handling deallocation correctly. Call `deinit` to deallocate the memory.
|
||||||
|
///
|
||||||
|
/// Processes can be accessed via `.items`
|
||||||
|
pub const ClogProcesses = struct {
|
||||||
|
items: []ClogProcess,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||||
|
try writer.print("{any}", .{value.items});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone(self: @This(), alloc: std.mem.Allocator) !ClogProcesses {
|
||||||
|
var items = try alloc.alloc(ClogProcess, self.items.len);
|
||||||
|
for(self.items, 0..) |item, idx| {
|
||||||
|
items[idx] = try item.clone(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ClogProcesses {
|
||||||
|
.items = items,
|
||||||
|
.alloc = alloc
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: @This()) void {
|
||||||
|
for (self.items) |item| {
|
||||||
|
item.deinit();
|
||||||
|
}
|
||||||
|
self.alloc.free(self.items);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Process metadata of a clogging process
|
||||||
|
pub const ClogProcess = struct {
|
||||||
|
// central data
|
||||||
|
pid: std.posix.pid_t,
|
||||||
|
inode: std.posix.ino_t,
|
||||||
|
fd: std.posix.fd_t,
|
||||||
|
comm: []u8,
|
||||||
|
exe: []u8,
|
||||||
|
cmdline: [][]u8,
|
||||||
|
|
||||||
|
// for housekeeping
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||||
|
try writer.writeAll("\nClogProcess{\n");
|
||||||
|
try writer.print("\t.pid={d}, .inode={d}, .fd={d},\n", .{ value.pid, value.inode, value.fd });
|
||||||
|
try writer.print("\t.comm={s}\n", .{value.comm});
|
||||||
|
try writer.print("\t.exe={s}\n", .{value.exe});
|
||||||
|
try writer.print("\t.cmdline={s}\n", .{value.cmdline});
|
||||||
|
try writer.writeAll("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone(self: @This(), alloc: std.mem.Allocator) !ClogProcess {
|
||||||
|
var cmdline: [][]u8 = try alloc.alloc([]u8, self.cmdline.len);
|
||||||
|
for(self.cmdline, 0..) |line, idx| {
|
||||||
|
cmdline[idx] = try alloc.dupe(u8, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ClogProcess {
|
||||||
|
.pid = self.pid,
|
||||||
|
.inode = self.inode,
|
||||||
|
.fd = self.fd,
|
||||||
|
.comm = try alloc.dupe(u8, self.comm),
|
||||||
|
.exe = try alloc.dupe(u8, self.exe),
|
||||||
|
.cmdline = cmdline,
|
||||||
|
|
||||||
|
.alloc = alloc
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: @This()) void {
|
||||||
|
self.alloc.free(self.comm);
|
||||||
|
self.alloc.free(self.exe);
|
||||||
|
for (self.cmdline) |cl| {
|
||||||
|
self.alloc.free(cl);
|
||||||
|
}
|
||||||
|
self.alloc.free(self.cmdline);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Find clogging processes that hold a file handle on an inode
|
||||||
|
pub fn find_by_inode(alloc: std.mem.Allocator, inode: std.posix.ino_t, proc_path: ?[]const u8) !ClogProcesses {
|
||||||
|
const base = proc_path orelse "/proc";
|
||||||
|
|
||||||
|
var clogs = std.ArrayList(ClogProcess).init(alloc);
|
||||||
|
defer clogs.deinit(); // noop we re-own memory at the end
|
||||||
|
|
||||||
|
var proc_dir = try std.fs.openDirAbsolute(base, .{ .iterate = true });
|
||||||
|
defer proc_dir.close();
|
||||||
|
|
||||||
|
var process_it = PidIterator{ .it = AccessSafeIterator{ .it = proc_dir.iterate() } };
|
||||||
|
while (try process_it.next()) |process_entry| {
|
||||||
|
var process_dir = proc_dir.openDir(process_entry.name, .{ .iterate = true }) catch |err| switch (err) {
|
||||||
|
error.AccessDenied => continue,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
defer process_dir.close();
|
||||||
|
|
||||||
|
var fd_dir = process_dir.openDir("fd", .{ .iterate = true }) catch |err| switch (err) {
|
||||||
|
error.AccessDenied => continue,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
defer fd_dir.close();
|
||||||
|
|
||||||
|
var fd_it = FdIterator{ .it = AccessSafeIterator{ .it = fd_dir.iterate() } };
|
||||||
|
fdit: while (try fd_it.next()) |fd_entry| {
|
||||||
|
_ = std.fmt.parseInt(std.posix.pid_t, fd_entry.name, 10) catch continue;
|
||||||
|
const fd_stat = fd_dir.statFile(fd_entry.name) catch |err| switch (err) {
|
||||||
|
error.AccessDenied => continue,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fd_stat.inode == inode) {
|
||||||
|
std.log.debug("Found inode {d} in {s}/{s}", .{ fd_stat.inode, process_entry.name, fd_entry.name });
|
||||||
|
|
||||||
|
const pid = try std.fmt.parseInt(std.posix.pid_t, process_entry.name, 10);
|
||||||
|
for (clogs.items) |clog| {
|
||||||
|
if (clog.pid == pid) {
|
||||||
|
std.log.debug("pid {d} already in clogs", .{pid});
|
||||||
|
continue :fdit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pfd: ProcessFileData = try parse_process_files(alloc, &process_dir) orelse continue;
|
||||||
|
|
||||||
|
try clogs.append(ClogProcess{
|
||||||
|
.pid = pid,
|
||||||
|
.inode = fd_stat.inode,
|
||||||
|
.fd = try std.fmt.parseInt(std.posix.pid_t, fd_entry.name, 10),
|
||||||
|
.comm = pfd.comm,
|
||||||
|
.cmdline = pfd.cmdline,
|
||||||
|
.exe = pfd.exe,
|
||||||
|
.alloc = alloc,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ClogProcesses{ .items = try clogs.toOwnedSlice(), .alloc = alloc };
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProcessFileData = struct {
|
||||||
|
comm: []u8,
|
||||||
|
cmdline: [][]u8,
|
||||||
|
exe: []u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn parse_process_files(alloc: std.mem.Allocator, process_dir: *const std.fs.Dir) !?ProcessFileData {
|
||||||
|
var comm_file = process_dir.openFile("comm", .{}) catch |err| switch (err) {
|
||||||
|
error.AccessDenied => return null,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
var cmdline_file = process_dir.openFile("cmdline", .{}) catch |err| switch (err) {
|
||||||
|
error.AccessDenied => return null,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
var exe_path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
|
const exe_path = process_dir.readLink("exe", &exe_path_buf) catch |err| switch (err) {
|
||||||
|
error.AccessDenied => return null,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
const comm = try comm_file.readToEndAlloc(alloc, 4096);
|
||||||
|
defer alloc.free(comm);
|
||||||
|
|
||||||
|
var cmdline = std.ArrayList([]u8).init(alloc);
|
||||||
|
const cmdline_raw = try cmdline_file.readToEndAlloc(alloc, 4096);
|
||||||
|
defer alloc.free(cmdline_raw);
|
||||||
|
var cmdline_it = std.mem.splitScalar(u8, cmdline_raw, 0x0);
|
||||||
|
while (cmdline_it.next()) |cl| {
|
||||||
|
try cmdline.append(try alloc.dupe(u8, std.mem.trim(u8, cl, "\n")));
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.comm = try alloc.dupe(u8, std.mem.trim(u8, comm, "\n")),
|
||||||
|
.cmdline = try cmdline.toOwnedSlice(),
|
||||||
|
.exe = try alloc.dupe(u8, exe_path),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const FdIterator = struct {
|
||||||
|
it: AccessSafeIterator,
|
||||||
|
|
||||||
|
pub fn next(self: *@This()) !?std.fs.Dir.Entry {
|
||||||
|
return try self.it.next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Iterator over directories that look like a process directory
|
||||||
|
const PidIterator = struct {
|
||||||
|
it: AccessSafeIterator,
|
||||||
|
|
||||||
|
pub fn next(self: *@This()) !?std.fs.Dir.Entry {
|
||||||
|
while (try self.it.next()) |entry| {
|
||||||
|
if (entry.kind == .directory) {
|
||||||
|
_ = std.fmt.parseInt(std.posix.pid_t, entry.name, 10) catch continue;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Iterator which ignores `AccessDenied` errors from child iterator
|
||||||
|
const AccessSafeIterator = struct {
|
||||||
|
it: std.fs.Dir.Iterator,
|
||||||
|
|
||||||
|
pub fn next(self: *@This()) !?std.fs.Dir.Entry {
|
||||||
|
while (true) {
|
||||||
|
const elem = self.it.next() catch |err| switch (err) {
|
||||||
|
error.AccessDenied => continue,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (elem) |e| {
|
||||||
|
self.it.dir.access(e.name, .{}) catch continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "simple" {
|
||||||
|
std.testing.log_level = .info;
|
||||||
|
|
||||||
|
const clogs = try find_by_inode(std.testing.allocator, 721, null);
|
||||||
|
defer clogs.deinit();
|
||||||
|
std.log.info("clogs: {any}", .{clogs});
|
||||||
|
}
|
187
src/procnet.zig
187
src/procnet.zig
|
@ -1,187 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const log = std.log;
|
|
||||||
const testing = std.testing;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const c = @cImport({
|
|
||||||
@cInclude("arpa/inet.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
pub const ProcNet = union(enum) {
|
|
||||||
V4: struct {
|
|
||||||
addr: u32,
|
|
||||||
port: u16,
|
|
||||||
inode: u32
|
|
||||||
},
|
|
||||||
V6: struct {
|
|
||||||
addr: u128,
|
|
||||||
port: u16,
|
|
||||||
inode: u32
|
|
||||||
},
|
|
||||||
|
|
||||||
pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
|
||||||
switch(value) {
|
|
||||||
.V4 => |v4| {
|
|
||||||
const src_addr_pp = c.inet_ntoa(.{.s_addr = v4.addr}); // allocates static global buffer, don't free
|
|
||||||
try writer.print("{s: <15}{d: <10}{d}", .{src_addr_pp, v4.port, v4.inode});
|
|
||||||
},
|
|
||||||
|
|
||||||
.V6 => |v6| {
|
|
||||||
for(@as(*const [16]u8, @ptrCast(&v6.addr)), 0..) |h, idx| {
|
|
||||||
if(idx % 2 == 0 and idx != 0) {
|
|
||||||
_ = try writer.write(":");
|
|
||||||
}
|
|
||||||
|
|
||||||
try writer.print("{X:0<2}", .{h});
|
|
||||||
}
|
|
||||||
|
|
||||||
try writer.print("\t{d: <10}{d}", .{v6.port, v6.inode});
|
|
||||||
},
|
|
||||||
|
|
||||||
.RAW => |raw| {
|
|
||||||
try writer.print("{d} {d}", .{raw.port, raw.inode});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
|
||||||
const proc_net_parsed: [][3][]u8 = try parse_proc_net(arena.allocator(), path);
|
|
||||||
|
|
||||||
var buf = try std.ArrayList(ProcNet).initCapacity(alloc, proc_net_parsed.len);
|
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
const addr_len_t = switch(addr_len){.V4 => u32, .V6 => u128};
|
|
||||||
|
|
||||||
for(proc_net_parsed) |line| {
|
|
||||||
const src_addr = try std.fmt.parseUnsigned(addr_len_t, line[0], 16);
|
|
||||||
const src_port = try std.fmt.parseUnsigned(u16, line[1], 16);
|
|
||||||
const inode = try std.fmt.parseUnsigned(u32, line[2], 10);
|
|
||||||
|
|
||||||
|
|
||||||
try buf.append(
|
|
||||||
switch(addr_len) {
|
|
||||||
.V4 => ProcNet{.V4 = .{.addr = src_addr, .port = src_port, .inode = inode}},
|
|
||||||
.V6 => ProcNet{.V6 = .{.addr = src_addr, .port = src_port, .inode = inode}},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.toOwnedSlice();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_proc_net(alloc: Allocator, path: []const u8) ![][3][]u8 {
|
|
||||||
var proc_net_file = try std.fs.openFileAbsolute(path, .{});
|
|
||||||
defer proc_net_file.close();
|
|
||||||
|
|
||||||
var buf_reader = std.io.bufferedReader(proc_net_file.reader());
|
|
||||||
var reader = buf_reader.reader();
|
|
||||||
|
|
||||||
try reader.skipUntilDelimiterOrEof('\n'); // skip header
|
|
||||||
|
|
||||||
// allocates caller-owned memory
|
|
||||||
var res = std.ArrayList([3][]u8).init(alloc);
|
|
||||||
defer res.deinit();
|
|
||||||
|
|
||||||
// used for internal allocations, owned
|
|
||||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
|
||||||
defer arena.deinit();
|
|
||||||
|
|
||||||
while(try reader.readUntilDelimiterOrEofAlloc(arena.allocator(), '\n', 256)) |line| {
|
|
||||||
|
|
||||||
var tokens = std.ArrayList([]const u8).init(arena.allocator()); // buffer for a line in the proc file split by tokenizer
|
|
||||||
defer tokens.deinit();
|
|
||||||
var tokenizer = std.mem.tokenize(u8, line, " \t\n");
|
|
||||||
while(tokenizer.next()) |elem| { try tokens.append(elem); }
|
|
||||||
|
|
||||||
var src_it = std.mem.splitScalar(u8, tokens.items[1], ':');
|
|
||||||
|
|
||||||
try res.append(
|
|
||||||
.{
|
|
||||||
try alloc.dupe(u8, src_it.next().?),
|
|
||||||
try alloc.dupe(u8, src_it.next().?),
|
|
||||||
try alloc.dupe(u8, tokens.items[9])
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.toOwnedSlice();
|
|
||||||
}
|
|
||||||
|
|
||||||
// test "basic 2" {
|
|
||||||
// std.testing.log_level = .info;
|
|
||||||
// const alloc = std.testing.allocator;
|
|
||||||
// try read_proc_net(alloc);
|
|
||||||
// }
|
|
||||||
|
|
||||||
test "basic 3" {
|
|
||||||
std.testing.log_level = .info;
|
|
||||||
const alloc = std.testing.allocator;
|
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
|
||||||
defer arena.deinit();
|
|
||||||
|
|
||||||
const res = try parse_proc_net(arena.allocator(), "/proc/net/tcp");
|
|
||||||
for(res) |line| {
|
|
||||||
for(line) |field| {
|
|
||||||
std.debug.print("{s}\t", .{field});
|
|
||||||
}
|
|
||||||
std.debug.print("\n", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "basic 4" {
|
|
||||||
std.testing.log_level = .info;
|
|
||||||
const alloc = std.testing.allocator;
|
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
|
||||||
defer arena.deinit();
|
|
||||||
|
|
||||||
const res = try read_proc_net(arena.allocator(), .V4, "/proc/net/tcp");
|
|
||||||
for(res) |pn| {
|
|
||||||
std.debug.print("{any}\n", .{pn});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "basic 5" {
|
|
||||||
std.testing.log_level = .info;
|
|
||||||
const alloc = std.testing.allocator;
|
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
|
||||||
defer arena.deinit();
|
|
||||||
|
|
||||||
const res = try read_proc_net(arena.allocator(), .V6, "/proc/net/tcp6");
|
|
||||||
for(res) |pn| {
|
|
||||||
std.debug.print("{any}\n", .{pn});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "basic 6" {
|
|
||||||
std.testing.log_level = .info;
|
|
||||||
const alloc = std.testing.allocator;
|
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
|
||||||
defer arena.deinit();
|
|
||||||
|
|
||||||
const res = try read_proc_net(arena.allocator(), .V4, "/proc/net/udp");
|
|
||||||
for(res) |pn| {
|
|
||||||
std.debug.print("{any}\n", .{pn});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "basic 7" {
|
|
||||||
std.testing.log_level = .info;
|
|
||||||
const alloc = std.testing.allocator;
|
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
|
||||||
defer arena.deinit();
|
|
||||||
|
|
||||||
const res = try read_proc_net(arena.allocator(), .V6, "/proc/net/udp6");
|
|
||||||
for(res) |pn| {
|
|
||||||
std.debug.print("{any}\n", .{pn});
|
|
||||||
}
|
|
||||||
}
|
|
113
src/procpid.zig
113
src/procpid.zig
|
@ -1,113 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const log = std.log;
|
|
||||||
const testing = std.testing;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const c = @cImport({
|
|
||||||
@cInclude("regex.h");
|
|
||||||
@cInclude("regex_slim.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const ProcPid = struct {
|
|
||||||
pid: u32,
|
|
||||||
inode: u32,
|
|
||||||
fd: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Process = struct {
|
|
||||||
proc_pid: ProcPid,
|
|
||||||
comm: []u8,
|
|
||||||
cmdline: [][]u8,
|
|
||||||
alloc: std.heap.ArenaAllocator,
|
|
||||||
|
|
||||||
pub fn deinit(self: @This()) void {
|
|
||||||
self.alloc.deinit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn find_proc(alloc: Allocator, inode: u32) ![]ProcPid {
|
|
||||||
var proc_dir = try std.fs.openDirAbsolute("/proc", .{ .iterate = true });
|
|
||||||
defer proc_dir.close();
|
|
||||||
|
|
||||||
const regex_t = c.alloc_regex_t() orelse {
|
|
||||||
log.err("Could not allocate regex_t memory", .{});
|
|
||||||
return error.RegexAllocFailed;
|
|
||||||
};
|
|
||||||
defer c.free_regex_t(regex_t);
|
|
||||||
|
|
||||||
if (c.regcomp(regex_t, "^[0-9]\\{1,\\}/fd/[0-9]\\{1,\\}$", c.REG_NOSUB) != 0) {
|
|
||||||
return error.REGCOMP;
|
|
||||||
}
|
|
||||||
|
|
||||||
var walker = try proc_dir.walk(alloc);
|
|
||||||
defer walker.deinit();
|
|
||||||
|
|
||||||
var buf = std.ArrayList(ProcPid).init(alloc);
|
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const entry = walker.next() catch |err| switch (err) {
|
|
||||||
error.AccessDenied => continue,
|
|
||||||
else => return err,
|
|
||||||
} orelse {
|
|
||||||
log.info("No more entry. Exiting.", .{});
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (c.regexec(regex_t, entry.path, 0, null, 0) == 0) {
|
|
||||||
const stat = proc_dir.statFile(entry.path) catch |err| switch(err) {
|
|
||||||
error.AccessDenied => continue,
|
|
||||||
else => return err
|
|
||||||
};
|
|
||||||
|
|
||||||
if(stat.kind == .unix_domain_socket) {
|
|
||||||
if(stat.inode == inode) {
|
|
||||||
log.info("Found procpid path {s}", .{entry.path});
|
|
||||||
|
|
||||||
// <pid>/fd/<fd>
|
|
||||||
var compit = try std.fs.path.componentIterator(entry.path);
|
|
||||||
const pid = try std.fmt.parseInt(u32, compit.next().?.name, 10); // parse <pid>
|
|
||||||
_ = compit.next().?.name; // skip /fd/
|
|
||||||
const fd = try std.fmt.parseInt(u32, compit.next().?.name, 10); // parse <fd>
|
|
||||||
|
|
||||||
try buf.append(ProcPid{
|
|
||||||
.pid = pid,
|
|
||||||
.inode = inode,
|
|
||||||
.fd = fd
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.toOwnedSlice();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_process(alloc: Allocator, proc_pid: ProcPid) !Process {
|
|
||||||
var fmt_buf = [_]u8{0}**20;
|
|
||||||
const path = try std.fmt.bufPrint(&fmt_buf, "/proc/{d}", .{proc_pid.pid});
|
|
||||||
const proc_dir = try std.fs.openDirAbsolute(path, .{});
|
|
||||||
|
|
||||||
var arena_alloc = std.heap.ArenaAllocator.init(alloc);
|
|
||||||
|
|
||||||
const comm_file = try proc_dir.openFile("comm", .{});
|
|
||||||
const comm = try comm_file.reader().readAllAlloc(arena_alloc.allocator(), 4096);
|
|
||||||
|
|
||||||
const cmdline_file = try proc_dir.openFile("cmdline", .{});
|
|
||||||
const cmdline_raw = try cmdline_file.reader().readAllAlloc(alloc, 4096);
|
|
||||||
defer alloc.free(cmdline_raw);
|
|
||||||
var cmdline_it = std.mem.splitScalar(u8, cmdline_raw, 0x0);
|
|
||||||
|
|
||||||
var cmdline_buf = std.ArrayList([]u8).init(arena_alloc.allocator());
|
|
||||||
defer cmdline_buf.deinit();
|
|
||||||
while(cmdline_it.next()) |cmdline_elem| {
|
|
||||||
try cmdline_buf.append(try arena_alloc.allocator().dupe(u8, cmdline_elem));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Process{
|
|
||||||
.proc_pid = proc_pid,
|
|
||||||
.comm = comm,
|
|
||||||
.cmdline = try cmdline_buf.toOwnedSlice(),
|
|
||||||
.alloc = arena_alloc
|
|
||||||
};
|
|
||||||
}
|
|
211
src/sockets.zig
Normal file
211
src/sockets.zig
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
//! 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,
|
||||||
|
|
||||||
|
pub fn clone(self: @This()) ClogSocket {
|
||||||
|
return ClogSocket {
|
||||||
|
.port = self.port,
|
||||||
|
.inode = self.inode,
|
||||||
|
.uid = self.uid,
|
||||||
|
.protocol_data = self.protocol_data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// If proc_path is `null` will default to `/proc/net`
|
||||||
|
pub fn parse(alloc: std.mem.Allocator, proc_path: ?[]u8) ![]ClogSocket {
|
||||||
|
const base = proc_path orelse "/proc/net";
|
||||||
|
|
||||||
|
var buf = std.ArrayList(ClogSocket).init(alloc);
|
||||||
|
defer buf.deinit(); // noop we re-own memory to parent at the end
|
||||||
|
|
||||||
|
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" });
|
||||||
|
|
||||||
|
parse_internal(tcp_v4_path, .tcp_v4, &buf) catch |err| {
|
||||||
|
switch(err) {
|
||||||
|
error.FileNotFound => {
|
||||||
|
std.log.warn("{s} not found, skipping", .{tcp_v4_path});
|
||||||
|
},
|
||||||
|
else => return err
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
parse_internal(tcp_v6_path, .tcp_v6, &buf) catch |err| {
|
||||||
|
switch(err) {
|
||||||
|
error.FileNotFound => {
|
||||||
|
std.log.warn("{s} not found, skipping", .{tcp_v6_path});
|
||||||
|
},
|
||||||
|
else => return err
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
parse_internal(udp_v4_path, .udp_v4, &buf) catch |err| {
|
||||||
|
switch(err) {
|
||||||
|
error.FileNotFound => {
|
||||||
|
std.log.warn("{s} not found, skipping", .{udp_v4_path});
|
||||||
|
},
|
||||||
|
else => return err
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
parse_internal(udp_v6_path, .udp_v6, &buf) catch |err| {
|
||||||
|
switch(err) {
|
||||||
|
error.FileNotFound => {
|
||||||
|
std.log.warn("{s} not found, skipping", .{udp_v6_path});
|
||||||
|
},
|
||||||
|
else => return err
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 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 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;
|
||||||
|
|
||||||
|
std.testing.log_level = .info;
|
||||||
|
|
||||||
|
const file_path = try std.fs.cwd().realpathAlloc(alloc, "./test/proc1");
|
||||||
|
defer alloc.free(file_path);
|
||||||
|
|
||||||
|
const clogs = try parse(alloc, file_path);
|
||||||
|
defer alloc.free(clogs);
|
||||||
|
}
|
8
test/proc1/tcp
Normal file
8
test/proc1/tcp
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
|
||||||
|
0: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 7987 1 00000000510949ee 99 0 0 10 0
|
||||||
|
1: 0100007F:0521 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 229198 1 000000000cc7de62 99 0 0 10 0
|
||||||
|
2: 017AA8C0:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12004 1 000000000e13fb3e 99 0 0 10 5
|
||||||
|
3: 9600A8C0:91BE 1A71528C:01BB 01 00000000:00000000 02:00005772 00000000 1000 0 312207 2 0000000039cb8c9e 61 3 31 10 -1
|
||||||
|
4: 9600A8C0:8CF0 5DF36B22:01BB 01 00000000:00000000 02:00001FA2 00000000 1000 0 264134 2 00000000a051e7c1 22 3 31 10 -1
|
||||||
|
5: 9600A8C0:B0A6 AC4C825E:01BB 01 00000000:00000000 02:00097528 00000000 1000 0 304874 2 000000009579cfcc 22 3 30 10 -1
|
||||||
|
6: 9600A8C0:D042 AC4C825E:01BB 01 00000000:00000000 02:0005F59F 00000000 1000 0 279726 2 00000000b9879d52 22 3 24 10 -1
|
7
test/proc1/tcp6
Normal file
7
test/proc1/tcp6
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
|
||||||
|
0: 00000000000000000000000001000000:0277 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 7986 1 00000000a819e168 99 0 0 10 0
|
||||||
|
1: 0000000000000000FFFF00000100007F:CE5D 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 44943 1 000000009005f71a 99 0 0 10 0
|
||||||
|
2: 8883022A00E0C11A96B7CA3E649815D5:9B08 00470626000000000000000034B11368:01BB 06 00000000:00000000 03:00001606 00000000 0 0 0 3 00000000f49b65c7
|
||||||
|
3: 8883022A00E0C11A96B7CA3E649815D5:9A5A 7C060120F02001140000000001000000:01BB 01 00000000:00000000 00:00000000 00000000 1000 0 291092 1 0000000038d2a6ed 23 3 30 10 -1
|
||||||
|
4: 8883022A00E0C11A96B7CA3E649815D5:E52E 5014002A0C080D40000000000E200000:01BB 01 00000000:00000000 00:00000000 00000000 1000 0 320332 1 00000000ec54adc7 23 3 31 10 -1
|
||||||
|
5: 8883022A00E0C11A96B7CA3E649815D5:A0BA C011002A000066000000000033000000:01BB 01 00000000:00000000 00:00000000 00000000 1000 0 306175 1 00000000e41ac297 23 3 28 10 -1
|
6
test/proc1/udp
Normal file
6
test/proc1/udp
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
|
||||||
|
17624: 017AA8C0:0035 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 12003 2 000000000be6b861 0
|
||||||
|
17638: 00000000:0043 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 12000 2 00000000183b2711 0
|
||||||
|
17639: 9600A8C0:0044 0100A8C0:0043 01 00000000:00000000 00:00000000 00000000 0 0 276252 2 000000000a09b1ba 0
|
||||||
|
22924: 00000000:14E9 00000000:0000 07 00000000:00000000 00:00000000 00000000 974 0 3352 2 000000002c689e03 0
|
||||||
|
31811: 00000000:B7A0 00000000:0000 07 00000000:00000000 00:00000000 00000000 974 0 3354 2 0000000071428bb3 0
|
5
test/proc1/udp6
Normal file
5
test/proc1/udp6
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
|
||||||
|
18117: 000080FE000000000716539EF9245F4B:0222 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 274919 2 00000000503fce27 0
|
||||||
|
20307: 00000000000000000000000000000000:8AB0 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 974 0 3355 2 00000000e8a66559 0
|
||||||
|
22924: 00000000000000000000000000000000:14E9 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 974 0 3353 2 00000000f71f77f1 0
|
||||||
|
30065: 00000000000000000000000000000000:B0CE 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 1000 0 316229 2 000000008792aef6 640
|
Loading…
Reference in a new issue