#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rl_clearnet.h" #include "config.def.h" static pid_t child_pid = -1; static void sigint_handler(int); static void usage(const char *); static int get_default_iface(char [IFNAMSIZ]); static int get_domain_name_servers(const char *, char *[4]); int main(int argc, char* argv[]) { assert(ARRAY_SIZE(commands) == ARRAY_SIZE(aliases)); int rc = 1; int tempfd = 0; int nsfd = 0; char cwd[PATH_MAX]; char temp_name[PATH_MAX] = {0}; char iface[IFNAMSIZ]; int mounted = 0; int cmdi = -1; char *dns[4] = {0}; size_t i; if (argc != 2) { usage(argv[0]); return 1; } if (!strcmp(argv[1], "-h")) { usage(argv[0]); return 1; } for (i = 0; i < (int)ARRAY_SIZE(aliases); i++) { if (!strcmp(argv[1], aliases[i])) { cmdi = (int)i; break; } } if (cmdi == -1) { fprintf(stderr, "error: alias %s not found\n", argv[1]); return 1; } if (geteuid() != 0) { fprintf(stderr, "error: you must be root.\n"); return 1; } if (unshare(CLONE_NEWNS) == -1) { perror("unshare"); return 1; } /* save cwd */ if (!getcwd(cwd, PATH_MAX)) { perror("getcwd"); return 1; } /* get default network interface */ if (get_default_iface(iface) != 0) { fprintf(stderr, "error: get_default_iface failed"); return 1; } /* get default domain name servers */ if (get_domain_name_servers(iface, dns) != 0) { fprintf(stderr, "error: get_domain_name_servers failed"); return 1; } /* create temp resolv.conf */ strcpy(temp_name, "/tmp/rl_clearnet.XXXXXX"); if ((tempfd = mkstemp(temp_name)) == -1) { perror("mkstemp"); goto end; } for (i = 0; i < ARRAY_SIZE(dns); i++) { if (dns[i]) dprintf(tempfd, "nameserver %s\n", dns[i]); } close(tempfd); chmod(temp_name, 0644); /* bind-mount it as /etc/resolv.conf */ if (mount(temp_name, "/etc/resolv.conf", NULL, MS_BIND, NULL) == -1) { perror("mount"); goto end; } mounted = 1; nsfd = open(NETNS_PATH, O_RDONLY); if (nsfd == -1) { perror("open"); goto end; } /* change to netns */ if (setns(nsfd, CLONE_NEWNET) == -1) { perror("setns"); goto end; } /* handle ctrl+c */ struct sigaction sa; sa.sa_handler = sigint_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGINT, &sa, NULL); child_pid = fork(); if (!child_pid) { /* change real and effective user and group */ if (EXEC_GUD != getegid()) { if (setregid(EXEC_GUD, EXEC_GUD) == -1) { perror("setregid"); goto end; } } if (EXEC_UID != geteuid()) { if (setreuid(EXEC_UID, EXEC_UID) == -1) { perror("setreuid"); goto end; } } /* restore cwd */ if (chdir(cwd) == -1) { perror("chdir"); goto end; } /* allocate environment variables list */ char **envp = malloc((ARRAY_SIZE(env_vars) + 1) * sizeof(char *)); if (!envp) { perror("malloc"); goto end; } size_t count = 0; for (i = 0; i < ARRAY_SIZE(env_vars); i++) { char *value = getenv(env_vars[i]); if (value && *value) { size_t len = strlen(env_vars[i]) + strlen(value) + 2; // key=value + '=' + '\0' envp[count] = malloc(len); if (!envp[count]) { for (size_t j = 0; j < count; j++) free(envp[j]); free(envp); perror("malloc"); return 1; } snprintf(envp[count], len, "%s=%s", env_vars[i], value); count++; } } envp[count] = NULL; /* launch program */ if (execvpe(*commands[cmdi], (char *const *)commands[cmdi], envp) == -1) perror("execvpe"); else rc = 0; for (i = 0; i < count; i++) free(envp[i]); free(envp); return rc; } rc = 0; int status = 0; wait(&status); if (WIFEXITED(status)) rc = WEXITSTATUS(status); else if (WIFSIGNALED(status)) rc = 128 + WTERMSIG(status); end: for (i = 0; i < ARRAY_SIZE(dns); i++) { if (dns[i]) free(dns[i]); } if (mounted) umount(temp_name); if (nsfd > 0) close(nsfd); if (*temp_name) unlink(temp_name); return rc; } void usage(const char *prog) { printf("Usage: %s [-h] ALIAS\n\n", prog); printf("Aliases:\n"); for (size_t i = 0; i < ARRAY_SIZE(aliases); i++) printf(" %s\n", aliases[i]); printf("\n"); } int get_default_iface(char dst[IFNAMSIZ]) { FILE *fp; char line[256]; char iface[IFNAMSIZ]; char destination[64], gateway[64], flags[64], refcnt[64], use[64], metric[64], mask[64], mtu[64], window[64], irtt[64]; struct ifreq ifr; int found = 0; fp = fopen("/proc/net/route", "r"); if (!fp) { perror("fopen"); return 1; } fgets(line, sizeof(line), fp); /* skip header */ while (fgets(line, sizeof(line), fp)) { sscanf(line, "%s %s %s %s %s %s %s %s %s %s %s", iface, destination, gateway, flags, refcnt, use, metric, mask, mtu, window, irtt); /* check if default route */ if (strcmp(destination, "00000000") == 0) { int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket"); fclose(fp); return 1; } memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, iface, IFNAMSIZ-1); if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) == 0) { if (ifr.ifr_hwaddr.sa_family == ARPHRD_ETHER) { strlcpy(dst, iface, IFNAMSIZ-1); found = 1; close(sockfd); break; } } else { perror("ioctl"); } close(sockfd); } } fclose(fp); return found ? 0 : 1; } int get_domain_name_servers(const char *iface, char *servers[4]) { char cmd[256]; FILE *fp; char line[1024]; char domain_name_servers_line[1024]; int found = 0; int server_count = 0; size_t len; if (snprintf(cmd, sizeof(cmd), DHCPCD_PATH " -U %s", iface) >= (int)sizeof(cmd)) { fprintf(stderr, "error: interface name is too long\n"); return 1; } fp = popen(cmd, "r"); if (!fp) { perror("popen"); return 1; } while (fgets(line, sizeof(line), fp)) { /* remove any trailing newline character */ line[strcspn(line, "\n")] = '\0'; /* if line starts with "domain_name_servers=" */ if (strncmp(line, "domain_name_servers=", 20) == 0) { found = 1; len = strlen(line + 20); if (len >= sizeof(domain_name_servers_line)) len = sizeof(domain_name_servers_line)-1; memcpy(domain_name_servers_line, line + 20, len); domain_name_servers_line[len] = '\0'; break; } } if (pclose(fp) == -1) { perror("pclose"); return 1; } if (!found) { fprintf(stderr, "error: domain_name_servers not found\n"); return 1; } /* parse domain_name_servers_line */ char *token = strtok(domain_name_servers_line, " "); while (token && server_count < 4) { servers[server_count] = strdup(token); if (!servers[server_count]) { fprintf(stderr, "error: memory allocation failed\n"); return 1; } server_count++; token = strtok(NULL, " "); } return 0; } void sigint_handler(int sig) { (void)sig; if (!child_pid) kill(child_pid, SIGTERM); }