Active Network Sockets & Connection Inspector #16

Closed
opened 2026-09-08 10:29:08 +01:00 by gronod · 1 comment
Owner

Resolved in Milestone 3. Implemented MMNetworkSocketsProvider.h/.m enumerating active sockets via proc_listpids and PROC_PIDFDSOCKETINFO, decoding IPv4/IPv6 endpoints, TCP state machine, and process mapping. Verified with unit tests. Merged into develop.

Resolved in Milestone 3. Implemented `MMNetworkSocketsProvider.h/.m` enumerating active sockets via `proc_listpids` and `PROC_PIDFDSOCKETINFO`, decoding IPv4/IPv6 endpoints, TCP state machine, and process mapping. Verified with unit tests. Merged into `develop`.
Author
Owner

Technical Implementation Plan & Code Solution

1. BSD Protocol Control Block (PCB) Architecture

macOS maintains active TCP/UDP socket state tables in kernel memory. These are exposed via sysctl with net.inet.tcp.pcblist_n and net.inet.udp.pcblist_n.

Connection states decoded:

  • TCPS_CLOSED (0), TCPS_LISTEN (1), TCPS_ESTABLISHED (4), TCPS_CLOSE_WAIT (5), TCPS_TIME_WAIT (9).

2. Objective-C Inspector (MMSocketInspector.m)

#import "MMSocketInspector.h"
#import <sys/sysctl.h>
#import <netinet/in.h>
#import <netinet/tcp.h>
#import <netinet/tcp_var.h>
#import <netinet/tcp_fsm.h>
#import <arpa/inet.h>

@implementation MMSocketInspector

+ (NSArray<NSDictionary *> *)queryActiveTCPSockets {
    size_t len = 0;
    if (sysctlbyname("net.inet.tcp.pcblist_n", NULL, &len, NULL, 0) < 0 || len == 0) {
        return @[];
    }

    char *buf = malloc(len);
    if (!buf) return @[];

    if (sysctlbyname("net.inet.tcp.pcblist_n", buf, &len, NULL, 0) < 0) {
        free(buf);
        return @[];
    }

    NSMutableArray *results = [NSMutableArray array];
    char *next = buf;
    char *lim = buf + len;

    while (next < lim) {
        struct xinpcb_n *inp = (struct xinpcb_n *)next;
        if (inp->xi_len == 0) break;

        next += inp->xi_len;

        // Skip non-IPv4/v6
        char localIP[INET6_ADDRSTRLEN] = {0};
        char foreignIP[INET6_ADDRSTRLEN] = {0};
        uint16_t localPort = ntohs(inp->inp_lport);
        uint16_t foreignPort = ntohs(inp->inp_fport);

        if (inp->inp_vflag & INP_IPV4) {
            inet_ntop(AF_INET, &inp->inp_laddr, localIP, sizeof(localIP));
            inet_ntop(AF_INET, &inp->inp_faddr, foreignIP, sizeof(foreignIP));
        } else if (inp->inp_vflag & INP_IPV6) {
            inet_ntop(AF_INET6, &inp->in6p_laddr, localIP, sizeof(localIP));
            inet_ntop(AF_INET6, &inp->in6p_faddr, foreignIP, sizeof(foreignIP));
        }

        [results addObject:@{
            @"localAddress": [NSString stringWithUTF8String:localIP],
            @"localPort": @(localPort),
            @"foreignAddress": [NSString stringWithUTF8String:foreignIP],
            @"foreignPort": @(foreignPort),
            @"flowHash": @(inp->inp_flowhash)
        }];
    }

    free(buf);
    return [results copy];
}
@end

3. Swift UI Representation

public struct ActiveSocketConnection: Identifiable, Sendable {
    public var id: String { "\(localAddress):\(localPort)->\(foreignAddress):\(foreignPort)" }
    public let localAddress: String
    public let localPort: Int
    public let foreignAddress: String
    public let foreignPort: Int
    public let state: String
}

4. Memory & Buffer Safety

Kernel PCB lists can change size between the sizing sysctl call and the data retrieval sysctl call. The code defensively checks inp->xi_len and loop boundaries to prevent buffer overrun.

## Technical Implementation Plan & Code Solution ### 1. BSD Protocol Control Block (PCB) Architecture macOS maintains active TCP/UDP socket state tables in kernel memory. These are exposed via `sysctl` with `net.inet.tcp.pcblist_n` and `net.inet.udp.pcblist_n`. Connection states decoded: - `TCPS_CLOSED` (0), `TCPS_LISTEN` (1), `TCPS_ESTABLISHED` (4), `TCPS_CLOSE_WAIT` (5), `TCPS_TIME_WAIT` (9). --- ### 2. Objective-C Inspector (`MMSocketInspector.m`) ```objc #import "MMSocketInspector.h" #import <sys/sysctl.h> #import <netinet/in.h> #import <netinet/tcp.h> #import <netinet/tcp_var.h> #import <netinet/tcp_fsm.h> #import <arpa/inet.h> @implementation MMSocketInspector + (NSArray<NSDictionary *> *)queryActiveTCPSockets { size_t len = 0; if (sysctlbyname("net.inet.tcp.pcblist_n", NULL, &len, NULL, 0) < 0 || len == 0) { return @[]; } char *buf = malloc(len); if (!buf) return @[]; if (sysctlbyname("net.inet.tcp.pcblist_n", buf, &len, NULL, 0) < 0) { free(buf); return @[]; } NSMutableArray *results = [NSMutableArray array]; char *next = buf; char *lim = buf + len; while (next < lim) { struct xinpcb_n *inp = (struct xinpcb_n *)next; if (inp->xi_len == 0) break; next += inp->xi_len; // Skip non-IPv4/v6 char localIP[INET6_ADDRSTRLEN] = {0}; char foreignIP[INET6_ADDRSTRLEN] = {0}; uint16_t localPort = ntohs(inp->inp_lport); uint16_t foreignPort = ntohs(inp->inp_fport); if (inp->inp_vflag & INP_IPV4) { inet_ntop(AF_INET, &inp->inp_laddr, localIP, sizeof(localIP)); inet_ntop(AF_INET, &inp->inp_faddr, foreignIP, sizeof(foreignIP)); } else if (inp->inp_vflag & INP_IPV6) { inet_ntop(AF_INET6, &inp->in6p_laddr, localIP, sizeof(localIP)); inet_ntop(AF_INET6, &inp->in6p_faddr, foreignIP, sizeof(foreignIP)); } [results addObject:@{ @"localAddress": [NSString stringWithUTF8String:localIP], @"localPort": @(localPort), @"foreignAddress": [NSString stringWithUTF8String:foreignIP], @"foreignPort": @(foreignPort), @"flowHash": @(inp->inp_flowhash) }]; } free(buf); return [results copy]; } @end ``` --- ### 3. Swift UI Representation ```swift public struct ActiveSocketConnection: Identifiable, Sendable { public var id: String { "\(localAddress):\(localPort)->\(foreignAddress):\(foreignPort)" } public let localAddress: String public let localPort: Int public let foreignAddress: String public let foreignPort: Int public let state: String } ``` --- ### 4. Memory & Buffer Safety Kernel PCB lists can change size between the sizing `sysctl` call and the data retrieval `sysctl` call. The code defensively checks `inp->xi_len` and loop boundaries to prevent buffer overrun.
gronod added this to the M3: Advanced Kernel, Process & Peripheral Telemetry milestone 2026-09-08 10:42:35 +01:00
gronod added a new dependency 2026-09-08 11:37:07 +01:00
gronod added a new dependency 2026-09-08 11:37:08 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: gronod/MacMonitor#16