From 3a0edda828071ba9310373c25a50e7c757234aa3 Mon Sep 17 00:00:00 2001 From: Mike Wang Date: Fri, 3 May 2024 14:15:52 +0800 Subject: [PATCH] HevIFwd: Add support for iptables forwarding --- src/hev-conf.c | 28 ++- src/hev-conf.h | 24 +++ src/hev-exec.c | 6 +- src/hev-exec.h | 8 + src/hev-ifwd.c | 451 +++++++++++++++++++++++++++++++++++++++++++++++++ src/hev-ifwd.h | 28 +++ src/hev-tnsk.c | 29 +++- 7 files changed, 567 insertions(+), 7 deletions(-) create mode 100644 src/hev-ifwd.c create mode 100644 src/hev-ifwd.h diff --git a/src/hev-conf.c b/src/hev-conf.c index 462837d..0f0e7fc 100644 --- a/src/hev-conf.c +++ b/src/hev-conf.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -17,6 +18,7 @@ #include "hev-conf.h" static int mode = SOCK_STREAM; +static int method = HEV_FWD_DEFAULT; static int type = AF_UNSPEC; static int keep; static int dmon; @@ -35,6 +37,18 @@ static const char *taddr; static const char *tport; static const char *iface; +static int +to_method(char *m) +{ + if (strcmp(m, "default") == 0) { + return HEV_FWD_DEFAULT; + } + if (strcmp(m, "iptables") == 0) { + return HEV_FWD_IPTABLES; + } + return HEV_FWD_UNKNOWN; +} + const char * hev_conf_help (void) { @@ -49,6 +63,7 @@ hev_conf_help (void) " -d run as daemon\n" " -i network interface\n" " -k seconds between each keep-alive\n" + " -m forward method\n" " -s [:port] domain name or address to STUN server\n" " -h [:port] domain name or address to HTTP server\n" " -e script path for notify mapped address\n" @@ -69,7 +84,7 @@ hev_conf_init (int argc, char *argv[]) { int opt; - while ((opt = getopt (argc, argv, "46udk:s:h:e:b:T:t:p:i:")) != -1) { + while ((opt = getopt (argc, argv, "46udk:m:s:h:e:b:T:t:p:i:")) != -1) { switch (opt) { case '4': type = AF_INET; @@ -86,6 +101,11 @@ hev_conf_init (int argc, char *argv[]) case 'k': keep = strtoul (optarg, NULL, 10) * 1000; break; + case 'm': + method = to_method(optarg); + if (method == HEV_FWD_UNKNOWN) + return -1; + break; case 's': sscanf (optarg, "%255[^:]:%5[0123456789]", stun, sport); break; @@ -243,3 +263,9 @@ hev_conf_hport (void) { return hport; } + +int +hev_conf_method (void) +{ + return method; +} diff --git a/src/hev-conf.h b/src/hev-conf.h index 9ce41f3..cd3f6c4 100644 --- a/src/hev-conf.h +++ b/src/hev-conf.h @@ -10,6 +10,21 @@ #ifndef __HEV_CONF_H__ #define __HEV_CONF_H__ +/** + * HevFwdMethod: + * @HEV_FWD_UNKNOWN: Unknown forward method. + * @HEV_FWD_DEFAULT: Default forward method. + * @HEV_FWD_IPTABLES: Forward using iptables. + * + * Since: 1.0 + */ +typedef enum +{ + HEV_FWD_UNKNOWN, + HEV_FWD_DEFAULT, + HEV_FWD_IPTABLES +} HevFwdMethod; + /** * hev_conf_help: * @@ -175,4 +190,13 @@ const char *hev_conf_sport (void); */ const char *hev_conf_hport (void); +/** + * hev_conf_method: + * + * Get forward method. + * + * Returns: returns integer number. + */ +int hev_conf_method (void); + #endif /* __HEV_CONF_H__ */ diff --git a/src/hev-exec.c b/src/hev-exec.c index 61ca411..d75a557 100644 --- a/src/hev-exec.c +++ b/src/hev-exec.c @@ -20,8 +20,8 @@ #include "hev-exec.h" -static void -signal_handler (int signum) +void +hev_exec_signal_handler (int signum) { waitpid (-1, NULL, WNOHANG); } @@ -43,7 +43,7 @@ hev_exec_run (int family, unsigned int maddr[4], unsigned short mport, pid_t pid; path = hev_conf_path (); - signal (SIGCHLD, signal_handler); + signal (SIGCHLD, hev_exec_signal_handler); q = (unsigned char *)maddr; p = (unsigned char *)&mport; diff --git a/src/hev-exec.h b/src/hev-exec.h index f964ac6..ba5b3a2 100644 --- a/src/hev-exec.h +++ b/src/hev-exec.h @@ -10,6 +10,14 @@ #ifndef __HEV_EXEC_H__ #define __HEV_EXEC_H__ +/** + * hev_exec_signal_handler: + * @signum: signal number + * + * Handler of SIGCHLD signal. + */ +void hev_exec_signal_handler (int signum); + /** * hev_exec_run: * @family: network family diff --git a/src/hev-ifwd.c b/src/hev-ifwd.c new file mode 100644 index 0000000..8cc0723 --- /dev/null +++ b/src/hev-ifwd.c @@ -0,0 +1,451 @@ +/* + ============================================================================ + Name : hev-ifwd.c + Author : Mike Wang + hev + Copyright : Copyright (c) 2024 xyz + Description : iptables forwarder + ============================================================================ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hev-conf.h" +#include "hev-ifwd.h" +#include "hev-misc.h" +#include "hev-exec.h" + +#define BUFFLEN 1024 + +typedef struct _IptNatRule IptNatRule; + +struct _IptNatRule +{ + char table[16]; + char chain[32]; + char proto[8]; + char ipv4[16]; + char port[16]; + char toipv4[16]; + char toport[16]; +}; + +static int ipt_initialized = 0; +static int ipt_atexit = 0; +static IptNatRule nat_rule; + +static int +call_subprocess (const char *file, char *const argv[], char *sstdout, + size_t nstdout, char *sstderr, size_t nstderr) +{ + int pipefd_o[2], pipefd_e[2]; + char blackhole[512]; + int status = -1; + int nbyte; + pid_t pid; + + signal(SIGCHLD, SIG_DFL); + + if (pipe (pipefd_o) == -1 || pipe (pipefd_e) == -1) { + LOGV (E, "%s", strerror (errno)); + goto err_exit; + } + + pid = fork (); + + if (pid == -1) { + LOGV (E, "%s", strerror (errno)); + goto err_close; + } + + if (pid == 0) { + if (dup2 (pipefd_o[1], STDOUT_FILENO) == -1 || + dup2 (pipefd_e[1], STDERR_FILENO) == -1) { + LOGV (E, "%s", strerror (errno)); + _exit (EXIT_FAILURE); + } + close (pipefd_o[0]); + close (pipefd_o[1]); + close (pipefd_e[0]); + close (pipefd_e[1]); + execvp (file, argv); + LOGV (E, "%s", strerror (errno)); + _exit (EXIT_FAILURE); + } + + close (pipefd_o[1]); + close (pipefd_e[1]); + + nbyte = read (pipefd_o[0], sstdout, nstdout ? nstdout - 1 : 0); + if (nbyte == -1) { + LOGV (E, "%s", strerror (errno)); + goto err_close_read; + } + + if (nstdout) + sstdout[nbyte] = 0; + + while (read (pipefd_o[0], blackhole, sizeof (blackhole)) > 0) { } + + nbyte = read (pipefd_e[0], sstderr, nstderr ? nstderr - 1 : 0); + if (nbyte == -1) { + LOGV (E, "%s", strerror (errno)); + goto err_close_read; + } + + if (nstderr) + sstderr[nbyte] = 0; + + while (read (pipefd_e[0], blackhole, sizeof (blackhole)) > 0) { } + + if (waitpid (pid, &status, 0) == -1) { + LOGV (E, "%s", strerror (errno)); + goto err_close_read; + } + + close (pipefd_o[0]); + close (pipefd_e[0]); + + signal(SIGCHLD, hev_exec_signal_handler); + + return WEXITSTATUS (status); + +err_close: + close (pipefd_o[1]); + close (pipefd_e[1]); + +err_close_read: + close (pipefd_o[0]); + close (pipefd_e[0]); + +err_exit: + signal(SIGCHLD, hev_exec_signal_handler); + return -1; +} + +static int +vercmp (int a_major, int a_minor, int a_fix, + int b_major, int b_minor, int b_fix) +{ + if (a_major < b_major) { + return -1; + } + if (a_major > b_major) { + return 1; + } + if (a_minor < b_minor) { + return -1; + } + if (a_minor > b_minor) { + return 1; + } + if (a_fix < b_fix) { + return -1; + } + if (a_fix > b_fix) { + return 1; + } + + return 0; +} + +static int +ipt_precheck (void) +{ + char *const cmd_ver[] = { "iptables", "--version", 0 }; + + char sstdout[BUFFLEN], sstderr[BUFFLEN]; + int ret; + int v_major, v_minor, v_fix, m; + + ret = call_subprocess ("iptables", cmd_ver, sstdout, sizeof (sstdout), + sstderr, sizeof (sstderr)); + fprintf (stderr, "%s", sstderr); + + if (ret) + return -1; + + m = sscanf (sstdout, "iptables v%d.%d.%d", &v_major, &v_minor, &v_fix); + + if (m != 3) { + LOGV (E, "%s", "failed to check iptables version"); + return -1; + } + + if (vercmp (v_major, v_minor, v_fix, 1, 4, 20) <= 0) { + LOGV (E, "%s", "iptables version >= 1.4.20 is required"); + return -1; + } + + return 0; +} + +static int +ipt_chain_exists (char *table, char *chain) +{ + char *const cmd[] = { "iptables", "--wait", "-t", table, + "--list-rules", chain, 0 }; + + char sstdout[BUFFLEN], sstderr[BUFFLEN]; + int ret; + + ret = call_subprocess ("iptables", cmd, sstdout, sizeof (sstdout), + sstderr, sizeof (sstderr)); + + if (ret == 1) { + return 0; + } else if (ret) { + fprintf (stderr, "%s", sstderr); + return -1; + } + return 1; +} + +static int +ipt_create_chain (char *table, char *chain) +{ + char *const cmd[] = { "iptables", "--wait", "-t", table, + "-N", chain, 0 }; + + char sstdout[BUFFLEN], sstderr[BUFFLEN]; + int ret; + + ret = call_subprocess ("iptables", cmd, sstdout, sizeof (sstdout), + sstderr, sizeof (sstderr)); + fprintf (stderr, "%s", sstderr); + + return ret; +} + +static int +ipt_insert_jump (char *table, char *chain, char *dest_chain) +{ + char *const cmd[] = { "iptables", "--wait", "-t", table, + "-I", chain, "-j", dest_chain, 0 }; + + char sstdout[BUFFLEN], sstderr[BUFFLEN]; + int ret; + + ret = call_subprocess ("iptables", cmd, sstdout, sizeof (sstdout), + sstderr, sizeof (sstderr)); + fprintf (stderr, "%s", sstderr); + + return ret; +} + +static int +ipt_insert_dnat (IptNatRule *rule) +{ + char todest[32]; + char *const cmd[] = { + "iptables", "--wait", + "-t", rule->table, + "-I", rule->chain, + "-p", rule->proto, + "--dst", rule->ipv4, + "--dport", rule->port, + "-j", "DNAT", + "--to-destination", todest, + 0 + }; + + char sstdout[BUFFLEN], sstderr[BUFFLEN]; + int ret; + + snprintf (todest, sizeof (todest), "%s:%s", rule->toipv4, rule->toport); + + ret = call_subprocess ("iptables", cmd, sstdout, sizeof (sstdout), + sstderr, sizeof (sstderr)); + fprintf (stderr, "%s", sstderr); + + return ret; +} + +static int +ipt_delete_dnat (IptNatRule *rule) +{ + char todest[32]; + char *const cmd[] = { + "iptables", "--wait", + "-t", rule->table, + "-D", rule->chain, + "-p", rule->proto, + "--dst", rule->ipv4, + "--dport", rule->port, + "-j", "DNAT", + "--to-destination", todest, + 0 + }; + + char sstdout[BUFFLEN], sstderr[BUFFLEN]; + int ret; + + snprintf (todest, sizeof (todest), "%s:%s", rule->toipv4, rule->toport); + + ret = call_subprocess ("iptables", cmd, sstdout, sizeof (sstdout), sstderr, + sizeof (sstderr)); + fprintf (stderr, "%s", sstderr); + + return ret; +} + +static int +ipt_init (void) +{ + int ret; + + if (ipt_initialized) + return 0; + + if (ipt_precheck ()) + return -1; + + ret = ipt_chain_exists ("nat", "NATMAP"); + + if (ret == -1) + return -1; + + if (!ret) { + if (ipt_create_chain ("nat", "NATMAP")) + return -1; + if (ipt_insert_jump ("nat", "PREROUTING", "NATMAP")) + return -1; + if (ipt_insert_jump ("nat", "OUTPUT", "NATMAP")) + return -1; + } + + ipt_initialized = 1; + return 0; +} + +static int +get_bind_info (int fd, char baddr[16], char bport[16]) +{ + int ret; + struct sockaddr_storage addr; + struct sockaddr_in *paddr; + socklen_t len = sizeof (addr); + + ret = getsockname (fd, (struct sockaddr *) &addr, &len); + if (ret < 0) { + LOG (E); + close (fd); + return -1; + } + + if (addr.ss_family != AF_INET) { + LOGV (E, "%s", "IPv6 is currently not supported."); + return -1; + } + + paddr = (struct sockaddr_in *) &addr; + inet_ntop (paddr->sin_family, &paddr->sin_addr, baddr, 16); + snprintf(bport, 16, "%u", ntohs(paddr->sin_port)); + + return 0; +} + +static int +ipt_create_nat_rule (int fd) +{ + struct in_addr addr_target, addr_local; + const char *proto; + const char *taddr; + const char *tport; + char baddr[16]; + char bport[16]; + int mode; + + mode = hev_conf_mode (); + taddr = hev_conf_taddr (); + tport = hev_conf_tport (); + proto = (mode == SOCK_STREAM) ? "tcp" : "udp"; + + if (get_bind_info(fd, baddr, bport)) + return -1; + + if (inet_pton (AF_INET, taddr, &addr_target) != 1) + return -1; + + if (inet_pton (AF_INET, "127.0.0.1", &addr_local) != 1) + return -1; + + if (addr_target.s_addr == addr_local.s_addr) + taddr = baddr; + + snprintf(nat_rule.table, sizeof(nat_rule.table), "nat"); + snprintf(nat_rule.chain, sizeof(nat_rule.chain), "NATMAP"); + snprintf(nat_rule.proto, sizeof(nat_rule.proto), "%s", proto); + snprintf(nat_rule.ipv4, sizeof(nat_rule.ipv4), "%s", baddr); + snprintf(nat_rule.port, sizeof(nat_rule.port), "%s", bport); + snprintf(nat_rule.toipv4, sizeof(nat_rule.toipv4), "%s", taddr); + snprintf(nat_rule.toport, sizeof(nat_rule.toport), "%s", tport); + + return 0; +} + +static void +exit_sig_handler(int sig) +{ + hev_ifwd_kill (); + signal (sig, SIG_DFL); + raise (sig); +} + +static int +ipt_register_atexit (void) +{ + int ret; + + if (ipt_atexit) + return 0; + + ret = atexit(hev_ifwd_kill); + if (ret) + LOG (E); + + signal(SIGINT, exit_sig_handler); + signal(SIGTERM, exit_sig_handler); + + ipt_atexit = 1; + return ret; +} + +void +hev_ifwd_run (int fd) +{ + if (ipt_init ()) { + LOGV (E, "%s", "iptables initialization failed."); + return; + } + if (ipt_create_nat_rule (fd)) { + LOGV (E, "%s", "Failed to create iptables rule."); + return; + } + if (ipt_insert_dnat (&nat_rule)) { + LOGV (E, "%s", "Failed to insert iptables rule."); + return; + } + if (ipt_register_atexit ()) { + LOGV (E, "%s", "Failed to register iptables cleanup function."); + return; + } +} + +void +hev_ifwd_kill (void) +{ + if (ipt_delete_dnat (&nat_rule)) { + LOGV (E, "%s", "Failed to delete iptables rule."); + return; + } +} diff --git a/src/hev-ifwd.h b/src/hev-ifwd.h new file mode 100644 index 0000000..c4953c5 --- /dev/null +++ b/src/hev-ifwd.h @@ -0,0 +1,28 @@ +/* + ============================================================================ + Name : hev-ifwd.c + Author : Mike Wang + hev + Copyright : Copyright (c) 2024 xyz + Description : iptables forwarder + ============================================================================ + */ +#ifndef __HEV_IFWD_H__ +#define __HEV_IFWD_H__ + +/** + * hev_ifwd_run: + * @fd: file desc + * + * Start iptables port forwarding. + */ +void hev_ifwd_run (int fd); + +/** + * hev_ifwd_kill: + * + * Stop iptables port forwarding. + */ +void hev_ifwd_kill (void); + +#endif /* __HEV_IFWD_H__ */ diff --git a/src/hev-tnsk.c b/src/hev-tnsk.c index 9f388f7..56f3032 100644 --- a/src/hev-tnsk.c +++ b/src/hev-tnsk.c @@ -16,6 +16,7 @@ #include #include "hev-conf.h" +#include "hev-ifwd.h" #include "hev-misc.h" #include "hev-sock.h" #include "hev-stun.h" @@ -75,9 +76,20 @@ static void stun_handler (void) { const char *tfwd = hev_conf_taddr (); + int method = hev_conf_method (); if (tfwd) { - hev_tfwd_run (fd); + switch (method) + { + case HEV_FWD_DEFAULT: + hev_tfwd_run (fd); + break; + case HEV_FWD_IPTABLES: + hev_ifwd_run (fd); + break; + default: + break; + } } } @@ -90,7 +102,7 @@ tnsk_run (void) const char *port; const char *hport; const char *iface; - int type; + int type, method; type = hev_conf_type (); http = hev_conf_http (); @@ -99,6 +111,7 @@ tnsk_run (void) port = hev_conf_bport (); hport = hev_conf_hport (); iface = hev_conf_iface (); + method = hev_conf_method (); fd = hev_sock_client_tcp (type, addr, port, http, hport, iface); if (fd < 0) { @@ -112,7 +125,17 @@ tnsk_run (void) http_keep_alive (fd, http); if (tfwd) { - hev_tfwd_kill (); + switch (method) + { + case HEV_FWD_DEFAULT: + hev_tfwd_kill (); + break; + case HEV_FWD_IPTABLES: + hev_ifwd_kill (); + break; + default: + break; + } } close (fd); }