commit b54abf64f17837088eaf29cec3d703c6149a98ce Author: ravindu644 Date: Thu Mar 5 14:47:45 2026 +0530 tmp: network isolation pt.1 diff --git a/src/android.c b/src/android.c index 898f732..1eab92e 100644 --- a/src/android.c +++ b/src/android.c @@ -113,34 +113,6 @@ void android_remount_data_suid(void) { } } -/* --------------------------------------------------------------------------- - * Networking / Firewall - * ---------------------------------------------------------------------------*/ - -void android_configure_iptables(void) { - if (!is_android()) - return; - - ds_log("Configuring iptables for container networking..."); - - char *cmds[][32] = {{"iptables", "-t", "filter", "-F", NULL}, - {"ip6tables", "-t", "filter", "-F", NULL}, - {"iptables", "-P", "FORWARD", "ACCEPT", NULL}, - {"iptables", "-t", "nat", "-A", "POSTROUTING", "-s", - DS_DEFAULT_SUBNET, "!", "-d", DS_DEFAULT_SUBNET, "-j", - "MASQUERADE", NULL}, - {"iptables", "-t", "nat", "-A", "OUTPUT", "-p", "tcp", - "-d", "127.0.0.1", "-m", "tcp", "--dport", "1:65535", - "-j", "REDIRECT", "--to-ports", "1-65535", NULL}, - {"iptables", "-t", "nat", "-A", "OUTPUT", "-p", "udp", - "-d", "127.0.0.1", "-m", "udp", "--dport", "1:65535", - "-j", "REDIRECT", "--to-ports", "1-65535", NULL}}; - - for (size_t i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) { - run_command_quiet(cmds[i]); - } -} - /* --------------------------------------------------------------------------- * Storage * ---------------------------------------------------------------------------*/ diff --git a/src/boot.c b/src/boot.c index 35ff05a..b404d5f 100644 --- a/src/boot.c +++ b/src/boot.c @@ -6,6 +6,7 @@ */ #include "droidspace.h" +#include int internal_boot(struct ds_config *cfg) { /* Defensive check: ensure configuration is valid */ @@ -34,12 +35,58 @@ int internal_boot(struct ds_config *cfg) { } } - /* 1. Isolated mount namespace */ + /* 1. Isolated namespaces + * Note: CLONE_NEWNET is now unshared by the intermediate before forking. + * This child process is already born into its isolated netns bubble. */ if (unshare(CLONE_NEWNS) < 0) { ds_error("Failed to unshare mount namespace: %s", strerror(errno)); return -1; } + /* Log current netns ID for debugging */ + if (cfg->net_mode != DS_NET_HOST) { + char ns_id[128]; + ssize_t len = readlink("/proc/self/ns/net", ns_id, sizeof(ns_id) - 1); + if (len > 0) { + ns_id[len] = '\0'; + ds_log("[DEBUG] Child netns (inherited from intermediate): %s", ns_id); + } + } + + /* 1b. Network Sync Handshake */ + if (cfg->net_mode != DS_NET_HOST) { + ds_log("[DEBUG] Child: Starting netns sync handshake..."); + close(cfg->net_ready_pipe[0]); /* Child writes READY */ + close(cfg->net_done_pipe[1]); /* Child reads DONE */ + + /* Signal Monitor: "I am ready for the veth plug" */ + ds_log("[DEBUG] Child: Sending READY signal to monitor..."); + if (write(cfg->net_ready_pipe[1], "R", 1) != 1) { + } + close(cfg->net_ready_pipe[1]); + + /* Wait for Monitor: "I have plugged the veth pair in" + * The monitor sends the name of the peer interface. */ + ds_log("[DEBUG] Child: Waiting for DONE signal (Interface Name) from " + "monitor..."); + char peer_name[IFNAMSIZ] = {0}; + if (read(cfg->net_done_pipe[0], peer_name, sizeof(peer_name)) <= 0) { + ds_warn("[DEBUG] Child: Failed to receive interface name from monitor."); + safe_strncpy(peer_name, "eth0", sizeof(peer_name)); + } else { + ds_log("[DEBUG] Child: Received peer interface name: %s", peer_name); + } + close(cfg->net_done_pipe[0]); + + /* Configure the interface */ + if (cfg->net_mode == DS_NET_NAT) { + /* We pass the peer name to a slightly modified setup function */ + if (setup_veth_child_side_named(cfg, peer_name) < 0) { + ds_error("Failed to setup container-side networking."); + } + } + } + /* 2. Make all mounts private to avoid leaking to host */ if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) < 0) { ds_error("Failed to make / private: %s", strerror(errno)); diff --git a/src/config.c b/src/config.c index 33d8aa3..13882ed 100644 --- a/src/config.c +++ b/src/config.c @@ -206,6 +206,13 @@ int ds_config_load(const char *config_path, struct ds_config *cfg) { parse_bind_mounts(val, cfg); } else if (strcmp(key, "dns_servers") == 0) { safe_strncpy(cfg->dns_servers, val, sizeof(cfg->dns_servers)); + } else if (strcmp(key, "net_mode") == 0) { + if (strcmp(val, "host") == 0) + cfg->net_mode = DS_NET_HOST; + else if (strcmp(val, "nat") == 0) + cfg->net_mode = DS_NET_NAT; + else if (strcmp(val, "none") == 0) + cfg->net_mode = DS_NET_NONE; } else if (strcmp(key, "foreground") == 0) { cfg->foreground = parse_bool(val); } else if (strcmp(key, "pidfile") == 0) { @@ -302,6 +309,13 @@ int ds_config_save(const char *config_path, struct ds_config *cfg) { fprintf(f_out, "volatile_mode=%d\n", cfg->volatile_mode); fprintf(f_out, "foreground=%d\n", cfg->foreground); + const char *net_mode_str = "host"; + if (cfg->net_mode == DS_NET_NAT) + net_mode_str = "nat"; + else if (cfg->net_mode == DS_NET_NONE) + net_mode_str = "none"; + fprintf(f_out, "net_mode=%s\n", net_mode_str); + if (cfg->env_file[0]) fprintf(f_out, "env_file=%s\n", cfg->env_file); if (cfg->uuid[0]) diff --git a/src/container.c b/src/container.c index fec77af..2f6917f 100644 --- a/src/container.c +++ b/src/container.c @@ -6,6 +6,7 @@ */ #include "droidspace.h" +#include /* --------------------------------------------------------------------------- * External Command Lock — CLI-only ownership @@ -542,6 +543,14 @@ int start_rootfs(struct ds_config *cfg) { * This eliminates ghost containers because the Monitor never handles * SIGHUP — it only checks a deterministic exit code. */ reboot_loop:; + int mid_sync_pipe[2] = {-1, -1}; + if (cfg->net_mode != DS_NET_HOST) { + if (pipe(cfg->net_ready_pipe) < 0 || pipe(cfg->net_done_pipe) < 0 || + pipe(mid_sync_pipe) < 0) { + ds_error("Failed to create networking sync pipes: %s", strerror(errno)); + _exit(EXIT_FAILURE); + } + } /* First boot only: ensure no stale container with the same name is running */ if (!cfg->reboot_cycle) { @@ -570,9 +579,13 @@ int start_rootfs(struct ds_config *cfg) { if (mid_pid == 0) { /* ── INTERMEDIATE PROCESS ── - * Create a fresh PID namespace for this boot cycle. */ - if (unshare(CLONE_NEWPID) < 0) { - ds_error("unshare(CLONE_NEWPID) failed: %s", strerror(errno)); + * Create a fresh PID and NET namespace for this boot cycle. */ + int ns_mid = CLONE_NEWPID; + if (cfg->net_mode != DS_NET_HOST) + ns_mid |= CLONE_NEWNET; + + if (unshare(ns_mid) < 0) { + ds_error("unshare(ns_mid) failed: %s", strerror(errno)); _exit(EXIT_FAILURE); } @@ -607,6 +620,15 @@ int start_rootfs(struct ds_config *cfg) { write_file_atomic(global_pf, pid_str); } + /* Send init PID to Monitor for networking setup */ + if (cfg->net_mode != DS_NET_HOST && mid_sync_pipe[1] >= 0) { + if (write(mid_sync_pipe[1], &init_pid, sizeof(pid_t)) != + sizeof(pid_t)) { + } + close(mid_sync_pipe[1]); + close(mid_sync_pipe[0]); + } + /* Wait for init to exit */ int init_status; while (waitpid(init_pid, &init_status, 0) < 0 && errno == EINTR) @@ -659,6 +681,41 @@ int start_rootfs(struct ds_config *cfg) { sync_pipe[1] = -1; } + /* Perform host-side networking handshake before blocking on intermediate */ + if (cfg->net_mode != DS_NET_HOST && mid_sync_pipe[0] >= 0) { + close(mid_sync_pipe[1]); + pid_t bootsession_init_pid = -1; + if (read(mid_sync_pipe[0], &bootsession_init_pid, sizeof(pid_t)) == + sizeof(pid_t)) { + ds_log("[DEBUG] Monitor: Waiting for child READY signal..."); + close(cfg->net_ready_pipe[1]); /* Monitor reads READY from child */ + close(cfg->net_done_pipe[0]); /* Monitor writes DONE to child */ + + char buf[8]; + if (read(cfg->net_ready_pipe[0], buf, sizeof(buf)) < 0) { + ds_warn("[DEBUG] Monitor: Failed to read READY signal"); + } else { + ds_log("[DEBUG] Monitor: Received READY signal from child (PID %d).", + bootsession_init_pid); + } + + char peer_name[IFNAMSIZ] = "eth0"; + if (cfg->net_mode == DS_NET_NAT) { + snprintf(peer_name, sizeof(peer_name), "ds-p%d", + bootsession_init_pid % 100000); + setup_veth_host_side(cfg, bootsession_init_pid); + } + + ds_log("[DEBUG] Monitor: Sending DONE signal to child (Peer: %s).", + peer_name); + write(cfg->net_done_pipe[1], peer_name, sizeof(peer_name)); + + close(cfg->net_ready_pipe[0]); + close(cfg->net_done_pipe[1]); + } + close(mid_sync_pipe[0]); + } + int status; while (waitpid(mid_pid, &status, 0) < 0 && errno == EINTR) ; @@ -1001,12 +1058,12 @@ int enter_namespace(pid_t pid) { return -1; } - const char *ns_names[] = {"mnt", "uts", "ipc", "pid", "cgroup"}; - int ns_fds[5]; + const char *ns_names[] = {"mnt", "uts", "ipc", "pid", "cgroup", "net"}; + int ns_fds[6]; char path[PATH_MAX]; /* 1. Open all namespace descriptors first (CRITICAL: before any setns) */ - for (int i = 0; i < 5; i++) { + for (int i = 0; i < 6; i++) { snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid, ns_names[i]); ns_fds[i] = open(path, O_RDONLY); if (ns_fds[i] < 0) { @@ -1026,14 +1083,14 @@ int enter_namespace(pid_t pid) { } /* 2. Enter namespaces */ - for (int i = 0; i < 5; i++) { + for (int i = 0; i < 6; i++) { if (ns_fds[i] < 0) continue; if (setns(ns_fds[i], 0) < 0) { if (i == 0) { /* mnt is mandatory */ ds_error("setns(mnt) failed: %s", strerror(errno)); - for (int j = i; j < 5; j++) + for (int j = i; j < 6; j++) if (ns_fds[j] >= 0) close(ns_fds[j]); return -1; diff --git a/src/droidspace.h b/src/droidspace.h index 3fa7dde..14df17a 100644 --- a/src/droidspace.h +++ b/src/droidspace.h @@ -168,6 +168,9 @@ extern int ds_log_silent; * Data structures * ---------------------------------------------------------------------------*/ +/* Networking modes */ +enum ds_net_mode { DS_NET_HOST, DS_NET_NAT, DS_NET_NONE }; + /* Bind mount entry */ struct ds_bind_mount { char src[PATH_MAX]; @@ -200,6 +203,7 @@ struct ds_config { char container_name[256]; /* --name= or auto-generated */ char hostname[256]; /* --hostname= or container_name */ char dns_servers[1024]; /* --dns= (comma/space separated) */ + enum ds_net_mode net_mode; /* --net=host|nat|none */ char dns_server_content[1024]; /* In-memory DNS config for boot */ /* UUID for PID discovery */ @@ -223,6 +227,10 @@ struct ds_config { int is_img_mount; /* 1 if rootfs was loop-mounted from .img */ char img_mount_point[PATH_MAX]; /* where the .img was mounted */ + /* Networking Synchronization */ + int net_ready_pipe[2]; /* Child -> Monitor: "I am in my new netns" */ + int net_done_pipe[2]; /* Monitor -> Child: "I have plugged in the veth" */ + /* Custom bind mounts (dynamically allocated) */ struct ds_bind_mount *binds; int bind_count; @@ -312,8 +320,6 @@ void android_optimizations(int enable); void android_set_selinux_permissive(void); int android_get_selinux_status(void); void android_remount_data_suid(void); -void android_configure_iptables(void); -void android_setup_paranoid_network_groups(void); int android_setup_storage(const char *rootfs_path); int android_seccomp_setup(int is_systemd); @@ -367,6 +373,11 @@ int setup_hardware_access(struct ds_config *cfg, gid_t *gpu_gids, int fix_networking_host(struct ds_config *cfg); int fix_networking_rootfs(struct ds_config *cfg); +void ds_configure_iptables(void); +int move_veth_to_netns_fd(const char *ifname, int netns_fd); +int setup_veth_host_side(struct ds_config *cfg, pid_t child_pid); +int setup_veth_child_side(struct ds_config *cfg); +int setup_veth_child_side_named(struct ds_config *cfg, const char *peer_name); int ds_get_dns_servers(const char *custom_dns, char *out, size_t size); int detect_ipv6_in_container(pid_t pid); diff --git a/src/main.c b/src/main.c index 68750dd..aea8533 100644 --- a/src/main.c +++ b/src/main.c @@ -50,6 +50,8 @@ void print_usage(void) { printf(" -E, --env=PATH Load environment variables from file\n"); printf( " -X, --termux-x11 Enable Termux-X11 support (Android only)\n"); + printf(" --net=MODE Networking mode: host (default), nat, " + "none\n"); printf( " -B, --bind-mount=SRC:DEST Bind mount host directory into container\n"); printf(" -C, --conf=PATH Load configuration from file\n"); @@ -222,6 +224,7 @@ int main(int argc, char **argv) { {"name", required_argument, 0, 'n'}, {"hostname", required_argument, 0, 'h'}, {"dns", required_argument, 0, 'd'}, + {"net", required_argument, 0, 257}, {"foreground", no_argument, 0, 'f'}, {"hw-access", no_argument, 0, 'H'}, {"termux-x11", no_argument, 0, 'X'}, @@ -274,6 +277,8 @@ int main(int argc, char **argv) { safe_strncpy(temp_i, optarg, sizeof(temp_i)); } else if (opt == 256) { reset_config = 1; + } else if (opt == 257) { + /* Handled in override pass */ } } optind = 0; /* Reset for next steps */ @@ -416,6 +421,19 @@ int main(int argc, char **argv) { } break; } + case 257: + if (optarg) { + if (strcmp(optarg, "host") == 0) + cfg.net_mode = DS_NET_HOST; + else if (strcmp(optarg, "nat") == 0) + cfg.net_mode = DS_NET_NAT; + else if (strcmp(optarg, "none") == 0) + cfg.net_mode = DS_NET_NONE; + else + ds_die("Invalid networking mode: %s (expected host, nat, none)", + optarg); + } + break; case 'v': print_usage(); ret = 0; diff --git a/src/network.c b/src/network.c index 7d7d373..fd17a5a 100644 --- a/src/network.c +++ b/src/network.c @@ -6,6 +6,11 @@ */ #include "droidspace.h" +#include +#include +#include +#include +#include /* --------------------------------------------------------------------------- * Host-side networking setup (before container boot) @@ -29,9 +34,32 @@ int ds_get_dns_servers(const char *custom_dns, char *out, size_t size) { count++; token = strtok_r(NULL, ", ", &saveptr); } + if (count > 0) + return count; } - /* 1. Global stable fallbacks (defined in droidspace.h) */ + /* 1. Try to detect host DNS, but skip loopback (e.g. systemd-resolved) */ + FILE *f = fopen("/etc/resolv.conf", "re"); + if (f) { + char line[256]; + while (fgets(line, sizeof(line), f)) { + if (strncmp(line, "nameserver", 10) == 0) { + char *ip = line + 10; + while (*ip && isspace(*ip)) + ip++; + + /* Skip loopback addresses (127.x.x.x) - they won't work in netns */ + if (strncmp(ip, "127.", 4) == 0) + continue; + + strncat(out, line, size - strlen(out) - 1); + count++; + } + } + fclose(f); + } + + /* 2. Global stable fallbacks (defined in droidspace.h) */ if (count == 0) { int n = snprintf(out, size, "nameserver %s\nnameserver %s\n", DS_DNS_DEFAULT_1, DS_DNS_DEFAULT_2); @@ -42,6 +70,22 @@ int ds_get_dns_servers(const char *custom_dns, char *out, size_t size) { return count; } +static void flush_stale_veths(void) { + DIR *dir = opendir("/sys/class/net"); + if (!dir) + return; + + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (strncmp(entry->d_name, "ds-v", 4) == 0) { + ds_log("[DEBUG] Flushing stale veth host: %s", entry->d_name); + char *veth_del[] = {"ip", "link", "delete", entry->d_name, NULL}; + run_command_quiet(veth_del); + } + } + closedir(dir); +} + int fix_networking_host(struct ds_config *cfg) { ds_log("Configuring host-side networking for %s...", cfg->container_name); @@ -67,9 +111,28 @@ int fix_networking_host(struct ds_config *cfg) { if (cfg->dns_servers[0]) ds_log("Setting up %d custom DNS servers...", count); - if (is_android()) { - /* Android specific NAT and firewall */ - android_configure_iptables(); + /* Flush any leftover interfaces from previous failed runs */ + flush_stale_veths(); + + /* Setup NAT and firewall (Universal) */ + if (cfg->net_mode == DS_NET_NAT) { + /* Enable IPv4 forwarding globally */ + ds_log("[DEBUG] Enabling global IPv4 forwarding..."); + char *sysctl_v4[] = {"sysctl", "-w", "net.ipv4.ip_forward=1", NULL}; + if (run_command_quiet(sysctl_v4) != 0) { + write_file("/proc/sys/net/ipv4/ip_forward", "1"); + } + + /* Enable IPv6 forwarding if IPv6 is enabled */ + if (cfg->enable_ipv6) { + char *sysctl_v6[] = {"sysctl", "-w", "net.ipv6.conf.all.forwarding=1", + NULL}; + if (run_command_quiet(sysctl_v6) != 0) { + write_file("/proc/sys/net/ipv6/conf/all/forwarding", "1"); + } + } + + ds_configure_iptables(); } return 0; @@ -172,3 +235,272 @@ int detect_ipv6_in_container(pid_t pid) { /* 0 means enabled, 1 means disabled */ return (buf[0] == '0') ? 1 : 0; } + +void ds_configure_iptables(void) { + ds_log("[DEBUG] Configuring iptables for container networking..."); + + /* 1. Set FORWARD policy to ACCEPT globally */ + char *forward_accept[] = {"iptables", "-P", "FORWARD", "ACCEPT", NULL}; + run_command_quiet(forward_accept); + + /* 2. Check if MASQUERADE rule exists */ + char *check_masq[] = {"iptables", "-t", "nat", "-C", "POSTROUTING", + "-s", "10.0.3.0/24", "!", "-d", "10.0.3.0/24", + "-j", "MASQUERADE", NULL}; + + /* 3. Add MASQUERADE rule if not exists */ + if (run_command_quiet(check_masq) != 0) { + ds_log("[DEBUG] Adding MASQUERADE rule..."); + char *add_masq[] = {"iptables", "-t", "nat", "-A", "POSTROUTING", + "-s", "10.0.3.0/24", "!", "-d", "10.0.3.0/24", + "-j", "MASQUERADE", NULL}; + if (run_command(add_masq) != 0) { + ds_warn("[DEBUG] Failed to add MASQUERADE rule"); + } else { + ds_log("[DEBUG] MASQUERADE rule added successfully"); + } + } else { + ds_log("[DEBUG] MASQUERADE rule already exists"); + } +} + +/* --------------------------------------------------------------------------- + * Move interface using netns FD (atomic & robust) + * ---------------------------------------------------------------------------*/ +int move_veth_to_netns_fd(const char *ifname, int netns_fd) { + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char buf[1024]; + } req; + + memset(&req, 0, sizeof(req)); + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.n.nlmsg_type = RTM_NEWLINK; + req.i.ifi_family = AF_UNSPEC; + + /* Get interface index */ + req.i.ifi_index = (int)if_nametoindex(ifname); + if (req.i.ifi_index == 0) { + ds_warn("[DEBUG] Netlink: interface %s not found", ifname); + return -1; + } + + /* Add IFLA_NET_NS_FD attribute */ + struct rtattr *rta = + (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.n.nlmsg_len)); + rta->rta_type = IFLA_NET_NS_FD; + rta->rta_len = RTA_LENGTH(sizeof(int)); + memcpy(RTA_DATA(rta), &netns_fd, sizeof(int)); + req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + rta->rta_len; + + /* Send via netlink */ + int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock < 0) { + ds_warn("[DEBUG] Netlink: failed to open socket: %s", strerror(errno)); + return -1; + } + + if (send(sock, &req, req.n.nlmsg_len, 0) < 0) { + ds_warn("[DEBUG] Netlink: send failed: %s", strerror(errno)); + close(sock); + return -1; + } + + /* Read ACK */ + char buf[4096]; + int len = recv(sock, buf, sizeof(buf), 0); + close(sock); + + if (len < 0) { + ds_warn("[DEBUG] Netlink: recv failed: %s", strerror(errno)); + return -1; + } + + struct nlmsghdr *h = (struct nlmsghdr *)buf; + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h); + if (err->error != 0) { + ds_warn("[DEBUG] Netlink: move failed: %s", strerror(-err->error)); + return -1; + } + } + + return 0; +} + +/* --------------------------------------------------------------------------- + * Isolated Networking (NAT) helper functions + * ---------------------------------------------------------------------------*/ + +int setup_veth_host_side(struct ds_config *cfg, pid_t child_pid) { + ds_log("Setting up host-side NAT networking for %s (PID %d)...", + cfg->container_name, child_pid); + + char veth_host[IFNAMSIZ]; + char veth_peer[IFNAMSIZ]; + /* Use deterministic but unique names for this session */ + snprintf(veth_host, sizeof(veth_host), "ds-v%d", child_pid % 100000); + snprintf(veth_peer, sizeof(veth_peer), "ds-p%d", child_pid % 100000); + + /* 0. Cleanup stale interfaces if they exist (crucial for local recovery) */ + ds_log("[DEBUG] Cleaning up any stale interfaces: %s, %s", veth_host, + veth_peer); + char *veth_del_h[] = {"ip", "link", "delete", veth_host, NULL}; + run_command_quiet(veth_del_h); + char *veth_del_p[] = {"ip", "link", "delete", veth_peer, NULL}; + run_command_quiet(veth_del_p); + + /* 1. Ensure bridge exists */ + if (access("/sys/class/net/ds-br0", F_OK) != 0) { + ds_log("[DEBUG] Creating bridge ds-br0..."); + char *br_add[] = {"ip", "link", "add", "ds-br0", "type", "bridge", NULL}; + if (run_command(br_add) != 0) + ds_warn("[DEBUG] Failed to create bridge ds-br0"); + + char *br_addr[] = {"ip", "addr", "add", "10.0.3.1/24", + "dev", "ds-br0", NULL}; + if (run_command(br_addr) != 0) + ds_warn("[DEBUG] Failed to add IP to ds-br0"); + + char *br_up[] = {"ip", "link", "set", "ds-br0", "up", NULL}; + if (run_command(br_up) != 0) + ds_warn("[DEBUG] Failed to bring up ds-br0"); + } else { + ds_log("[DEBUG] Bridge ds-br0 already exists."); + } + + /* LATE-STAGE HARDENING: Apply interface-specific settings *after* bridge is + * up */ + if (cfg->net_mode == DS_NET_NAT) { + ds_log("[DEBUG] Applying late-stage hardening for Android NAT..."); + + /* 1. Disable rp_filter on bridge (Essential for Android) */ + char *rp_br0[] = {"sysctl", "-w", "net.ipv4.conf.ds-br0.rp_filter=0", NULL}; + if (run_command_quiet(rp_br0) != 0) { + write_file("/proc/sys/net/ipv4/conf/ds-br0/rp_filter", "0"); + } + + /* 2. Explicitly Accept Forwarding for ds-br0 (Bypass hidden Android chains) + */ + char *fw_in[] = {"iptables", "-I", "FORWARD", "1", "-i", + "ds-br0", "-j", "ACCEPT", NULL}; + run_command_quiet(fw_in); + char *fw_out[] = {"iptables", "-I", "FORWARD", "1", "-o", + "ds-br0", "-j", "ACCEPT", NULL}; + run_command_quiet(fw_out); + + /* 3. MTU Clamping (Prevention for mobile network MTU issues) */ + char *mss_clamp[] = {"iptables", "-t", + "mangle", "-I", + "POSTROUTING", "1", + "-p", "tcp", + "--tcp-flags", "SYN,RST", + "SYN", "-j", + "TCPMSS", "--clamp-mss-to-pmtu", + NULL}; + run_command_quiet(mss_clamp); + } + + /* 2. Create veth pair */ + ds_log("[DEBUG] Creating veth pair %s <-> %s...", veth_host, veth_peer); + char *veth_add[] = {"ip", "link", "add", veth_host, "type", + "veth", "peer", "name", veth_peer, NULL}; + if (run_command(veth_add) != 0) { + ds_warn("[DEBUG] CRITICAL: Failed to create veth pair %s <-> %s", veth_host, + veth_peer); + return -1; + } + + /* 3. Attach host side to bridge */ + ds_log("[DEBUG] Attaching %s to ds-br0...", veth_host); + char *veth_master[] = {"ip", "link", "set", veth_host, + "master", "ds-br0", NULL}; + run_command(veth_master); + char *veth_up[] = {"ip", "link", "set", veth_host, "up", NULL}; + run_command(veth_up); + + /* 4. Move child side into container namespace */ + char netns_path[PATH_MAX]; + snprintf(netns_path, sizeof(netns_path), "/proc/%d/ns/net", child_pid); + + /* Wait for /proc entry (up to 1s) */ + int child_netns_fd = -1; + for (int i = 0; i < 100; i++) { + child_netns_fd = open(netns_path, O_RDONLY | O_CLOEXEC); + if (child_netns_fd >= 0) + break; + usleep(10000); // 10ms + } + + if (child_netns_fd < 0) { + ds_warn("Failed to open child netns %s: %s", netns_path, strerror(errno)); + return -1; + } + + ds_log("[DEBUG] Moving %s into netns of PID %d using FD %d...", veth_peer, + child_pid, child_netns_fd); + if (move_veth_to_netns_fd(veth_peer, child_netns_fd) < 0) { + ds_warn("Failed to move %s into container namespace.", veth_peer); + close(child_netns_fd); + return -1; + } + close(child_netns_fd); + ds_log("[DEBUG] Successfully moved %s to PID %d", veth_peer, child_pid); + + return 0; +} + +int setup_veth_child_side_named(struct ds_config *cfg, const char *peer_name) { + /* Inside the new netns, everything is down by default */ + ds_log( + "[DEBUG] Child: Configuring isolated networking. Local PID: %d, Peer: %s", + getpid(), peer_name); + + /* 0. Rename interface to eth0 */ + if (strcmp(peer_name, "eth0") != 0) { + ds_log("[DEBUG] Renaming %s to eth0...", peer_name); + char mutable_peer[IFNAMSIZ]; + safe_strncpy(mutable_peer, peer_name, sizeof(mutable_peer)); + char *veth_rename[] = {"ip", "link", "set", mutable_peer, + "name", "eth0", NULL}; + if (run_command(veth_rename) != 0) { + ds_warn("[DEBUG] Failed to rename %s to eth0.", peer_name); + } + } + + /* 1. Loopback */ + char *lo_up[] = {"ip", "link", "set", "lo", "up", NULL}; + run_command_quiet(lo_up); + + /* 2. Configure eth0 (veth peer moved by parent) */ + unsigned int hash = 2166136261U; + for (const char *p = cfg->container_name; *p; p++) { + hash ^= (unsigned int)(*p); + hash *= 16777619U; + } + int last_octet = (hash % 250) + 2; + + char ip_str[64]; + snprintf(ip_str, sizeof(ip_str), "10.0.3.%d/24", last_octet); + + ds_log("Configuring eth0 with IP %s...", ip_str); + char *eth_addr[] = {"ip", "addr", "add", ip_str, "dev", "eth0", NULL}; + run_command_quiet(eth_addr); + + char *eth_up[] = {"ip", "link", "set", "eth0", "up", NULL}; + run_command_quiet(eth_up); + + /* 3. Default route */ + char *route_add[] = {"ip", "route", "add", "default", + "via", "10.0.3.1", NULL}; + run_command_quiet(route_add); + + return 0; +} + +int setup_veth_child_side(struct ds_config *cfg) { + /* Compatibility wrapper */ + return setup_veth_child_side_named(cfg, "eth0"); +}