More robust port detection system, allow for user configured custom port services, add README

This commit is contained in:
mrkmntal 2026-09-19 19:29:02 -04:00
commit b527e079db
3 changed files with 244 additions and 6 deletions

2
README
View file

@ -1,2 +0,0 @@
This is basically a clone of socketstat (specifically the ss -tulnp behavior), but for Debian GNU/Hurd
It uses tcc for now, nice light compiler

87
README.md Normal file
View file

@ -0,0 +1,87 @@
# hss - Hurd Socket Stat
> A practical, heuristics-based socket table emulator for GNU/Hurd that mimics modern Linux `ss` and `netstat` behavior.
## Architectural Motivation
On monolithic kernels like Linux, `ss` queries a centralized kernel-space socket array via Netlink (`sock_diag`). GNU/Hurd has no monolithic kernel — the network stack and tasks are isolated user-space translators. A single global "socket-to-PID" lookup table does not exist by design. `hss` emulates this missing table from user space.
## Features
- **Non-blocking heuristics**: Lightning-fast connection scans using `O_NONBLOCK` and `select()` across interfaces
- **Low-level Mach/Hurd inspection**: Crawls the Mach process server using `proc_getallpids` and `proc_getprocargs`
- **Zero memory leaks**: Explicit `vm_deallocate` for all Mach page memory
- **Fast runtime**: Resolves the entire user-space distributed map in ~0.320 seconds
- **Dual compiler support**: Compiles with both `tcc` (rapid development) and `gcc` (production)
## Sample Output
```
Netid State Local Address:Port Process
tcp LISTEN 192.168.86.42:22 users:(("sshd-session",pid=13635),...)
tcp LISTEN localhost:8000 users:(("python3",pid=14434))
```
## Usage
```bash
hss [target] [options]
```
### Options
| Option | Description |
|--------|-------------|
| `target` | Host to scan (default: localhost) |
| `--all-processes` | Show all processes for each port |
| `--process-limit=N` | Limit processes shown per port (default: 3) |
### Examples
```bash
# Scan localhost with default settings
hss
# Scan remote host
hss 192.168.1.100
# Show all processes
hss localhost --all-processes
# Limit to 5 processes per port
hss localhost --process-limit=5
```
## Compilation & Build
### Dependencies
- `libc6-dev` (standard C library)
- `hurd-dev` (Hurd system headers)
- `gcc` or `tcc` (compiler)
### GCC (Production)
```bash
gcc -Wall -Wextra -O2 -o hss hss.c
```
### TCC (Development)
```bash
tcc -run hss.c
```
## Debian Package Target
Planned for official inclusion with a standard `debian/` directory targeting `Architecture: hurd-any`.
## License
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
## Author
Mark Robillard Jr (MARKMENTAL)

161
hss.c
View file

@ -32,9 +32,153 @@
#define DEFAULT_PROC_LIMIT 3
#define MAX_ETH_IPS 10
#define MAX_DISPLAY_IPS 3
#define MAX_PORTS 256
#define CONFIG_PATH "~/.config/.hssrc"
#define MAX_SERVICE_NAME_LEN 20
#define MAX_RANGE_SIZE 20
static int initial_ports[] = {22, 80, 443, 21, 3306, 5432, 6379, 8080, 1337};
#define INITIAL_PORTS_LEN (sizeof(initial_ports) / sizeof(initial_ports[0]))
static const int common_modern_ports[] = {
8080, 8443, 3000, 5000, 8000, 8888, 9000, 9090, 1337, 27017
};
#define NUM_MODERN_PORTS (sizeof(common_modern_ports) / sizeof(common_modern_ports[0]))
struct port_entry {
int port;
char service_name[64];
};
static int is_common_port(int port) {
if (port >= 1 && port <= 1023) return 1;
for (size_t i = 0; i < NUM_MODERN_PORTS; i++) {
if (common_modern_ports[i] == port) return 1;
}
return 0;
}
static int get_common_modern_ports(struct port_entry *entries, int max_entries) {
int count = 0;
for (size_t i = 0; i < NUM_MODERN_PORTS && count < max_entries; i++) {
entries[count].port = common_modern_ports[i];
snprintf(entries[count].service_name, sizeof(entries[count].service_name),
"%d", common_modern_ports[i]);
count++;
}
return count;
}
static int parse_etc_services(struct port_entry *entries, int max_entries) {
FILE *f = fopen("/etc/services", "r");
if (!f) return 0;
char line[256];
int count = 0;
while (fgets(line, sizeof(line), f) && count < max_entries) {
if (line[0] == '#' || line[0] == '\n') continue;
char name[64];
int port_start, port_end;
char proto[16];
if (sscanf(line, "%63s %d-%d/%15s", name, &port_start, &port_end, proto) == 4) {
if (strstr(proto, "6")) continue;
if (port_end - port_start + 1 > MAX_RANGE_SIZE) {
port_end = port_start + MAX_RANGE_SIZE - 1;
}
for (int p = port_start; p <= port_end && count < max_entries; p++) {
if (p > 0 && p < 65536 && is_common_port(p)) {
entries[count].port = p;
if (strlen(name) > MAX_SERVICE_NAME_LEN) {
strncpy(entries[count].service_name, name, MAX_SERVICE_NAME_LEN - 3);
entries[count].service_name[MAX_SERVICE_NAME_LEN - 3] = '.';
entries[count].service_name[MAX_SERVICE_NAME_LEN - 2] = '.';
entries[count].service_name[MAX_SERVICE_NAME_LEN - 1] = '.';
entries[count].service_name[MAX_SERVICE_NAME_LEN] = '\0';
} else {
strncpy(entries[count].service_name, name, MAX_SERVICE_NAME_LEN);
entries[count].service_name[MAX_SERVICE_NAME_LEN] = '\0';
}
count++;
}
}
} else if (sscanf(line, "%63s %d/%15s", name, &port_start, proto) == 3) {
if (strstr(proto, "6")) continue;
if (port_start > 0 && port_start < 65536 && is_common_port(port_start)) {
entries[count].port = port_start;
if (strlen(name) > MAX_SERVICE_NAME_LEN) {
strncpy(entries[count].service_name, name, MAX_SERVICE_NAME_LEN - 3);
entries[count].service_name[MAX_SERVICE_NAME_LEN - 3] = '.';
entries[count].service_name[MAX_SERVICE_NAME_LEN - 2] = '.';
entries[count].service_name[MAX_SERVICE_NAME_LEN - 1] = '.';
entries[count].service_name[MAX_SERVICE_NAME_LEN] = '\0';
} else {
strncpy(entries[count].service_name, name, MAX_SERVICE_NAME_LEN);
entries[count].service_name[MAX_SERVICE_NAME_LEN] = '\0';
}
count++;
}
}
}
fclose(f);
return count;
}
static int parse_config_file(const char *path, struct port_entry *entries, int max_entries) {
char expanded_path[256];
if (path[0] == '~') {
const char *home = getenv("HOME");
if (!home) return 0;
snprintf(expanded_path, sizeof(expanded_path), "%s%s", home, path + 1);
path = expanded_path;
}
FILE *f = fopen(path, "r");
if (!f) return 0;
char line[256];
int count = 0;
while (fgets(line, sizeof(line), f) && count < max_entries) {
if (line[0] == '#' || line[0] == '\n') continue;
int port = atoi(line);
if (port > 0 && port < 65536) {
entries[count].port = port;
snprintf(entries[count].service_name, sizeof(entries[count].service_name), "custom-%d", port);
count++;
}
}
fclose(f);
return count;
}
static int merge_port_lists(struct port_entry *etc_services, int num_etc,
struct port_entry *modern, int num_modern,
struct port_entry *custom, int num_custom,
struct port_entry *merged, int max_merged) {
int count = 0;
int seen[65536] = {0};
for (int i = 0; i < num_etc && count < max_merged; i++) {
if (!seen[etc_services[i].port]) {
merged[count++] = etc_services[i];
seen[etc_services[i].port] = 1;
}
}
for (int i = 0; i < num_modern && count < max_merged; i++) {
if (!seen[modern[i].port]) {
merged[count++] = modern[i];
seen[modern[i].port] = 1;
}
}
for (int i = 0; i < num_custom && count < max_merged; i++) {
if (!seen[custom[i].port]) {
merged[count++] = custom[i];
seen[custom[i].port] = 1;
}
}
for (int i = 0; i < count - 1; i++) {
for (int j = i + 1; j < count; j++) {
if (merged[j].port < merged[i].port) {
struct port_entry tmp = merged[i];
merged[i] = merged[j];
merged[j] = tmp;
}
}
}
return count;
}
static int get_all_ethernet_ips(char ips[][INET_ADDRSTRLEN], int max_ips) {
struct ifaddrs *ifaddr, *ifa;
@ -195,9 +339,18 @@ int main(int argc, char *argv[]) {
file_t sock_port = file_name_lookup(_SERVERS_SOCKET "/2", 0, 0);
if (sock_port == MACH_PORT_NULL) fprintf(stderr, "hss: Warning - IPv4 socket translator not responding\n");
else mach_port_deallocate(mach_task_self(), sock_port);
struct port_entry etc_services[MAX_PORTS];
int num_etc = parse_etc_services(etc_services, MAX_PORTS);
struct port_entry modern[MAX_PORTS];
int num_modern = get_common_modern_ports(modern, MAX_PORTS);
struct port_entry custom[MAX_PORTS];
int num_custom = parse_config_file(CONFIG_PATH, custom, MAX_PORTS);
struct port_entry merged[MAX_PORTS];
int num_merged = merge_port_lists(etc_services, num_etc, modern, num_modern,
custom, num_custom, merged, MAX_PORTS);
printf("%-8s %-12s %-30s %s\n", "Netid", "State", "Local Address:Port", "Process");
for (size_t i = 0; i < INITIAL_PORTS_LEN; i++) {
int port = initial_ports[i];
for (int i = 0; i < num_merged; i++) {
int port = merged[i].port;
if (!probe_port(target, port, local_addr, sizeof(local_addr), eth_ips, num_eth_ips)) continue;
find_pids_for_port_hurd(proc_server, port, status, sizeof(status), proc_limit);
printf("%-8s %-12s %-30s %s\n", "tcp", "LISTEN", local_addr, status);