This commit is contained in:
E. S. 2024-11-27 02:19:12 +03:00
commit 440f4151a2
5 changed files with 388 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.o
rl_clearnet

22
Makefile Normal file
View File

@ -0,0 +1,22 @@
CC := gcc
CFLAGS = -O2 -std=c99 -Wall -W
LDFLAGS =
INSTALL = /usr/bin/env install
PREFIX = /usr/local
all: rl_clearnet.o
$(CC) $(CFLAGS) -o rl_clearnet $^ $(LDFLAGS)
install: all
$(INSTALL) rl_clearnet $(PREFIX)/bin
chmod u+s $(PREFIX)/bin/rl_clearnet
clean:
rm -f *.o rl_clearnet
%.o: %.c
$(CC) $(CFLAGS) -c $^ -I. -o $@
.PHONY: all install clean

13
config.def.h Normal file
View File

@ -0,0 +1,13 @@
#define NETNS_PATH "/run/netns/clearnet"
#define DHCPCD_PATH "/usr/bin/dhcpcd"
#define EXEC_UID 1000
#define EXEC_GUD 1000
static char *env_vars[] = {"DISPLAY", "HOME", "PWD", "EDITOR", "USER", "XAUTHORITY", "LANG", "DBUS_SESSION_BUS_ADDRESS"};
static char *firefox_cmd[] = {"firefox", "-P", "clearnet", NULL};
static char *thunderbird_cmd[] = {"thunderbird", "-P", "clearnet", NULL};
static char *ping_cmd[] = {"ping", "8.8.8.8", NULL};
static char **commands[] = {firefox_cmd, thunderbird_cmd, ping_cmd};
static char *aliases[] = {"fx", "tb", "ping"};

350
rl_clearnet.c Normal file
View File

@ -0,0 +1,350 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <fcntl.h>
#include <assert.h>
#include <signal.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/limits.h>
#include <linux/if_arp.h>
#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);
}

1
rl_clearnet.h Normal file
View File

@ -0,0 +1 @@
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))