From ca54079b3fd880f31d9c2fbfd6131e511bcb47c9 Mon Sep 17 00:00:00 2001 From: Shawn Carey Date: Tue, 7 Jan 2025 10:19:51 -0500 Subject: [PATCH] zfw integration (#1054) rebase zfw integration. run commands on worker thread --- programs/ziti-edge-tunnel/CMakeLists.txt | 3 + .../ziti-edge-tunnel/include/linux/diverter.h | 32 +++ programs/ziti-edge-tunnel/instance.c | 4 +- programs/ziti-edge-tunnel/linux/diverter.c | 264 ++++++++++++++++++ .../netif_driver/linux/utils.c | 45 ++- .../netif_driver/linux/utils.h | 3 + programs/ziti-edge-tunnel/ziti-edge-tunnel.c | 47 +++- 7 files changed, 392 insertions(+), 6 deletions(-) create mode 100644 programs/ziti-edge-tunnel/include/linux/diverter.h create mode 100644 programs/ziti-edge-tunnel/linux/diverter.c diff --git a/programs/ziti-edge-tunnel/CMakeLists.txt b/programs/ziti-edge-tunnel/CMakeLists.txt index 20bf3f35..0c1fb70c 100644 --- a/programs/ziti-edge-tunnel/CMakeLists.txt +++ b/programs/ziti-edge-tunnel/CMakeLists.txt @@ -42,6 +42,9 @@ if (WIN32) windows/log_utils.c include/service-utils.h) endif () +if (LINUX) + set(ZITI_INSTANCE_OS linux/diverter.c include/linux/diverter.h) +endif () add_executable(ziti-edge-tunnel ziti-edge-tunnel.c ${NETIF_DRIVER_SOURCE} ${ZITI_INSTANCE_COMMON} ${ZITI_INSTANCE_OS}) set_property(TARGET ziti-edge-tunnel PROPERTY C_STANDARD 11) diff --git a/programs/ziti-edge-tunnel/include/linux/diverter.h b/programs/ziti-edge-tunnel/include/linux/diverter.h new file mode 100644 index 00000000..2cd75b4a --- /dev/null +++ b/programs/ziti-edge-tunnel/include/linux/diverter.h @@ -0,0 +1,32 @@ +/* + Copyright NetFoundry Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +// support interactions with zfw command line utility + +#include +#include "model/dtos.h" + +extern char *diverter_if; +extern bool diverter; +extern bool firewall; + +void diverter_init(uint32_t dns_prefix, uint32_t dns_prefix_len, const char *tun_name); +void diverter_quit(); +void init_diverter_interface(const char *interface, const char *direction); +void diverter_add_svc(const tunnel_service *svc); +void diverter_remove_svc(const tunnel_service *svc); +void set_diverter(uint32_t dns_prefix, unsigned char dns_prefix_len, const char *tun_name); +void diverter_cleanup(); \ No newline at end of file diff --git a/programs/ziti-edge-tunnel/instance.c b/programs/ziti-edge-tunnel/instance.c index 4e556ecf..910f0305 100644 --- a/programs/ziti-edge-tunnel/instance.c +++ b/programs/ziti-edge-tunnel/instance.c @@ -428,9 +428,7 @@ tunnel_service *get_tunnel_service(tunnel_identity* id, ziti_service* zs) { svc->Permissions.Dial = ziti_service_has_permission(zs, ziti_session_type_Dial); setTunnelPostureDataTimeout(svc, zs); setTunnelServiceAddress(svc, zs); - if(svc->Permissions.Bind){ - setTunnelAllowedSourceAddress(svc, zs); - } + setTunnelAllowedSourceAddress(svc, zs); return svc; } diff --git a/programs/ziti-edge-tunnel/linux/diverter.c b/programs/ziti-edge-tunnel/linux/diverter.c new file mode 100644 index 00000000..9d7dfd10 --- /dev/null +++ b/programs/ziti-edge-tunnel/linux/diverter.c @@ -0,0 +1,264 @@ +/* + Copyright NetFoundry Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include + +#include "ziti/ziti_log.h" +#include "linux/diverter.h" +#include "../netif_driver/linux/utils.h" + +char check_alt[IF_NAMESIZE]; +char *diverter_path = "/opt/openziti/bin"; +char zfw_path[PATH_MAX]; +char *tc_ingress_object = "zfw_tc_ingress.o"; +char *tc_egress_object = "zfw_tc_outbound_track.o"; +char *xdp_ingress_object = "zfw_xdp_tun_ingress.o"; + +char *diverter_if = NULL; +bool diverter = false; +bool firewall = false; + +void diverter_init(uint32_t dns_prefix, uint32_t dns_prefix_len, const char *tun_name) { + if(!diverter && !firewall){ + diverter_if = getenv("ZITI_DIVERTER"); + if(diverter_if && strlen(diverter_if)){ + diverter = true; + } + char *zifi_firewall = getenv("ZITI_FIREWALL"); + if(zifi_firewall && strlen(zifi_firewall)){ + diverter = true; + firewall = true; + diverter_if = getenv("ZITI_FIREWALL"); + } + } + char *diverter_env_path = getenv("ZFW_OBJECT_PATH"); + if(diverter_env_path && strlen(diverter_env_path)){ + diverter_path = diverter_env_path; + snprintf(zfw_path, sizeof(zfw_path), "%s/%s", diverter_env_path, "zfw"); + }else{ + snprintf(zfw_path, sizeof(zfw_path), "%s/%s", diverter_path, "zfw"); + } + if(diverter && tun_name){ + if(!firewall){ + diverter_quit(); + } + if (is_executable(zfw_path)){ + int count = 0; + char *interface = strtok(diverter_if,","); + while(interface != NULL){ + uint32_t idx = if_nametoindex(interface); + if (!idx) + { + ZITI_LOG(WARN,"Diverter interface not found: %s", interface); + interface = strtok(NULL,","); + continue; + } + if(if_indextoname(idx, check_alt)){ + interface = check_alt; + } + init_diverter_interface(interface, "ingress"); + init_diverter_interface(interface, "egress"); + interface = strtok(NULL,","); + count++; + } + if(count){ + set_diverter(dns_prefix, dns_prefix_len, tun_name); + }else{ + ZITI_LOG(ERROR,"No valid diverter interfaces found"); + exit(1); + } + }else{ + ZITI_LOG(ERROR, "Diverter binary not found"); + exit(1); + } + } +} + +static void diverter_update(const char *ip, uint8_t prefix_len, uint16_t lowport, uint16_t highport, const char *protocol, const char *service_id, const char *action) { + int rndm; + uv_random(NULL, NULL, &rndm, sizeof(rndm), 0, NULL); + unsigned short random_port = 1024 + rndm % (65535 - 1023); + // called while uv loop is running, so queue the command to prevent i/o stalls + queue_command("%s %s -c %s -m %d -l %d -h %d -t %d -p %s -s %s", zfw_path, action, ip, prefix_len, lowport, highport, random_port, protocol, service_id); +} + +void diverter_quit() { + run_command("%s -Q", zfw_path); +} + +void init_diverter_interface(const char *interface, const char *direction) { + // run_command is ok here because the uv loop is not yet running when this is called + const char *obj = (strcmp(direction, "ingress") == 0 ? tc_ingress_object : tc_egress_object); + int ec = run_command("%s -X %s -O %s/%s -z %s", zfw_path, interface, diverter_path, obj, direction); + if (ec != 0) { + ZITI_LOG(WARN, "zfw -X failed"); + return; + } + + // set tun mode + ec = run_command("%s -T %s", zfw_path, interface); + if (ec != 0) { + ZITI_LOG(WARN, "zfw -T failed"); + return; + } + + // enable ipv6 + ec = run_command("%s -6 %s", zfw_path, interface); + if (ec != 0) { + ZITI_LOG(WARN, "zfw -6 failed"); + return; + } + + // pass non-tuple + if (!firewall) { + run_command("%s -q %s", zfw_path, interface); + } +} + +static void bind_diverter_route(const char *ip, uint8_t prefix_len) { + // called while uv loop is running, so queue the command to prevent i/o stalls + queue_command("%s -B %s -m %d", zfw_path, ip, prefix_len); +} + +static void unbind_diverter_route(const char *ip, uint8_t prefix_len) { + // called while uv loop is running, so queue the command to prevent i/o stalls + queue_command("%s -J %s -m %d", zfw_path, ip, prefix_len); +} + +void diverter_add_svc(const tunnel_service *svc) { + if(svc && diverter){ + if(svc->Permissions.Dial){ + for(int x = 0; svc->Addresses && (svc->Addresses[x] != NULL); x++){ + if(!svc->Addresses[x]->IsHost){ + for(int i = 0; (svc->Ports != NULL) && (svc->Ports[i] != NULL); i++){ + for(int j = 0; (svc->Protocols != NULL) && (svc->Protocols[j] != NULL); j++){ + if((svc->AllowedSourceAddresses && svc->AllowedSourceAddresses[0] != NULL) || firewall){ + diverter_update(svc->Addresses[x]->IP, svc->Addresses[x]->Prefix, svc->Ports[i]->Low, svc->Ports[i]->High, svc->Protocols[j], svc->Id, "-I"); + } + } + } + + } + } + } + else if(svc->Permissions.Bind){ + for(int x = 0; svc->AllowedSourceAddresses && (svc->AllowedSourceAddresses[x] != NULL); x++){ + if(!svc->AllowedSourceAddresses[x]->IsHost){ + bind_diverter_route(svc->AllowedSourceAddresses[x]->IP, svc->AllowedSourceAddresses[x]->Prefix); + } + } + } + } +} +void diverter_remove_svc(const tunnel_service *svc) { + if(svc && diverter){ + if(svc->Permissions.Bind){ + for(int x = 0; svc->AllowedSourceAddresses && (svc->AllowedSourceAddresses[x] != NULL); x++){ + if(!svc->AllowedSourceAddresses[x]->IsHost){ + unbind_diverter_route(svc->AllowedSourceAddresses[x]->IP, svc->AllowedSourceAddresses[x]->Prefix); + } + } + } + if(svc->Permissions.Dial){ + for(int x = 0; svc->Addresses && (svc->Addresses[x] != NULL); x++){ + if(!svc->Addresses[x]->IsHost){ + for(int i = 0; (svc->Ports != NULL) && (svc->Ports[i] != NULL); i++){ + for(int j = 0; (svc->Protocols != NULL) && (svc->Protocols[j] != NULL); j++){ + if((svc->AllowedSourceAddresses && svc->AllowedSourceAddresses[0] != NULL) || firewall){ + diverter_update(svc->Addresses[x]->IP, svc->Addresses[x]->Prefix, svc->Ports[i]->Low, svc->Ports[i]->High, svc->Protocols[j], svc->Id, "-D"); + } + } + } + + } + } + } + } +} + +static void diverter_binding_flush() { + // called by exit handler, so run_command is appropriate + run_command("%s -F -j", zfw_path); +} + +static void diverter_ingress_flush() { + // called by exit handler, so run_command is appropriate + run_command("%s -F -z ingress", zfw_path); +} + +static void setup_xdp(const char *tun_name) { + // run_command is ok here because the uv loop is not yet running when this is called + run_command("/usr/sbin/ip link set %s xdpgeneric obj %s/%s sec xdp_redirect", tun_name, diverter_path, xdp_ingress_object); +} + +static void add_user_rules() { + // called by exit handler, so run_command is appropriate + run_command("%s -A", zfw_path); +} + +static void disable_firewall() { + // run_command is ok here because the uv loop is not yet running when this is called + run_command("%s -I -c 0.0.0.0 -m 0 -l 1 -h 65535 -t 0 -p tcp", zfw_path); + run_command("%s -I -c 0.0.0.0 -m 0 -l 1 -h 65535 -t 0 -p udp", zfw_path); + run_command("%s -I -c :: -m 0 -l 1 -h 65535 -t 0 -p tcp", zfw_path); + run_command("%s -I -c :: -m 0 -l 1 -h 65535 -t 0 -p udp", zfw_path); +} + +static void pass_dns_range(uint32_t dns_prefix, uint8_t dns_prefix_len) { + // run_command is ok here because the uv loop is not yet running when this is called + char prefix[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &dns_prefix, prefix, INET_ADDRSTRLEN); + run_command("%s -I -c %s -m %d -l 1 -h 65535 -t 65535 -p tcp", zfw_path, prefix, dns_prefix_len); + run_command("%s -I -c %s -m %d -l 1 -h 65535 -t 65535 -p udp", zfw_path, prefix, dns_prefix_len); +} + +void set_diverter(uint32_t dns_prefix, uint8_t dns_prefix_len, const char *tun_name) +{ + if (!firewall) { + ZITI_LOG(INFO,"Starting ziti-edge-tunnel in diverter mode"); + } else { + ZITI_LOG(INFO,"Starting ziti-edge-tunnel in diverter firewall mode"); + } + if (!firewall) { + disable_firewall(); + } else { + if (is_executable(zfw_path)) { + ZITI_LOG(INFO,"loading user defined FW rules"); + add_user_rules(); + } else { + ZITI_LOG(DEBUG, "Diverter user defined FW rules not found"); + } + pass_dns_range(dns_prefix, dns_prefix_len); + } + setup_xdp(tun_name); +} + +void diverter_cleanup(void) { + if (diverter && !firewall) { + diverter_binding_flush(); + diverter_quit(); + } else if (firewall) { + diverter_binding_flush(); + diverter_ingress_flush(); + add_user_rules(); + } +} \ No newline at end of file diff --git a/programs/ziti-edge-tunnel/netif_driver/linux/utils.c b/programs/ziti-edge-tunnel/netif_driver/linux/utils.c index ca9dbb81..a94f5b43 100644 --- a/programs/ziti-edge-tunnel/netif_driver/linux/utils.c +++ b/programs/ziti-edge-tunnel/netif_driver/linux/utils.c @@ -24,7 +24,7 @@ int run_command_va(bool log_nonzero_ec, const char* cmd, va_list args) { char cmdline[1024]; - vsprintf(cmdline, cmd, args); + vsnprintf(cmdline, sizeof(cmdline), cmd, args); int rc = system(cmdline); if (rc != 0 && log_nonzero_ec) { @@ -50,6 +50,49 @@ int run_command_ex(bool log_nonzero_ec, const char *cmd, ...) { return r; } +struct queued_command_s { + char *cmdline; + int exitcode; +}; + +static void do_queued_command(uv_work_t *wr) { + struct queued_command_s *qcmd = wr->data; + ZITI_LOG(DEBUG, "running '%s'", qcmd->cmdline); + qcmd->exitcode = system(qcmd->cmdline); + ZITI_LOG(DEBUG, "system(%s) returned %d", qcmd->cmdline, qcmd->exitcode); +} + +static void default_after_queued_command(uv_work_t *wr, int status) { + struct queued_command_s *qcmd = wr->data; + free(qcmd->cmdline); + free(qcmd); + free(wr); +} + +int queue_command_va(uv_after_work_cb after, const char *cmd, va_list args) { + uv_work_t *wr = calloc(1, sizeof(uv_work_t)); + struct queued_command_s *qcmd = calloc(1, sizeof(struct queued_command_s)); + wr->data = qcmd; + vasprintf(&qcmd->cmdline, cmd, args); + return uv_queue_work(uv_default_loop(), wr, do_queued_command, after); +} + +int queue_command(const char *cmd, ...) { + va_list args; + va_start(args, cmd); + int r = queue_command_va(default_after_queued_command, cmd, args); + va_end(args); + return r; +} + +int queue_command_ex(uv_after_work_cb after, const char *cmd, ...) { + va_list args; + va_start(args, cmd); + int r = queue_command_va(after, cmd, args); + va_end(args); + return r; +} + bool is_executable(const char *path) { struct stat s; return (stat(path, &s) == 0 && (s.st_mode & S_IXUSR)); diff --git a/programs/ziti-edge-tunnel/netif_driver/linux/utils.h b/programs/ziti-edge-tunnel/netif_driver/linux/utils.h index 4cde5633..0e83fa88 100644 --- a/programs/ziti-edge-tunnel/netif_driver/linux/utils.h +++ b/programs/ziti-edge-tunnel/netif_driver/linux/utils.h @@ -19,5 +19,8 @@ int run_command_va(bool log_nonzero_ec, const char* cmd, va_list args); int run_command(const char *cmd, ...); int run_command_ex(bool log_nonzero_ec, const char *cmd, ...); +int queue_command(const char *fmt, ...); +int queue_command_ex(uv_after_work_cb after, const char *cmd, ...); +int queue_command_va(uv_after_work_cb after, const char *cmd, va_list args); bool is_executable(const char *path); bool is_symlink(const char *path); diff --git a/programs/ziti-edge-tunnel/ziti-edge-tunnel.c b/programs/ziti-edge-tunnel/ziti-edge-tunnel.c index 0103012a..2069c0a8 100644 --- a/programs/ziti-edge-tunnel/ziti-edge-tunnel.c +++ b/programs/ziti-edge-tunnel/ziti-edge-tunnel.c @@ -34,6 +34,7 @@ #include "netif_driver/darwin/utun.h" #elif __linux__ #include "netif_driver/linux/tun.h" +#include "linux/diverter.h" #elif _WIN32 #include #include @@ -551,6 +552,9 @@ static void on_event(const base_event *ev) { svc = get_tunnel_service(id, svc_ev->removed_services[svc_idx]); } ZITI_LOG(INFO, "=============== service event (removed) - %s:%s ===============", svc->Name, svc->Id); +#if __linux__ + diverter_remove_svc(svc); +#endif #if _WIN32 if (svc->Addresses != NULL) { for (int i = 0; svc->Addresses[i]; i++) { @@ -575,6 +579,9 @@ static void on_event(const base_event *ev) { tunnel_service *svc = get_tunnel_service(id, svc_ev->added_services[svc_idx]); svc_event.AddedServices[svc_idx] = svc; ZITI_LOG(INFO, "=============== service event (added) - %s:%s ===============", svc->Name, svc->Id); +#if __linux__ + diverter_add_svc(svc); +#endif #if _WIN32 if (svc->Addresses != NULL) { for (int i = 0; svc->Addresses[i]; i++) { @@ -873,6 +880,9 @@ static int run_tunnel(uv_loop_t *ziti_loop, uint32_t tun_ip, uint32_t dns_ip, co tunnel_upstream_dns *a[] = { &upstream, NULL}; ziti_dns_set_upstream(ziti_loop, a); } +#if __linux__ + diverter_init(dns_ip4_addr.u_addr.ip4.addr, dns_subnet_zaddr.addr.cidr.bits, tun->get_name(tun->handle)); +#endif run_tunneler_loop(ziti_loop); if (tun->close) { tun->close(tun->handle); @@ -979,6 +989,9 @@ static int make_socket_path(uv_loop_t *loop) { #if __linux__ || __APPLE__ static void on_exit_signal(uv_signal_t *s, int sig) { ZITI_LOG(WARN, "received signal: %s", strsignal(sig)); +#if __linux__ + diverter_cleanup(); +#endif exit(1); } #endif @@ -1105,6 +1118,10 @@ static struct option run_options[] = { { "dns-ip-range", required_argument, NULL, 'd'}, { "dns-upstream", required_argument, NULL, 'u'}, { "proxy", required_argument, NULL, 'x' }, +#if __linux__ + { "diverter", required_argument, NULL, 'D' }, + { "diverter-fw", required_argument, NULL, 'f' }, +#endif }; static struct option run_host_options[] = { @@ -1171,9 +1188,25 @@ static int run_opts(int argc, char *argv[]) { optind = 0; bool identity_provided = false; - while ((c = getopt_long(argc, argv, "i:I:v:r:d:u:x:", +#if __linux__ +#define DIVERTER_SHORT_OPTS "D:f:" +#else +#define DIVERTER_SHORT_OPTS "" +#endif + while ((c = getopt_long(argc, argv, "i:I:v:r:d:u:x:"DIVERTER_SHORT_OPTS, run_options, &option_index)) != -1) { switch (c) { +#if __linux__ + case 'D': + diverter = true; + diverter_if = optarg; + break; + case 'f': + diverter = true; + firewall = true; + diverter_if = optarg; + break; +#endif case 'i': { struct cfg_instance_s *inst = calloc(1, sizeof(struct cfg_instance_s)); inst->cfg = strdup(optarg); @@ -2430,8 +2463,17 @@ static CommandLine enroll_cmd = make_command( "\t-n|--name\tidentity name\n" "\t-v|--verbose N\tset log level, higher level -- more verbose (default 3)\n", parse_enroll_opts, enroll); +#if __linux__ +#define DIVERTER_OPTS_SUMMARY "[-D|--diverter ] [-f|--diverter-fw ] " +#define DIVERTER_OPTS_DETAIL "\t-D|--diverter \tset diverter mode to true on \n" \ + "\t-f|--diverter-fw \tset diverter to true in firewall mode on )\n" +#else +#define DIVERTER_OPTS_SUMMARY "" +#define DIVERTER_OPTS_DETAIL "" +#endif + static CommandLine run_cmd = make_command("run", "run Ziti tunnel (required superuser access)", - "-i [-r N] [-v N] [-d|--dns-ip-range N.N.N.N/N] [-u|--dns-upstream N.N.N.N]\n", + "-i [-r N] [-v N] [-d|--dns-ip-range N.N.N.N/N] " DIVERTER_OPTS_SUMMARY "[-u|--dns-upstream N.N.N.N]\n", "\t-i|--identity \trun with provided identity file (required)\n" "\t-I|--identity-dir \tload identities from provided directory\n" "\t-x|--proxy type://[username[:password]@]hostname_or_ip:port\tproxy to use when" @@ -2440,6 +2482,7 @@ static CommandLine run_cmd = make_command("run", "run Ziti tunnel (required supe "\t-r|--refresh N\tset service polling interval in seconds (default 10)\n" "\t-d|--dns-ip-range \tspecify CIDR block in which service DNS names" " are assigned in N.N.N.N/n format (default " DEFAULT_DNS_CIDR ")\n" + DIVERTER_OPTS_DETAIL "\t-u|--dns-upstream \tresolver listening on 53/udp for DNS queries that do not match a Ziti service\n", run_opts, run); static CommandLine run_host_cmd = make_command("run-host", "run Ziti tunnel to host services",