I am implementing TCP from scratch using raw sockets on macOS. I want to:
Current approach:
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:
Thank you!
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: