Files
DroidDeck/tools/linuxfs/preload/netif.c
T

2186 lines
81 KiB
C

/*
* Ported from WinNative (maxjivi05, feature/wayland-gamescope, 2026-09-18/19), GPL-3.0.
*/
#define _GNU_SOURCE 1
#include <arpa/inet.h>
#include <dlfcn.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <linux/if_addr.h>
#include <linux/if_link.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <limits.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netpacket/packet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>
#define LINK_FILE "/etc/droiddeck-net"
#define LO_NAME "lo"
#define LO_INDEX 1
#define LO_MTU 65536
#define MAX_ADDRS 16
#define FRESH_NS 1000000000LL
struct link_addr {
int family;
unsigned char bytes[16];
unsigned prefix;
};
struct link {
char name[IFNAMSIZ];
unsigned index;
int mtu;
unsigned char mac[6];
struct link_addr addrs[MAX_ADDRS];
int addr_count;
struct link_addr gateways[2];
int gateway_count;
};
static int parse_addr(const char *text, struct link_addr *out) {
memset(out, 0, sizeof(*out));
if (inet_pton(AF_INET, text, out->bytes) == 1) out->family = AF_INET;
else if (inet_pton(AF_INET6, text, out->bytes) == 1) out->family = AF_INET6;
else return 0;
return 1;
}
typedef FILE *(*fopen_fn)(const char *, const char *);
static int read_link(struct link *link) {
static fopen_fn real_fopen;
if (real_fopen == NULL) real_fopen = (fopen_fn) dlsym(RTLD_NEXT, "fopen");
FILE *f = real_fopen ? real_fopen(LINK_FILE, "re") : NULL;
if (f == NULL) return 0;
memset(link, 0, sizeof(*link));
char line[160], text[64];
unsigned m[6], prefix;
while (fgets(line, sizeof(line), f)) {
if (sscanf(line, "if %15s %u %d", link->name, &link->index, &link->mtu) == 3) continue;
if (sscanf(line, "mac %2x:%2x:%2x:%2x:%2x:%2x", m, m + 1, m + 2, m + 3, m + 4, m + 5) == 6) {
for (int i = 0; i < 6; i++) link->mac[i] = (unsigned char) m[i];
} else if (sscanf(line, "addr %63s %u", text, &prefix) == 2) {
struct link_addr addr;
if (link->addr_count < MAX_ADDRS && parse_addr(text, &addr)
&& prefix <= (addr.family == AF_INET ? 32u : 128u)) {
addr.prefix = prefix;
link->addrs[link->addr_count++] = addr;
}
} else if (sscanf(line, "gw %63s", text) == 1) {
struct link_addr addr;
if (link->gateway_count < 2 && parse_addr(text, &addr)) {
link->gateways[link->gateway_count++] = addr;
}
}
}
fclose(f);
return link->name[0] != '\0' && link->index > LO_INDEX && link->addr_count > 0;
}
static long long now_ns(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
return ts.tv_sec * 1000000000LL + ts.tv_nsec;
}
static int fresh(long long *at) {
long long then = __atomic_load_n(at, __ATOMIC_RELAXED);
return then != 0 && now_ns() - then < FRESH_NS;
}
static pthread_mutex_t link_lock = PTHREAD_MUTEX_INITIALIZER;
static struct link link_read;
static int link_ok;
static long long link_at;
static int load_link(struct link *link) {
pthread_mutex_lock(&link_lock);
if (!fresh(&link_at)) {
link_ok = read_link(&link_read);
__atomic_store_n(&link_at, now_ns(), __ATOMIC_RELAXED);
}
int ok = link_ok;
if (ok) *link = link_read;
pthread_mutex_unlock(&link_lock);
return ok;
}
static void link_lock_before_fork(void) { pthread_mutex_lock(&link_lock); }
static void link_unlock_after_fork(void) { pthread_mutex_unlock(&link_lock); }
static void link_reset_after_fork(void) { pthread_mutex_init(&link_lock, NULL); }
__attribute__((constructor)) static void install_link_fork_handlers(void) {
pthread_atfork(link_lock_before_fork, link_unlock_after_fork, link_reset_after_fork);
}
static long long ifaddrs_unusable_at, nameindex_unusable_at;
static void prefix_mask(int family, unsigned prefix, unsigned char *out) {
int len = family == AF_INET ? 4 : 16;
for (int i = 0; i < len; i++, prefix -= prefix < 8 ? prefix : 8) {
out[i] = prefix >= 8 ? 0xff : (unsigned char) (0xff00 >> prefix);
}
}
static int is_link_local(const struct link_addr *addr) {
return addr->family == AF_INET6 && addr->bytes[0] == 0xfe && (addr->bytes[1] & 0xc0) == 0x80;
}
struct entry {
struct ifaddrs ifa;
struct sockaddr_storage addr, mask, extra;
char name[IFNAMSIZ];
};
static struct entry *fill_entry(struct entry *e, const char *name, unsigned flags) {
snprintf(e->name, sizeof(e->name), "%s", name);
e->ifa.ifa_name = e->name;
e->ifa.ifa_flags = flags;
e->ifa.ifa_addr = (struct sockaddr *) &e->addr;
e[-1].ifa.ifa_next = &e->ifa;
return e;
}
static void fill_packet(struct entry *e, unsigned index, const unsigned char *mac) {
struct sockaddr_ll *ll = (struct sockaddr_ll *) &e->addr;
ll->sll_family = AF_PACKET;
ll->sll_ifindex = (int) index;
ll->sll_hatype = mac ? ARPHRD_ETHER : ARPHRD_LOOPBACK;
ll->sll_halen = 6;
if (mac) memcpy(ll->sll_addr, mac, 6);
}
static void fill_inet(struct entry *e, const struct link_addr *addr, unsigned index) {
unsigned char mask[16];
prefix_mask(addr->family, addr->prefix, mask);
e->ifa.ifa_netmask = (struct sockaddr *) &e->mask;
if (addr->family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *) &e->addr;
struct sockaddr_in *smask = (struct sockaddr_in *) &e->mask;
sin->sin_family = smask->sin_family = AF_INET;
memcpy(&sin->sin_addr, addr->bytes, 4);
memcpy(&smask->sin_addr, mask, 4);
if (e->ifa.ifa_flags & IFF_BROADCAST) {
struct sockaddr_in *bcast = (struct sockaddr_in *) &e->extra;
bcast->sin_family = AF_INET;
bcast->sin_addr.s_addr = sin->sin_addr.s_addr | ~smask->sin_addr.s_addr;
e->ifa.ifa_broadaddr = (struct sockaddr *) bcast;
}
} else {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &e->addr;
struct sockaddr_in6 *smask = (struct sockaddr_in6 *) &e->mask;
sin6->sin6_family = smask->sin6_family = AF_INET6;
memcpy(&sin6->sin6_addr, addr->bytes, 16);
memcpy(&smask->sin6_addr, mask, 16);
if (is_link_local(addr)) sin6->sin6_scope_id = index;
}
}
static const struct link_addr LO_V4 = { AF_INET, { 127, 0, 0, 1 }, 8 };
static const struct link_addr LO_V6 = { AF_INET6, { [15] = 1 }, 128 };
static int real_list_usable(const struct ifaddrs *list) {
for (const struct ifaddrs *e = list; e; e = e->ifa_next) {
if (e->ifa_addr && e->ifa_addr->sa_family == AF_INET && !(e->ifa_flags & IFF_LOOPBACK)) {
return 1;
}
}
return 0;
}
typedef void (*freeifaddrs_fn)(struct ifaddrs *);
static freeifaddrs_fn real_freeifaddrs;
int getifaddrs(struct ifaddrs **out) {
typedef int (*getifaddrs_fn)(struct ifaddrs **);
static getifaddrs_fn real;
if (real == NULL) real = (getifaddrs_fn) dlsym(RTLD_NEXT, "getifaddrs");
if (real_freeifaddrs == NULL) real_freeifaddrs = (freeifaddrs_fn) dlsym(RTLD_NEXT, "freeifaddrs");
struct ifaddrs *list = NULL;
int asked = !fresh(&ifaddrs_unusable_at);
int rc = -1, saved = errno;
if (asked) {
rc = real(&list);
saved = errno;
if (rc == 0 && real_list_usable(list)) {
*out = list;
return 0;
}
}
struct link link;
if (!load_link(&link)) {
if (!asked) return real(out);
if (rc == 0) *out = list;
errno = saved;
return rc;
}
if (asked) __atomic_store_n(&ifaddrs_unusable_at, now_ns(), __ATOMIC_RELAXED);
if (rc == 0 && list) real_freeifaddrs(list);
struct entry *block = calloc((size_t) link.addr_count + 5, sizeof(*block));
if (block == NULL) {
errno = ENOMEM;
return -1;
}
unsigned lo_flags = IFF_UP | IFF_RUNNING | IFF_LOOPBACK;
unsigned up_flags = IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST;
struct entry *e = block;
fill_packet(fill_entry(++e, LO_NAME, lo_flags), LO_INDEX, NULL);
fill_inet(fill_entry(++e, LO_NAME, lo_flags), &LO_V4, LO_INDEX);
fill_inet(fill_entry(++e, LO_NAME, lo_flags), &LO_V6, LO_INDEX);
fill_packet(fill_entry(++e, link.name, up_flags), link.index, link.mac);
for (int i = 0; i < link.addr_count; i++) {
fill_inet(fill_entry(++e, link.name, up_flags), &link.addrs[i], link.index);
}
block[1].ifa.ifa_data = &block[1].ifa;
*out = &block[1].ifa;
return 0;
}
void freeifaddrs(struct ifaddrs *list) {
if (list == NULL) return;
if (list->ifa_data == list) {
free((struct entry *) list - 1);
return;
}
if (real_freeifaddrs == NULL) real_freeifaddrs = (freeifaddrs_fn) dlsym(RTLD_NEXT, "freeifaddrs");
real_freeifaddrs(list);
}
struct name_block {
struct if_nameindex index[3];
char names[2][IFNAMSIZ];
};
struct if_nameindex *if_nameindex(void) {
typedef struct if_nameindex *(*if_nameindex_fn)(void);
static if_nameindex_fn real;
if (real == NULL) real = (if_nameindex_fn) dlsym(RTLD_NEXT, "if_nameindex");
struct if_nameindex *list = NULL;
int asked = !fresh(&nameindex_unusable_at);
int saved = errno;
if (asked) {
list = real();
saved = errno;
if (list && list[0].if_index != 0) return list;
}
struct link link;
struct name_block *block;
if (!load_link(&link)) {
if (!asked) return real();
errno = saved;
return list;
}
if (asked) __atomic_store_n(&nameindex_unusable_at, now_ns(), __ATOMIC_RELAXED);
if ((block = calloc(1, sizeof(*block))) == NULL) {
if (asked) errno = saved;
return list;
}
if (list) if_freenameindex(list);
snprintf(block->names[0], IFNAMSIZ, "%s", LO_NAME);
snprintf(block->names[1], IFNAMSIZ, "%s", link.name);
block->index[0] = (struct if_nameindex) { LO_INDEX, block->names[0] };
block->index[1] = (struct if_nameindex) { link.index, block->names[1] };
return block->index;
}
void if_freenameindex(struct if_nameindex *list) {
typedef void (*if_freenameindex_fn)(struct if_nameindex *);
if (list == NULL) return;
if (list[0].if_name == ((struct name_block *) list)->names[0]) {
free(list);
return;
}
((if_freenameindex_fn) dlsym(RTLD_NEXT, "if_freenameindex"))(list);
}
unsigned int if_nametoindex(const char *name) {
typedef unsigned int (*if_nametoindex_fn)(const char *);
unsigned int index = ((if_nametoindex_fn) dlsym(RTLD_NEXT, "if_nametoindex"))(name);
if (index != 0 || name == NULL) return index;
int saved = errno;
struct link link;
if (strcmp(name, LO_NAME) == 0) return LO_INDEX;
if (load_link(&link) && strcmp(name, link.name) == 0) return link.index;
errno = saved;
return 0;
}
char *if_indextoname(unsigned int index, char name[IF_NAMESIZE]) {
typedef char *(*if_indextoname_fn)(unsigned int, char *);
char *found = ((if_indextoname_fn) dlsym(RTLD_NEXT, "if_indextoname"))(index, name);
if (found || name == NULL) return found;
int saved = errno;
struct link link;
if (index == LO_INDEX) return strcpy(name, LO_NAME);
if (load_link(&link) && index == link.index) return strcpy(name, link.name);
errno = saved;
return NULL;
}
static int mac_unusable(const struct ifreq *ifr) {
const unsigned char *a = (const unsigned char *) ifr->ifr_hwaddr.sa_data;
if (ifr->ifr_hwaddr.sa_family != ARPHRD_ETHER) return 1;
int rest = a[1] | a[2] | a[3] | a[4] | a[5];
return (a[0] & 0x03) || (rest == 0 && (a[0] == 0 || a[0] == 0x02));
}
static const struct link_addr *first_v4(const struct link *link) {
for (int i = 0; i < link->addr_count; i++) {
if (link->addrs[i].family == AF_INET) return &link->addrs[i];
}
return NULL;
}
static int answer_ifreq(unsigned long request, struct ifreq *ifr, int real_rc) {
char name[IFNAMSIZ + 1] = { 0 };
memcpy(name, ifr->ifr_name, IFNAMSIZ);
int loopback = strcmp(name, LO_NAME) == 0;
struct link link;
if (!load_link(&link) || (!loopback && strcmp(name, link.name) != 0)) return 0;
if (request == SIOCGIFHWADDR) {
if (real_rc == 0 && (loopback || !mac_unusable(ifr))) return 0;
memset(&ifr->ifr_hwaddr, 0, sizeof(ifr->ifr_hwaddr));
ifr->ifr_hwaddr.sa_family = loopback ? ARPHRD_LOOPBACK : ARPHRD_ETHER;
if (!loopback) memcpy(ifr->ifr_hwaddr.sa_data, link.mac, 6);
return 1;
}
if (real_rc == 0) return 0;
const struct link_addr *v4 = loopback ? &LO_V4 : first_v4(&link);
struct sockaddr_in *sin = (struct sockaddr_in *) &ifr->ifr_addr;
unsigned char mask[16];
switch (request) {
case SIOCGIFFLAGS:
ifr->ifr_flags = loopback ? IFF_UP | IFF_RUNNING | IFF_LOOPBACK
: IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST;
return 1;
case SIOCGIFMTU:
ifr->ifr_mtu = loopback ? LO_MTU : link.mtu;
return 1;
case SIOCGIFINDEX:
ifr->ifr_ifindex = loopback ? LO_INDEX : (int) link.index;
return 1;
case SIOCGIFADDR:
case SIOCGIFNETMASK:
case SIOCGIFBRDADDR:
if (v4 == NULL) return 0;
prefix_mask(AF_INET, v4->prefix, mask);
memset(sin, 0, sizeof(*sin));
sin->sin_family = AF_INET;
for (int i = 0; i < 4; i++) {
unsigned char *out = (unsigned char *) &sin->sin_addr + i;
*out = request == SIOCGIFNETMASK ? mask[i]
: request == SIOCGIFADDR ? v4->bytes[i]
: (unsigned char) (v4->bytes[i] | ~mask[i]);
}
return 1;
default:
return 0;
}
}
static int is_link_query(unsigned long request) {
return request == SIOCGIFHWADDR || request == SIOCGIFFLAGS || request == SIOCGIFMTU
|| request == SIOCGIFINDEX || request == SIOCGIFADDR || request == SIOCGIFNETMASK
|| request == SIOCGIFBRDADDR;
}
void bl_kgsl_chip_id_fixup(unsigned long request, void *arg, int rc);
#include "../../quest/kgsl_compat.h"
int bl_kgsl_poll(int (*real)(int, unsigned long, void *), int fd, unsigned long request,
void *arg, int *rc) __attribute__((visibility("hidden")));
void bl_kgsl_track(int (*real)(int, unsigned long, void *), int fd, unsigned long request,
void *arg, int *rc) __attribute__((visibility("hidden")));
int bl_ntsync_ioctl(int fd, unsigned long request, void *arg, int *rc) __attribute__((visibility("hidden")));
int bl_drm_gem_close(int fd, unsigned long request, void *arg, int *rc) __attribute__((visibility("hidden")));
int bl_venc_ioctl(int fd, unsigned long request, void *arg, int *rc) __attribute__((visibility("hidden")));
int bl_vdec_ioctl(int fd, unsigned long request, void *arg, int *rc) __attribute__((visibility("hidden")));
int bl_winebus_rumble_ioctl(int (*real)(int, unsigned long, void *), int fd, unsigned long request,
void *arg, void *caller, int *rc) __attribute__((visibility("hidden")));
int bl_writewatch_ioctl(int (*real)(int, unsigned long, void *), int fd, unsigned long request, void *arg,
int *rc) __attribute__((visibility("hidden")));
int ioctl(int fd, unsigned long request, ...) {
typedef int (*ioctl_fn)(int, unsigned long, void *);
static ioctl_fn real;
va_list ap;
va_start(ap, request);
void *arg = va_arg(ap, void *);
va_end(ap);
int rc;
if (bl_ntsync_ioctl(fd, request, arg, &rc)) return rc;
if (bl_venc_ioctl(fd, request, arg, &rc)) return rc;
if (bl_vdec_ioctl(fd, request, arg, &rc)) return rc;
if (bl_drm_gem_close(fd, request, arg, &rc)) return rc;
if (real == NULL) real = (ioctl_fn) dlsym(RTLD_NEXT, "ioctl");
if (bl_winebus_rumble_ioctl(real, fd, request, arg, __builtin_return_address(0), &rc)) return rc;
if (bl_writewatch_ioctl(real, fd, request, arg, &rc)) return rc;
if (!bl_kgsl_poll(real, fd, request, arg, &rc)) rc = quest_kgsl_ioctl(real, fd, request, arg);
bl_kgsl_track(real, fd, request, arg, &rc);
bl_kgsl_chip_id_fixup(request, arg, rc);
if (arg == NULL || !is_link_query(request)) return rc;
int saved = errno;
if (answer_ifreq(request, (struct ifreq *) arg, rc)) return 0;
errno = saved;
return rc;
}
static void print_v6(FILE *out, const unsigned char *bytes) {
for (int i = 0; i < 16; i++) fprintf(out, "%02x", bytes[i]);
}
static unsigned v6_scope(const struct link_addr *addr) {
if (memcmp(addr, &LO_V6, sizeof(*addr)) == 0) return 0x10;
return is_link_local(addr) ? 0x20 : 0x00;
}
static void write_table(FILE *out, const char *table, const struct link *link) {
static const unsigned char zero[16];
unsigned char mask[16];
if (strcmp(table, "route") == 0) {
fprintf(out, "Iface\tDestination\tGateway \tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT\n");
for (int i = 0; i < link->gateway_count; i++) {
if (link->gateways[i].family != AF_INET) continue;
unsigned gw;
memcpy(&gw, link->gateways[i].bytes, 4);
fprintf(out, "%s\t00000000\t%08X\t0003\t0\t0\t0\t00000000\t0\t0\t0\n", link->name, gw);
}
for (int i = 0; i < link->addr_count; i++) {
if (link->addrs[i].family != AF_INET) continue;
unsigned addr, net;
prefix_mask(AF_INET, link->addrs[i].prefix, mask);
memcpy(&addr, link->addrs[i].bytes, 4);
memcpy(&net, mask, 4);
fprintf(out, "%s\t%08X\t00000000\t0001\t0\t0\t0\t%08X\t0\t0\t0\n",
link->name, addr & net, net);
}
} else if (strcmp(table, "ipv6_route") == 0) {
for (int i = 0; i < link->gateway_count; i++) {
if (link->gateways[i].family != AF_INET6) continue;
print_v6(out, zero);
fprintf(out, " 00 ");
print_v6(out, zero);
fprintf(out, " 00 ");
print_v6(out, link->gateways[i].bytes);
fprintf(out, " 00000400 00000001 00000000 00000003 %8s\n", link->name);
}
for (int i = 0; i < link->addr_count; i++) {
if (link->addrs[i].family != AF_INET6) continue;
unsigned char net[16];
prefix_mask(AF_INET6, link->addrs[i].prefix, mask);
for (int b = 0; b < 16; b++) net[b] = link->addrs[i].bytes[b] & mask[b];
print_v6(out, net);
fprintf(out, " %02x ", link->addrs[i].prefix);
print_v6(out, zero);
fprintf(out, " 00 ");
print_v6(out, zero);
fprintf(out, " 00000100 00000001 00000000 00000001 %8s\n", link->name);
}
} else if (strcmp(table, "if_inet6") == 0) {
print_v6(out, LO_V6.bytes);
fprintf(out, " %02x %02x %02x 80 %8s\n", LO_INDEX, LO_V6.prefix, v6_scope(&LO_V6), LO_NAME);
for (int i = 0; i < link->addr_count; i++) {
if (link->addrs[i].family != AF_INET6) continue;
print_v6(out, link->addrs[i].bytes);
fprintf(out, " %02x %02x %02x 80 %8s\n", link->index, link->addrs[i].prefix,
v6_scope(&link->addrs[i]), link->name);
}
} else if (strcmp(table, "dev") == 0) {
fprintf(out, "Inter-| Receive | Transmit\n"
" face |bytes packets errs drop fifo frame compressed multicast|"
"bytes packets errs drop fifo colls carrier compressed\n");
fprintf(out, "%6s: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", LO_NAME);
fprintf(out, "%6s: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", link->name);
} else if (strcmp(table, "arp") == 0) {
fprintf(out, "IP address HW type Flags HW address Mask Device\n");
} else {
fprintf(out, "1\n");
}
}
#define CARRIER_TABLE 5
static const char *const net_tables[] = { "route", "ipv6_route", "if_inet6", "dev", "arp", "carrier" };
static long long net_denied_at[sizeof(net_tables) / sizeof(net_tables[0])];
#define CARRIER_SLOTS 8
static struct { char path[64]; long long at; } carrier_denied[CARRIER_SLOTS];
static int denied_recently(int table, const char *path) {
if (table != CARRIER_TABLE) return fresh(&net_denied_at[table]);
int hit = 0;
pthread_mutex_lock(&link_lock);
for (int i = 0; i < CARRIER_SLOTS && !hit; i++) {
hit = strcmp(carrier_denied[i].path, path) == 0 && fresh(&carrier_denied[i].at);
}
pthread_mutex_unlock(&link_lock);
return hit;
}
static void remember_denial(int table, const char *path) {
if (table != CARRIER_TABLE) {
__atomic_store_n(&net_denied_at[table], now_ns(), __ATOMIC_RELAXED);
return;
}
if (strlen(path) >= sizeof(carrier_denied[0].path)) return;
pthread_mutex_lock(&link_lock);
int slot = 0;
for (int i = 0; i < CARRIER_SLOTS; i++) {
if (strcmp(carrier_denied[i].path, path) == 0) { slot = i; break; }
if (carrier_denied[i].at < carrier_denied[slot].at) slot = i;
}
snprintf(carrier_denied[slot].path, sizeof(carrier_denied[slot].path), "%s", path);
carrier_denied[slot].at = now_ns();
pthread_mutex_unlock(&link_lock);
}
static int denied_table(const char *path) {
if (strncmp(path, "/proc/net/", 10) == 0) {
for (int i = 0; i < CARRIER_TABLE; i++) {
if (strcmp(path + 10, net_tables[i]) == 0) return i;
}
return -1;
}
size_t len = strlen(path);
if (strncmp(path, "/sys/class/net/", 15) == 0 && len > 8 && strcmp(path + len - 8, "/carrier") == 0) {
return CARRIER_TABLE;
}
return -1;
}
static FILE *synthesize(const char *table) {
struct link link;
if (!load_link(&link)) return NULL;
int fd = memfd_create("droiddeck-net", MFD_CLOEXEC);
if (fd < 0) return NULL;
FILE *out = fdopen(fd, "w+");
if (out == NULL) {
close(fd);
return NULL;
}
write_table(out, table, &link);
if (fflush(out) != 0) {
fclose(out);
return NULL;
}
rewind(out);
return out;
}
int bl_status_without_tracer(const char *path, int flags, const void *caller) __attribute__((visibility("hidden")));
const char *bl_oom_adj_redirect(const char *path, int writing) __attribute__((visibility("hidden")));
static FILE *open_stream(fopen_fn real, const char *path, const char *mode, const void *caller) {
int status = mode && mode[0] == 'r' && !strchr(mode, '+')
? bl_status_without_tracer(path, O_RDONLY | O_CLOEXEC, caller) : -1;
if (status >= 0) {
FILE *copy = fdopen(status, "r");
if (copy == NULL) close(status);
return copy;
}
path = bl_oom_adj_redirect(path, mode && (mode[0] == 'w' || mode[0] == 'a' || strchr(mode, '+')));
int table = path && mode && mode[0] == 'r' ? denied_table(path) : -1;
FILE *f;
if (table >= 0 && denied_recently(table, path) && (f = synthesize(net_tables[table])) != NULL) return f;
f = real(path, mode);
if (f || table < 0) return f;
int saved = errno;
if ((f = synthesize(net_tables[table])) != NULL) {
if (saved == EACCES) remember_denial(table, path);
return f;
}
errno = saved;
return NULL;
}
FILE *fopen(const char *path, const char *mode) {
static fopen_fn real;
if (real == NULL) real = (fopen_fn) dlsym(RTLD_NEXT, "fopen");
return open_stream(real, path, mode, __builtin_return_address(0));
}
FILE *fopen64(const char *path, const char *mode) {
static fopen_fn real;
if (real == NULL) real = (fopen_fn) dlsym(RTLD_NEXT, "fopen64");
return open_stream(real, path, mode, __builtin_return_address(0));
}
#define NL_REQUEST_MAX 1024
#define NL_REPLY_MAX 4096
#ifndef IFF_LOWER_UP
#define IFF_LOWER_UP 0x10000
#endif
#ifndef IF_OPER_UP
#define IF_OPER_UP 6
#endif
#ifndef SO_DOMAIN
#define SO_DOMAIN 39
#endif
#ifndef SO_PROTOCOL
#define SO_PROTOCOL 38
#endif
struct nl_socket {
int peer;
int reservation;
dev_t device;
ino_t inode;
ino_t peer_inode;
ino_t reservation_inode;
unsigned port;
unsigned references;
int type;
int cap_ack;
struct nl_socket *next;
};
struct nl_fd {
int fd;
struct nl_socket *socket;
struct nl_fd *next;
};
#define NL_FD_PAGE_SIZE 1024
#define NL_FD_BUCKETS 256
#define NL_FD_WORD_BITS (sizeof(unsigned long) * CHAR_BIT)
struct nl_fd_page {
unsigned number;
unsigned long bits[NL_FD_PAGE_SIZE / NL_FD_WORD_BITS];
struct nl_fd_page *next;
};
struct nl_metadata {
uint64_t nonce;
uint32_t port;
uint32_t type;
uint32_t cap_ack;
char magic[12];
};
_Static_assert(sizeof(struct nl_metadata) == 32, "netlink socket metadata ABI");
struct nl_packet {
struct nl_packet *next;
size_t len;
unsigned char data[];
};
struct nl_reply {
unsigned char data[NL_REPLY_MAX];
size_t len;
int overflow;
};
static const char nl_client_magic[12] = "bl-nl-c-v1";
static const char nl_peer_magic[12] = "bl-nl-p-v1";
static const char nl_reservation_magic[12] = "bl-nl-r-v1";
static struct nl_socket *nl_sockets;
static struct nl_fd *nl_fds;
static struct nl_fd_page *nl_fd_pages[NL_FD_BUCKETS];
static unsigned nl_fd_count;
static int nl_fd_index_incomplete;
static unsigned nl_next_port = 0x80000000u;
static pthread_mutex_t nl_sockets_lock = PTHREAD_MUTEX_INITIALIZER;
static pid_t nl_owner;
static __thread unsigned nl_reply_port;
static __thread int nl_reply_capped;
static void nl_lock_before_fork(void) { pthread_mutex_lock(&nl_sockets_lock); }
static void nl_unlock_after_fork(void) { pthread_mutex_unlock(&nl_sockets_lock); }
static void nl_reset_after_fork(void) {
pthread_mutex_init(&nl_sockets_lock, NULL);
nl_owner = getpid();
}
static int nl_foreign(void) { return getpid() != nl_owner; }
static void nl_restore_inherited(void);
__attribute__((constructor)) static void nl_sockets_init(void) {
nl_owner = getpid();
pthread_atfork(nl_lock_before_fork, nl_unlock_after_fork, nl_reset_after_fork);
nl_restore_inherited();
}
static struct nl_fd_page *nl_fd_page_of(int fd) {
unsigned number = (unsigned) fd / NL_FD_PAGE_SIZE;
struct nl_fd_page *page =
__atomic_load_n(&nl_fd_pages[number % NL_FD_BUCKETS], __ATOMIC_ACQUIRE);
for (; page; page = page->next) {
if (page->number == number) return page;
}
return NULL;
}
static int nl_tracked(int fd) {
if (fd < 0 || __atomic_load_n(&nl_fd_count, __ATOMIC_ACQUIRE) == 0) return 0;
struct nl_fd_page *page = nl_fd_page_of(fd);
unsigned offset = (unsigned) fd % NL_FD_PAGE_SIZE;
if (page && (__atomic_load_n(&page->bits[offset / NL_FD_WORD_BITS], __ATOMIC_ACQUIRE)
& (1ul << (offset % NL_FD_WORD_BITS)))) return 1;
return __atomic_load_n(&nl_fd_index_incomplete, __ATOMIC_ACQUIRE);
}
static void nl_register(struct nl_fd *entry) {
struct nl_fd_page *page = nl_fd_page_of(entry->fd);
if (page == NULL) {
page = calloc(1, sizeof(*page));
if (page) {
page->number = (unsigned) entry->fd / NL_FD_PAGE_SIZE;
unsigned bucket = page->number % NL_FD_BUCKETS;
page->next = nl_fd_pages[bucket];
__atomic_store_n(&nl_fd_pages[bucket], page, __ATOMIC_RELEASE);
} else {
__atomic_store_n(&nl_fd_index_incomplete, 1, __ATOMIC_RELEASE);
}
}
entry->next = nl_fds;
nl_fds = entry;
if (page) {
unsigned offset = (unsigned) entry->fd % NL_FD_PAGE_SIZE;
__atomic_fetch_or(&page->bits[offset / NL_FD_WORD_BITS],
1ul << (offset % NL_FD_WORD_BITS), __ATOMIC_RELEASE);
}
__atomic_fetch_add(&nl_fd_count, 1, __ATOMIC_RELEASE);
}
static int nl_identity(int fd, dev_t device, ino_t inode) {
struct stat status;
return fstat(fd, &status) == 0 && S_ISSOCK(status.st_mode)
&& status.st_dev == device && status.st_ino == inode;
}
static int nl_address(int fd, int peer, struct sockaddr_un *addr, struct nl_metadata *metadata,
const char *magic) {
static int (*real_name)(int, struct sockaddr *, socklen_t *);
static int (*real_peer)(int, struct sockaddr *, socklen_t *);
if (real_name == NULL) real_name = (int (*)(int, struct sockaddr *, socklen_t *))
dlsym(RTLD_NEXT, "getsockname");
if (real_peer == NULL) real_peer = (int (*)(int, struct sockaddr *, socklen_t *))
dlsym(RTLD_NEXT, "getpeername");
socklen_t len = sizeof(*addr);
memset(addr, 0, sizeof(*addr));
int result = (peer ? real_peer : real_name)(fd, (struct sockaddr *) addr, &len);
if (result != 0 || len != offsetof(struct sockaddr_un, sun_path) + 1 + sizeof(*metadata)
|| addr->sun_family != AF_UNIX || addr->sun_path[0] != '\0') return 0;
memcpy(metadata, addr->sun_path + 1, sizeof(*metadata));
return memcmp(metadata->magic, magic, sizeof(metadata->magic)) == 0;
}
static int nl_bind_address(int fd, struct nl_metadata *metadata, struct sockaddr_un *addr) {
static int (*real_bind)(int, const struct sockaddr *, socklen_t);
if (real_bind == NULL) real_bind = (int (*)(int, const struct sockaddr *, socklen_t))
dlsym(RTLD_NEXT, "bind");
memset(addr, 0, sizeof(*addr));
addr->sun_family = AF_UNIX;
memcpy(addr->sun_path + 1, metadata, sizeof(*metadata));
return real_bind(fd, (const struct sockaddr *) addr,
offsetof(struct sockaddr_un, sun_path) + 1 + sizeof(*metadata));
}
static int nl_connect_address(int fd, const struct sockaddr_un *addr) {
static int (*real_connect)(int, const struct sockaddr *, socklen_t);
if (real_connect == NULL) real_connect = (int (*)(int, const struct sockaddr *, socklen_t))
dlsym(RTLD_NEXT, "connect");
return real_connect(fd, (const struct sockaddr *) addr,
offsetof(struct sockaddr_un, sun_path) + 1 + sizeof(struct nl_metadata));
}
static void nl_close_peer(struct nl_socket *socket) {
if (socket->peer < 0) return;
if (!nl_identity(socket->peer, socket->device, socket->peer_inode)) return;
static int (*real_close)(int);
if (real_close == NULL) real_close = (int (*)(int)) dlsym(RTLD_NEXT, "close");
if (real_close) real_close(socket->peer);
}
static int nl_reserve_port(struct nl_socket *socket, unsigned port) {
static int (*real_socket)(int, int, int);
static int (*real_close)(int);
if (real_socket == NULL) real_socket = (int (*)(int, int, int)) dlsym(RTLD_NEXT, "socket");
if (real_close == NULL) real_close = (int (*)(int)) dlsym(RTLD_NEXT, "close");
int descriptor = real_socket(AF_UNIX, SOCK_DGRAM, 0);
if (descriptor < 0) return -1;
struct nl_metadata metadata = { .port = port };
struct sockaddr_un addr;
struct stat status;
memcpy(metadata.magic, nl_reservation_magic, sizeof(metadata.magic));
if (nl_bind_address(descriptor, &metadata, &addr) != 0 || fstat(descriptor, &status) != 0) {
int saved = errno;
real_close(descriptor);
errno = saved;
return -1;
}
socket->reservation = descriptor;
socket->reservation_inode = status.st_ino;
return 0;
}
static void nl_close_reservation(struct nl_socket *socket) {
if (socket->reservation < 0
|| !nl_identity(socket->reservation, socket->device, socket->reservation_inode)) return;
static int (*real_close)(int);
if (real_close == NULL) real_close = (int (*)(int)) dlsym(RTLD_NEXT, "close");
if (real_close) real_close(socket->reservation);
socket->reservation = -1;
}
static int nl_replay_packets(int peer, struct nl_packet *packets) {
static ssize_t (*real_send)(int, const void *, size_t, int);
if (real_send == NULL) real_send = (ssize_t (*)(int, const void *, size_t, int))
dlsym(RTLD_NEXT, "send");
int error = 0;
while (packets) {
struct nl_packet *packet = packets;
packets = packet->next;
if (real_send(peer, packet->data, packet->len, MSG_DONTWAIT | MSG_NOSIGNAL) < 0 && error == 0)
error = errno;
free(packet);
}
if (error) errno = error;
return error ? -1 : 0;
}
static void nl_discard_packets(struct nl_packet *packets) {
while (packets) {
struct nl_packet *next = packets->next;
free(packets);
packets = next;
}
}
static int nl_save_packets(int fd, struct nl_packet **packets) {
static ssize_t (*real_recv)(int, void *, size_t, int);
if (real_recv == NULL) real_recv = (ssize_t (*)(int, void *, size_t, int))
dlsym(RTLD_NEXT, "recv");
struct nl_packet **tail = packets;
*packets = NULL;
for (;;) {
ssize_t len = real_recv(fd, NULL, 0, MSG_PEEK | MSG_TRUNC | MSG_DONTWAIT);
if (len < 0) return errno == EAGAIN || errno == EWOULDBLOCK ? 0 : -1;
if ((size_t) len > SIZE_MAX - sizeof(struct nl_packet)) {
errno = EMSGSIZE;
return -1;
}
struct nl_packet *packet = malloc(sizeof(*packet) + (size_t) len);
if (packet == NULL) return -1;
ssize_t received = real_recv(fd, packet->data, (size_t) len, MSG_DONTWAIT);
if (received < 0) {
int saved = errno;
free(packet);
errno = saved;
return -1;
}
packet->len = (size_t) received;
packet->next = NULL;
*tail = packet;
tail = &packet->next;
}
}
static int nl_replace_peer(int fd, struct nl_socket *socket, unsigned port, int capped) {
static int (*real_socket)(int, int, int);
static int (*real_close)(int);
if (real_socket == NULL) real_socket = (int (*)(int, int, int)) dlsym(RTLD_NEXT, "socket");
if (real_close == NULL) real_close = (int (*)(int)) dlsym(RTLD_NEXT, "close");
struct sockaddr_un client_addr, peer_addr;
struct nl_metadata client, peer = { .port = port, .type = socket->type, .cap_ack = capped };
if (!nl_address(fd, 0, &client_addr, &client, nl_client_magic)) {
errno = EINVAL;
return -1;
}
int descriptor = real_socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (descriptor < 0) return -1;
struct nl_packet *packets = NULL;
struct stat status;
if (fstat(descriptor, &status) != 0) goto failed;
peer.nonce = status.st_ino;
memcpy(peer.magic, nl_peer_magic, sizeof(peer.magic));
if (nl_bind_address(descriptor, &peer, &peer_addr) != 0
|| nl_save_packets(fd, &packets) != 0) goto failed;
if (nl_connect_address(fd, &peer_addr) != 0
|| nl_connect_address(descriptor, &client_addr) != 0) goto failed;
struct timeval timeout;
socklen_t timeout_len = sizeof(timeout);
static int (*real_getopt)(int, int, int, void *, socklen_t *);
static int (*real_setopt)(int, int, int, const void *, socklen_t);
if (real_getopt == NULL) real_getopt = (int (*)(int, int, int, void *, socklen_t *))
dlsym(RTLD_NEXT, "getsockopt");
if (real_setopt == NULL) real_setopt = (int (*)(int, int, int, const void *, socklen_t))
dlsym(RTLD_NEXT, "setsockopt");
if (real_getopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, &timeout_len) == 0)
real_setopt(descriptor, SOL_SOCKET, SO_SNDTIMEO, &timeout, timeout_len);
int send_buffer;
socklen_t buffer_len = sizeof(send_buffer);
if (real_getopt(fd, SOL_SOCKET, SO_SNDBUF, &send_buffer, &buffer_len) == 0) {
send_buffer /= 2;
real_setopt(descriptor, SOL_SOCKET, SO_SNDBUF, &send_buffer, sizeof(send_buffer));
}
int replayed = nl_replay_packets(descriptor, packets);
int saved = errno;
nl_close_peer(socket);
socket->peer = descriptor;
socket->peer_inode = status.st_ino;
socket->port = port;
socket->cap_ack = capped;
errno = saved;
return replayed;
failed: {
int saved = errno;
if (socket->peer >= 0 && nl_identity(socket->peer, socket->device, socket->peer_inode))
nl_replay_packets(socket->peer, packets);
else
nl_discard_packets(packets);
real_close(descriptor);
errno = saved;
return -1;
}
}
static void nl_release(struct nl_socket *socket) {
if (--socket->references != 0) return;
for (struct nl_socket **entry = &nl_sockets; *entry; entry = &(*entry)->next) {
if (*entry != socket) continue;
*entry = socket->next;
break;
}
nl_close_peer(socket);
nl_close_reservation(socket);
free(socket);
}
static void nl_remove(struct nl_fd **entry) {
struct nl_fd *removed = *entry;
*entry = removed->next;
struct nl_fd *alias;
for (alias = nl_fds; alias; alias = alias->next) {
if (alias->fd == removed->fd) break;
}
struct nl_fd_page *page = nl_fd_page_of(removed->fd);
if (alias == NULL && page) {
unsigned offset = (unsigned) removed->fd % NL_FD_PAGE_SIZE;
__atomic_fetch_and(&page->bits[offset / NL_FD_WORD_BITS],
~(1ul << (offset % NL_FD_WORD_BITS)), __ATOMIC_RELEASE);
}
__atomic_fetch_sub(&nl_fd_count, 1, __ATOMIC_RELEASE);
nl_release(removed->socket);
free(removed);
}
static struct nl_fd *nl_restore(int fd) {
struct sockaddr_un client_addr, peer_addr;
struct nl_metadata client, peer;
struct stat status;
if (!nl_address(fd, 0, &client_addr, &client, nl_client_magic)
|| !nl_address(fd, 1, &peer_addr, &peer, nl_peer_magic)
|| (peer.type != SOCK_RAW && peer.type != SOCK_DGRAM)
|| client.type != peer.type || peer.cap_ack > 1
|| fstat(fd, &status) != 0 || client.nonce != (uint64_t) status.st_ino) return NULL;
struct nl_fd *entry = malloc(sizeof(*entry));
if (entry == NULL) return NULL;
struct nl_socket *socket;
for (socket = nl_sockets; socket; socket = socket->next) {
if (socket->device == status.st_dev && socket->inode == status.st_ino) break;
}
if (socket == NULL) {
socket = calloc(1, sizeof(*socket));
if (socket == NULL) {
free(entry);
return NULL;
}
socket->peer = -1;
socket->reservation = -1;
socket->device = status.st_dev;
socket->inode = status.st_ino;
socket->type = peer.type;
if (nl_replace_peer(fd, socket, peer.port, peer.cap_ack) != 0) {
free(socket);
free(entry);
return NULL;
}
socket->next = nl_sockets;
nl_sockets = socket;
}
socket->references++;
entry->fd = fd;
entry->socket = socket;
nl_register(entry);
return entry;
}
static struct nl_fd *nl_find(int fd) {
if (fd < 0) return NULL;
for (struct nl_fd **entry = &nl_fds; *entry; entry = &(*entry)->next) {
if ((*entry)->fd != fd) continue;
struct nl_socket *socket = (*entry)->socket;
if (nl_identity(fd, socket->device, socket->inode)) return *entry;
nl_remove(entry);
break;
}
return NULL;
}
static void nl_discover(int fd) {
int saved = errno;
if (nl_tracked(fd)) {
pthread_mutex_lock(&nl_sockets_lock);
int found = nl_find(fd) != NULL;
pthread_mutex_unlock(&nl_sockets_lock);
if (found) {
errno = saved;
return;
}
}
struct sockaddr_un addr;
struct nl_metadata metadata;
if (nl_address(fd, 0, &addr, &metadata, nl_client_magic)) {
pthread_mutex_lock(&nl_sockets_lock);
if (nl_find(fd) == NULL) nl_restore(fd);
pthread_mutex_unlock(&nl_sockets_lock);
}
errno = saved;
}
static void nl_restore_inherited(void) {
int directory = (int) syscall(SYS_openat, AT_FDCWD, "/proc/self/fd",
O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (directory < 0) return;
int *descriptors = NULL;
size_t count = 0, capacity = 0;
union {
max_align_t alignment;
unsigned char bytes[4096];
} buffer;
long length;
while ((length = syscall(SYS_getdents64, directory, buffer.bytes, sizeof(buffer.bytes))) > 0) {
size_t offset = 0;
while (offset < (size_t) length) {
struct dirent *entry = (struct dirent *) (buffer.bytes + offset);
size_t minimum = offsetof(struct dirent, d_name) + 1;
if (entry->d_reclen < minimum || entry->d_reclen > (size_t) length - offset) break;
char *end;
long descriptor = strtol(entry->d_name, &end, 10);
if (end != entry->d_name && !*end && descriptor >= 0 && descriptor <= INT_MAX &&
descriptor != directory) {
if (count == capacity) {
size_t enlarged = capacity ? capacity * 2 : 16;
int *allocated = realloc(descriptors, enlarged * sizeof(*descriptors));
if (allocated == NULL) {
offset = (size_t) length;
break;
}
descriptors = allocated;
capacity = enlarged;
}
descriptors[count++] = (int) descriptor;
}
offset += entry->d_reclen;
}
}
int saved = errno;
syscall(SYS_close, directory);
errno = saved;
for (size_t index = 0; index < count; index++) {
nl_discover(descriptors[index]);
}
for (size_t index = 0; index < count; index++) {
int descriptor = descriptors[index];
struct sockaddr_un addr;
struct nl_metadata metadata;
if (!nl_address(descriptor, 0, &addr, &metadata, nl_reservation_magic)) continue;
struct stat status;
if (fstat(descriptor, &status) != 0) continue;
struct nl_socket *socket;
for (socket = nl_sockets; socket; socket = socket->next) {
if (socket->port == metadata.port) break;
}
if (socket) {
socket->reservation = descriptor;
socket->reservation_inode = status.st_ino;
} else {
static int (*real_close)(int);
if (real_close == NULL) real_close = (int (*)(int)) dlsym(RTLD_NEXT, "close");
real_close(descriptor);
}
}
free(descriptors);
}
static int nl_nonblocking(int fd) {
static int (*real_fcntl)(int, int, ...);
int flags;
if (real_fcntl == NULL) real_fcntl = (int (*)(int, int, ...)) dlsym(RTLD_NEXT, "fcntl");
if (real_fcntl == NULL) return 0;
flags = real_fcntl(fd, F_GETFL);
return flags >= 0 && (flags & O_NONBLOCK) != 0;
}
static int nl_peer_of(int fd) {
int peer = -1;
if (!nl_tracked(fd)) return peer;
pthread_mutex_lock(&nl_sockets_lock);
struct nl_fd *entry = nl_find(fd);
if (entry) {
struct sockaddr_un addr;
struct nl_metadata metadata;
struct nl_socket *socket = entry->socket;
if (nl_address(fd, 1, &addr, &metadata, nl_peer_magic)) {
if ((metadata.nonce == socket->peer_inode
&& nl_identity(socket->peer, socket->device, socket->peer_inode))
|| nl_replace_peer(fd, socket, metadata.port, metadata.cap_ack) == 0) {
if (socket->port != 0 && (socket->reservation < 0
|| !nl_identity(socket->reservation, socket->device, socket->reservation_inode)))
nl_reserve_port(socket, socket->port);
peer = socket->peer;
}
}
}
pthread_mutex_unlock(&nl_sockets_lock);
return peer;
}
static int nl_dup_peer_of(int fd, unsigned *port, int *capped) {
static int (*real_fcntl)(int, int, ...);
int peer = -1;
if (!nl_tracked(fd)) return peer;
pthread_mutex_lock(&nl_sockets_lock);
struct nl_fd *entry = nl_find(fd);
if (entry) {
if (real_fcntl == NULL)
real_fcntl = (int (*)(int, int, ...)) dlsym(RTLD_NEXT, "fcntl");
if (real_fcntl == NULL) {
errno = ENOSYS;
} else {
peer = real_fcntl(entry->socket->peer, F_DUPFD_CLOEXEC, 0);
if (peer >= 0) {
if (port) *port = entry->socket->port;
if (capped) *capped = entry->socket->cap_ack;
}
}
} else {
errno = EBADF;
}
pthread_mutex_unlock(&nl_sockets_lock);
return peer;
}
static unsigned nl_port_of(int fd) {
unsigned port = 0;
if (!nl_tracked(fd)) return port;
pthread_mutex_lock(&nl_sockets_lock);
struct nl_fd *entry = nl_find(fd);
if (entry) port = entry->socket->port;
pthread_mutex_unlock(&nl_sockets_lock);
return port;
}
__attribute__((visibility("hidden"))) int bl_nl_socket(int type, int *fd);
__attribute__((visibility("hidden"))) int bl_nl_bind(int fd, const struct sockaddr *addr,
socklen_t len, int *rc);
__attribute__((visibility("hidden"))) int bl_nl_getsockname(int fd, struct sockaddr *addr,
socklen_t *len, int *rc);
__attribute__((visibility("hidden"))) int bl_nl_owns(int fd);
__attribute__((visibility("hidden"))) void bl_nl_closed(int fd);
__attribute__((visibility("hidden"))) void bl_nl_range_closed(unsigned int first, unsigned int last);
__attribute__((visibility("hidden"))) int bl_nl_before_dup(int fd, void **state);
__attribute__((visibility("hidden"))) void bl_nl_after_dup(void *state, int fd);
__attribute__((visibility("hidden"))) int bl_nl_connect(int fd, const struct sockaddr *addr,
socklen_t len, int *rc);
__attribute__((visibility("hidden"))) int bl_nl_getpeername(int fd, struct sockaddr *addr,
socklen_t *len, int *rc);
__attribute__((visibility("hidden"))) int bl_nl_setsockopt(int fd, int level, int option,
const void *value, socklen_t len,
int (*real)(int, int, int, const void *, socklen_t),
int *rc);
__attribute__((visibility("hidden"))) int bl_nl_getsockopt(int fd, int level, int option,
void *value, socklen_t *len,
int (*real)(int, int, int, void *, socklen_t *),
int *rc);
__attribute__((visibility("hidden"))) void bl_nl_received(struct msghdr *msg);
__attribute__((visibility("hidden"))) int bl_nl_recvmsg(int fd, struct msghdr *msg, int flags,
ssize_t (*real)(int, struct msghdr *, int),
ssize_t *rc);
int bl_nl_owns(int fd) { return nl_peer_of(fd) >= 0; }
int bl_nl_socket(int type, int *fd) {
int kind = type & 0xf;
*fd = -1;
if (type & ~(0xf | SOCK_CLOEXEC | SOCK_NONBLOCK)) {
errno = EINVAL;
return 1;
}
if (kind != SOCK_RAW && kind != SOCK_DGRAM) {
errno = kind >= 11 ? EINVAL : ESOCKTNOSUPPORT;
return 1;
}
int pair[2];
struct stat client_status, peer_status;
struct nl_fd *entry = malloc(sizeof(*entry));
struct nl_socket *socket = calloc(1, sizeof(*socket));
if (entry == NULL || socket == NULL) {
free(entry);
free(socket);
errno = ENOMEM;
return 1;
}
int flags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
if (socketpair(AF_UNIX, SOCK_DGRAM | flags, 0, pair) != 0) {
free(entry);
free(socket);
return 1;
}
if (fcntl(pair[1], F_SETFD, FD_CLOEXEC) != 0 || fcntl(pair[1], F_SETFL, 0) != 0
|| fstat(pair[0], &client_status) != 0 || fstat(pair[1], &peer_status) != 0)
goto failed;
struct nl_metadata client = { .nonce = client_status.st_ino, .type = kind };
struct nl_metadata peer = { .nonce = peer_status.st_ino, .type = kind };
struct sockaddr_un addr;
memcpy(client.magic, nl_client_magic, sizeof(client.magic));
memcpy(peer.magic, nl_peer_magic, sizeof(peer.magic));
if (nl_bind_address(pair[0], &client, &addr) != 0
|| nl_bind_address(pair[1], &peer, &addr) != 0) goto failed;
socket->peer = pair[1];
socket->reservation = -1;
socket->device = client_status.st_dev;
socket->inode = client_status.st_ino;
socket->peer_inode = peer_status.st_ino;
socket->type = kind;
socket->references = 1;
entry->fd = pair[0];
entry->socket = socket;
pthread_mutex_lock(&nl_sockets_lock);
socket->next = nl_sockets;
nl_sockets = socket;
nl_register(entry);
pthread_mutex_unlock(&nl_sockets_lock);
*fd = pair[0];
return 1;
failed: {
int saved = errno;
close(pair[0]);
close(pair[1]);
free(entry);
free(socket);
errno = saved;
return 1;
}
}
static int nl_bind_port(int fd, unsigned requested, int automatic) {
int result = -1;
pthread_mutex_lock(&nl_sockets_lock);
struct nl_fd *entry = nl_find(fd);
if (entry) {
struct nl_socket *socket = entry->socket;
if (socket->port != 0) {
if (automatic || requested == socket->port) result = 0;
else errno = EINVAL;
} else {
unsigned port = requested ? requested : (unsigned) getpid();
for (;;) {
struct nl_socket *other;
for (other = nl_sockets; other; other = other->next) {
if (other != socket && other->port == port) break;
}
if (other == NULL) {
if (nl_reserve_port(socket, port) == 0) {
result = nl_replace_peer(fd, socket, port, socket->cap_ack);
if (result != 0) nl_close_reservation(socket);
break;
}
if (errno != EADDRINUSE || requested != 0) break;
}
if (requested != 0) {
errno = EADDRINUSE;
break;
}
port = nl_next_port++;
if (port == 0) port = nl_next_port++;
}
}
} else {
errno = EBADF;
}
pthread_mutex_unlock(&nl_sockets_lock);
return result;
}
static int nl_autobind(int fd) {
return nl_bind_port(fd, 0, 1);
}
int bl_nl_bind(int fd, const struct sockaddr *addr, socklen_t len, int *rc) {
const struct sockaddr_nl *nl;
if (addr && len >= sizeof(sa_family_t) && addr->sa_family == AF_NETLINK) nl_discover(fd);
if (!bl_nl_owns(fd)) return 0;
if (addr == NULL || len < sizeof(struct sockaddr_nl) || addr->sa_family != AF_NETLINK) {
errno = EINVAL;
*rc = -1;
return 1;
}
nl = (const struct sockaddr_nl *) addr;
if (nl->nl_groups != 0) {
errno = EACCES;
*rc = -1;
return 1;
}
*rc = nl_bind_port(fd, nl->nl_pid, 0);
return 1;
}
int bl_nl_getsockname(int fd, struct sockaddr *addr, socklen_t *len, int *rc) {
struct sockaddr_nl nl;
socklen_t size;
if (!bl_nl_owns(fd)) return 0;
if (addr == NULL || len == NULL) {
errno = EFAULT;
*rc = -1;
return 1;
}
memset(&nl, 0, sizeof(nl));
nl.nl_family = AF_NETLINK;
nl.nl_pid = nl_port_of(fd);
size = *len < sizeof(nl) ? *len : (socklen_t) sizeof(nl);
memcpy(addr, &nl, size);
*len = sizeof(nl);
*rc = 0;
return 1;
}
void bl_nl_closed(int fd) {
if (!nl_tracked(fd)) return;
if (nl_foreign()) return;
bl_nl_range_closed((unsigned int) fd, (unsigned int) fd);
}
void bl_nl_range_closed(unsigned int first, unsigned int last) {
if (__atomic_load_n(&nl_fd_count, __ATOMIC_ACQUIRE) == 0) return;
if (nl_foreign()) return;
pthread_mutex_lock(&nl_sockets_lock);
struct nl_fd **entry = &nl_fds;
while (*entry) {
if ((unsigned int) (*entry)->fd >= first && (unsigned int) (*entry)->fd <= last)
nl_remove(entry);
else
entry = &(*entry)->next;
}
pthread_mutex_unlock(&nl_sockets_lock);
}
int bl_nl_before_dup(int fd, void **state) {
*state = NULL;
if (!nl_tracked(fd)) return 0;
if (nl_foreign()) return 0;
pthread_mutex_lock(&nl_sockets_lock);
struct nl_fd *entry = nl_find(fd);
if (entry) {
struct nl_fd *reserved = malloc(sizeof(*reserved));
if (reserved == NULL) {
pthread_mutex_unlock(&nl_sockets_lock);
errno = ENOMEM;
return -1;
}
reserved->socket = entry->socket;
entry->socket->references++;
*state = reserved;
}
pthread_mutex_unlock(&nl_sockets_lock);
return 0;
}
void bl_nl_after_dup(void *state, int fd) {
int saved = errno;
struct nl_fd *reserved = state;
if (reserved == NULL && !nl_tracked(fd)) {
errno = saved;
return;
}
if (nl_foreign()) {
errno = saved;
return;
}
pthread_mutex_lock(&nl_sockets_lock);
if (reserved && fd >= 0) {
reserved->fd = fd;
nl_register(reserved);
}
if (fd >= 0) {
for (struct nl_fd **entry = &nl_fds; *entry; entry = &(*entry)->next) {
if ((*entry)->fd != fd || *entry == reserved) continue;
nl_remove(entry);
break;
}
}
if (reserved && fd < 0) {
nl_release(reserved->socket);
free(reserved);
}
pthread_mutex_unlock(&nl_sockets_lock);
errno = saved;
}
int dup(int fd) {
static int (*real)(int);
void *state;
if (real == NULL) real = (int (*)(int)) dlsym(RTLD_NEXT, "dup");
if (bl_nl_before_dup(fd, &state) != 0) return -1;
int result = real(fd);
bl_nl_after_dup(state, result);
return result;
}
static int nl_destination(const struct sockaddr *addr, socklen_t len) {
if (addr == NULL || len < sizeof(struct sockaddr_nl)) return -EINVAL;
if (addr->sa_family != AF_NETLINK) return -EAFNOSUPPORT;
const struct sockaddr_nl *destination = (const struct sockaddr_nl *) addr;
if (destination->nl_groups != 0) return -EACCES;
return destination->nl_pid == 0 ? 0 : -ECONNREFUSED;
}
int bl_nl_connect(int fd, const struct sockaddr *addr, socklen_t len, int *rc) {
if (addr && len >= sizeof(sa_family_t) && addr->sa_family == AF_NETLINK) nl_discover(fd);
if (!bl_nl_owns(fd)) return 0;
int disconnect = addr && len >= sizeof(sa_family_t) && addr->sa_family == AF_UNSPEC;
int error = disconnect ? 0 : nl_destination(addr, len);
if (error) {
errno = addr == NULL ? EFAULT : -error;
*rc = -1;
return 1;
}
*rc = disconnect ? 0 : nl_autobind(fd);
return 1;
}
static void nl_source_name(struct sockaddr *addr, socklen_t *len) {
struct sockaddr_nl source;
memset(&source, 0, sizeof(source));
source.nl_family = AF_NETLINK;
socklen_t capacity = *len < sizeof(source) ? *len : (socklen_t) sizeof(source);
if (capacity > 0) memcpy(addr, &source, capacity);
*len = sizeof(source);
}
int bl_nl_getpeername(int fd, struct sockaddr *addr, socklen_t *len, int *rc) {
if (!bl_nl_owns(fd)) return 0;
if (addr == NULL || len == NULL) {
errno = EFAULT;
*rc = -1;
} else {
nl_source_name(addr, len);
*rc = 0;
}
return 1;
}
int bl_nl_setsockopt(int fd, int level, int option, const void *value, socklen_t len,
int (*real)(int, int, int, const void *, socklen_t), int *rc) {
if (level == SOL_NETLINK) nl_discover(fd);
int peer = nl_peer_of(fd);
if (peer < 0) return 0;
if (level != SOL_NETLINK) {
*rc = real(fd, level, option, value, len);
if (*rc == 0 && level == SOL_SOCKET
&& (option == SO_SNDTIMEO || option == SO_SNDBUF))
*rc = real(peer, level, option, value, len);
return 1;
}
if (option != NETLINK_CAP_ACK && option != NETLINK_EXT_ACK) {
errno = option == NETLINK_ADD_MEMBERSHIP || option == NETLINK_DROP_MEMBERSHIP
? EACCES : ENOPROTOOPT;
*rc = -1;
} else if (value == NULL || len < sizeof(int)) {
errno = value == NULL ? EFAULT : EINVAL;
*rc = -1;
} else {
if (option == NETLINK_CAP_ACK) {
int enabled;
memcpy(&enabled, value, sizeof(enabled));
pthread_mutex_lock(&nl_sockets_lock);
struct nl_fd *entry = nl_find(fd);
if (entry && nl_replace_peer(fd, entry->socket, entry->socket->port, enabled != 0) != 0) {
pthread_mutex_unlock(&nl_sockets_lock);
*rc = -1;
return 1;
}
pthread_mutex_unlock(&nl_sockets_lock);
}
*rc = 0;
}
return 1;
}
int bl_nl_getsockopt(int fd, int level, int option, void *value, socklen_t *len,
int (*real)(int, int, int, void *, socklen_t *), int *rc) {
if (level != SOL_SOCKET || (option != SO_TYPE && option != SO_DOMAIN && option != SO_PROTOCOL))
return 0;
if (!nl_tracked(fd)) {
if (option != SO_DOMAIN) return 0;
*rc = real(fd, level, option, value, len);
if (*rc != 0) return 1;
int domain = AF_UNIX;
if (value && len && *len >= sizeof(domain)) memcpy(&domain, value, sizeof(domain));
if (domain != AF_UNIX) return 1;
nl_discover(fd);
if (!nl_tracked(fd)) return 1;
}
pthread_mutex_lock(&nl_sockets_lock);
struct nl_fd *entry = nl_find(fd);
if (entry == NULL) {
pthread_mutex_unlock(&nl_sockets_lock);
return 0;
}
int result = option == SO_TYPE ? entry->socket->type :
option == SO_DOMAIN ? AF_NETLINK : NETLINK_ROUTE;
pthread_mutex_unlock(&nl_sockets_lock);
if (len == NULL || (value == NULL && *len > 0)) {
errno = EFAULT;
*rc = -1;
} else if (*len > INT_MAX) {
errno = EINVAL;
*rc = -1;
} else {
socklen_t capacity = *len < sizeof(result) ? *len : (socklen_t) sizeof(result);
if (capacity) memcpy(value, &result, capacity);
*len = capacity;
*rc = 0;
}
return 1;
}
void bl_nl_received(struct msghdr *msg) {
for (struct cmsghdr *control = CMSG_FIRSTHDR(msg); control; control = CMSG_NXTHDR(msg, control)) {
if (control->cmsg_len < CMSG_LEN(0)) break;
if (control->cmsg_level != SOL_SOCKET || control->cmsg_type != SCM_RIGHTS) continue;
size_t count = (control->cmsg_len - CMSG_LEN(0)) / sizeof(int);
for (size_t index = 0; index < count; index++) {
int descriptor;
memcpy(&descriptor, CMSG_DATA(control) + index * sizeof(descriptor), sizeof(descriptor));
nl_discover(descriptor);
}
}
}
int bl_nl_recvmsg(int fd, struct msghdr *msg, int flags,
ssize_t (*real)(int, struct msghdr *, int), ssize_t *rc) {
ssize_t result;
socklen_t capacity = 0;
if (!bl_nl_owns(fd)) return 0;
if (msg && msg->msg_name) capacity = msg->msg_namelen;
result = real(fd, msg, flags);
if (result >= 0 && msg && msg->msg_name) {
msg->msg_namelen = capacity;
nl_source_name(msg->msg_name, &msg->msg_namelen);
}
*rc = result;
return 1;
}
ssize_t recvfrom(int fd, void *buffer, size_t length, int flags,
struct sockaddr *addr, socklen_t *addrlen) {
static ssize_t (*real)(int, void *, size_t, int, struct sockaddr *, socklen_t *);
if (real == NULL)
real = (ssize_t (*)(int, void *, size_t, int, struct sockaddr *, socklen_t *))
dlsym(RTLD_NEXT, "recvfrom");
int owned = bl_nl_owns(fd);
socklen_t capacity = owned && addr && addrlen ? *addrlen : 0;
ssize_t result = real(fd, buffer, length, flags, addr, addrlen);
if (result >= 0 && owned && addr && addrlen) {
*addrlen = capacity;
nl_source_name(addr, addrlen);
}
return result;
}
static unsigned char *reply_start(struct nl_reply *out, uint16_t type, uint16_t flags,
uint32_t seq, size_t payload) {
size_t offset = NLMSG_ALIGN(out->len);
struct nlmsghdr *header;
if (payload > sizeof(out->data)
|| offset + NLMSG_SPACE(payload) > sizeof(out->data)) {
out->overflow = 1;
return NULL;
}
header = (struct nlmsghdr *) (out->data + offset);
memset(header, 0, NLMSG_SPACE(payload));
header->nlmsg_len = NLMSG_LENGTH(payload);
header->nlmsg_type = type;
header->nlmsg_flags = flags;
header->nlmsg_seq = seq;
header->nlmsg_pid = nl_reply_port;
out->len = offset + NLMSG_ALIGN(header->nlmsg_len);
return (unsigned char *) header;
}
static void reply_attr(struct nl_reply *out, unsigned char *message, int type,
const void *data, size_t len) {
struct nlmsghdr *header = (struct nlmsghdr *) message;
size_t base = (size_t) (message - out->data);
size_t offset = NLMSG_ALIGN(header->nlmsg_len);
size_t total = RTA_ALIGN(RTA_LENGTH(len));
struct rtattr *attr;
if (base + offset + total > sizeof(out->data)) {
out->overflow = 1;
return;
}
attr = (struct rtattr *) (message + offset);
memset(attr, 0, total);
attr->rta_type = (unsigned short) type;
attr->rta_len = (unsigned short) RTA_LENGTH(len);
if (len > 0) memcpy(RTA_DATA(attr), data, len);
header->nlmsg_len = (unsigned) (offset + total);
out->len = base + NLMSG_ALIGN(header->nlmsg_len);
}
static void reply_done(struct nl_reply *out, uint32_t seq) {
unsigned char *message = reply_start(out, NLMSG_DONE, NLM_F_MULTI, seq, sizeof(int));
int code = 0;
if (message == NULL) return;
memcpy(NLMSG_DATA((struct nlmsghdr *) message), &code, sizeof(code));
}
static void reply_error(struct nl_reply *out, const struct nlmsghdr *request, int error) {
int capped = error == 0 || nl_reply_capped;
size_t extra = capped ? 0 : request->nlmsg_len - NLMSG_HDRLEN;
unsigned char *message = reply_start(out, NLMSG_ERROR, capped ? NLM_F_CAPPED : 0,
request->nlmsg_seq, sizeof(struct nlmsgerr) + extra);
struct nlmsgerr *reply;
if (message == NULL) return;
reply = (struct nlmsgerr *) NLMSG_DATA((struct nlmsghdr *) message);
memset(reply, 0, sizeof(*reply));
reply->error = error;
reply->msg = *request;
if (extra) memcpy(reply + 1, NLMSG_DATA(request), extra);
}
static void network_of(const struct link_addr *addr, unsigned char *out) {
unsigned char mask[16];
int len = addr->family == AF_INET ? 4 : 16;
prefix_mask(addr->family, addr->prefix, mask);
for (int i = 0; i < len; i++) out[i] = (unsigned char) (addr->bytes[i] & mask[i]);
}
static void broadcast_of(const struct link_addr *addr, unsigned char *out) {
unsigned char mask[16];
prefix_mask(AF_INET, addr->prefix, mask);
for (int i = 0; i < 4; i++) out[i] = (unsigned char) (addr->bytes[i] | (unsigned char) ~mask[i]);
}
static const struct link_addr *first_addr(const struct link *link, int family) {
for (int i = 0; i < link->addr_count; i++) {
if (link->addrs[i].family == family) return &link->addrs[i];
}
return NULL;
}
static void reply_link(struct nl_reply *out, const struct link *link, int loopback,
uint32_t seq, uint16_t flags) {
struct ifinfomsg info;
const char *name;
unsigned mtu;
unsigned char state = IF_OPER_UP;
unsigned char *message;
memset(&info, 0, sizeof(info));
info.ifi_family = AF_UNSPEC;
info.ifi_type = loopback ? ARPHRD_LOOPBACK : ARPHRD_ETHER;
info.ifi_index = loopback ? LO_INDEX : (link ? (int) link->index : 2);
info.ifi_flags = IFF_UP | IFF_RUNNING | IFF_LOWER_UP
| (loopback ? IFF_LOOPBACK : (IFF_BROADCAST | IFF_MULTICAST));
info.ifi_change = 0xffffffff;
message = reply_start(out, RTM_NEWLINK, flags, seq, sizeof(info));
if (message == NULL) return;
memcpy(NLMSG_DATA((struct nlmsghdr *) message), &info, sizeof(info));
name = loopback ? LO_NAME : (link ? link->name : "eth0");
mtu = loopback ? LO_MTU : (unsigned) (link ? link->mtu : 1500);
reply_attr(out, message, IFLA_IFNAME, name, strlen(name) + 1);
reply_attr(out, message, IFLA_MTU, &mtu, sizeof(mtu));
reply_attr(out, message, IFLA_OPERSTATE, &state, sizeof(state));
if (!loopback && link && (link->mac[0] | link->mac[1] | link->mac[2]
| link->mac[3] | link->mac[4] | link->mac[5])) {
reply_attr(out, message, IFLA_ADDRESS, link->mac, 6);
}
}
static void reply_addr(struct nl_reply *out, const struct link_addr *addr, unsigned index,
int loopback, uint32_t seq, uint16_t flags) {
struct ifaddrmsg info;
int size = addr->family == AF_INET ? 4 : 16;
unsigned char *message;
memset(&info, 0, sizeof(info));
info.ifa_family = (unsigned char) addr->family;
info.ifa_prefixlen = (unsigned char) addr->prefix;
info.ifa_flags = IFA_F_PERMANENT;
info.ifa_scope = (unsigned char) (loopback ? RT_SCOPE_HOST
: (is_link_local(addr) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE));
info.ifa_index = index;
message = reply_start(out, RTM_NEWADDR, flags, seq, sizeof(info));
if (message == NULL) return;
memcpy(NLMSG_DATA((struct nlmsghdr *) message), &info, sizeof(info));
reply_attr(out, message, IFA_LOCAL, addr->bytes, size);
reply_attr(out, message, IFA_ADDRESS, addr->bytes, size);
if (!loopback && addr->family == AF_INET && addr->prefix <= 30) {
unsigned char broadcast[4];
broadcast_of(addr, broadcast);
reply_attr(out, message, IFA_BROADCAST, broadcast, 4);
}
}
static void reply_route(struct nl_reply *out, int family, int destination_length,
const unsigned char *destination, const unsigned char *gateway,
const unsigned char *source, unsigned index, int protocol, int scope,
int type, uint32_t seq, uint16_t flags) {
struct rtmsg info;
int size = family == AF_INET ? 4 : 16;
unsigned char *message;
memset(&info, 0, sizeof(info));
info.rtm_family = (unsigned char) family;
info.rtm_dst_len = (unsigned char) destination_length;
info.rtm_table = type == RTN_LOCAL ? RT_TABLE_LOCAL : RT_TABLE_MAIN;
info.rtm_protocol = (unsigned char) protocol;
info.rtm_scope = (unsigned char) scope;
info.rtm_type = (unsigned char) type;
message = reply_start(out, RTM_NEWROUTE, flags, seq, sizeof(info));
if (message == NULL) return;
memcpy(NLMSG_DATA((struct nlmsghdr *) message), &info, sizeof(info));
if (destination_length > 0 && destination) reply_attr(out, message, RTA_DST, destination, size);
if (gateway) reply_attr(out, message, RTA_GATEWAY, gateway, size);
if (source) reply_attr(out, message, RTA_PREFSRC, source, size);
reply_attr(out, message, RTA_OIF, &index, sizeof(index));
}
static void reply_addrs(struct nl_reply *out, const struct link *link, int family, uint32_t seq) {
if (family == AF_UNSPEC || family == AF_INET)
reply_addr(out, &LO_V4, LO_INDEX, 1, seq, NLM_F_MULTI);
if (family == AF_UNSPEC || family == AF_INET6)
reply_addr(out, &LO_V6, LO_INDEX, 1, seq, NLM_F_MULTI);
if (link == NULL) return;
for (int i = 0; i < link->addr_count; i++) {
if (family != AF_UNSPEC && link->addrs[i].family != family) continue;
reply_addr(out, &link->addrs[i], link->index, 0, seq, NLM_F_MULTI);
}
}
static void reply_routes(struct nl_reply *out, const struct link *link, int family, uint32_t seq) {
if (link == NULL) return;
for (int pass = 0; pass < 2; pass++) {
int current = pass == 0 ? AF_INET : AF_INET6;
const struct link_addr *source;
if (family != AF_UNSPEC && family != current) continue;
source = first_addr(link, current);
for (int i = 0; i < link->gateway_count; i++) {
if (link->gateways[i].family != current) continue;
reply_route(out, current, 0, NULL, link->gateways[i].bytes,
source ? source->bytes : NULL, link->index, RTPROT_STATIC,
RT_SCOPE_UNIVERSE, RTN_UNICAST, seq, NLM_F_MULTI);
}
for (int i = 0; i < link->addr_count; i++) {
unsigned char network[16];
if (link->addrs[i].family != current) continue;
network_of(&link->addrs[i], network);
reply_route(out, current, link->addrs[i].prefix, network, NULL,
link->addrs[i].bytes, link->index, RTPROT_KERNEL,
RT_SCOPE_LINK, RTN_UNICAST, seq, NLM_F_MULTI);
}
}
}
static const struct rtattr *request_attr(const struct nlmsghdr *request, size_t offset, int type) {
int length;
const struct rtattr *attr;
if (request->nlmsg_len < NLMSG_HDRLEN + offset) return NULL;
length = (int) (request->nlmsg_len - NLMSG_HDRLEN - offset);
attr = (const struct rtattr *) ((const unsigned char *) NLMSG_DATA(request) + offset);
for (; RTA_OK(attr, length); attr = RTA_NEXT(attr, length)) {
if ((int) attr->rta_type == type) return attr;
}
return NULL;
}
static int address_matches(const unsigned char *destination, const struct link_addr *addr) {
unsigned char mask[16];
int size = addr->family == AF_INET ? 4 : 16;
prefix_mask(addr->family, addr->prefix, mask);
for (int index = 0; index < size; index++) {
if ((destination[index] & mask[index]) != (addr->bytes[index] & mask[index])) return 0;
}
return 1;
}
static int reply_route_get(struct nl_reply *out, const struct link *link,
const struct nlmsghdr *request) {
if (request->nlmsg_len < NLMSG_LENGTH(sizeof(struct rtmsg))) return -EINVAL;
const struct rtmsg *info = (const struct rtmsg *) NLMSG_DATA(request);
int family = info->rtm_family;
if (family != AF_INET && family != AF_INET6) return -EAFNOSUPPORT;
int size = family == AF_INET ? 4 : 16;
const struct rtattr *destination_attr = request_attr(request, sizeof(struct rtmsg), RTA_DST);
unsigned char zero[16] = { 0 };
if (destination_attr && RTA_PAYLOAD(destination_attr) < (size_t) size) return -EINVAL;
const unsigned char *destination = destination_attr ? RTA_DATA(destination_attr) : zero;
const struct link_addr *loopback = family == AF_INET ? &LO_V4 : &LO_V6;
const struct link_addr *source = NULL;
const struct link_addr *gateway = NULL;
int local = address_matches(destination, loopback);
if (local) source = loopback;
for (int index = 0; link && !local && index < link->addr_count; index++) {
const struct link_addr *addr = &link->addrs[index];
if (addr->family != family) continue;
if (memcmp(destination, addr->bytes, size) == 0) {
source = addr;
local = 1;
break;
}
if (address_matches(destination, addr) && (!source || addr->prefix > source->prefix))
source = addr;
}
if (source) {
reply_route(out, family, size * 8, destination, NULL, source->bytes,
local ? LO_INDEX : link->index, RTPROT_KERNEL,
local ? RT_SCOPE_HOST : RT_SCOPE_LINK,
local ? RTN_LOCAL : RTN_UNICAST, request->nlmsg_seq, 0);
return 0;
}
for (int i = 0; link && i < link->gateway_count; i++) {
if (link->gateways[i].family == family) {
gateway = &link->gateways[i];
break;
}
}
if (gateway == NULL) return -ENETUNREACH;
source = first_addr(link, family);
for (int index = 0; index < link->addr_count; index++) {
if (link->addrs[index].family == family && !is_link_local(&link->addrs[index])) {
source = &link->addrs[index];
break;
}
}
reply_route(out, family, size * 8, destination,
gateway->bytes, source ? source->bytes : NULL, link->index, RTPROT_STATIC,
RT_SCOPE_UNIVERSE, RTN_UNICAST, request->nlmsg_seq, 0);
return 0;
}
static void reply_request(const struct nlmsghdr *request, const struct link *link,
struct nl_reply *out) {
int dump = (request->nlmsg_flags & NLM_F_DUMP) != 0;
int error = 0;
uint16_t flags = dump ? NLM_F_MULTI : 0;
switch (request->nlmsg_type) {
case RTM_GETLINK: {
int index = 0;
if (request->nlmsg_len >= NLMSG_LENGTH(sizeof(struct ifinfomsg)))
index = ((const struct ifinfomsg *) NLMSG_DATA(request))->ifi_index;
if (!dump && index == 0) {
const struct rtattr *name_attr = request_attr(request, sizeof(struct ifinfomsg), IFLA_IFNAME);
size_t payload = name_attr ? RTA_PAYLOAD(name_attr) : 0;
const char *data = name_attr ? RTA_DATA(name_attr) : NULL;
size_t length = payload;
if (length && data[length - 1] == '\0') length--;
if (name_attr == NULL) {
error = -EINVAL;
} else if (payload == 0 || length >= IFNAMSIZ) {
error = -ERANGE;
} else {
char name[IFNAMSIZ];
memcpy(name, data, length);
name[length] = '\0';
if (strcmp(name, LO_NAME) == 0)
index = LO_INDEX;
else if (link && strcmp(name, link->name) == 0)
index = (int) link->index;
else
error = -ENODEV;
}
}
if (dump) {
reply_link(out, NULL, 1, request->nlmsg_seq, flags);
if (link) reply_link(out, link, 0, request->nlmsg_seq, flags);
reply_done(out, request->nlmsg_seq);
} else if (!error && (index == LO_INDEX || (link && index == (int) link->index))) {
reply_link(out, link, index == LO_INDEX, request->nlmsg_seq, 0);
} else if (!error) {
error = -ENODEV;
}
break;
}
case RTM_GETADDR: {
int family = AF_UNSPEC;
if (request->nlmsg_len >= NLMSG_LENGTH(sizeof(struct ifaddrmsg)))
family = ((const struct ifaddrmsg *) NLMSG_DATA(request))->ifa_family;
if (dump) {
reply_addrs(out, link, family, request->nlmsg_seq);
reply_done(out, request->nlmsg_seq);
} else {
error = -EOPNOTSUPP;
}
break;
}
case RTM_GETROUTE: {
int family = AF_UNSPEC;
if (request->nlmsg_len >= NLMSG_LENGTH(sizeof(struct rtmsg)))
family = ((const struct rtmsg *) NLMSG_DATA(request))->rtm_family;
if (dump) {
reply_routes(out, link, family, request->nlmsg_seq);
reply_done(out, request->nlmsg_seq);
} else {
error = reply_route_get(out, link, request);
}
break;
}
case RTM_GETNEIGH:
if (dump) reply_done(out, request->nlmsg_seq);
else error = -EOPNOTSUPP;
break;
default:
error = -EPERM;
break;
}
if (error || (!dump && (request->nlmsg_flags & NLM_F_ACK)))
reply_error(out, request, error);
}
static void build_reply(const void *data, size_t len, struct nl_reply *out) {
struct link link;
int have = load_link(&link);
size_t offset = 0;
out->len = 0;
out->overflow = 0;
while (offset + sizeof(struct nlmsghdr) <= len) {
const struct nlmsghdr *request =
(const struct nlmsghdr *) ((const unsigned char *) data + offset);
if (request->nlmsg_len < sizeof(struct nlmsghdr) || offset + request->nlmsg_len > len)
break;
reply_request(request, have ? &link : NULL, out);
offset += NLMSG_ALIGN(request->nlmsg_len);
}
}
static ssize_t send_raw(int fd, const void *data, size_t len, int extra) {
static ssize_t (*real)(int, const void *, size_t, int);
if (real == NULL) real = (ssize_t (*)(int, const void *, size_t, int)) dlsym(RTLD_NEXT, "send");
if (real == NULL) {
errno = ENOSYS;
return -1;
}
return real(fd, data, len, MSG_NOSIGNAL | extra);
}
static int deliver(int fd, const void *data, size_t len, int flags, ssize_t *rc) {
struct nl_reply reply;
int peer;
int extra = 0;
if (nl_peer_of(fd) < 0) return 0;
if (nl_autobind(fd) != 0) {
*rc = -1;
return 1;
}
if (nl_peer_of(fd) < 0) {
errno = EBADF;
*rc = -1;
return 1;
}
peer = nl_dup_peer_of(fd, &nl_reply_port, &nl_reply_capped);
if (peer < 0) {
*rc = -1;
return 1;
}
build_reply(data, len, &reply);
if (reply.overflow) {
int saved = EMSGSIZE;
close(peer);
errno = saved;
*rc = -1;
return 1;
}
if (reply.len == 0) {
struct nlmsghdr *request = (struct nlmsghdr *) data;
if (len < sizeof(struct nlmsghdr)) {
int saved = EINVAL;
close(peer);
errno = saved;
*rc = -1;
return 1;
}
reply.len = 0;
nl_reply_capped = 1;
reply_error(&reply, request, -EOPNOTSUPP);
}
if ((flags & MSG_DONTWAIT) || nl_nonblocking(fd)) extra = MSG_DONTWAIT;
*rc = send_raw(peer, reply.data, reply.len, extra) < 0 ? -1 : (ssize_t) len;
int saved = errno;
close(peer);
errno = saved;
return 1;
}
static int collect(const struct msghdr *msg, unsigned char *small, size_t capacity,
unsigned char **buffer, size_t *length) {
size_t used = 0;
if (msg == NULL || (msg->msg_iovlen && msg->msg_iov == NULL)) {
errno = EFAULT;
return 0;
}
if (msg->msg_iovlen > IOV_MAX) {
errno = EMSGSIZE;
return 0;
}
for (size_t index = 0; index < msg->msg_iovlen; index++) {
const struct iovec *part = &msg->msg_iov[index];
if (part->iov_len && part->iov_base == NULL) {
errno = EFAULT;
return 0;
}
if (part->iov_len > (size_t) SSIZE_MAX - used) {
errno = EMSGSIZE;
return 0;
}
used += part->iov_len;
}
if (used == 0) {
errno = EINVAL;
return 0;
}
*buffer = used <= capacity ? small : malloc(used);
if (*buffer == NULL) {
errno = ENOMEM;
return 0;
}
*length = used;
used = 0;
for (size_t index = 0; index < msg->msg_iovlen; index++) {
const struct iovec *part = &msg->msg_iov[index];
if (part->iov_len) memcpy(*buffer + used, part->iov_base, part->iov_len);
used += part->iov_len;
}
return 1;
}
static ssize_t sendmsg_request(int fd, const struct msghdr *msg, int flags) {
unsigned char small[NL_REQUEST_MAX];
unsigned char *buffer;
size_t length = 0;
if (!collect(msg, small, sizeof(small), &buffer, &length)) {
return -1;
}
ssize_t result;
int handled = deliver(fd, buffer, length, flags, &result);
int saved = errno;
if (buffer != small) free(buffer);
if (!handled) {
saved = EBADF;
result = -1;
}
errno = saved;
return result;
}
ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) {
static ssize_t (*real)(int, const struct msghdr *, int);
if (bl_nl_owns(fd)) {
int error = msg && msg->msg_name ? nl_destination(msg->msg_name, msg->msg_namelen) : 0;
if (error) {
errno = -error;
return -1;
}
return sendmsg_request(fd, msg, flags);
}
if (real == NULL) real = (ssize_t (*)(int, const struct msghdr *, int)) dlsym(RTLD_NEXT, "sendmsg");
if (real == NULL) {
errno = ENOSYS;
return -1;
}
return real(fd, msg, flags);
}
ssize_t sendto(int fd, const void *buffer, size_t length, int flags,
const struct sockaddr *addr, socklen_t addrlen) {
static ssize_t (*real)(int, const void *, size_t, int, const struct sockaddr *, socklen_t);
ssize_t rc;
if (bl_nl_owns(fd)) {
int error = addr ? nl_destination(addr, addrlen) : 0;
if (error) {
errno = -error;
return -1;
}
if (deliver(fd, buffer, length, flags, &rc)) return rc;
}
if (real == NULL)
real = (ssize_t (*)(int, const void *, size_t, int, const struct sockaddr *, socklen_t))
dlsym(RTLD_NEXT, "sendto");
if (real == NULL) {
errno = ENOSYS;
return -1;
}
return real(fd, buffer, length, flags, addr, addrlen);
}
ssize_t send(int fd, const void *buffer, size_t length, int flags) {
static ssize_t (*real)(int, const void *, size_t, int);
ssize_t rc;
if (bl_nl_owns(fd) && deliver(fd, buffer, length, flags, &rc)) return rc;
if (real == NULL) real = (ssize_t (*)(int, const void *, size_t, int)) dlsym(RTLD_NEXT, "send");
if (real == NULL) {
errno = ENOSYS;
return -1;
}
return real(fd, buffer, length, flags);
}
ssize_t write(int fd, const void *buffer, size_t length) {
static ssize_t (*real)(int, const void *, size_t);
ssize_t rc;
if (bl_nl_owns(fd) && deliver(fd, buffer, length, 0, &rc)) return rc;
if (real == NULL) real = (ssize_t (*)(int, const void *, size_t)) dlsym(RTLD_NEXT, "write");
if (real == NULL) {
errno = ENOSYS;
return -1;
}
return real(fd, buffer, length);
}
ssize_t writev(int fd, const struct iovec *iov, int iovcnt) {
static ssize_t (*real)(int, const struct iovec *, int);
if (iovcnt >= 0 && bl_nl_owns(fd)) {
struct msghdr msg = { .msg_iov = (struct iovec *) iov, .msg_iovlen = (size_t) iovcnt };
return sendmsg_request(fd, &msg, 0);
}
if (real == NULL) real = (ssize_t (*)(int, const struct iovec *, int)) dlsym(RTLD_NEXT, "writev");
if (real == NULL) {
errno = ENOSYS;
return -1;
}
return real(fd, iov, iovcnt);
}