Search code examples
cmacosnetwork-programmingtcpraw-sockets

Proper way to obtain dynamic source port for raw socket (IP_HDRINCL) on macOS for custom TCP implementation


I am implementing TCP from scratch using raw sockets on macOS. I want to:

  1. Get a dynamic source port from the kernel
  2. Use this port with IP_HDRINCL for full IP/TCP header control

Current approach:

  • step 1: Create TCP socket to get dynamic port
  • step 2: Create raw socket with IP_HDRINCL
  • step 3: Bind raw socket to obtained port

Code snippet in C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <errno.h>

int main() {
    int tcp_sock, raw_sock;
    struct sockaddr_in addr;
    socklen_t len;
    int one = 1;

    // First get a dynamic port using TCP socket
    tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (tcp_sock == -1) {
        perror("TCP socket creation failed");
        return 1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = 0;  // Request dynamic port
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(tcp_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("TCP bind failed");
        close(tcp_sock);
        return 1;
    }

    // Get assigned port
    len = sizeof(addr);
    if (getsockname(tcp_sock, (struct sockaddr *)&addr, &len) == -1) {
        perror("TCP getsockname failed");
        close(tcp_sock);
        return 1;
    }

    uint16_t dynamic_port = ntohs(addr.sin_port);
    printf("Kernel assigned port: %d\n", dynamic_port);

    // Now create raw socket and bind to same port
    raw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (raw_sock == -1) {
        perror("RAW socket creation failed");
        close(tcp_sock);
        return 1;
    }

    if (setsockopt(raw_sock, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) {
        perror("setsockopt IP_HDRINCL failed");
        close(tcp_sock);
        close(raw_sock);
        return 1;
    }

    addr.sin_port = htons(dynamic_port);
    if (bind(raw_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("RAW bind failed");
        close(tcp_sock);
        close(raw_sock);
        return 1;
    }

    // Cleanup
    close(tcp_sock);
    close(raw_sock);
    return 0;
}

Questions:

  1. Is this the correct approach for macOS?
  2. Are there race conditions around port allocation?
  3. Are there better alternatives for custom TCP implementation?

Thank you!


Solution

  • The kernel will only provide you with a port if you use UDP or TCP. The Darwin kernel will call the in_pcbbbind function to get a random port assigned. Linux has inet_connection_sock.

    As you are setting up a raw socket and you need the source port to construct your TCP and IP header, your best bet is to randomly generate a port, bind to it and handle the case that it's already occupied.

    The true reason is that IP according to RFC 791 has no port defined. Therefore, when you create a raw socket, you need to handle the port. For TCP, RFC6056 recommends the most secure way to implement the random port assignment.

    See also Ephemeral ports, a related question on serverfault and an excellent blog post on raw sockets.

    To wrap up on your questions:

    1. no, your approach is not the recommended way, you should: (1) randomly generate a port, (2) attempt to bind to that port, (3) try another port if that fails.
    2. you don't have any parallelization so no race conditions, see SO answer
    3. yes, see above.