// netcheck's Go tools bundle (novox/hq ADR 0188, ADR 0193): a process the node's runtime launches // and speaks MCP over stdio to, through the Go SDK. It serves the two checks that are the machine's // own sockets and resolver — a TCP connect, a DNS lookup (of the machine's resolver, or of one named), // what listens here, and the private network's peers (hq issue 522) — and nothing that changes anything. // Beside them it watches the tunnel and says on the bus when a peer's endpoint moves or its handshake goes // stale or fresh again. // The module's HTTP check is its TypeScript bundle; the runtime serves both under one module. package main import ( "context" "fmt" "os" "strings" "time" stdio "git.novox.be/novox/mesh-sdk/go" ) func main() { go watchTunnel(func(event string, body any) error { return stdio.Emit(event, body) }) // An empty name serves as the module the runtime names (MESH_SERVED_MODULE): netcheck. if err := stdio.Serve("", tools()); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func tools() []stdio.Tool { return []stdio.Tool{ { Name: "netcheck_tunnel", Description: "The private network's peers as this machine sees them (what wg show reads): each peer's machine where " + "the mesh's names say it, its allowed ips, the endpoint (address:port) it was last heard from, its last handshake " + "and that handshake's age, whether it is stale (older than three minutes, or never), and the bytes received and " + "sent. Never a key. On the hub it says what address and port every machine reaches it from.", Run: func(map[string]any) (any, error) { cx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() return readTunnel(cx) }, }, { Name: "netcheck_listening", Description: "What listens on this machine: every TCP socket in the listen state and every bound UDP socket, " + "from the kernel's own tables (what ss -lunt reads), with the program holding it where this tool may see it. " + "port narrows it to one port, e.g. 53 to see on which addresses the resolver answers.", Input: map[string]any{ "port": map[string]any{"type": "integer", "description": "only this port (default every port)"}, }, Run: func(args map[string]any) (any, error) { port := 0 if _, given := args["port"]; given { p, err := whole(args, "port", 0) if err != nil { return nil, err } if p < 1 || p > 65535 { return nil, fmt.Errorf("port %d is not a port (1-65535)", p) } port = p } return Listening("/proc", port), nil }, }, { Name: "netcheck_tcp", Description: "Check whether a TCP port is reachable from this machine: opens one connection " + "and closes it at once, sending nothing. Answers reachable, elapsed_ms and the error when not.", Input: map[string]any{ "host": map[string]any{"type": "string", "description": "host name or IP address"}, "port": map[string]any{"type": "integer", "description": "TCP port, 1-65535"}, "timeout_ms": map[string]any{"type": "integer", "description": "give up after this long (default 3000, at most 30000)"}, }, Run: func(args map[string]any) (any, error) { host, err := text(args, "host") if err != nil { return nil, err } port, err := whole(args, "port", 0) if err != nil { return nil, err } timeout, err := timeoutOf(args) if err != nil { return nil, err } return CheckTCP(host, port, timeout) }, }, { Name: "netcheck_dns", Description: "Look a name up with this machine's resolver (its /etc/resolv.conf and /etc/hosts), or — with server — ask " + "that one resolver alone over UDP (or TCP), answering its response code (NOERROR, NXDOMAIN, REFUSED…). " + "type is A, AAAA, CNAME, TXT or MX; answers the records found, or the error.", Input: map[string]any{ "name": map[string]any{"type": "string", "description": "the name to look up"}, "type": map[string]any{"type": "string", "enum": []string{"A", "AAAA", "CNAME", "TXT", "MX"}, "description": "record type (default A)"}, "server": map[string]any{"type": "string", "description": "ask this resolver alone: an address, with :port or without (53), e.g. 10.10.0.1"}, "tcp": map[string]any{"type": "boolean", "description": "ask the server over TCP rather than UDP (default false)"}, "timeout_ms": map[string]any{"type": "integer", "description": "give up after this long (default 3000, at most 30000)"}, }, Run: func(args map[string]any) (any, error) { name, err := text(args, "name") if err != nil { return nil, err } kind, _ := args["type"].(string) timeout, err := timeoutOf(args) if err != nil { return nil, err } server, _ := args["server"].(string) overTCP, _ := args["tcp"].(bool) return AskDNS(name, kind, strings.TrimSpace(server), overTCP, timeout) }, }, } } func say(format string, args ...any) { fmt.Fprintf(os.Stderr, "[netcheck] "+format+"\n", args...) }