Skip to content

Instantly share code, notes, and snippets.

@valentt
Created December 29, 2025 00:22
Show Gist options
  • Select an option

  • Save valentt/bfd77aa170e189edf9b22e3933a69def to your computer and use it in GitHub Desktop.

Select an option

Save valentt/bfd77aa170e189edf9b22e3933a69def to your computer and use it in GitHub Desktop.
sroamd.c patched - FD_SETSIZE fix (select->poll) + server_socket guard
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <poll.h>
#include <netlink/errno.h>
#include "interface.h"
#include "client.h"
#include "lease.h"
#include "ra.h"
#include "dhcpv4.h"
#include "flood.h"
#include "netlink.h"
#include "util.h"
static const unsigned char v4mapped[16] =
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0 };
static void
callback(int add, int ifindex, const unsigned char *mac)
{
struct interface *interface;
struct client *client;
struct datum *lease;
interface = find_interface(ifindex);
if(interface == NULL)
return;
debugf("%s: %s station %s\n",
interface->ifname, add ? "add" : "del", format_48(mac));
if(add) {
unsigned char myv4[4];
int rc;
struct timespec tv;
client = update_association(interface, mac, ASSOCIATION_TIME);
if(client == NULL) {
fprintf(stderr, "Failed to add client.\n");
flush_association(mac, ASSOCIATION_TIME);
return;
}
lease = find_lease(mac, 0);
if(lease) {
const unsigned char *addr;
addr = lease_address(lease, 0);
if(addr == NULL) {
fprintf(stderr, "Bad lease.\n");
return;
}
update_client_route(client, addr, 0);
}
lease = find_lease(mac, 1);
if(lease) {
const unsigned char *addr;
addr = lease_address(lease, 1);
if(addr == NULL) {
fprintf(stderr, "Bad lease.\n");
return;
}
update_client_route(client, addr, 1);
}
tv.tv_sec = 0;
tv.tv_nsec = 100 * 1000 * 1000;
nanosleep(&tv, NULL);
rc = interface_v4(interface, myv4);
if(rc >= 0) {
rc = send_gratuitous_arp(myv4, interface, mac);
if(rc < 0)
perror("send_gratuitous_arp");
}
rc = send_gratuitous_na(interface);
if(rc < 0)
perror("send_gratuitous_na");
} else {
flush_association(mac, ASSOCIATION_TIME);
}
}
void
datum_callback(struct datum *datum, int conflict)
{
if(datum->keylen < 1)
return;
switch(datum_key(datum)[0]) {
case DATUM_ASSOCIATED: {
struct client *client;
if(datum->keylen != 7 || (datum->vallen != 0 && datum->vallen != 8)) {
fprintf(stderr, "Corrupt association.\n");
return;
}
if(datum->vallen == 0)
return;
client = find_client(datum_key(datum) + 1);
if(client != NULL && memcmp(datum_val(datum), myid, 8) != 0) {
debugf("Disassociating %s.\n", format_48(client->mac));
netlink_disassociate(client->interface->ifindex, client->mac,
client->interface->mac);
flush_client(client->mac);
if(conflict)
flush_association(client->mac, ASSOCIATION_TIME);
}
break;
}
case DATUM_IPv4_LEASE:
case DATUM_IPv6_LEASE:
update_lease_routes(datum);
break;
}
}
static volatile sig_atomic_t exiting = 0, dumping = 0;
static void
sigexit(int signo)
{
exiting = 1;
}
static void
sigdump(int signo)
{
dumping = 1;
}
static void
init_signals(void)
{
struct sigaction sa;
sigset_t ss;
sigemptyset(&ss);
sa.sa_handler = sigexit;
sa.sa_mask = ss;
sa.sa_flags = 0;
sigaction(SIGTERM, &sa, NULL);
sigemptyset(&ss);
sa.sa_handler = sigexit;
sa.sa_mask = ss;
sa.sa_flags = 0;
sigaction(SIGHUP, &sa, NULL);
sigemptyset(&ss);
sa.sa_handler = sigexit;
sa.sa_mask = ss;
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigemptyset(&ss);
sa.sa_handler = sigdump;
sa.sa_mask = ss;
sa.sa_flags = 0;
sigaction(SIGUSR1, &sa, NULL);
#ifdef SIGINFO
sigemptyset(&ss);
sa.sa_handler = sigdump;
sa.sa_mask = ss;
sa.sa_flags = 0;
sigaction(SIGINFO, &sa, NULL);
#endif
}
static void
check_interface(struct interface *iif)
{
int ifindex, rc;
ifindex = if_nametoindex(iif->ifname);
if(ifindex != iif->ifindex)
iif->ifindex = ifindex;
if(iif->ifindex > 0) {
rc = if_macaddr(iif->ifname, iif->ifindex, iif->mac);
if(rc < 0)
memset(iif->mac, 0, 6);
}
}
int
main(int argc, char **argv)
{
int rc, opt;
while(1) {
opt = getopt(argc, argv, "f:P:d:N:F:");
if(opt < 0)
break;
switch(opt) {
case 'f': {
int p;
char *end;
p = strtol(optarg, &end, 0);
if(*end != '\0' || p <= 0 || p > 0xFFFF)
goto usage;
server_port = p;
}
break;
case 'P': {
unsigned char buf[16];
int plen, af;
af = parse_prefix(optarg, buf, &plen);
if(af == 4) {
memcpy(v4prefix, buf, 4);
v4plen = plen;
} else if(af == 6) {
memcpy(v6prefix, buf, 16);
v6plen = plen;
} else {
goto usage;
}
}
break;
case 'd': {
char *end;
debug_level = strtol(optarg, &end, 0);
if(*end != '\0')
goto usage;
break;
}
case 'N': {
unsigned char buf[16];
int af;
af = parse_address(optarg, buf);
if(af == 4) {
if(numdnsv4 >= 16)
goto usage;
memcpy(dnsv4[numdnsv4++], buf, 4);
} else if(af == 6) {
if(numdnsv6 >= 16)
goto usage;
memcpy(dnsv6[numdnsv6++], buf, 16);
} else {
goto usage;
}
}
break;
case 'F': {
unsigned char buf[16];
unsigned short port;
int af;
af = parse_addrport(optarg, buf, &port);
if(af >= 0) {
struct sockaddr_in6 sin6;
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
if(af == 4) {
memcpy(&sin6.sin6_addr, v4mapped, 12);
memcpy((unsigned char*)&sin6.sin6_addr + 12, buf, 4);
} else if(af == 6) {
memcpy(&sin6.sin6_addr, buf, 16);
} else
goto usage;
sin6.sin6_port = htons(port);
flood_connect(&sin6);
} else {
goto usage;
}
}
break;
default:
goto usage;
}
}
rc = read_random_bytes(myid, sizeof(myid));
if(rc < 0) {
perror("read_random_bytes");
exit(1);
}
for(int i = optind; i < argc; i++) {
struct interface *n =
realloc(interfaces,
(numinterfaces + 1) * sizeof(struct interface));
if(n == NULL) {
perror("alloc(interfaces)");
exit(1);
}
interfaces = n;
memset(&interfaces[numinterfaces], 0, sizeof(struct interface));
interfaces[numinterfaces].ifname = argv[i];
numinterfaces++;
}
init_signals();
rc = netlink_init(callback);
if(rc < 0) {
perror("netlink_init");
exit(1);
}
for(int i = 0; i < numinterfaces; i++) {
check_interface(&interfaces[i]);
if(interfaces[i].ifindex > 0) {
rc = netlink_dump(interfaces[i].ifindex);
if(rc < 0)
perror("netlink_dump");
}
}
rc = flood_setup(datum_callback);
if(rc < 0) {
perror("flood_setup");
exit(1);
}
if(numinterfaces > 0) {
rc = ra_setup();
if(rc < 0) {
perror("ra_setup");
exit(1);
}
rc = dhcpv4_setup();
if(rc < 0) {
perror("dhcpv4_setup");
exit(1);
}
}
/* Main event loop using poll() instead of select() to avoid FD_SETSIZE limit.
* This fixes the bug where fd_set operations fail when file descriptors
* exceed 1024 (FD_SETSIZE), which commonly happens in network namespace
* testing with many interfaces.
*/
while(1) {
struct pollfd *pollfds = NULL;
int nfds = 0;
int fds_capacity;
int nls = netlink_socket();
struct timespec now, deadline;
int timeout_ms;
/* Index tracking for result checking */
int idx_nls = -1, idx_ra = -1, idx_dhcpv4 = -1, idx_server = -1;
int *idx_neighs = NULL;
/* Calculate capacity: nls + ra + dhcpv4 + server + numneighs */
fds_capacity = 4 + numneighs;
pollfds = malloc(fds_capacity * sizeof(struct pollfd));
if(pollfds == NULL) {
perror("malloc(pollfds)");
sleep(1);
continue;
}
if(numneighs > 0) {
idx_neighs = malloc(numneighs * sizeof(int));
if(idx_neighs == NULL) {
perror("malloc(idx_neighs)");
free(pollfds);
sleep(1);
continue;
}
}
/* Add netlink socket */
idx_nls = nfds;
pollfds[nfds].fd = nls;
pollfds[nfds].events = POLLIN;
pollfds[nfds].revents = 0;
nfds++;
/* Add RA and DHCPv4 sockets */
if(numinterfaces > 0) {
idx_ra = nfds;
pollfds[nfds].fd = ra_socket;
pollfds[nfds].events = POLLIN;
pollfds[nfds].revents = 0;
nfds++;
idx_dhcpv4 = nfds;
pollfds[nfds].fd = dhcpv4_socket;
pollfds[nfds].events = POLLIN;
pollfds[nfds].revents = 0;
nfds++;
}
/* Add server socket - with validity check */
if(server_socket >= 0) {
idx_server = nfds;
pollfds[nfds].fd = server_socket;
pollfds[nfds].events = POLLIN;
pollfds[nfds].revents = 0;
nfds++;
}
/* Add neighbor sockets */
for(int i = 0; i < numneighs; i++) {
idx_neighs[i] = -1;
if(neighs[i].fd >= 0) {
idx_neighs[i] = nfds;
pollfds[nfds].fd = neighs[i].fd;
pollfds[nfds].events = POLLIN;
if(neighs[i].out.len > 0)
pollfds[nfds].events |= POLLOUT;
pollfds[nfds].revents = 0;
nfds++;
}
}
/* Calculate timeout */
clock_gettime(CLOCK_MONOTONIC, &now);
ts_minus(&deadline, &expire_neighs_time, &now);
timeout_ms = deadline.tv_sec * 1000 + deadline.tv_nsec / 1000000;
if(timeout_ms < 0) timeout_ms = 0;
rc = poll(pollfds, nfds, timeout_ms);
if(rc < 0 && errno != EINTR) {
perror("poll");
sleep(1);
}
clock_gettime(CLOCK_MONOTONIC, &now);
if(exiting) {
free(pollfds);
free(idx_neighs);
break;
}
if(dumping) {
static const char zeroes[8] = {0};
printf("Interfaces");
for(int i = 0; i < numinterfaces; i++)
printf(" %s", interfaces[i].ifname);
printf(".\n");
for(int i = 0; i < numdata; i++) {
if(data[i]->keylen < 1) {
printf("Datum %d %d", data[i]->keylen, data[i]->vallen);
continue;
}
switch(datum_key(data[i])[0]) {
case DATUM_IPv4_LEASE:
case DATUM_IPv6_LEASE: {
char addr[INET6_ADDRSTRLEN];
if(datum_key(data[i])[0] == DATUM_IPv4_LEASE &&
data[i]->keylen == 5) {
inet_ntop(AF_INET, datum_key(data[i]) + 1,
addr, sizeof(addr));
} else if(datum_key(data[i])[0] == DATUM_IPv6_LEASE &&
data[i]->keylen == 9) {
unsigned char ipv6[16];
memcpy(ipv6, datum_key(data[i]) + 1, 8);
memset(ipv6 + 8, 0, 8);
inet_ntop(AF_INET6, ipv6, addr, sizeof(addr));
strncat(addr, "/64", sizeof(addr) - strlen(addr) - 1);
} else {
strncpy(addr, "(corrupt)", sizeof(addr));
}
printf("Lease %s %s %d %ds.\n",
addr,
data[i]->vallen == 6 ?
format_48(datum_val(data[i])) :
"(corrupt)",
data[i]->seqno,
(int)(data[i]->time - now.tv_sec));
break;
}
case DATUM_ASSOCIATED: {
printf("Assoc %s %s %d %ds.\n",
data[i]->keylen == 7 ?
format_48(datum_key(data[i]) + 1) : "(corrupt)",
data[i]->vallen == 0 ? "(gone)" :
data[i]->vallen == 8 ?
format_64(datum_val(data[i])) : "(corrupt)",
data[i]->seqno,
(int)(data[i]->time - now.tv_sec));
break;
}
default:
printf("Datum %d %d %d %d %ds.\n",
data[i]->keylen, data[i]->vallen,
datum_key(data[i])[0],
data[i]->seqno,
(int)(data[i]->time - now.tv_sec));
}
}
for(int i = 0; i < numclients; i++) {
char buf[INET6_ADDRSTRLEN];
printf("Client %s if %s",
format_48(clients[i].mac), clients[i].interface->ifname);
if(memcmp(clients[i].ipv4, zeroes, 8) != 0) {
inet_ntop(AF_INET, clients[i].ipv4, buf, sizeof(buf));
printf(" ipv4 %s", buf);
}
if(memcmp(clients[i].ipv6, zeroes, 8) != 0) {
unsigned char ipv6[16];
memcpy(ipv6, clients[i].ipv6, 8);
memset(ipv6 + 8, 0, 8);
inet_ntop(AF_INET6, ipv6, buf, sizeof(buf));
printf(" ipv6 %s/64", buf);
}
printf(".\n");
}
printf("\n");
for(int i = 0; i < numneighs; i++) {
printf("Neighbour %d.\n", neighs[i].fd);
}
fflush(stdout);
dumping = 0;
}
if(rc >= 0) {
/* Check netlink socket */
if(idx_nls >= 0 && (pollfds[idx_nls].revents & POLLIN)) {
rc = netlink_listen();
if(rc < 0)
nl_perror(rc, "netlink_listen");
}
/* Check server socket */
if(idx_server >= 0 && (pollfds[idx_server].revents & POLLIN))
flood_accept();
/* Check neighbor sockets */
for(int i = 0; i < numneighs; i++) {
if(neighs[i].fd >= 0 && idx_neighs != NULL && idx_neighs[i] >= 0) {
if(pollfds[idx_neighs[i]].revents & POLLIN)
flood_read(&neighs[i]);
if(pollfds[idx_neighs[i]].revents & POLLOUT)
flood_write(&neighs[i]);
}
}
/* Check RA and DHCPv4 sockets */
if(numinterfaces > 0) {
if(idx_ra >= 0 && (pollfds[idx_ra].revents & POLLIN))
receive_rs();
if(idx_dhcpv4 >= 0 && (pollfds[idx_dhcpv4].revents & POLLIN))
dhcpv4_receive();
}
}
if(ts_compare(&now, &expire_neighs_time) >= 0)
expire_neighs();
free(pollfds);
free(idx_neighs);
}
client_cleanup();
if(numinterfaces > 0) {
ra_cleanup();
dhcpv4_cleanup();
}
flood_cleanup();
return 0;
usage:
fprintf(stderr,
"Usage: sroamd [-d level] [-P prefix]... [-N nameserver]...\n"
" [-f port] [-F addr:port]... [interface]...\n");
exit(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment