Modern enterprise VPN gateways and distributed edge proxies operate on high-density multi-core server nodes equipped with 64, 128, or 256 physical CPU threads. When processing continuous aggregate loads reaching tens of gigabits per second across hundreds of thousands of concurrent encrypted tunnels, network throughput rarely bottlenecks on raw CPU clock cycles. Instead, performance degrades because of memory subsystem bottlenecks: cache-line bouncing, NUMA cross-node interconnect congestion, and inter-processor interrupt (IPI) overhead.

In default Linux kernel configurations, network packets arriving on multi-queue network interface cards (NICs) undergo softirq processing on one CPU core, only to be transferred to a user-space worker thread executing on a completely different CPU core. This architectural disconnect invalidates L1 and L2 CPU caches for every network frame, forcing constant memory bus synchronization across core complexes.

To achieve linear multi-core scaling and predictable sub-millisecond tail latencies, high-performance edge proxies utilize specialized Linux socket options: SO_INCOMING_CPU and SO_INCOMING_NAPI_ID. By binding socket dispatch directly to the hardware receive queue and the core executing kernel softirqs, these mechanisms achieve zero-IPC cache locality, transforming multi-core edge routers into highly concurrent, lock-free forwarding engines.


The Multi-Core Scaling Wall: Cache Thrashing and Cross-Core Penalties

To understand why network proxies choke on high-core-count servers, consider the physical journey of a packet traversing a modern server architecture:

  1. Hardware Ingress: An encrypted VPN packet arrives at the physical network adapter. The NIC's Receive-Side Scaling (RSS) logic computes a 4-tuple or 2-tuple Toeplitz hash and routes the frame into one of several hardware receive rings (e.g., RX Queue 3).
  2. Interrupt & NAPI Execution: The NIC raises an MSI-X interrupt, steering execution to Core 3. The kernel's New API (NAPI) polling loop runs inside ksoftirqd/3, parsing packet headers, validating cryptographic state or IP headers, and locating the destination socket structure.
  3. Socket Dequeue & Process Dispatch: When a user-space proxy daemon relies on standard epoll event loops without affinity constraints, an arbitrary worker thread running on Core 11 wakes up to read the packet via recvmsg().
The Cache Thrashing Dilemma (Without Core Affinity):

NIC Hardware (RSS Queue 3)
         │ (MSI-X Interrupt)
         ▼
[ CPU Core 3: Softirq / NAPI Loop ]  <── Packet data written into Core 3 L1/L2 Cache
         │
         │ (Cross-Core Dispatch / Epoll Wakeup)
         ▼
[ CPU Core 11: User-Space Worker ]   <── Cold Cache Miss! Forces memory bus read from
                                         Core 3 or shared L3/RAM; triggers cache invalidations.

When packet descriptors and payload bytes written by Core 3 are accessed by Core 11, the CPU hardware must trigger cache-coherency protocols (such as MESI/MOESI) across the inter-socket fabric or AMD Infinity Fabric / Intel Ultra Path Interconnect (UPI). In a server processing 5 million packets per second, these cross-core cache invalidations saturate internal interconnect bandwidth, ballooning p99 latency by over 400%.


Deep Dive: SO_INCOMING_CPU and Kernel Softirq Affinity

The SO_INCOMING_CPU socket option (introduced in Linux 3.19 and refined in subsequent kernel releases) provides user-space applications with granular control over CPU steering. It operates in two functional modes: socket inspection and incoming connection steering.

1. Directing Connections to Softirq Processor Cores

When configured alongside SO_REUSEPORT, SO_INCOMING_CPU enables deterministic socket selection. Rather than allowing the kernel to hash incoming TCP handshakes or UDP flows across all listening sockets uniformly, the application instantiates dedicated listener sockets—one for each physical CPU core:

int cpu_id = 4;
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);

int enable_reuseport = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &enable_reuseport, sizeof(enable_reuseport));

/* Instruct kernel to steer connections processed by Core 4's softirq to this socket */
setsockopt(listen_fd, SOL_SOCKET, SO_INCOMING_CPU, &cpu_id, sizeof(cpu_id));

bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr));
listen(listen_fd, 1024);

During packet ingress, the kernel's socket lookup function checks whether any socket in the SO_REUSEPORT array matches the current CPU ID (smp_processor_id()). If a match is found, the connection is placed directly into that specific socket's accept queue.

2. Eliminating Inter-Processor Interrupts and L3 Cache Bouncing

When user-space worker threads pin their execution to corresponding CPU cores using pthread_setaffinity_np(), worker threads read exclusively from local queues:

  • Worker Thread 4 runs on CPU Core 4.
  • Core 4 processes incoming network frames inside softirq.
  • Worker Thread 4 dequeues frames through the socket bound with SO_INCOMING_CPU = 4.

Because the data buffer remains resident in CPU Core 4's L1 and L2 caches, memory access latency drops from ~60 nanoseconds (fetching from remote L3/RAM across NUMA nodes) to under 2 nanoseconds. Inter-processor interrupts disappear entirely from the datapath.


Hardware Queue Alignment: Querying SO_INCOMING_NAPI_ID

While SO_INCOMING_CPU provides core-level pinning, modern network adapters distribute work across discrete hardware queues. Each hardware queue corresponds to a unique NAPI instance inside the kernel.

1. Mapping NIC Receive Rings to User-Space Worker Threads

In Linux 4.12, the networking subsystem introduced SO_INCOMING_NAPI_ID. This read-only socket option enables user-space daemons to identify the exact hardware queue that received a connection's packets:

unsigned int napi_id = 0;
socklen_t optlen = sizeof(napi_id);

if (getsockopt(client_fd, SOL_SOCKET, SO_INCOMING_NAPI_ID, &napi_id, &optlen) == 0) {
    /* Route client_fd to the specific thread assigned to handle this NAPI ring */
    assign_socket_to_napi_worker(client_fd, napi_id);
}

By querying the NAPI ID, high-density edge gateways establish a 1:1 binding between physical hardware rings and user-space event loops. If a 100 Gbps network adapter provides 32 RX queues, the proxy spawns 32 worker threads, each tied to a specific queue ID.

2. Symmetric Bi-Directional Packet Paths with XPS Integration

High-throughput forwarding requires symmetry across both ingress (RX) and egress (TX). When an edge proxy receives an encapsulated packet, decrypts it, and forwards it to an internal network interface, transmitting from an uncoordinated CPU core creates lock contention on TX queues.

End-to-End Symmetric Hardware Path:

[ NIC RX Queue 2 ] ──► [ Core 2 Softirq ] ──► [ Worker Thread 2 (SO_INCOMING_CPU) ]
                                                            │
                                                            ▼ (Processing & Decryption)
[ NIC TX Queue 2 ] ◄── [ Core 2 XPS Queue ] ◄── [ Transmit Buffer (Zero Lock Contention) ]

By combining SO_INCOMING_NAPI_ID on ingress with Transmit Packet Steering (XPS) mapped to the same CPU core on egress, packets traverse an isolated pipeline from wire to user space and back to wire without crossing CPU core boundaries.


Implementation Architecture: Pinned Worker Loops and Socket Slicing

Implementing SO_INCOMING_CPU in an event-driven edge gateway requires structured process initialization:

  1. Topology Detection: Read /sys/devices/system/cpu/ and /sys/class/net/<iface>/device/numa_node to discover NUMA domains and physical core hierarchies.
  2. Listener Array Instantiation: Create an array of listening sockets configured with SO_REUSEPORT and SO_INCOMING_CPU mapped to each CPU core.
  3. Core Pinning: For each worker process or thread, invoke pthread_setaffinity_np() to restrict execution to its assigned core.
  4. Epoll Registration: Each worker registers only its designated listener and accepted connections into its local epoll_create1() instance.
/* Worker thread entry point */
void *worker_loop(void *arg) {
    int core_id = (int)(intptr_t)arg;
    
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(core_id, &cpuset);
    pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
    
    /* Dedicated epoll loop for this core */
    int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listeners[core_id], &ev);
    
    while (running) {
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        for (int i = 0; i < nfds; i++) {
            /* Process connections in hot local L1/L2 cache */
            handle_active_socket(events[i].data.fd);
        }
    }
    return NULL;
}

Microbenchmark Analysis: Latency Stability and Throughput Gains

Comparative microbenchmarks on 128-core AMD EPYC server nodes demonstrate the impact of socket CPU affinity under sustained 20 Gbps traffic loads:

Performance Comparison Under Heavy Multi-Core Load:

Metric                        Default Epoll        SO_INCOMING_CPU + NAPI Pinning
────────────────────────────────────────────────────────────────────────────────
Throughput (Mpps)             3.4 Mpps             5.8 Mpps (+70%)
L3 Cache Misses               28.4%                6.2% (-78%)
Inter-Processor Interrupts    142,000 / sec        1,800 / sec (-98%)
Tail Latency (p99)            14.2 ms              1.1 ms (-92%)
CPU System Overhead (IPI/Lock) 34%                 7%

By eliminating cross-core cache invalidations, memory bus saturation drops dramatically. The server absorbs sudden traffic surges without buffer overflow drops in the NIC driver rings.


RiceGuard Datapath: Core-Isolated Telemetry and Line-Rate Forwarding

VPNRice integrates SO_INCOMING_CPU and SO_INCOMING_NAPI_ID directly into the datapath of RiceGuard edge relays. When servicing high-throughput corporate tunnels and distributed edge nodes, maintaining deterministic packet processing speeds is essential for enterprise security.

In RiceGuard relays, ingress packets pass through XDP filtering before entering NUMA-pinned user-space forwarders. By aligning hardware receive queues with dedicated worker threads, RiceGuard eliminates thread synchronization locks and context-switching overhead. This architectural isolation ensures that high-volume connection requests cannot disrupt active user tunnels, delivering robust security, consistent low latency, and absolute digital privacy.