mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 11:01:07 +00:00
Every Electron AppImage hung without a window or crashed at its first page. Four causes, each enough on its own: - The session preload's hooks found the real functions with dlsym(RTLD_NEXT). Before glibc 2.34 a thread's first dlsym callocs, and Chromium's PartitionAlloc calls syscall() from inside its first malloc: the allocator waited for itself. On older glibc (the x86 runtime's 2.31) next.c now walks the libraries after the preload with no allocation and picks each function's default version, as glibc 2.36 does; the old lookup handed realpath() and posix_spawn() their compat versions, and realpath(path, NULL) failed. arm64 keeps dlsym. - libpci's sysfs backend exits when /sys/bus/pci/devices is listable but a device's files are not (a RedMagic's PCIe root port), taking every Chromium GPU process with it. An empty directory is bound over it on devices where that happens. - FEX_OUTPUTLOG=stderr made FEX log to its own duplicate of descriptor 2. Chromium dup2()s its IPC socket onto that low number in each child it starts, then lists /proc/self/fd, which FEX logs into the socket. Empty, FEX writes to descriptor 2 itself. - The tracer preload served every /proc/<pid>/status as a memfd, which is open for writing. Chromium hands each renderer its status as a read-only file and checks that it is one. Only a process's own status (pid, tid, self, thread-self) is rewritten now.
592 lines
22 KiB
C
592 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 <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>
|
|
|
|
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
|
|
|
|
#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) bl_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) bl_next("getifaddrs");
|
|
if (real_freeifaddrs == NULL) real_freeifaddrs = (freeifaddrs_fn) bl_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) bl_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) bl_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) bl_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) bl_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) bl_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) bl_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) bl_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) bl_next("fopen64");
|
|
return open_stream(real, path, mode);
|
|
}
|