Files
DroidDeck/tools/linuxfs/preload/netif.c
T
MaxsTechReview 15b2d19f68 Performance: idle front end, proot stops, launch-time passes and log collection (#529)
* Front end: draw the capsule wall without clipping it every frame

The tilted wall clipped each capsule to a rounded rect and recomposed its columns on every drift
step, and a rotated rounded clip is rasterised in software on the render threads: the idle main
page held about 1.7 cores. The art is now loaded at the capsule's size with its corners already
rounded, each column draws its capsules itself in one layer moved by the drift, and the drift
advances at most every 15 ms. The idle page takes about a third of a core.

* libfakeinput: let calls on descriptors it does not serve skip its lock

Every read, write, ioctl, close and poll of every process in a Proton session went through the
controller lock and the three map lookups, Wine's wineserver pipes and the game's files alike.
A bitmap of the descriptors the maps hold, set before one is handed out and cleared when it is
closed, lets everything else go straight to the kernel.

* Pad motion: ask the display for its rotation four times a second

The rotation was looked up for every sensor reading, every 2 ms, and cost more than the rest of
the reading; it is now refreshed every 250 ms, and the readings no longer allocate pairs.

* Session: keep the library passes out of a game's launch

The redist seeder stamped each prefix with the checksum of the whole set of names, which differs
between the session's pass and the game launch's (the game's own names are added), so every
launch re-appended the markers to every prefix in the library while the game loaded. Each
prefix's marker now lists the names it was given and only the missing ones are added.

The beat's seed and compatibility passes, and the Lossless Scaling ownership probe (which loads
steamclient.so), also wait while a wineserver runs: a launch rewrites the manifest and made them
due at exactly that moment. The game's own launch seeds its prefix regardless.

* Agent: keep the focus watcher's X connection open while nothing changes

The connect timeout stayed on the socket, so with no property change the read timed out, and the
watcher dropped the connection, reconnected and took a fresh snapshot every few seconds.

* Game environment: start Cyberpunk 2077 without REDlauncher

The prelauncher opens REDlauncher, a Qt WebEngine window that hands over to the game half a
minute later and then stays up beside it, well over a core busy for as long as the game runs; its
own --launcher-skip started the game twice. The game's executable is started directly instead.

* netif: answer Wine's network driver from a reading of the link

Mortal Kombat X has Wine's network driver enumerate every interface, address and route table
hundreds of times a second. Each enumeration read /etc/droiddeck-net several times, asked the
kernel for its interface dump again (a netlink socket bound under proot, refused each time) and
looked the real functions up with dlsym. The link file is now read at most once a second, a
refused dump is not asked for again within that second, and the real functions are looked up
once.

* Fast path: create memfds without a stop in proot

proot looks at a memfd's name only to refuse the few that are then executed or locked; every other
memfd_create is now issued from the trampoline. The session's /proc/net and status copies made
one per read, a stop each. Without memfd in the kernel the call still goes to proot's ashmem.

* Logs: run each redaction rule only on a line that could match it

A session's end copies some 70 MB of the Steam client's logs and puts every line through all
twenty-odd rules, most of a minute of a core on the phone after every session. Each rule now runs
only on a line holding what it cannot match without: its keyword, the five separators of a MAC,
the four dashes of a GUID, a long enough hex or token run, the learned account or address text.
Lines with non-ASCII text still meet every case-insensitive rule. Over 895,000 lines of real
client logs, with the device's accounts and addresses learned, the output is identical and the
pass takes a twentieth of the time.
2026-10-10 19:00:33 -04:00

591 lines
22 KiB
C

/*
* Ported from WinNative (maxjivi05, feature/wayland-gamescope, 2026-09-18/19), GPL-3.0, with names
* changed to this project's. The reasoning below is the original author's.
*
* Network interfaces for a sandbox that hides them.
*
* Android denies an app the rtnetlink dump behind getifaddrs() and if_nameindex(), the hardware
* address ioctl, and every table under /proc/net. Wine builds its adapter, address, route and
* neighbour tables from exactly those, so a game saw a machine with no adapter, no address, no
* gateway and no MAC, and one that checks its adapters before going online waited for good.
* The app knows the real link from ConnectivityManager and writes it to /etc/droiddeck-net;
* whatever the kernel refuses is answered from that file. Anything the kernel does answer wins.
*/
#define _GNU_SOURCE 1
#include <arpa/inet.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.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;
}
/* Wine's network driver reads the link several times for each table it builds, and some games
* have it build them all hundreds of times a second. The app rewrites the file when the device
* changes network, so a reading stands for a second. */
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;
}
/* See drm.c: a fork must not leave the lock held by a thread the child does not have. */
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);
}
/* When the kernel's own list was of no use, it is not asked again for a second: each asking binds
* a netlink socket, a stop in the tracer, only to be refused again. */
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;
}
/* One allocation per list. ifa_data of the head points at the head itself, which glibc never
* does, so freeifaddrs() can tell the two kinds of list apart without keeping any state. */
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);
/* A leading slot keeps fill_entry()'s back link in bounds; the list starts after it. */
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);
}
/* Names live in the same allocation as the array, right behind it; glibc strdup()s each one. */
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;
/* Android hands out 02:00:00:00:00:00 where it hides the real address. */
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); /* kgslid.c */
int bl_kgsl_poll(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")));
/* Every DRM and evdev call comes through here, so the lookup is done once. Two threads racing
* to store it store the same pointer. */
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_drm_gem_close(fd, request, arg, &rc)) return rc;
if (real == NULL) real = (ioctl_fn) dlsym(RTLD_NEXT, "ioctl");
if (!bl_kgsl_poll(real, fd, request, arg, &rc)) rc = real(fd, request, arg);
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");
}
}
static const char *denied_table(const char *path) {
static const char *const tables[] = { "route", "ipv6_route", "if_inet6", "dev", "arp" };
if (strncmp(path, "/proc/net/", 10) == 0) {
for (size_t i = 0; i < sizeof(tables) / sizeof(tables[0]); i++) {
if (strcmp(path + 10, tables[i]) == 0) return tables[i];
}
return NULL;
}
size_t len = strlen(path);
if (strncmp(path, "/sys/class/net/", 15) == 0 && len > 8 && strcmp(path + len - 8, "/carrier") == 0) {
return "carrier";
}
return NULL;
}
/* A memfd rather than fmemopen(): the stream owns its contents, so fclose() frees everything. */
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;
}
/* tracer.c */
int bl_status_without_tracer(const char *path, int flags) __attribute__((visibility("hidden")));
static FILE *open_stream(fopen_fn real, const char *path, const char *mode) {
int status = mode && mode[0] == 'r' && !strchr(mode, '+')
? bl_status_without_tracer(path, O_RDONLY | O_CLOEXEC) : -1;
if (status >= 0) {
FILE *copy = fdopen(status, "r");
if (copy == NULL) close(status);
return copy;
}
FILE *f = real(path, mode);
if (f || path == NULL || mode == NULL || mode[0] != 'r') return f;
int saved = errno;
const char *table = denied_table(path);
if (table && (f = synthesize(table)) != NULL) 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);
}
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);
}