From cdd61ffbccfcc81b560d2bec54c9a431ab6a025a Mon Sep 17 00:00:00 2001 From: Frank Lichtenheld Date: Sun, 31 Aug 2025 17:59:32 +0200 Subject: [PATCH 01/16] socket: Create socket_util with non-socket functions This extracts a lot of the helper functions that do not actually work on sockets, but instead on addresses or similar. This includes - openvpn_getaddrinfo and related functions - print_* - setenv_* And lots of the inline functions. This move will make it easier to add unit tests for these moved functions. Change-Id: I7393459b975fb9b3e0a42743f58645f769d1be5a Signed-off-by: Frank Lichtenheld Acked-by: Gert Doering Message-Id: <20250831155939.29240-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32727.html Signed-off-by: Gert Doering --- CMakeLists.txt | 2 + src/openvpn/Makefile.am | 1 + src/openvpn/clinat.c | 2 +- src/openvpn/dhcp.c | 2 +- src/openvpn/dns.c | 2 +- src/openvpn/manage.h | 2 +- src/openvpn/misc.c | 21 - src/openvpn/misc.h | 3 - src/openvpn/mroute.c | 2 +- src/openvpn/mss.h | 1 + src/openvpn/networking_iproute2.c | 2 +- src/openvpn/options.c | 2 +- src/openvpn/options.h | 2 +- src/openvpn/pool.c | 2 +- src/openvpn/socket.c | 883 ---------------------------- src/openvpn/socket.h | 463 +-------------- src/openvpn/socket_util.c | 936 ++++++++++++++++++++++++++++++ src/openvpn/socket_util.h | 491 ++++++++++++++++ src/openvpn/ssl_common.h | 2 +- src/openvpn/tun.c | 2 +- 20 files changed, 1443 insertions(+), 1380 deletions(-) create mode 100644 src/openvpn/socket_util.c create mode 100644 src/openvpn/socket_util.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3866e21f2..45044af00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -555,6 +555,8 @@ set(SOURCE_FILES src/openvpn/sig.h src/openvpn/socket.c src/openvpn/socket.h + src/openvpn/socket_util.c + src/openvpn/socket_util.h src/openvpn/socks.c src/openvpn/socks.h src/openvpn/ssl.c diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 1e17c3fc8..217f89729 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -128,6 +128,7 @@ openvpn_SOURCES = \ shaper.c shaper.h \ sig.c sig.h \ socket.c socket.h \ + socket_util.c socket_util.h \ socks.c socks.h \ ssl.c ssl.h ssl_backend.h \ ssl_openssl.c ssl_openssl.h \ diff --git a/src/openvpn/clinat.c b/src/openvpn/clinat.c index a3032855f..48a205717 100644 --- a/src/openvpn/clinat.c +++ b/src/openvpn/clinat.c @@ -28,7 +28,7 @@ #include "clinat.h" #include "proto.h" -#include "socket.h" +#include "socket_util.h" #include "memdbg.h" static bool diff --git a/src/openvpn/dhcp.c b/src/openvpn/dhcp.c index 0a7689f78..7abade520 100644 --- a/src/openvpn/dhcp.c +++ b/src/openvpn/dhcp.c @@ -27,7 +27,7 @@ #include "syshead.h" #include "dhcp.h" -#include "socket.h" +#include "socket_util.h" #include "error.h" #include "memdbg.h" diff --git a/src/openvpn/dns.c b/src/openvpn/dns.c index 8e28c2b77..8554089c0 100644 --- a/src/openvpn/dns.c +++ b/src/openvpn/dns.c @@ -27,7 +27,7 @@ #include "syshead.h" #include "dns.h" -#include "socket.h" +#include "socket_util.h" #include "options.h" #include "run_command.h" diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index 083caf5ce..b8892c856 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -48,7 +48,7 @@ #include "misc.h" #include "event.h" -#include "socket.h" +#include "socket_util.h" #include "mroute.h" #define MANAGEMENT_VERSION 5 diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c index 17f7706ea..59bf52b9a 100644 --- a/src/openvpn/misc.c +++ b/src/openvpn/misc.c @@ -72,27 +72,6 @@ set_std_files_to_null(bool stdin_only) #endif } -/* - * Prepend a random string to hostname to prevent DNS caching. - * For example, foo.bar.gov would be modified to .foo.bar.gov. - * Of course, this requires explicit support in the DNS server (wildcard). - */ -const char * -hostname_randomize(const char *hostname, struct gc_arena *gc) -{ -#define n_rnd_bytes 6 - - uint8_t rnd_bytes[n_rnd_bytes]; - const char *rnd_str; - struct buffer hname = alloc_buf_gc(strlen(hostname) + sizeof(rnd_bytes) * 2 + 4, gc); - - prng_bytes(rnd_bytes, sizeof(rnd_bytes)); - rnd_str = format_hex_ex(rnd_bytes, sizeof(rnd_bytes), 40, 0, NULL, gc); - buf_printf(&hname, "%s.%s", rnd_str, hostname); - return BSTR(&hname); -#undef n_rnd_bytes -} - #ifdef ENABLE_MANAGEMENT /* Get username/password from the management interface */ static bool diff --git a/src/openvpn/misc.h b/src/openvpn/misc.h index 1b10cd9e5..e87fd85b1 100644 --- a/src/openvpn/misc.h +++ b/src/openvpn/misc.h @@ -44,9 +44,6 @@ const char **make_arg_array(const char *first, const char *parms, struct gc_aren const char **make_extended_arg_array(char **p, bool is_inline, struct gc_arena *gc); -/* prepend a random prefix to hostname */ -const char *hostname_randomize(const char *hostname, struct gc_arena *gc); - /* * Get and store a username/password */ diff --git a/src/openvpn/mroute.c b/src/openvpn/mroute.c index a598037ba..ab01874b2 100644 --- a/src/openvpn/mroute.c +++ b/src/openvpn/mroute.c @@ -30,7 +30,7 @@ #include "mroute.h" #include "proto.h" #include "error.h" -#include "socket.h" +#include "socket_util.h" #include "memdbg.h" diff --git a/src/openvpn/mss.h b/src/openvpn/mss.h index 05f12a73f..bcbb5a8dc 100644 --- a/src/openvpn/mss.h +++ b/src/openvpn/mss.h @@ -26,6 +26,7 @@ #include "proto.h" #include "error.h" #include "mtu.h" +#include "socket.h" #include "ssl_common.h" void mss_fixup_ipv4(struct buffer *buf, uint16_t maxmss); diff --git a/src/openvpn/networking_iproute2.c b/src/openvpn/networking_iproute2.c index 0635b5d6b..e9be3a455 100644 --- a/src/openvpn/networking_iproute2.c +++ b/src/openvpn/networking_iproute2.c @@ -30,7 +30,7 @@ #include "misc.h" #include "openvpn.h" #include "run_command.h" -#include "socket.h" +#include "socket_util.h" #include #include diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 648d526fe..74946a486 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -45,7 +45,7 @@ #include "ssl_ncp.h" #include "options.h" #include "misc.h" -#include "socket.h" +#include "socket_util.h" #include "packet_id.h" #include "pkcs11.h" #include "win32.h" diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 4fa6800d1..44f3fc938 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -33,7 +33,7 @@ #include "mtu.h" #include "route.h" #include "tun.h" -#include "socket.h" +#include "socket_util.h" #include "plugin.h" #include "manage.h" #include "proxy.h" diff --git a/src/openvpn/pool.c b/src/openvpn/pool.c index a41364a54..fde6cea8d 100644 --- a/src/openvpn/pool.c +++ b/src/openvpn/pool.c @@ -29,7 +29,7 @@ #include "pool.h" #include "buffer.h" #include "error.h" -#include "socket.h" +#include "socket_util.h" #include "otime.h" #include "memdbg.h" diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c index 8fc10bc9a..306170c5b 100644 --- a/src/openvpn/socket.c +++ b/src/openvpn/socket.c @@ -405,428 +405,6 @@ err: throw_signal_soft(SIGHUP, "Preresolving failed"); } -/** - * Small helper function for openvpn_getaddrinfo to print the address - * family when resolving fails - */ -static const char * -getaddrinfo_addr_family_name(int af) -{ - switch (af) - { - case AF_INET: - return "[AF_INET]"; - - case AF_INET6: - return "[AF_INET6]"; - } - return ""; -} - -/* - * Translate IPv4/IPv6 addr or hostname into struct addrinfo - * If resolve error, try again for resolve_retry_seconds seconds. - */ -int -openvpn_getaddrinfo(unsigned int flags, const char *hostname, const char *servname, - int resolve_retry_seconds, struct signal_info *sig_info, int ai_family, - struct addrinfo **res) -{ - struct addrinfo hints; - int status; - struct signal_info sigrec = { 0 }; - int msglevel = (flags & GETADDR_FATAL) ? M_FATAL : D_RESOLVE_ERRORS; - struct gc_arena gc = gc_new(); - const char *print_hostname; - const char *print_servname; - - ASSERT(res); - - ASSERT(hostname || servname); - ASSERT(!(flags & GETADDR_HOST_ORDER)); - - if (servname) - { - print_servname = servname; - } - else - { - print_servname = ""; - } - - if (flags & GETADDR_MSG_VIRT_OUT) - { - msglevel |= M_MSG_VIRT_OUT; - } - - if ((flags & (GETADDR_FATAL_ON_SIGNAL | GETADDR_WARN_ON_SIGNAL)) && !sig_info) - { - sig_info = &sigrec; - } - - /* try numeric ip addr first */ - CLEAR(hints); - hints.ai_flags = AI_NUMERICHOST; - - if (flags & GETADDR_PASSIVE) - { - hints.ai_flags |= AI_PASSIVE; - } - - if (flags & GETADDR_DATAGRAM) - { - hints.ai_socktype = SOCK_DGRAM; - } - else - { - hints.ai_socktype = SOCK_STREAM; - } - - /* if hostname is not set, we want to bind to 'ANY', with - * the correct address family - v4-only or v6/v6-dual-stack */ - if (!hostname) - { - hints.ai_family = ai_family; - } - - status = getaddrinfo(hostname, servname, &hints, res); - - if (status != 0) /* parse as numeric address failed? */ - { - const int fail_wait_interval = 5; /* seconds */ - /* Add +4 to cause integer division rounding up (1 + 4) = 5, (0+4)/5=0 */ - int resolve_retries = - (flags & GETADDR_TRY_ONCE) ? 1 : ((resolve_retry_seconds + 4) / fail_wait_interval); - const char *fmt; - int level = 0; - - /* this is not a numeric IP, therefore force resolution using the - * provided ai_family */ - hints.ai_family = ai_family; - - if (hostname && (flags & GETADDR_RANDOMIZE)) - { - hostname = hostname_randomize(hostname, &gc); - } - - if (hostname) - { - print_hostname = hostname; - } - else - { - print_hostname = "undefined"; - } - - fmt = "RESOLVE: Cannot resolve host address: %s:%s%s (%s)"; - if ((flags & GETADDR_MENTION_RESOLVE_RETRY) && !resolve_retry_seconds) - { - fmt = "RESOLVE: Cannot resolve host address: %s:%s%s (%s)" - "(I would have retried this name query if you had " - "specified the --resolv-retry option.)"; - } - - if (!(flags & GETADDR_RESOLVE) || status == EAI_FAIL) - { - msg(msglevel, "RESOLVE: Cannot parse IP address: %s:%s (%s)", print_hostname, - print_servname, gai_strerror(status)); - goto done; - } - -#ifdef ENABLE_MANAGEMENT - if (flags & GETADDR_UPDATE_MANAGEMENT_STATE) - { - if (management) - { - management_set_state(management, OPENVPN_STATE_RESOLVE, NULL, NULL, NULL, NULL, - NULL); - } - } -#endif - - /* - * Resolve hostname - */ - while (true) - { -#ifndef _WIN32 - /* force resolv.conf reload */ - res_init(); -#endif - /* try hostname lookup */ - hints.ai_flags &= ~AI_NUMERICHOST; - dmsg(D_SOCKET_DEBUG, "GETADDRINFO flags=0x%04x ai_family=%d ai_socktype=%d", flags, - hints.ai_family, hints.ai_socktype); - status = getaddrinfo(hostname, servname, &hints, res); - - if (sig_info) - { - get_signal(&sig_info->signal_received); - if (sig_info->signal_received) /* were we interrupted by a signal? */ - { - /* why are we overwriting SIGUSR1 ? */ - if (signal_reset(sig_info, SIGUSR1) == SIGUSR1) /* ignore SIGUSR1 */ - { - msg(level, "RESOLVE: Ignored SIGUSR1 signal received during " - "DNS resolution attempt"); - } - else - { - /* turn success into failure (interrupted syscall) */ - if (0 == status) - { - ASSERT(res); - freeaddrinfo(*res); - *res = NULL; - status = EAI_AGAIN; /* = temporary failure */ - errno = EINTR; - } - goto done; - } - } - } - - /* success? */ - if (0 == status) - { - break; - } - - /* resolve lookup failed, should we - * continue or fail? */ - level = msglevel; - if (resolve_retries > 0) - { - level = D_RESOLVE_ERRORS; - } - - msg(level, fmt, print_hostname, print_servname, getaddrinfo_addr_family_name(ai_family), - gai_strerror(status)); - - if (--resolve_retries <= 0) - { - goto done; - } - - management_sleep(fail_wait_interval); - } - - ASSERT(res); - - /* hostname resolve succeeded */ - - /* - * Do not choose an IP Addresse by random or change the order * - * of IP addresses, doing so will break RFC 3484 address selection * - */ - } - else - { - /* IP address parse succeeded */ - if (flags & GETADDR_RANDOMIZE) - { - msg(M_WARN, "WARNING: ignoring --remote-random-hostname because the " - "hostname is an IP address"); - } - } - -done: - if (sig_info && sig_info->signal_received) - { - int level = 0; - if (flags & GETADDR_FATAL_ON_SIGNAL) - { - level = M_FATAL; - } - else if (flags & GETADDR_WARN_ON_SIGNAL) - { - level = M_WARN; - } - msg(level, "RESOLVE: signal received during DNS resolution attempt"); - } - - gc_free(&gc); - return status; -} - -/* - * We do our own inet_aton because the glibc function - * isn't very good about error checking. - */ -int -openvpn_inet_aton(const char *dotted_quad, struct in_addr *addr) -{ - unsigned int a, b, c, d; - - CLEAR(*addr); - if (sscanf(dotted_quad, "%u.%u.%u.%u", &a, &b, &c, &d) == 4) - { - if (a < 256 && b < 256 && c < 256 && d < 256) - { - addr->s_addr = htonl(a << 24 | b << 16 | c << 8 | d); - return OIA_IP; /* good dotted quad */ - } - } - if (string_class(dotted_quad, CC_DIGIT | CC_DOT, 0)) - { - return OIA_ERROR; /* probably a badly formatted dotted quad */ - } - else - { - return OIA_HOSTNAME; /* probably a hostname */ - } -} - -bool -ip_addr_dotted_quad_safe(const char *dotted_quad) -{ - /* verify non-NULL */ - if (!dotted_quad) - { - return false; - } - - /* verify length is within limits */ - if (strlen(dotted_quad) > 15) - { - return false; - } - - /* verify that all chars are either numeric or '.' and that no numeric - * substring is greater than 3 chars */ - { - int nnum = 0; - const char *p = dotted_quad; - int c; - - while ((c = *p++)) - { - if (c >= '0' && c <= '9') - { - ++nnum; - if (nnum > 3) - { - return false; - } - } - else if (c == '.') - { - nnum = 0; - } - else - { - return false; - } - } - } - - /* verify that string will convert to IP address */ - { - struct in_addr a; - return openvpn_inet_aton(dotted_quad, &a) == OIA_IP; - } -} - -bool -ipv6_addr_safe(const char *ipv6_text_addr) -{ - /* verify non-NULL */ - if (!ipv6_text_addr) - { - return false; - } - - /* verify length is within limits */ - if (strlen(ipv6_text_addr) > INET6_ADDRSTRLEN) - { - return false; - } - - /* verify that string will convert to IPv6 address */ - { - struct in6_addr a6; - return inet_pton(AF_INET6, ipv6_text_addr, &a6) == 1; - } -} - -static bool -dns_addr_safe(const char *addr) -{ - if (addr) - { - const size_t len = strlen(addr); - return len > 0 && len <= 255 && string_class(addr, CC_ALNUM | CC_DASH | CC_DOT, 0); - } - else - { - return false; - } -} - -bool -ip_or_dns_addr_safe(const char *addr, const bool allow_fqdn) -{ - if (ip_addr_dotted_quad_safe(addr)) - { - return true; - } - else if (allow_fqdn) - { - return dns_addr_safe(addr); - } - else - { - return false; - } -} - -bool -mac_addr_safe(const char *mac_addr) -{ - /* verify non-NULL */ - if (!mac_addr) - { - return false; - } - - /* verify length is within limits */ - if (strlen(mac_addr) > 17) - { - return false; - } - - /* verify that all chars are either alphanumeric or ':' and that no - * alphanumeric substring is greater than 2 chars */ - { - int nnum = 0; - const char *p = mac_addr; - int c; - - while ((c = *p++)) - { - if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) - { - ++nnum; - if (nnum > 2) - { - return false; - } - } - else if (c == ':') - { - nnum = 0; - } - else - { - return false; - } - } - } - - /* error-checking is left to script invoked in lladdr.c */ - return true; -} - static int socket_get_sndbuf(socket_descriptor_t sd) { @@ -2670,467 +2248,6 @@ socket_listen_event_handle(struct link_socket *s) #endif } -/* - * Format IP addresses in ascii - */ - -const char * -print_sockaddr_ex(const struct sockaddr *sa, const char *separator, const unsigned int flags, - struct gc_arena *gc) -{ - struct buffer out = alloc_buf_gc(128, gc); - bool addr_is_defined = false; - char hostaddr[NI_MAXHOST] = ""; - char servname[NI_MAXSERV] = ""; - int status; - - socklen_t salen = 0; - switch (sa->sa_family) - { - case AF_INET: - if (!(flags & PS_DONT_SHOW_FAMILY)) - { - buf_puts(&out, "[AF_INET]"); - } - salen = sizeof(struct sockaddr_in); - addr_is_defined = ((struct sockaddr_in *)sa)->sin_addr.s_addr != 0; - break; - - case AF_INET6: - if (!(flags & PS_DONT_SHOW_FAMILY)) - { - buf_puts(&out, "[AF_INET6]"); - } - salen = sizeof(struct sockaddr_in6); - addr_is_defined = !IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)sa)->sin6_addr); - break; - - case AF_UNSPEC: - if (!(flags & PS_DONT_SHOW_FAMILY)) - { - return "[AF_UNSPEC]"; - } - else - { - return ""; - } - - default: - ASSERT(0); - } - - status = getnameinfo(sa, salen, hostaddr, sizeof(hostaddr), servname, sizeof(servname), - NI_NUMERICHOST | NI_NUMERICSERV); - - if (status != 0) - { - buf_printf(&out, "[nameinfo() err: %s]", gai_strerror(status)); - return BSTR(&out); - } - - if (!(flags & PS_DONT_SHOW_ADDR)) - { - if (addr_is_defined) - { - buf_puts(&out, hostaddr); - } - else - { - buf_puts(&out, "[undef]"); - } - } - - if ((flags & PS_SHOW_PORT) || (flags & PS_SHOW_PORT_IF_DEFINED)) - { - if (separator) - { - buf_puts(&out, separator); - } - - buf_puts(&out, servname); - } - - return BSTR(&out); -} - -const char * -print_link_socket_actual(const struct link_socket_actual *act, struct gc_arena *gc) -{ - return print_link_socket_actual_ex(act, ":", PS_SHOW_PORT | PS_SHOW_PKTINFO, gc); -} - -#ifndef IF_NAMESIZE -#define IF_NAMESIZE 16 -#endif - -const char * -print_link_socket_actual_ex(const struct link_socket_actual *act, const char *separator, - const unsigned int flags, struct gc_arena *gc) -{ - if (act) - { - struct buffer out = alloc_buf_gc(128, gc); - buf_printf(&out, "%s", print_sockaddr_ex(&act->dest.addr.sa, separator, flags, gc)); -#if ENABLE_IP_PKTINFO - char ifname[IF_NAMESIZE] = "[undef]"; - - if ((flags & PS_SHOW_PKTINFO) && addr_defined_ipi(act)) - { - switch (act->dest.addr.sa.sa_family) - { - case AF_INET: - { - struct openvpn_sockaddr sa; - CLEAR(sa); - sa.addr.in4.sin_family = AF_INET; -#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) - sa.addr.in4.sin_addr = act->pi.in4.ipi_spec_dst; - if_indextoname(act->pi.in4.ipi_ifindex, ifname); -#elif defined(IP_RECVDSTADDR) - sa.addr.in4.sin_addr = act->pi.in4; - ifname[0] = 0; -#else /* if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) */ -#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix syshead.h) -#endif - buf_printf(&out, " (via %s%%%s)", - print_sockaddr_ex(&sa.addr.sa, separator, 0, gc), ifname); - } - break; - - case AF_INET6: - { - struct sockaddr_in6 sin6; - char buf[INET6_ADDRSTRLEN] = "[undef]"; - CLEAR(sin6); - sin6.sin6_family = AF_INET6; - sin6.sin6_addr = act->pi.in6.ipi6_addr; - if_indextoname(act->pi.in6.ipi6_ifindex, ifname); - if (getnameinfo((struct sockaddr *)&sin6, sizeof(struct sockaddr_in6), buf, - sizeof(buf), NULL, 0, NI_NUMERICHOST) - == 0) - { - buf_printf(&out, " (via %s%%%s)", buf, ifname); - } - else - { - buf_printf(&out, " (via [getnameinfo() err]%%%s)", ifname); - } - } - break; - } - } -#endif /* if ENABLE_IP_PKTINFO */ - return BSTR(&out); - } - else - { - return "[NULL]"; - } -} - -/* - * Convert an in_addr_t in host byte order - * to an ascii dotted quad. - */ -const char * -print_in_addr_t(in_addr_t addr, unsigned int flags, struct gc_arena *gc) -{ - struct in_addr ia; - char *out = gc_malloc(INET_ADDRSTRLEN, true, gc); - - if (addr || !(flags & IA_EMPTY_IF_UNDEF)) - { - CLEAR(ia); - ia.s_addr = (flags & IA_NET_ORDER) ? addr : htonl(addr); - - inet_ntop(AF_INET, &ia, out, INET_ADDRSTRLEN); - } - return out; -} - -/* - * Convert an in6_addr in host byte order - * to an ascii representation of an IPv6 address - */ -const char * -print_in6_addr(struct in6_addr a6, unsigned int flags, struct gc_arena *gc) -{ - char *out = gc_malloc(INET6_ADDRSTRLEN, true, gc); - - if (memcmp(&a6, &in6addr_any, sizeof(a6)) != 0 || !(flags & IA_EMPTY_IF_UNDEF)) - { - inet_ntop(AF_INET6, &a6, out, INET6_ADDRSTRLEN); - } - return out; -} - -/* - * Convert an in_port_t in host byte order to a string - */ -const char * -print_in_port_t(in_port_t port, struct gc_arena *gc) -{ - struct buffer buffer = alloc_buf_gc(8, gc); - buf_printf(&buffer, "%hu", port); - return BSTR(&buffer); -} - -/* add some offset to an ipv6 address - * (add in steps of 8 bits, taking overflow into next round) - */ -struct in6_addr -add_in6_addr(struct in6_addr base, uint32_t add) -{ - int i; - - for (i = 15; i >= 0 && add > 0; i--) - { - register int carry; - register uint32_t h; - - h = (unsigned char)base.s6_addr[i]; - base.s6_addr[i] = (h + add) & UINT8_MAX; - - /* using explicit carry for the 8-bit additions will catch - * 8-bit and(!) 32-bit overruns nicely - */ - carry = ((h & 0xff) + (add & 0xff)) >> 8; - add = (add >> 8) + carry; - } - return base; -} - -/* set environmental variables for ip/port in *addr */ -void -setenv_sockaddr(struct env_set *es, const char *name_prefix, const struct openvpn_sockaddr *addr, - const unsigned int flags) -{ - char name_buf[256]; - - char buf[INET6_ADDRSTRLEN]; - switch (addr->addr.sa.sa_family) - { - case AF_INET: - if (flags & SA_IP_PORT) - { - snprintf(name_buf, sizeof(name_buf), "%s_ip", name_prefix); - } - else - { - snprintf(name_buf, sizeof(name_buf), "%s", name_prefix); - } - - inet_ntop(AF_INET, &addr->addr.in4.sin_addr, buf, sizeof(buf)); - setenv_str(es, name_buf, buf); - - if ((flags & SA_IP_PORT) && addr->addr.in4.sin_port) - { - snprintf(name_buf, sizeof(name_buf), "%s_port", name_prefix); - setenv_int(es, name_buf, ntohs(addr->addr.in4.sin_port)); - } - break; - - case AF_INET6: - if (IN6_IS_ADDR_V4MAPPED(&addr->addr.in6.sin6_addr)) - { - struct in_addr ia; - memcpy(&ia.s_addr, &addr->addr.in6.sin6_addr.s6_addr[12], sizeof(ia.s_addr)); - snprintf(name_buf, sizeof(name_buf), "%s_ip", name_prefix); - inet_ntop(AF_INET, &ia, buf, sizeof(buf)); - } - else - { - snprintf(name_buf, sizeof(name_buf), "%s_ip6", name_prefix); - inet_ntop(AF_INET6, &addr->addr.in6.sin6_addr, buf, sizeof(buf)); - } - setenv_str(es, name_buf, buf); - - if ((flags & SA_IP_PORT) && addr->addr.in6.sin6_port) - { - snprintf(name_buf, sizeof(name_buf), "%s_port", name_prefix); - setenv_int(es, name_buf, ntohs(addr->addr.in6.sin6_port)); - } - break; - } -} - -void -setenv_in_addr_t(struct env_set *es, const char *name_prefix, in_addr_t addr, - const unsigned int flags) -{ - if (addr || !(flags & SA_SET_IF_NONZERO)) - { - struct openvpn_sockaddr si; - CLEAR(si); - si.addr.in4.sin_family = AF_INET; - si.addr.in4.sin_addr.s_addr = htonl(addr); - setenv_sockaddr(es, name_prefix, &si, flags); - } -} - -void -setenv_in6_addr(struct env_set *es, const char *name_prefix, const struct in6_addr *addr, - const unsigned int flags) -{ - if (!IN6_IS_ADDR_UNSPECIFIED(addr) || !(flags & SA_SET_IF_NONZERO)) - { - struct openvpn_sockaddr si; - CLEAR(si); - si.addr.in6.sin6_family = AF_INET6; - si.addr.in6.sin6_addr = *addr; - setenv_sockaddr(es, name_prefix, &si, flags); - } -} - -void -setenv_link_socket_actual(struct env_set *es, const char *name_prefix, - const struct link_socket_actual *act, const unsigned int flags) -{ - setenv_sockaddr(es, name_prefix, &act->dest, flags); -} - -/* - * Convert protocol names between index and ascii form. - */ - -struct proto_names -{ - const char *short_form; - const char *display_form; - sa_family_t proto_af; - int proto; -}; - -/* Indexed by PROTO_x */ -static const struct proto_names proto_names[] = { - { "proto-uninitialized", "proto-NONE", AF_UNSPEC, PROTO_NONE }, - /* try IPv4 and IPv6 (client), bind dual-stack (server) */ - { "udp", "UDP", AF_UNSPEC, PROTO_UDP }, - { "tcp-server", "TCP_SERVER", AF_UNSPEC, PROTO_TCP_SERVER }, - { "tcp-client", "TCP_CLIENT", AF_UNSPEC, PROTO_TCP_CLIENT }, - { "tcp", "TCP", AF_UNSPEC, PROTO_TCP }, - /* force IPv4 */ - { "udp4", "UDPv4", AF_INET, PROTO_UDP }, - { "tcp4-server", "TCPv4_SERVER", AF_INET, PROTO_TCP_SERVER }, - { "tcp4-client", "TCPv4_CLIENT", AF_INET, PROTO_TCP_CLIENT }, - { "tcp4", "TCPv4", AF_INET, PROTO_TCP }, - /* force IPv6 */ - { "udp6", "UDPv6", AF_INET6, PROTO_UDP }, - { "tcp6-server", "TCPv6_SERVER", AF_INET6, PROTO_TCP_SERVER }, - { "tcp6-client", "TCPv6_CLIENT", AF_INET6, PROTO_TCP_CLIENT }, - { "tcp6", "TCPv6", AF_INET6, PROTO_TCP }, -}; - -int -ascii2proto(const char *proto_name) -{ - for (size_t i = 0; i < SIZE(proto_names); ++i) - { - if (!strcmp(proto_name, proto_names[i].short_form)) - { - return proto_names[i].proto; - } - } - return -1; -} - -sa_family_t -ascii2af(const char *proto_name) -{ - for (size_t i = 0; i < SIZE(proto_names); ++i) - { - if (!strcmp(proto_name, proto_names[i].short_form)) - { - return proto_names[i].proto_af; - } - } - return 0; -} - -const char * -proto2ascii(int proto, sa_family_t af, bool display_form) -{ - for (size_t i = 0; i < SIZE(proto_names); ++i) - { - if (proto_names[i].proto_af == af && proto_names[i].proto == proto) - { - if (display_form) - { - return proto_names[i].display_form; - } - else - { - return proto_names[i].short_form; - } - } - } - - return "[unknown protocol]"; -} - -const char * -proto2ascii_all(struct gc_arena *gc) -{ - struct buffer out = alloc_buf_gc(256, gc); - - for (size_t i = 0; i < SIZE(proto_names); ++i) - { - if (i) - { - buf_printf(&out, " "); - } - buf_printf(&out, "[%s]", proto_names[i].short_form); - } - return BSTR(&out); -} - -const char * -addr_family_name(int af) -{ - switch (af) - { - case AF_INET: - return "AF_INET"; - - case AF_INET6: - return "AF_INET6"; - } - return "AF_UNSPEC"; -} - -/* - * Given a local proto, return local proto - * if !remote, or compatible remote proto - * if remote. - * - * This is used for options compatibility - * checking. - * - * IPv6 and IPv4 protocols are comptabile but OpenVPN - * has always sent UDPv4, TCPv4 over the wire. Keep these - * strings for backward compatibility - */ -const char * -proto_remote(int proto, bool remote) -{ - ASSERT(proto >= 0 && proto < PROTO_N); - if (proto == PROTO_UDP) - { - return "UDPv4"; - } - - if ((remote && proto == PROTO_TCP_CLIENT) || (!remote && proto == PROTO_TCP_SERVER)) - { - return "TCPv4_SERVER"; - } - if ((remote && proto == PROTO_TCP_SERVER) || (!remote && proto == PROTO_TCP_CLIENT)) - { - return "TCPv4_CLIENT"; - } - - ASSERT(0); - return ""; /* Make the compiler happy */ -} /* * Bad incoming address lengths that differ from what diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h index ed375901e..cce91834e 100644 --- a/src/openvpn/socket.h +++ b/src/openvpn/socket.h @@ -34,6 +34,7 @@ #include "socks.h" #include "misc.h" #include "tun.h" +#include "socket_util.h" /* * OpenVPN's default port number as assigned by IANA. @@ -60,18 +61,6 @@ typedef uint16_t packet_size_type; /* convert a packet_size_type from network to host order */ #define ntohps(x) ntohs(x) -/* OpenVPN sockaddr struct */ -struct openvpn_sockaddr -{ - /*int dummy;*/ /* add offset to force a bug if sa not explicitly dereferenced */ - union - { - struct sockaddr sa; - struct sockaddr_in in4; - struct sockaddr_in6 in6; - } addr; -}; - /* struct to hold preresolved host names */ struct cached_dns_entry { @@ -83,25 +72,6 @@ struct cached_dns_entry struct cached_dns_entry *next; }; -/* actual address of remote, based on source address of received packets */ -struct link_socket_actual -{ - /*int dummy;*/ /* add offset to force a bug if dest not explicitly dereferenced */ - - struct openvpn_sockaddr dest; -#if ENABLE_IP_PKTINFO - union - { -#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) - struct in_pktinfo in4; -#elif defined(IP_RECVDSTADDR) - struct in_addr in4; -#endif - struct in6_pktinfo in6; - } pi; -#endif -}; - /* IP addresses which are persistent across SIGUSR1s */ struct link_socket_addr { @@ -349,58 +319,6 @@ void link_socket_close(struct link_socket *sock); void sd_close(socket_descriptor_t *sd); -#define PS_SHOW_PORT_IF_DEFINED (1 << 0) -#define PS_SHOW_PORT (1 << 1) -#define PS_SHOW_PKTINFO (1 << 2) -#define PS_DONT_SHOW_ADDR (1 << 3) -#define PS_DONT_SHOW_FAMILY (1 << 4) - -const char *print_sockaddr_ex(const struct sockaddr *addr, const char *separator, - const unsigned int flags, struct gc_arena *gc); - -static inline const char * -print_openvpn_sockaddr(const struct openvpn_sockaddr *addr, struct gc_arena *gc) -{ - return print_sockaddr_ex(&addr->addr.sa, ":", PS_SHOW_PORT, gc); -} - -static inline const char * -print_sockaddr(const struct sockaddr *addr, struct gc_arena *gc) -{ - return print_sockaddr_ex(addr, ":", PS_SHOW_PORT, gc); -} - - -const char *print_link_socket_actual_ex(const struct link_socket_actual *act, const char *separator, - const unsigned int flags, struct gc_arena *gc); - -const char *print_link_socket_actual(const struct link_socket_actual *act, struct gc_arena *gc); - - -#define IA_EMPTY_IF_UNDEF (1 << 0) -#define IA_NET_ORDER (1 << 1) -const char *print_in_addr_t(in_addr_t addr, unsigned int flags, struct gc_arena *gc); - -const char *print_in6_addr(struct in6_addr addr6, unsigned int flags, struct gc_arena *gc); - -const char *print_in_port_t(in_port_t port, struct gc_arena *gc); - -struct in6_addr add_in6_addr(struct in6_addr base, uint32_t add); - -#define SA_IP_PORT (1 << 0) -#define SA_SET_IF_NONZERO (1 << 1) -void setenv_sockaddr(struct env_set *es, const char *name_prefix, - const struct openvpn_sockaddr *addr, const unsigned int flags); - -void setenv_in_addr_t(struct env_set *es, const char *name_prefix, in_addr_t addr, - const unsigned int flags); - -void setenv_in6_addr(struct env_set *es, const char *name_prefix, const struct in6_addr *addr, - const unsigned int flags); - -void setenv_link_socket_actual(struct env_set *es, const char *name_prefix, - const struct link_socket_actual *act, const unsigned int flags); - void bad_address_length(int actual, int expected); /* IPV4_INVALID_ADDR: returned by link_socket_current_remote() @@ -432,21 +350,6 @@ void link_socket_update_buffer_sizes(struct link_socket *sock, int rcvbuf, int s * Low-level functions */ -/* return values of openvpn_inet_aton */ -#define OIA_HOSTNAME 0 -#define OIA_IP 1 -#define OIA_ERROR -1 -int openvpn_inet_aton(const char *dotted_quad, struct in_addr *addr); - -/* integrity validation on pulled options */ -bool ip_addr_dotted_quad_safe(const char *dotted_quad); - -bool ip_or_dns_addr_safe(const char *addr, const bool allow_fqdn); - -bool mac_addr_safe(const char *mac_addr); - -bool ipv6_addr_safe(const char *ipv6_text_addr); - socket_descriptor_t create_socket_tcp(struct addrinfo *); socket_descriptor_t socket_do_accept(socket_descriptor_t sd, struct link_socket_actual *act, @@ -472,134 +375,6 @@ bool unix_socket_get_peer_uid_gid(const socket_descriptor_t sd, int *uid, int *g #endif /* if UNIX_SOCK_SUPPORT */ -/* - * DNS resolution - */ - -#define GETADDR_RESOLVE (1 << 0) -#define GETADDR_FATAL (1 << 1) -#define GETADDR_HOST_ORDER (1 << 2) -#define GETADDR_MENTION_RESOLVE_RETRY (1 << 3) -#define GETADDR_FATAL_ON_SIGNAL (1 << 4) -#define GETADDR_WARN_ON_SIGNAL (1 << 5) -#define GETADDR_MSG_VIRT_OUT (1 << 6) -#define GETADDR_TRY_ONCE (1 << 7) -#define GETADDR_UPDATE_MANAGEMENT_STATE (1 << 8) -#define GETADDR_RANDOMIZE (1 << 9) -#define GETADDR_PASSIVE (1 << 10) -#define GETADDR_DATAGRAM (1 << 11) - -#define GETADDR_CACHE_MASK (GETADDR_DATAGRAM | GETADDR_PASSIVE) - -/** - * Translate an IPv4 addr or hostname from string form to in_addr_t - * - * In case of resolve error, it will try again for - * resolve_retry_seconds seconds. - */ -in_addr_t getaddr(unsigned int flags, const char *hostname, int resolve_retry_seconds, - bool *succeeded, struct signal_info *sig_info); - -/** - * Translate an IPv6 addr or hostname from string form to in6_addr - */ -bool get_ipv6_addr(const char *hostname, struct in6_addr *network, unsigned int *netbits, - int msglevel); - -int openvpn_getaddrinfo(unsigned int flags, const char *hostname, const char *servname, - int resolve_retry_seconds, struct signal_info *sig_info, int ai_family, - struct addrinfo **res); - -/* - * Transport protocol naming and other details. - */ - -/* - * Use enum's instead of #define to allow for easier - * optional proto support - */ -enum proto_num -{ - PROTO_NONE, /* catch for uninitialized */ - PROTO_UDP, - PROTO_TCP, - PROTO_TCP_SERVER, - PROTO_TCP_CLIENT, - PROTO_N -}; - -static inline bool -proto_is_net(int proto) -{ - ASSERT(proto >= 0 && proto < PROTO_N); - return proto != PROTO_NONE; -} - -/** - * @brief Returns if the protocol being used is UDP - */ -static inline bool -proto_is_udp(int proto) -{ - ASSERT(proto >= 0 && proto < PROTO_N); - return proto == PROTO_UDP; -} - -/** - * @brief Return if the protocol is datagram (UDP) - * - */ -static inline bool -proto_is_dgram(int proto) -{ - return proto_is_udp(proto); -} - -/** - * @brief returns if the proto is a TCP variant (tcp-server, tcp-client or tcp) - */ -static inline bool -proto_is_tcp(int proto) -{ - ASSERT(proto >= 0 && proto < PROTO_N); - return proto == PROTO_TCP_CLIENT || proto == PROTO_TCP_SERVER; -} - - -int ascii2proto(const char *proto_name); - -sa_family_t ascii2af(const char *proto_name); - -const char *proto2ascii(int proto, sa_family_t af, bool display_form); - -const char *proto2ascii_all(struct gc_arena *gc); - -const char *proto_remote(int proto, bool remote); - -const char *addr_family_name(int af); - -/* - * Overhead added to packets by various protocols. - */ -static inline int -datagram_overhead(sa_family_t af, int proto) -{ - int overhead = 0; - overhead += (proto == PROTO_UDP) ? 8 : 20; - overhead += (af == AF_INET) ? 20 : 40; - return overhead; -} - -/* - * Misc inline functions - */ - -static inline bool -link_socket_proto_connection_oriented(int proto) -{ - return !proto_is_dgram(proto); -} - static inline bool link_socket_connection_oriented(const struct link_socket *sock) { @@ -613,242 +388,6 @@ link_socket_connection_oriented(const struct link_socket *sock) } } -static inline bool -addr_defined(const struct openvpn_sockaddr *addr) -{ - if (!addr) - { - return 0; - } - switch (addr->addr.sa.sa_family) - { - case AF_INET: - return addr->addr.in4.sin_addr.s_addr != 0; - - case AF_INET6: - return !IN6_IS_ADDR_UNSPECIFIED(&addr->addr.in6.sin6_addr); - - default: - return 0; - } -} - -static inline bool -addr_local(const struct sockaddr *addr) -{ - if (!addr) - { - return false; - } - switch (addr->sa_family) - { - case AF_INET: - return ((const struct sockaddr_in *)addr)->sin_addr.s_addr == htonl(INADDR_LOOPBACK); - - case AF_INET6: - return IN6_IS_ADDR_LOOPBACK(&((const struct sockaddr_in6 *)addr)->sin6_addr); - - default: - return false; - } -} - - -static inline bool -addr_defined_ipi(const struct link_socket_actual *lsa) -{ -#if ENABLE_IP_PKTINFO - if (!lsa) - { - return 0; - } - switch (lsa->dest.addr.sa.sa_family) - { -#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) - case AF_INET: - return lsa->pi.in4.ipi_spec_dst.s_addr != 0; - -#elif defined(IP_RECVDSTADDR) - case AF_INET: - return lsa->pi.in4.s_addr != 0; - -#endif - case AF_INET6: - return !IN6_IS_ADDR_UNSPECIFIED(&lsa->pi.in6.ipi6_addr); - - default: - return 0; - } -#else /* if ENABLE_IP_PKTINFO */ - ASSERT(0); -#endif - return false; -} - -static inline bool -link_socket_actual_defined(const struct link_socket_actual *act) -{ - return act && addr_defined(&act->dest); -} - -static inline bool -addr_match(const struct openvpn_sockaddr *a1, const struct openvpn_sockaddr *a2) -{ - switch (a1->addr.sa.sa_family) - { - case AF_INET: - return a1->addr.in4.sin_addr.s_addr == a2->addr.in4.sin_addr.s_addr; - - case AF_INET6: - return IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr, &a2->addr.in6.sin6_addr); - } - ASSERT(0); - return false; -} - -static inline bool -addrlist_match(const struct openvpn_sockaddr *a1, const struct addrinfo *addrlist) -{ - const struct addrinfo *curele; - for (curele = addrlist; curele; curele = curele->ai_next) - { - switch (a1->addr.sa.sa_family) - { - case AF_INET: - if (a1->addr.in4.sin_addr.s_addr - == ((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr) - { - return true; - } - break; - - case AF_INET6: - if (IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr, - &((struct sockaddr_in6 *)curele->ai_addr)->sin6_addr)) - { - return true; - } - break; - - default: - ASSERT(0); - } - } - return false; -} - -static inline bool -addrlist_port_match(const struct openvpn_sockaddr *a1, const struct addrinfo *a2) -{ - const struct addrinfo *curele; - for (curele = a2; curele; curele = curele->ai_next) - { - switch (a1->addr.sa.sa_family) - { - case AF_INET: - if (curele->ai_family == AF_INET - && a1->addr.in4.sin_addr.s_addr - == ((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr - && a1->addr.in4.sin_port == ((struct sockaddr_in *)curele->ai_addr)->sin_port) - { - return true; - } - break; - - case AF_INET6: - if (curele->ai_family == AF_INET6 - && IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr, - &((struct sockaddr_in6 *)curele->ai_addr)->sin6_addr) - && a1->addr.in6.sin6_port - == ((struct sockaddr_in6 *)curele->ai_addr)->sin6_port) - { - return true; - } - break; - - default: - ASSERT(0); - } - } - return false; -} - - -static inline bool -addr_port_match(const struct openvpn_sockaddr *a1, const struct openvpn_sockaddr *a2) -{ - switch (a1->addr.sa.sa_family) - { - case AF_INET: - return a1->addr.in4.sin_addr.s_addr == a2->addr.in4.sin_addr.s_addr - && a1->addr.in4.sin_port == a2->addr.in4.sin_port; - - case AF_INET6: - return IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr, &a2->addr.in6.sin6_addr) - && a1->addr.in6.sin6_port == a2->addr.in6.sin6_port; - } - ASSERT(0); - return false; -} - -static inline bool -addr_match_proto(const struct openvpn_sockaddr *a1, const struct openvpn_sockaddr *a2, - const int proto) -{ - return link_socket_proto_connection_oriented(proto) ? addr_match(a1, a2) - : addr_port_match(a1, a2); -} - - -static inline bool -addrlist_match_proto(const struct openvpn_sockaddr *a1, struct addrinfo *addr_list, const int proto) -{ - return link_socket_proto_connection_oriented(proto) ? addrlist_match(a1, addr_list) - : addrlist_port_match(a1, addr_list); -} - -static inline void -addr_zero_host(struct openvpn_sockaddr *addr) -{ - switch (addr->addr.sa.sa_family) - { - case AF_INET: - addr->addr.in4.sin_addr.s_addr = 0; - break; - - case AF_INET6: - memset(&addr->addr.in6.sin6_addr, 0, sizeof(struct in6_addr)); - break; - } -} - -static inline int -af_addr_size(sa_family_t af) -{ - switch (af) - { - case AF_INET: - return sizeof(struct sockaddr_in); - - case AF_INET6: - return sizeof(struct sockaddr_in6); - - default: -#if 0 - /* could be called from socket_do_accept() with empty addr */ - msg(M_ERR, "Bad address family: %d\n", af); - ASSERT(0); -#endif - return 0; - } -} - -static inline bool -link_socket_actual_match(const struct link_socket_actual *a1, const struct link_socket_actual *a2) -{ - return addr_port_match(&a1->dest, &a2->dest); -} - #if PORT_SHARE static inline bool diff --git a/src/openvpn/socket_util.c b/src/openvpn/socket_util.c new file mode 100644 index 000000000..13f8c3223 --- /dev/null +++ b/src/openvpn/socket_util.c @@ -0,0 +1,936 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2025 OpenVPN Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see . + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "syshead.h" + +#include "socket_util.h" +#include "crypto.h" +#include "manage.h" + +/* + * Format IP addresses in ascii + */ + +const char * +print_sockaddr_ex(const struct sockaddr *sa, const char *separator, const unsigned int flags, + struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc(128, gc); + bool addr_is_defined = false; + char hostaddr[NI_MAXHOST] = ""; + char servname[NI_MAXSERV] = ""; + int status; + + socklen_t salen = 0; + switch (sa->sa_family) + { + case AF_INET: + if (!(flags & PS_DONT_SHOW_FAMILY)) + { + buf_puts(&out, "[AF_INET]"); + } + salen = sizeof(struct sockaddr_in); + addr_is_defined = ((struct sockaddr_in *)sa)->sin_addr.s_addr != 0; + break; + + case AF_INET6: + if (!(flags & PS_DONT_SHOW_FAMILY)) + { + buf_puts(&out, "[AF_INET6]"); + } + salen = sizeof(struct sockaddr_in6); + addr_is_defined = !IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)sa)->sin6_addr); + break; + + case AF_UNSPEC: + if (!(flags & PS_DONT_SHOW_FAMILY)) + { + return "[AF_UNSPEC]"; + } + else + { + return ""; + } + + default: + ASSERT(0); + } + + status = getnameinfo(sa, salen, hostaddr, sizeof(hostaddr), servname, sizeof(servname), + NI_NUMERICHOST | NI_NUMERICSERV); + + if (status != 0) + { + buf_printf(&out, "[nameinfo() err: %s]", gai_strerror(status)); + return BSTR(&out); + } + + if (!(flags & PS_DONT_SHOW_ADDR)) + { + if (addr_is_defined) + { + buf_puts(&out, hostaddr); + } + else + { + buf_puts(&out, "[undef]"); + } + } + + if ((flags & PS_SHOW_PORT) || (flags & PS_SHOW_PORT_IF_DEFINED)) + { + if (separator) + { + buf_puts(&out, separator); + } + + buf_puts(&out, servname); + } + + return BSTR(&out); +} + +const char * +print_link_socket_actual(const struct link_socket_actual *act, struct gc_arena *gc) +{ + return print_link_socket_actual_ex(act, ":", PS_SHOW_PORT | PS_SHOW_PKTINFO, gc); +} + +#ifndef IF_NAMESIZE +#define IF_NAMESIZE 16 +#endif + +const char * +print_link_socket_actual_ex(const struct link_socket_actual *act, const char *separator, + const unsigned int flags, struct gc_arena *gc) +{ + if (act) + { + struct buffer out = alloc_buf_gc(128, gc); + buf_printf(&out, "%s", print_sockaddr_ex(&act->dest.addr.sa, separator, flags, gc)); +#if ENABLE_IP_PKTINFO + char ifname[IF_NAMESIZE] = "[undef]"; + + if ((flags & PS_SHOW_PKTINFO) && addr_defined_ipi(act)) + { + switch (act->dest.addr.sa.sa_family) + { + case AF_INET: + { + struct openvpn_sockaddr sa; + CLEAR(sa); + sa.addr.in4.sin_family = AF_INET; +#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) + sa.addr.in4.sin_addr = act->pi.in4.ipi_spec_dst; + if_indextoname(act->pi.in4.ipi_ifindex, ifname); +#elif defined(IP_RECVDSTADDR) + sa.addr.in4.sin_addr = act->pi.in4; + ifname[0] = 0; +#else /* if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) */ +#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix syshead.h) +#endif + buf_printf(&out, " (via %s%%%s)", + print_sockaddr_ex(&sa.addr.sa, separator, 0, gc), ifname); + } + break; + + case AF_INET6: + { + struct sockaddr_in6 sin6; + char buf[INET6_ADDRSTRLEN] = "[undef]"; + CLEAR(sin6); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = act->pi.in6.ipi6_addr; + if_indextoname(act->pi.in6.ipi6_ifindex, ifname); + if (getnameinfo((struct sockaddr *)&sin6, sizeof(struct sockaddr_in6), buf, + sizeof(buf), NULL, 0, NI_NUMERICHOST) + == 0) + { + buf_printf(&out, " (via %s%%%s)", buf, ifname); + } + else + { + buf_printf(&out, " (via [getnameinfo() err]%%%s)", ifname); + } + } + break; + } + } +#endif /* if ENABLE_IP_PKTINFO */ + return BSTR(&out); + } + else + { + return "[NULL]"; + } +} + +/* + * Convert an in_addr_t in host byte order + * to an ascii dotted quad. + */ +const char * +print_in_addr_t(in_addr_t addr, unsigned int flags, struct gc_arena *gc) +{ + struct in_addr ia; + char *out = gc_malloc(INET_ADDRSTRLEN, true, gc); + + if (addr || !(flags & IA_EMPTY_IF_UNDEF)) + { + CLEAR(ia); + ia.s_addr = (flags & IA_NET_ORDER) ? addr : htonl(addr); + + inet_ntop(AF_INET, &ia, out, INET_ADDRSTRLEN); + } + return out; +} + +/* + * Convert an in6_addr in host byte order + * to an ascii representation of an IPv6 address + */ +const char * +print_in6_addr(struct in6_addr a6, unsigned int flags, struct gc_arena *gc) +{ + char *out = gc_malloc(INET6_ADDRSTRLEN, true, gc); + + if (memcmp(&a6, &in6addr_any, sizeof(a6)) != 0 || !(flags & IA_EMPTY_IF_UNDEF)) + { + inet_ntop(AF_INET6, &a6, out, INET6_ADDRSTRLEN); + } + return out; +} + +/* + * Convert an in_port_t in host byte order to a string + */ +const char * +print_in_port_t(in_port_t port, struct gc_arena *gc) +{ + struct buffer buffer = alloc_buf_gc(8, gc); + buf_printf(&buffer, "%hu", port); + return BSTR(&buffer); +} + +/* add some offset to an ipv6 address + * (add in steps of 8 bits, taking overflow into next round) + */ +struct in6_addr +add_in6_addr(struct in6_addr base, uint32_t add) +{ + int i; + + for (i = 15; i >= 0 && add > 0; i--) + { + register int carry; + register uint32_t h; + + h = (unsigned char)base.s6_addr[i]; + base.s6_addr[i] = (h + add) & UINT8_MAX; + + /* using explicit carry for the 8-bit additions will catch + * 8-bit and(!) 32-bit overruns nicely + */ + carry = ((h & 0xff) + (add & 0xff)) >> 8; + add = (add >> 8) + carry; + } + return base; +} + +/* set environmental variables for ip/port in *addr */ +void +setenv_sockaddr(struct env_set *es, const char *name_prefix, const struct openvpn_sockaddr *addr, + const unsigned int flags) +{ + char name_buf[256]; + + char buf[INET6_ADDRSTRLEN]; + switch (addr->addr.sa.sa_family) + { + case AF_INET: + if (flags & SA_IP_PORT) + { + snprintf(name_buf, sizeof(name_buf), "%s_ip", name_prefix); + } + else + { + snprintf(name_buf, sizeof(name_buf), "%s", name_prefix); + } + + inet_ntop(AF_INET, &addr->addr.in4.sin_addr, buf, sizeof(buf)); + setenv_str(es, name_buf, buf); + + if ((flags & SA_IP_PORT) && addr->addr.in4.sin_port) + { + snprintf(name_buf, sizeof(name_buf), "%s_port", name_prefix); + setenv_int(es, name_buf, ntohs(addr->addr.in4.sin_port)); + } + break; + + case AF_INET6: + if (IN6_IS_ADDR_V4MAPPED(&addr->addr.in6.sin6_addr)) + { + struct in_addr ia; + memcpy(&ia.s_addr, &addr->addr.in6.sin6_addr.s6_addr[12], sizeof(ia.s_addr)); + snprintf(name_buf, sizeof(name_buf), "%s_ip", name_prefix); + inet_ntop(AF_INET, &ia, buf, sizeof(buf)); + } + else + { + snprintf(name_buf, sizeof(name_buf), "%s_ip6", name_prefix); + inet_ntop(AF_INET6, &addr->addr.in6.sin6_addr, buf, sizeof(buf)); + } + setenv_str(es, name_buf, buf); + + if ((flags & SA_IP_PORT) && addr->addr.in6.sin6_port) + { + snprintf(name_buf, sizeof(name_buf), "%s_port", name_prefix); + setenv_int(es, name_buf, ntohs(addr->addr.in6.sin6_port)); + } + break; + } +} + +void +setenv_in_addr_t(struct env_set *es, const char *name_prefix, in_addr_t addr, + const unsigned int flags) +{ + if (addr || !(flags & SA_SET_IF_NONZERO)) + { + struct openvpn_sockaddr si; + CLEAR(si); + si.addr.in4.sin_family = AF_INET; + si.addr.in4.sin_addr.s_addr = htonl(addr); + setenv_sockaddr(es, name_prefix, &si, flags); + } +} + +void +setenv_in6_addr(struct env_set *es, const char *name_prefix, const struct in6_addr *addr, + const unsigned int flags) +{ + if (!IN6_IS_ADDR_UNSPECIFIED(addr) || !(flags & SA_SET_IF_NONZERO)) + { + struct openvpn_sockaddr si; + CLEAR(si); + si.addr.in6.sin6_family = AF_INET6; + si.addr.in6.sin6_addr = *addr; + setenv_sockaddr(es, name_prefix, &si, flags); + } +} + +void +setenv_link_socket_actual(struct env_set *es, const char *name_prefix, + const struct link_socket_actual *act, const unsigned int flags) +{ + setenv_sockaddr(es, name_prefix, &act->dest, flags); +} + +/* + * Convert protocol names between index and ascii form. + */ + +struct proto_names +{ + const char *short_form; + const char *display_form; + sa_family_t proto_af; + int proto; +}; + +/* Indexed by PROTO_x */ +static const struct proto_names proto_names[] = { + { "proto-uninitialized", "proto-NONE", AF_UNSPEC, PROTO_NONE }, + /* try IPv4 and IPv6 (client), bind dual-stack (server) */ + { "udp", "UDP", AF_UNSPEC, PROTO_UDP }, + { "tcp-server", "TCP_SERVER", AF_UNSPEC, PROTO_TCP_SERVER }, + { "tcp-client", "TCP_CLIENT", AF_UNSPEC, PROTO_TCP_CLIENT }, + { "tcp", "TCP", AF_UNSPEC, PROTO_TCP }, + /* force IPv4 */ + { "udp4", "UDPv4", AF_INET, PROTO_UDP }, + { "tcp4-server", "TCPv4_SERVER", AF_INET, PROTO_TCP_SERVER }, + { "tcp4-client", "TCPv4_CLIENT", AF_INET, PROTO_TCP_CLIENT }, + { "tcp4", "TCPv4", AF_INET, PROTO_TCP }, + /* force IPv6 */ + { "udp6", "UDPv6", AF_INET6, PROTO_UDP }, + { "tcp6-server", "TCPv6_SERVER", AF_INET6, PROTO_TCP_SERVER }, + { "tcp6-client", "TCPv6_CLIENT", AF_INET6, PROTO_TCP_CLIENT }, + { "tcp6", "TCPv6", AF_INET6, PROTO_TCP }, +}; + +int +ascii2proto(const char *proto_name) +{ + for (size_t i = 0; i < SIZE(proto_names); ++i) + { + if (!strcmp(proto_name, proto_names[i].short_form)) + { + return proto_names[i].proto; + } + } + return -1; +} + +sa_family_t +ascii2af(const char *proto_name) +{ + for (size_t i = 0; i < SIZE(proto_names); ++i) + { + if (!strcmp(proto_name, proto_names[i].short_form)) + { + return proto_names[i].proto_af; + } + } + return 0; +} + +const char * +proto2ascii(int proto, sa_family_t af, bool display_form) +{ + for (size_t i = 0; i < SIZE(proto_names); ++i) + { + if (proto_names[i].proto_af == af && proto_names[i].proto == proto) + { + if (display_form) + { + return proto_names[i].display_form; + } + else + { + return proto_names[i].short_form; + } + } + } + + return "[unknown protocol]"; +} + +const char * +proto2ascii_all(struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc(256, gc); + + for (size_t i = 0; i < SIZE(proto_names); ++i) + { + if (i) + { + buf_printf(&out, " "); + } + buf_printf(&out, "[%s]", proto_names[i].short_form); + } + return BSTR(&out); +} + +const char * +addr_family_name(int af) +{ + switch (af) + { + case AF_INET: + return "AF_INET"; + + case AF_INET6: + return "AF_INET6"; + } + return "AF_UNSPEC"; +} + +/* + * Given a local proto, return local proto + * if !remote, or compatible remote proto + * if remote. + * + * This is used for options compatibility + * checking. + * + * IPv6 and IPv4 protocols are comptabile but OpenVPN + * has always sent UDPv4, TCPv4 over the wire. Keep these + * strings for backward compatibility + */ +const char * +proto_remote(int proto, bool remote) +{ + ASSERT(proto >= 0 && proto < PROTO_N); + if (proto == PROTO_UDP) + { + return "UDPv4"; + } + + if ((remote && proto == PROTO_TCP_CLIENT) || (!remote && proto == PROTO_TCP_SERVER)) + { + return "TCPv4_SERVER"; + } + if ((remote && proto == PROTO_TCP_SERVER) || (!remote && proto == PROTO_TCP_CLIENT)) + { + return "TCPv4_CLIENT"; + } + + ASSERT(0); + return ""; /* Make the compiler happy */ +} + +/** + * Small helper function for openvpn_getaddrinfo to print the address + * family when resolving fails + */ +static const char * +getaddrinfo_addr_family_name(int af) +{ + switch (af) + { + case AF_INET: + return "[AF_INET]"; + + case AF_INET6: + return "[AF_INET6]"; + } + return ""; +} + +/* + * Prepend a random string to hostname to prevent DNS caching. + * For example, foo.bar.gov would be modified to .foo.bar.gov. + * Of course, this requires explicit support in the DNS server (wildcard). + */ +static const char * +hostname_randomize(const char *hostname, struct gc_arena *gc) +{ +#define n_rnd_bytes 6 + + uint8_t rnd_bytes[n_rnd_bytes]; + const char *rnd_str; + struct buffer hname = alloc_buf_gc(strlen(hostname) + sizeof(rnd_bytes) * 2 + 4, gc); + + prng_bytes(rnd_bytes, sizeof(rnd_bytes)); + rnd_str = format_hex_ex(rnd_bytes, sizeof(rnd_bytes), 40, 0, NULL, gc); + buf_printf(&hname, "%s.%s", rnd_str, hostname); + return BSTR(&hname); +#undef n_rnd_bytes +} + +/* + * Translate IPv4/IPv6 addr or hostname into struct addrinfo + * If resolve error, try again for resolve_retry_seconds seconds. + */ +int +openvpn_getaddrinfo(unsigned int flags, const char *hostname, const char *servname, + int resolve_retry_seconds, struct signal_info *sig_info, int ai_family, + struct addrinfo **res) +{ + struct addrinfo hints; + int status; + struct signal_info sigrec = { 0 }; + int msglevel = (flags & GETADDR_FATAL) ? M_FATAL : D_RESOLVE_ERRORS; + struct gc_arena gc = gc_new(); + const char *print_hostname; + const char *print_servname; + + ASSERT(res); + + ASSERT(hostname || servname); + ASSERT(!(flags & GETADDR_HOST_ORDER)); + + if (servname) + { + print_servname = servname; + } + else + { + print_servname = ""; + } + + if (flags & GETADDR_MSG_VIRT_OUT) + { + msglevel |= M_MSG_VIRT_OUT; + } + + if ((flags & (GETADDR_FATAL_ON_SIGNAL | GETADDR_WARN_ON_SIGNAL)) && !sig_info) + { + sig_info = &sigrec; + } + + /* try numeric ip addr first */ + CLEAR(hints); + hints.ai_flags = AI_NUMERICHOST; + + if (flags & GETADDR_PASSIVE) + { + hints.ai_flags |= AI_PASSIVE; + } + + if (flags & GETADDR_DATAGRAM) + { + hints.ai_socktype = SOCK_DGRAM; + } + else + { + hints.ai_socktype = SOCK_STREAM; + } + + /* if hostname is not set, we want to bind to 'ANY', with + * the correct address family - v4-only or v6/v6-dual-stack */ + if (!hostname) + { + hints.ai_family = ai_family; + } + + status = getaddrinfo(hostname, servname, &hints, res); + + if (status != 0) /* parse as numeric address failed? */ + { + const int fail_wait_interval = 5; /* seconds */ + /* Add +4 to cause integer division rounding up (1 + 4) = 5, (0+4)/5=0 */ + int resolve_retries = + (flags & GETADDR_TRY_ONCE) ? 1 : ((resolve_retry_seconds + 4) / fail_wait_interval); + const char *fmt; + int level = 0; + + /* this is not a numeric IP, therefore force resolution using the + * provided ai_family */ + hints.ai_family = ai_family; + + if (hostname && (flags & GETADDR_RANDOMIZE)) + { + hostname = hostname_randomize(hostname, &gc); + } + + if (hostname) + { + print_hostname = hostname; + } + else + { + print_hostname = "undefined"; + } + + fmt = "RESOLVE: Cannot resolve host address: %s:%s%s (%s)"; + if ((flags & GETADDR_MENTION_RESOLVE_RETRY) && !resolve_retry_seconds) + { + fmt = "RESOLVE: Cannot resolve host address: %s:%s%s (%s)" + "(I would have retried this name query if you had " + "specified the --resolv-retry option.)"; + } + + if (!(flags & GETADDR_RESOLVE) || status == EAI_FAIL) + { + msg(msglevel, "RESOLVE: Cannot parse IP address: %s:%s (%s)", print_hostname, + print_servname, gai_strerror(status)); + goto done; + } + +#ifdef ENABLE_MANAGEMENT + if (flags & GETADDR_UPDATE_MANAGEMENT_STATE) + { + if (management) + { + management_set_state(management, OPENVPN_STATE_RESOLVE, NULL, NULL, NULL, NULL, + NULL); + } + } +#endif + + /* + * Resolve hostname + */ + while (true) + { +#ifndef _WIN32 + /* force resolv.conf reload */ + res_init(); +#endif + /* try hostname lookup */ + hints.ai_flags &= ~AI_NUMERICHOST; + dmsg(D_SOCKET_DEBUG, "GETADDRINFO flags=0x%04x ai_family=%d ai_socktype=%d", flags, + hints.ai_family, hints.ai_socktype); + status = getaddrinfo(hostname, servname, &hints, res); + + if (sig_info) + { + get_signal(&sig_info->signal_received); + if (sig_info->signal_received) /* were we interrupted by a signal? */ + { + /* why are we overwriting SIGUSR1 ? */ + if (signal_reset(sig_info, SIGUSR1) == SIGUSR1) /* ignore SIGUSR1 */ + { + msg(level, "RESOLVE: Ignored SIGUSR1 signal received during " + "DNS resolution attempt"); + } + else + { + /* turn success into failure (interrupted syscall) */ + if (0 == status) + { + ASSERT(res); + freeaddrinfo(*res); + *res = NULL; + status = EAI_AGAIN; /* = temporary failure */ + errno = EINTR; + } + goto done; + } + } + } + + /* success? */ + if (0 == status) + { + break; + } + + /* resolve lookup failed, should we + * continue or fail? */ + level = msglevel; + if (resolve_retries > 0) + { + level = D_RESOLVE_ERRORS; + } + + msg(level, fmt, print_hostname, print_servname, getaddrinfo_addr_family_name(ai_family), + gai_strerror(status)); + + if (--resolve_retries <= 0) + { + goto done; + } + + management_sleep(fail_wait_interval); + } + + ASSERT(res); + + /* hostname resolve succeeded */ + + /* + * Do not choose an IP Addresse by random or change the order * + * of IP addresses, doing so will break RFC 3484 address selection * + */ + } + else + { + /* IP address parse succeeded */ + if (flags & GETADDR_RANDOMIZE) + { + msg(M_WARN, "WARNING: ignoring --remote-random-hostname because the " + "hostname is an IP address"); + } + } + +done: + if (sig_info && sig_info->signal_received) + { + int level = 0; + if (flags & GETADDR_FATAL_ON_SIGNAL) + { + level = M_FATAL; + } + else if (flags & GETADDR_WARN_ON_SIGNAL) + { + level = M_WARN; + } + msg(level, "RESOLVE: signal received during DNS resolution attempt"); + } + + gc_free(&gc); + return status; +} + +/* + * We do our own inet_aton because the glibc function + * isn't very good about error checking. + */ +int +openvpn_inet_aton(const char *dotted_quad, struct in_addr *addr) +{ + unsigned int a, b, c, d; + + CLEAR(*addr); + if (sscanf(dotted_quad, "%u.%u.%u.%u", &a, &b, &c, &d) == 4) + { + if (a < 256 && b < 256 && c < 256 && d < 256) + { + addr->s_addr = htonl(a << 24 | b << 16 | c << 8 | d); + return OIA_IP; /* good dotted quad */ + } + } + if (string_class(dotted_quad, CC_DIGIT | CC_DOT, 0)) + { + return OIA_ERROR; /* probably a badly formatted dotted quad */ + } + else + { + return OIA_HOSTNAME; /* probably a hostname */ + } +} + +bool +ip_addr_dotted_quad_safe(const char *dotted_quad) +{ + /* verify non-NULL */ + if (!dotted_quad) + { + return false; + } + + /* verify length is within limits */ + if (strlen(dotted_quad) > 15) + { + return false; + } + + /* verify that all chars are either numeric or '.' and that no numeric + * substring is greater than 3 chars */ + { + int nnum = 0; + const char *p = dotted_quad; + int c; + + while ((c = *p++)) + { + if (c >= '0' && c <= '9') + { + ++nnum; + if (nnum > 3) + { + return false; + } + } + else if (c == '.') + { + nnum = 0; + } + else + { + return false; + } + } + } + + /* verify that string will convert to IP address */ + { + struct in_addr a; + return openvpn_inet_aton(dotted_quad, &a) == OIA_IP; + } +} + +bool +ipv6_addr_safe(const char *ipv6_text_addr) +{ + /* verify non-NULL */ + if (!ipv6_text_addr) + { + return false; + } + + /* verify length is within limits */ + if (strlen(ipv6_text_addr) > INET6_ADDRSTRLEN) + { + return false; + } + + /* verify that string will convert to IPv6 address */ + { + struct in6_addr a6; + return inet_pton(AF_INET6, ipv6_text_addr, &a6) == 1; + } +} + +static bool +dns_addr_safe(const char *addr) +{ + if (addr) + { + const size_t len = strlen(addr); + return len > 0 && len <= 255 && string_class(addr, CC_ALNUM | CC_DASH | CC_DOT, 0); + } + else + { + return false; + } +} + +bool +ip_or_dns_addr_safe(const char *addr, const bool allow_fqdn) +{ + if (ip_addr_dotted_quad_safe(addr)) + { + return true; + } + else if (allow_fqdn) + { + return dns_addr_safe(addr); + } + else + { + return false; + } +} + +bool +mac_addr_safe(const char *mac_addr) +{ + /* verify non-NULL */ + if (!mac_addr) + { + return false; + } + + /* verify length is within limits */ + if (strlen(mac_addr) > 17) + { + return false; + } + + /* verify that all chars are either alphanumeric or ':' and that no + * alphanumeric substring is greater than 2 chars */ + { + int nnum = 0; + const char *p = mac_addr; + int c; + + while ((c = *p++)) + { + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) + { + ++nnum; + if (nnum > 2) + { + return false; + } + } + else if (c == ':') + { + nnum = 0; + } + else + { + return false; + } + } + } + + /* error-checking is left to script invoked in lladdr.c */ + return true; +} diff --git a/src/openvpn/socket_util.h b/src/openvpn/socket_util.h new file mode 100644 index 000000000..5ea37dd8a --- /dev/null +++ b/src/openvpn/socket_util.h @@ -0,0 +1,491 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2025 OpenVPN Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see . + */ + +#ifndef SOCKET_UTIL_H +#define SOCKET_UTIL_H + +#include "buffer.h" +#include "env_set.h" +#include "sig.h" + +#define PS_SHOW_PORT_IF_DEFINED (1 << 0) +#define PS_SHOW_PORT (1 << 1) +#define PS_SHOW_PKTINFO (1 << 2) +#define PS_DONT_SHOW_ADDR (1 << 3) +#define PS_DONT_SHOW_FAMILY (1 << 4) + +/* OpenVPN sockaddr struct */ +struct openvpn_sockaddr +{ + /*int dummy;*/ /* add offset to force a bug if sa not explicitly dereferenced */ + union + { + struct sockaddr sa; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + } addr; +}; + +/* actual address of remote, based on source address of received packets */ +struct link_socket_actual +{ + /*int dummy;*/ /* add offset to force a bug if dest not explicitly dereferenced */ + + struct openvpn_sockaddr dest; +#if ENABLE_IP_PKTINFO + union + { +#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) + struct in_pktinfo in4; +#elif defined(IP_RECVDSTADDR) + struct in_addr in4; +#endif + struct in6_pktinfo in6; + } pi; +#endif +}; + +const char *print_sockaddr_ex(const struct sockaddr *addr, const char *separator, + const unsigned int flags, struct gc_arena *gc); + +static inline const char * +print_openvpn_sockaddr(const struct openvpn_sockaddr *addr, struct gc_arena *gc) +{ + return print_sockaddr_ex(&addr->addr.sa, ":", PS_SHOW_PORT, gc); +} + +static inline const char * +print_sockaddr(const struct sockaddr *addr, struct gc_arena *gc) +{ + return print_sockaddr_ex(addr, ":", PS_SHOW_PORT, gc); +} + + +const char *print_link_socket_actual_ex(const struct link_socket_actual *act, const char *separator, + const unsigned int flags, struct gc_arena *gc); + +const char *print_link_socket_actual(const struct link_socket_actual *act, struct gc_arena *gc); + + +#define IA_EMPTY_IF_UNDEF (1 << 0) +#define IA_NET_ORDER (1 << 1) +const char *print_in_addr_t(in_addr_t addr, unsigned int flags, struct gc_arena *gc); + +const char *print_in6_addr(struct in6_addr addr6, unsigned int flags, struct gc_arena *gc); + +const char *print_in_port_t(in_port_t port, struct gc_arena *gc); + +struct in6_addr add_in6_addr(struct in6_addr base, uint32_t add); + +#define SA_IP_PORT (1 << 0) +#define SA_SET_IF_NONZERO (1 << 1) +void setenv_sockaddr(struct env_set *es, const char *name_prefix, + const struct openvpn_sockaddr *addr, const unsigned int flags); + +void setenv_in_addr_t(struct env_set *es, const char *name_prefix, in_addr_t addr, + const unsigned int flags); + +void setenv_in6_addr(struct env_set *es, const char *name_prefix, const struct in6_addr *addr, + const unsigned int flags); + +void setenv_link_socket_actual(struct env_set *es, const char *name_prefix, + const struct link_socket_actual *act, const unsigned int flags); + +/* + * DNS resolution + */ + +#define GETADDR_RESOLVE (1 << 0) +#define GETADDR_FATAL (1 << 1) +#define GETADDR_HOST_ORDER (1 << 2) +#define GETADDR_MENTION_RESOLVE_RETRY (1 << 3) +#define GETADDR_FATAL_ON_SIGNAL (1 << 4) +#define GETADDR_WARN_ON_SIGNAL (1 << 5) +#define GETADDR_MSG_VIRT_OUT (1 << 6) +#define GETADDR_TRY_ONCE (1 << 7) +#define GETADDR_UPDATE_MANAGEMENT_STATE (1 << 8) +#define GETADDR_RANDOMIZE (1 << 9) +#define GETADDR_PASSIVE (1 << 10) +#define GETADDR_DATAGRAM (1 << 11) + +#define GETADDR_CACHE_MASK (GETADDR_DATAGRAM | GETADDR_PASSIVE) + +/** + * Translate an IPv4 addr or hostname from string form to in_addr_t + * + * In case of resolve error, it will try again for + * resolve_retry_seconds seconds. + */ +in_addr_t getaddr(unsigned int flags, const char *hostname, int resolve_retry_seconds, + bool *succeeded, struct signal_info *sig_info); + +/** + * Translate an IPv6 addr or hostname from string form to in6_addr + */ +bool get_ipv6_addr(const char *hostname, struct in6_addr *network, unsigned int *netbits, + int msglevel); + +int openvpn_getaddrinfo(unsigned int flags, const char *hostname, const char *servname, + int resolve_retry_seconds, struct signal_info *sig_info, int ai_family, + struct addrinfo **res); + +/* return values of openvpn_inet_aton */ +#define OIA_HOSTNAME 0 +#define OIA_IP 1 +#define OIA_ERROR -1 +int openvpn_inet_aton(const char *dotted_quad, struct in_addr *addr); + +/* integrity validation on pulled options */ +bool ip_addr_dotted_quad_safe(const char *dotted_quad); + +bool ip_or_dns_addr_safe(const char *addr, const bool allow_fqdn); + +bool mac_addr_safe(const char *mac_addr); + +bool ipv6_addr_safe(const char *ipv6_text_addr); + +/* + * Transport protocol naming and other details. + */ + +/* + * Use enum's instead of #define to allow for easier + * optional proto support + */ +enum proto_num +{ + PROTO_NONE, /* catch for uninitialized */ + PROTO_UDP, + PROTO_TCP, + PROTO_TCP_SERVER, + PROTO_TCP_CLIENT, + PROTO_N +}; + +static inline bool +proto_is_net(int proto) +{ + ASSERT(proto >= 0 && proto < PROTO_N); + return proto != PROTO_NONE; +} + +/** + * @brief Returns if the protocol being used is UDP + */ +static inline bool +proto_is_udp(int proto) +{ + ASSERT(proto >= 0 && proto < PROTO_N); + return proto == PROTO_UDP; +} + +/** + * @brief Return if the protocol is datagram (UDP) + * + */ +static inline bool +proto_is_dgram(int proto) +{ + return proto_is_udp(proto); +} + +/** + * @brief returns if the proto is a TCP variant (tcp-server, tcp-client or tcp) + */ +static inline bool +proto_is_tcp(int proto) +{ + ASSERT(proto >= 0 && proto < PROTO_N); + return proto == PROTO_TCP_CLIENT || proto == PROTO_TCP_SERVER; +} + +int ascii2proto(const char *proto_name); + +sa_family_t ascii2af(const char *proto_name); + +const char *proto2ascii(int proto, sa_family_t af, bool display_form); + +const char *proto2ascii_all(struct gc_arena *gc); + +const char *proto_remote(int proto, bool remote); + +const char *addr_family_name(int af); + +static inline bool +addr_defined(const struct openvpn_sockaddr *addr) +{ + if (!addr) + { + return 0; + } + switch (addr->addr.sa.sa_family) + { + case AF_INET: + return addr->addr.in4.sin_addr.s_addr != 0; + + case AF_INET6: + return !IN6_IS_ADDR_UNSPECIFIED(&addr->addr.in6.sin6_addr); + + default: + return 0; + } +} + +static inline bool +addr_local(const struct sockaddr *addr) +{ + if (!addr) + { + return false; + } + switch (addr->sa_family) + { + case AF_INET: + return ((const struct sockaddr_in *)addr)->sin_addr.s_addr == htonl(INADDR_LOOPBACK); + + case AF_INET6: + return IN6_IS_ADDR_LOOPBACK(&((const struct sockaddr_in6 *)addr)->sin6_addr); + + default: + return false; + } +} + + +static inline bool +addr_defined_ipi(const struct link_socket_actual *lsa) +{ +#if ENABLE_IP_PKTINFO + if (!lsa) + { + return 0; + } + switch (lsa->dest.addr.sa.sa_family) + { +#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) + case AF_INET: + return lsa->pi.in4.ipi_spec_dst.s_addr != 0; + +#elif defined(IP_RECVDSTADDR) + case AF_INET: + return lsa->pi.in4.s_addr != 0; + +#endif + case AF_INET6: + return !IN6_IS_ADDR_UNSPECIFIED(&lsa->pi.in6.ipi6_addr); + + default: + return 0; + } +#else /* if ENABLE_IP_PKTINFO */ + ASSERT(0); +#endif + return false; +} + +/* + * Overhead added to packets by various protocols. + */ +static inline int +datagram_overhead(sa_family_t af, int proto) +{ + int overhead = 0; + overhead += (proto == PROTO_UDP) ? 8 : 20; + overhead += (af == AF_INET) ? 20 : 40; + return overhead; +} + +/* + * Misc inline functions + */ + +static inline bool +link_socket_proto_connection_oriented(int proto) +{ + return !proto_is_dgram(proto); +} + +static inline bool +link_socket_actual_defined(const struct link_socket_actual *act) +{ + return act && addr_defined(&act->dest); +} + +static inline bool +addr_match(const struct openvpn_sockaddr *a1, const struct openvpn_sockaddr *a2) +{ + switch (a1->addr.sa.sa_family) + { + case AF_INET: + return a1->addr.in4.sin_addr.s_addr == a2->addr.in4.sin_addr.s_addr; + + case AF_INET6: + return IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr, &a2->addr.in6.sin6_addr); + } + ASSERT(0); + return false; +} + +static inline bool +addrlist_match(const struct openvpn_sockaddr *a1, const struct addrinfo *addrlist) +{ + const struct addrinfo *curele; + for (curele = addrlist; curele; curele = curele->ai_next) + { + switch (a1->addr.sa.sa_family) + { + case AF_INET: + if (a1->addr.in4.sin_addr.s_addr + == ((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr) + { + return true; + } + break; + + case AF_INET6: + if (IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr, + &((struct sockaddr_in6 *)curele->ai_addr)->sin6_addr)) + { + return true; + } + break; + + default: + ASSERT(0); + } + } + return false; +} + +static inline bool +addrlist_port_match(const struct openvpn_sockaddr *a1, const struct addrinfo *a2) +{ + const struct addrinfo *curele; + for (curele = a2; curele; curele = curele->ai_next) + { + switch (a1->addr.sa.sa_family) + { + case AF_INET: + if (curele->ai_family == AF_INET + && a1->addr.in4.sin_addr.s_addr + == ((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr + && a1->addr.in4.sin_port == ((struct sockaddr_in *)curele->ai_addr)->sin_port) + { + return true; + } + break; + + case AF_INET6: + if (curele->ai_family == AF_INET6 + && IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr, + &((struct sockaddr_in6 *)curele->ai_addr)->sin6_addr) + && a1->addr.in6.sin6_port + == ((struct sockaddr_in6 *)curele->ai_addr)->sin6_port) + { + return true; + } + break; + + default: + ASSERT(0); + } + } + return false; +} + + +static inline bool +addr_port_match(const struct openvpn_sockaddr *a1, const struct openvpn_sockaddr *a2) +{ + switch (a1->addr.sa.sa_family) + { + case AF_INET: + return a1->addr.in4.sin_addr.s_addr == a2->addr.in4.sin_addr.s_addr + && a1->addr.in4.sin_port == a2->addr.in4.sin_port; + + case AF_INET6: + return IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr, &a2->addr.in6.sin6_addr) + && a1->addr.in6.sin6_port == a2->addr.in6.sin6_port; + } + ASSERT(0); + return false; +} + +static inline bool +addr_match_proto(const struct openvpn_sockaddr *a1, const struct openvpn_sockaddr *a2, + const int proto) +{ + return link_socket_proto_connection_oriented(proto) ? addr_match(a1, a2) + : addr_port_match(a1, a2); +} + + +static inline bool +addrlist_match_proto(const struct openvpn_sockaddr *a1, struct addrinfo *addr_list, const int proto) +{ + return link_socket_proto_connection_oriented(proto) ? addrlist_match(a1, addr_list) + : addrlist_port_match(a1, addr_list); +} + +static inline void +addr_zero_host(struct openvpn_sockaddr *addr) +{ + switch (addr->addr.sa.sa_family) + { + case AF_INET: + addr->addr.in4.sin_addr.s_addr = 0; + break; + + case AF_INET6: + memset(&addr->addr.in6.sin6_addr, 0, sizeof(struct in6_addr)); + break; + } +} + +static inline int +af_addr_size(sa_family_t af) +{ + switch (af) + { + case AF_INET: + return sizeof(struct sockaddr_in); + + case AF_INET6: + return sizeof(struct sockaddr_in6); + + default: +#if 0 + /* could be called from socket_do_accept() with empty addr */ + msg(M_ERR, "Bad address family: %d\n", af); + ASSERT(0); +#endif + return 0; + } +} + +static inline bool +link_socket_actual_match(const struct link_socket_actual *a1, const struct link_socket_actual *a2) +{ + return addr_port_match(&a1->dest, &a2->dest); +} + +#endif diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index e8fde86ab..428bf5a94 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -30,7 +30,7 @@ #define SSL_COMMON_H_ #include "session_id.h" -#include "socket.h" +#include "socket_util.h" #include "packet_id.h" #include "crypto.h" #include "options.h" diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c index f54b608d7..6c3096b58 100644 --- a/src/openvpn/tun.c +++ b/src/openvpn/tun.c @@ -39,7 +39,7 @@ #include "fdmisc.h" #include "common.h" #include "run_command.h" -#include "socket.h" +#include "socket_util.h" #include "manage.h" #include "route.h" #include "win32.h" -- 2.47.3 From 10d64c184404c532fe0861d5c57fb1613a608d4c Mon Sep 17 00:00:00 2001 From: Frank Lichtenheld Date: Sun, 31 Aug 2025 17:11:26 +0200 Subject: [PATCH 02/16] Add new unit test module test_socket With a first UT that tests add_in6_addr() (and print_in6_addr implicitly). Change-Id: If546f64a4554b292623bfcfe9ee53bac17dfa803 Signed-off-by: Frank Lichtenheld Acked-by: Gert Doering Message-Id: <20250831151133.25684-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32723.html Signed-off-by: Gert Doering --- CMakeLists.txt | 17 ++- tests/unit_tests/openvpn/Makefile.am | 31 +++-- tests/unit_tests/openvpn/mock_management.c | 14 +++ tests/unit_tests/openvpn/test_socket.c | 126 +++++++++++++++++++++ 4 files changed, 176 insertions(+), 12 deletions(-) create mode 100644 tests/unit_tests/openvpn/test_socket.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 45044af00..35513e9c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -628,9 +628,11 @@ if (NOT WIN32) target_compile_options(openvpn PRIVATE -DPLUGIN_LIBDIR=\"${PLUGIN_DIR}\") find_library(resolv resolv) - # some platform like BSDs already include resolver functionality in the libc and not have an extra resolv library + # some platform like BSDs already include resolver functionality in the libc + # and do not have an extra resolv library if (${resolv} OR APPLE) - target_link_libraries(openvpn PUBLIC -lresolv) + set(RESOLV_LIBRARIES resolv) + target_link_libraries(openvpn PUBLIC ${RESOLV_LIBRARIES}) endif () endif () @@ -653,6 +655,7 @@ if (BUILD_TESTING) "test_packet_id" "test_pkt" "test_provider" + "test_socket" "test_ssl" "test_user_pass" "test_push_update_msg" @@ -849,6 +852,16 @@ if (BUILD_TESTING) src/openvpn/base64.c ) + target_link_libraries(test_socket PUBLIC ${RESOLV_LIBRARIES}) + target_sources(test_socket PRIVATE + tests/unit_tests/openvpn/mock_get_random.c + tests/unit_tests/openvpn/mock_management.c + tests/unit_tests/openvpn/mock_win32_execve.c + src/openvpn/env_set.c + src/openvpn/run_command.c + src/openvpn/socket_util.c + ) + target_sources(test_user_pass PRIVATE tests/unit_tests/openvpn/mock_get_random.c tests/unit_tests/openvpn/mock_win32_execve.c diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index b24e03c10..7a7fec7fd 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -4,23 +4,17 @@ EXTRA_DIST = input AM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING) Unit-Tests' -test_binaries= +test_binaries = crypto_testdriver packet_id_testdriver auth_token_testdriver \ + ncp_testdriver misc_testdriver pkt_testdriver ssl_testdriver \ + user_pass_testdriver push_update_msg_testdriver provider_testdriver socket_testdriver if HAVE_LD_WRAP_SUPPORT test_binaries += argv_testdriver buffer_testdriver -endif - -test_binaries += crypto_testdriver packet_id_testdriver auth_token_testdriver ncp_testdriver misc_testdriver \ - pkt_testdriver ssl_testdriver user_pass_testdriver push_update_msg_testdriver - -if HAVE_LD_WRAP_SUPPORT if !WIN32 test_binaries += tls_crypt_testdriver endif endif -test_binaries += provider_testdriver - if WIN32 test_binaries += cryptoapi_testdriver LDADD = -lws2_32 @@ -343,4 +337,21 @@ push_update_msg_testdriver_SOURCES = test_push_update_msg.c \ $(top_srcdir)/src/openvpn/platform.c \ $(top_srcdir)/src/openvpn/push_util.c \ $(top_srcdir)/src/openvpn/options_util.c \ - $(top_srcdir)/src/openvpn/otime.c \ No newline at end of file + $(top_srcdir)/src/openvpn/otime.c + +socket_testdriver_CFLAGS = \ + -I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \ + -DSOURCEDIR=\"$(top_srcdir)\" @TEST_CFLAGS@ + +socket_testdriver_LDFLAGS = @TEST_LDFLAGS@ $(SOCKETS_LIBS) + +socket_testdriver_SOURCES = test_socket.c \ + mock_msg.c test_common.h \ + mock_get_random.c \ + mock_management.c \ + $(top_srcdir)/src/openvpn/buffer.c \ + $(top_srcdir)/src/openvpn/win32-util.c \ + $(top_srcdir)/src/openvpn/platform.c \ + $(top_srcdir)/src/openvpn/env_set.c \ + $(top_srcdir)/src/openvpn/run_command.c \ + $(top_srcdir)/src/openvpn/socket_util.c diff --git a/tests/unit_tests/openvpn/mock_management.c b/tests/unit_tests/openvpn/mock_management.c index b24e4c4f1..28e541c96 100644 --- a/tests/unit_tests/openvpn/mock_management.c +++ b/tests/unit_tests/openvpn/mock_management.c @@ -46,4 +46,18 @@ management_query_pk_sig(struct management *man, const char *b64_data, const char { return NULL; } + +void +management_set_state(struct management *man, const int state, const char *detail, + const in_addr_t *tun_local_ip, const struct in6_addr *tun_local_ip6, + const struct openvpn_sockaddr *local_addr, + const struct openvpn_sockaddr *remote_addr) +{ +} + #endif + +void +management_sleep(const int n) +{ +} diff --git a/tests/unit_tests/openvpn/test_socket.c b/tests/unit_tests/openvpn/test_socket.c new file mode 100644 index 000000000..2da2529c2 --- /dev/null +++ b/tests/unit_tests/openvpn/test_socket.c @@ -0,0 +1,126 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2021-2025 Arne Schwabe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see . + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "syshead.h" + +#include +#include +#include +#include +#include +#include + +#include "socket.h" +#include "win32.h" + +/* stubs for some unused functions instead of pulling in too many dependencies */ +struct signal_info siginfo_static; /* GLOBAL */ + +int +signal_reset(struct signal_info *si, int signum) +{ + assert_true(0); + return 0; +} + +#ifdef _WIN32 +struct win32_signal win32_signal; /* GLOBAL */ + +int +win32_signal_get(struct win32_signal *ws) +{ + assert_true(0); + return 0; +} +#endif + +int +parse_line(const char *line, char **p, const int n, const char *file, const int line_num, + int msglevel, struct gc_arena *gc) +{ + assert_true(0); + return 0; +} + +static void +test_add_in6_addr_tc(const char *orig_str, uint32_t add, const char *expect_str) +{ + struct in6_addr orig, result, expected; + struct gc_arena gc = gc_new(); + assert_int_equal(inet_pton(AF_INET6, orig_str, &orig), 1); + assert_int_equal(inet_pton(AF_INET6, expect_str, &expected), 1); + result = add_in6_addr(orig, add); + const char *result_str = print_in6_addr(result, 0, &gc); + assert_string_equal(result_str, expect_str); + assert_memory_equal(&result, &expected, sizeof(struct in6_addr)); + gc_free(&gc); +} + +static bool +check_mapped_ipv4_address(void) +{ + struct gc_arena gc = gc_new(); + const char *ipv4_output = "::255.255.255.255"; + struct in6_addr addr; + assert_int_equal(inet_pton(AF_INET6, ipv4_output, &addr), 1); + const char *test_output = print_in6_addr(addr, 0, &gc); + bool ret = strcmp(test_output, ipv4_output) == 0; + gc_free(&gc); + return ret; +} + +static void +test_add_in6_addr(void **state) +{ + /* Note that some of the result strings need to account for + print_in6_addr formatting the addresses potentially as IPv4 */ + bool mapped_ipv4 = check_mapped_ipv4_address(); + test_add_in6_addr_tc("::", 1, "::1"); + test_add_in6_addr_tc("::ff", 1, "::100"); + test_add_in6_addr_tc("::ffff", 1, mapped_ipv4 ? "::0.1.0.0" : "::1:0"); + test_add_in6_addr_tc("ffff::ffff", 1, "ffff::1:0"); + test_add_in6_addr_tc("::ffff:ffff", 1, "::1:0:0"); + test_add_in6_addr_tc("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 1, "::"); + test_add_in6_addr_tc("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 2, "::1"); + + test_add_in6_addr_tc("::", UINT32_MAX, mapped_ipv4 ? "::255.255.255.255" : "::ffff:ffff"); + test_add_in6_addr_tc("::1", UINT32_MAX, "::1:0:0"); + test_add_in6_addr_tc("::ffff", UINT32_MAX, "::1:0:fffe"); + test_add_in6_addr_tc("ffff::ffff", UINT32_MAX, "ffff::1:0:fffe"); + test_add_in6_addr_tc("::ffff:ffff", UINT32_MAX, "::1:ffff:fffe"); + test_add_in6_addr_tc("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", UINT32_MAX, + mapped_ipv4 ? "::255.255.255.254" : "::ffff:fffe"); +} + +const struct CMUnitTest socket_tests[] = { + cmocka_unit_test(test_add_in6_addr) +}; + +int +main(void) +{ + return cmocka_run_group_tests(socket_tests, NULL, NULL); +} -- 2.47.3 From 84ef4848e7c7325759c67a1bfee231e13efe9f89 Mon Sep 17 00:00:00 2001 From: Frank Lichtenheld Date: Sun, 31 Aug 2025 17:12:52 +0200 Subject: [PATCH 03/16] socket_util: Clean up conversion warnings in add_in6_addr Change-Id: Id3b8719ee6b457ce2d85156b39e0cea771a97e74 Signed-off-by: Frank Lichtenheld Acked-by: Gert Doering Message-Id: <20250831151259.25788-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32725.html Signed-off-by: Gert Doering --- src/openvpn/socket_util.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/openvpn/socket_util.c b/src/openvpn/socket_util.c index 13f8c3223..9b7312d19 100644 --- a/src/openvpn/socket_util.c +++ b/src/openvpn/socket_util.c @@ -241,14 +241,12 @@ print_in_port_t(in_port_t port, struct gc_arena *gc) struct in6_addr add_in6_addr(struct in6_addr base, uint32_t add) { - int i; - - for (i = 15; i >= 0 && add > 0; i--) + for (int i = 15; i >= 0 && add > 0; i--) { - register int carry; + register uint32_t carry; register uint32_t h; - h = (unsigned char)base.s6_addr[i]; + h = base.s6_addr[i]; base.s6_addr[i] = (h + add) & UINT8_MAX; /* using explicit carry for the 8-bit additions will catch -- 2.47.3 From cd58786548554263d18531f570d5350ec667fa0c Mon Sep 17 00:00:00 2001 From: Frank Lichtenheld Date: Tue, 26 Aug 2025 20:48:57 +0200 Subject: [PATCH 04/16] manage: Make sure various management flags are treated as unsigned The variables that hold them are already unsigned, make sure the flags are as well to avoid spurious conversion warnings. Change-Id: I0937165c5efa95136bd951345a076e33e396f26a Signed-off-by: Frank Lichtenheld Acked-by: Arne Schwabe Message-Id: <20250826184904.22057-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32687.html Signed-off-by: Gert Doering --- src/openvpn/init.c | 2 +- src/openvpn/manage.h | 74 +++++++++++++++++++++---------------------- src/openvpn/options.h | 6 ++-- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 9dd3b966b..39ea8e4a4 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -370,7 +370,7 @@ management_callback_remote_cmd(void *arg, const char **p) && ((ce->flags >> CE_MAN_QUERY_REMOTE_SHIFT) & CE_MAN_QUERY_REMOTE_MASK) == CE_MAN_QUERY_REMOTE_QUERY) { - int flags = 0; + unsigned int flags = 0; if (!strcmp(p[1], "ACCEPT")) { flags = CE_MAN_QUERY_REMOTE_ACCEPT; diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index b8892c856..e9e1f95ef 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -24,24 +24,24 @@ #define MANAGE_H /* management_open flags */ -#define MF_SERVER (1 << 0) -#define MF_QUERY_PASSWORDS (1 << 1) -#define MF_HOLD (1 << 2) -#define MF_SIGNAL (1 << 3) -#define MF_FORGET_DISCONNECT (1 << 4) -#define MF_CONNECT_AS_CLIENT (1 << 5) -#define MF_CLIENT_AUTH (1 << 6) -/* #define MF_CLIENT_PF (1<<7) *REMOVED FEATURE* */ -#define MF_UNIX_SOCK (1 << 8) -#define MF_EXTERNAL_KEY (1 << 9) -#define MF_EXTERNAL_KEY_NOPADDING (1 << 10) -#define MF_EXTERNAL_KEY_PKCS1PAD (1 << 11) -#define MF_UP_DOWN (1 << 12) -#define MF_QUERY_REMOTE (1 << 13) -#define MF_QUERY_PROXY (1 << 14) -#define MF_EXTERNAL_CERT (1 << 15) -#define MF_EXTERNAL_KEY_PSSPAD (1 << 16) -#define MF_EXTERNAL_KEY_DIGEST (1 << 17) +#define MF_SERVER (1u << 0) +#define MF_QUERY_PASSWORDS (1u << 1) +#define MF_HOLD (1u << 2) +#define MF_SIGNAL (1u << 3) +#define MF_FORGET_DISCONNECT (1u << 4) +#define MF_CONNECT_AS_CLIENT (1u << 5) +#define MF_CLIENT_AUTH (1u << 6) +/* #define MF_CLIENT_PF (1u << 7) *REMOVED FEATURE* */ +#define MF_UNIX_SOCK (1u << 8) +#define MF_EXTERNAL_KEY (1u << 9) +#define MF_EXTERNAL_KEY_NOPADDING (1u << 10) +#define MF_EXTERNAL_KEY_PKCS1PAD (1u << 11) +#define MF_UP_DOWN (1u << 12) +#define MF_QUERY_REMOTE (1u << 13) +#define MF_QUERY_PROXY (1u << 14) +#define MF_EXTERNAL_CERT (1u << 15) +#define MF_EXTERNAL_KEY_PSSPAD (1u << 16) +#define MF_EXTERNAL_KEY_DIGEST (1u << 17) #ifdef ENABLE_MANAGEMENT @@ -64,9 +64,9 @@ struct man_def_auth_context { unsigned long cid; -#define DAF_CONNECTION_ESTABLISHED (1 << 0) -#define DAF_CONNECTION_CLOSED (1 << 1) -#define DAF_INITIAL_AUTH (1 << 2) +#define DAF_CONNECTION_ESTABLISHED (1u << 0) +#define DAF_CONNECTION_CLOSED (1u << 1) +#define DAF_INITIAL_AUTH (1u << 2) unsigned int flags; unsigned int mda_key_id_counter; @@ -117,23 +117,23 @@ struct log_entry union log_entry_union u; }; -#define LOG_PRINT_LOG_PREFIX (1 << 0) -#define LOG_PRINT_ECHO_PREFIX (1 << 1) -#define LOG_PRINT_STATE_PREFIX (1 << 2) +#define LOG_PRINT_LOG_PREFIX (1u << 0) +#define LOG_PRINT_ECHO_PREFIX (1u << 1) +#define LOG_PRINT_STATE_PREFIX (1u << 2) -#define LOG_PRINT_INT_DATE (1 << 3) -#define LOG_PRINT_MSG_FLAGS (1 << 4) -#define LOG_PRINT_STATE (1 << 5) -#define LOG_PRINT_LOCAL_IP (1 << 6) +#define LOG_PRINT_INT_DATE (1u << 3) +#define LOG_PRINT_MSG_FLAGS (1u << 4) +#define LOG_PRINT_STATE (1u << 5) +#define LOG_PRINT_LOCAL_IP (1u << 6) -#define LOG_PRINT_CRLF (1 << 7) -#define LOG_FATAL_NOTIFY (1 << 8) +#define LOG_PRINT_CRLF (1u << 7) +#define LOG_FATAL_NOTIFY (1u << 8) -#define LOG_PRINT_INTVAL (1 << 9) +#define LOG_PRINT_INTVAL (1u << 9) -#define LOG_PRINT_REMOTE_IP (1 << 10) +#define LOG_PRINT_REMOTE_IP (1u << 10) -#define LOG_ECHO_TO_LOG (1 << 11) +#define LOG_ECHO_TO_LOG (1u << 11) const char *log_entry_print(const struct log_entry *e, unsigned int flags, struct gc_arena *gc); @@ -175,7 +175,7 @@ struct management_callback { void *arg; -#define MCF_SERVER (1 << 0) /* is OpenVPN being run as a server? */ +#define MCF_SERVER (1u << 0) /* is OpenVPN being run as a server? */ unsigned int flags; void (*status)(void *arg, const int version, struct status_output *so); @@ -253,9 +253,9 @@ struct man_settings int client_gid; /* flags for handling the management interface "signal" command */ -#define MANSIG_IGNORE_USR1_HUP (1 << 0) -#define MANSIG_MAP_USR1_TO_HUP (1 << 1) -#define MANSIG_MAP_USR1_TO_TERM (1 << 2) +#define MANSIG_IGNORE_USR1_HUP (1u << 0) +#define MANSIG_MAP_USR1_TO_HUP (1u << 1) +#define MANSIG_MAP_USR1_TO_TERM (1u << 2) unsigned int mansig; }; diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 44f3fc938..bb2c052e0 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -147,14 +147,14 @@ struct connection_entry int explicit_exit_notification; /* Explicitly tell peer when we are exiting via OCC_EXIT or [RESTART] message */ -#define CE_DISABLED (1 << 0) -#define CE_MAN_QUERY_PROXY (1 << 1) +#define CE_DISABLED (1u << 0) +#define CE_MAN_QUERY_PROXY (1u << 1) #define CE_MAN_QUERY_REMOTE_UNDEF 0 #define CE_MAN_QUERY_REMOTE_QUERY 1 #define CE_MAN_QUERY_REMOTE_ACCEPT 2 #define CE_MAN_QUERY_REMOTE_MOD 3 #define CE_MAN_QUERY_REMOTE_SKIP 4 -#define CE_MAN_QUERY_REMOTE_MASK (0x07) +#define CE_MAN_QUERY_REMOTE_MASK (0x07u) #define CE_MAN_QUERY_REMOTE_SHIFT (2) unsigned int flags; -- 2.47.3 From 10476e240fcbaa1240ee04e2a6ae52a9cc0cea14 Mon Sep 17 00:00:00 2001 From: Frank Lichtenheld Date: Mon, 1 Sep 2025 21:45:25 +0200 Subject: [PATCH 05/16] forward: Make sure pip flags are treated as unsigned process_ip_header already expects them to be unsigned, make sure the flags are to avoid spurious conversion warnings. Change-Id: I6d42c67b8dc5512933bed482bd9c2be80c63e993 Signed-off-by: Frank Lichtenheld Acked-by: Gert Doering Message-Id: <20250901194531.13683-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32740.html Signed-off-by: Gert Doering --- src/openvpn/forward.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/openvpn/forward.h b/src/openvpn/forward.h index d5641491d..79d0f8b5f 100644 --- a/src/openvpn/forward.h +++ b/src/openvpn/forward.h @@ -314,13 +314,13 @@ bool send_control_channel_string_dowork(struct tls_session *session, const char */ void reschedule_multi_process(struct context *c); -#define PIPV4_PASSTOS (1 << 0) -#define PIP_MSSFIX (1 << 1) /* v4 and v6 */ -#define PIP_OUTGOING (1 << 2) -#define PIPV4_EXTRACT_DHCP_ROUTER (1 << 3) -#define PIPV4_CLIENT_NAT (1 << 4) -#define PIPV6_ICMP_NOHOST_CLIENT (1 << 5) -#define PIPV6_ICMP_NOHOST_SERVER (1 << 6) +#define PIPV4_PASSTOS (1u << 0) +#define PIP_MSSFIX (1u << 1) /* v4 and v6 */ +#define PIP_OUTGOING (1u << 2) +#define PIPV4_EXTRACT_DHCP_ROUTER (1u << 3) +#define PIPV4_CLIENT_NAT (1u << 4) +#define PIPV6_ICMP_NOHOST_CLIENT (1u << 5) +#define PIPV6_ICMP_NOHOST_SERVER (1u << 6) void process_ip_header(struct context *c, unsigned int flags, struct buffer *buf, -- 2.47.3 From 6d450085c6a66d0c9e59eafddb83759166fb48c7 Mon Sep 17 00:00:00 2001 From: Lev Stipakov Date: Tue, 2 Sep 2025 14:25:36 +0200 Subject: [PATCH 06/16] dco-win: add support for multipeer stats Use the new driver API to fetch per-peer link and VPN byte counters in both client and server modes. Two usage modes are supported: - Single peer: pass the peer ID and a fixed-size output buffer. If the IOCTL is not supported (old driver), fall back to the legacy API. - All peers: first call the IOCTL with a small output buffer to get the required size, then allocate a buffer and call again to fetch stats for all peers. Change-Id: I525d7300e49f9a5a18e7146ee35ccc2af8184b8a Signed-off-by: Lev Stipakov Acked-by: Gert Doering Message-Id: <20250902122542.31023-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32744.html Signed-off-by: Gert Doering --- src/openvpn/dco_win.c | 171 ++++++++++++++++++++++++++++++++++++- src/openvpn/dco_win.h | 2 + src/openvpn/ovpn_dco_win.h | 14 +++ 3 files changed, 184 insertions(+), 3 deletions(-) diff --git a/src/openvpn/dco_win.c b/src/openvpn/dco_win.c index 5317ac10b..01ba017e1 100644 --- a/src/openvpn/dco_win.c +++ b/src/openvpn/dco_win.c @@ -30,6 +30,7 @@ #include "forward.h" #include "tun.h" #include "crypto.h" +#include "multi.h" #include "ssl_common.h" #include "openvpn.h" @@ -190,6 +191,8 @@ ovpn_dco_init(struct context *c) { dco_context_t *dco = &c->c1.tuntap->dco; + dco->c = c; + switch (c->mode) { case MODE_POINT_TO_POINT: @@ -714,12 +717,132 @@ dco_do_read(dco_context_t *dco) int dco_get_peer_stats_multi(dco_context_t *dco, const bool raise_sigusr1_on_err) { - /* Not implemented. */ - return 0; + struct gc_arena gc = gc_new(); + + int ret = 0; + struct tuntap *tt = dco->tt; + + if (!tuntap_defined(tt)) + { + ret = -1; + goto done; + } + + OVPN_GET_PEER_STATS ps = { + .PeerId = -1 + }; + + DWORD required_size = 0, bytes_returned = 0; + /* first, figure out buffer size */ + if (!DeviceIoControl(tt->hand, OVPN_IOCTL_GET_PEER_STATS, &ps, sizeof(ps), &required_size, sizeof(DWORD), &bytes_returned, NULL)) + { + if (GetLastError() == ERROR_MORE_DATA) + { + if (bytes_returned != sizeof(DWORD)) + { + msg(M_WARN, "%s: invalid bytes returned for size query (%lu, expected %zu)", __func__, bytes_returned, sizeof(DWORD)); + ret = -1; + goto done; + } + /* required_size now contains the size written by the driver */ + if (required_size == 0) + { + ret = 0; /* no peers to process */ + goto done; + } + if (required_size < sizeof(OVPN_PEER_STATS)) + { + msg(M_WARN, "%s: invalid required size %lu (minimum %zu)", __func__, required_size, sizeof(OVPN_PEER_STATS)); + ret = -1; + goto done; + } + } + else + { + msg(M_WARN | M_ERRNO, "%s: failed to fetch required buffer size", __func__); + ret = -1; + goto done; + } + } + else + { + /* unexpected success? */ + if (bytes_returned == 0) + { + ret = 0; /* no peers to process */ + goto done; + } + + msg(M_WARN, "%s: first DeviceIoControl call succeeded unexpectedly (%lu bytes returned)", __func__, bytes_returned); + ret = -1; + goto done; + } + + + /* allocate the buffer and fetch stats */ + OVPN_PEER_STATS *peer_stats = gc_malloc(required_size, true, &gc); + if (!peer_stats) + { + msg(M_WARN, "%s: failed to allocate buffer of size %lu", __func__, required_size); + ret = -1; + goto done; + } + + if (!DeviceIoControl(tt->hand, OVPN_IOCTL_GET_PEER_STATS, &ps, sizeof(ps), peer_stats, required_size, &bytes_returned, NULL)) + { + /* unlikely case when a peer has been added since fetching buffer size, not an error! */ + if (GetLastError() == ERROR_MORE_DATA) + { + msg(M_WARN, "%s: peer has been added, skip fetching stats", __func__); + ret = 0; + goto done; + } + + msg(M_WARN | M_ERRNO, "%s: failed to fetch multipeer stats", __func__); + ret = -1; + goto done; + } + + /* iterate over stats and update peers */ + for (int i = 0; i < bytes_returned / sizeof(OVPN_PEER_STATS); ++i) + { + OVPN_PEER_STATS *stat = &peer_stats[i]; + + if (stat->PeerId >= dco->c->multi->max_clients) + { + msg(M_WARN, "%s: received out of bound peer_id %u (max=%u)", __func__, stat->PeerId, + dco->c->multi->max_clients); + continue; + } + + struct multi_instance *mi = dco->c->multi->instances[stat->PeerId]; + if (!mi) + { + msg(M_WARN, "%s: received data for a non-existing peer %u", __func__, stat->PeerId); + continue; + } + + /* update peer stats */ + struct context_2 *c2 = &mi->context.c2; + c2->dco_read_bytes = stat->LinkRxBytes; + c2->dco_write_bytes = stat->LinkTxBytes; + c2->tun_read_bytes = stat->VpnRxBytes; + c2->tun_write_bytes = stat->VpnTxBytes; + } + +done: + gc_free(&gc); + + if (raise_sigusr1_on_err && ret < 0) + { + register_signal(dco->c->sig, SIGUSR1, "dco peer stats error"); + } + + return ret; } int -dco_get_peer_stats(struct context *c, const bool raise_sigusr1_on_err) +dco_get_peer_stats_fallback(struct context *c, const bool raise_sigusr1_on_err) { struct tuntap *tt = c->c1.tuntap; @@ -747,6 +870,48 @@ dco_get_peer_stats(struct context *c, const bool raise_sigusr1_on_err) return 0; } +int +dco_get_peer_stats(struct context *c, const bool raise_sigusr1_on_err) +{ + struct tuntap *tt = c->c1.tuntap; + + if (!tuntap_defined(tt)) + { + return -1; + } + + /* first, try a new ioctl */ + OVPN_GET_PEER_STATS ps = { .PeerId = c->c2.tls_multi->dco_peer_id }; + + OVPN_PEER_STATS peer_stats = { 0 }; + DWORD bytes_returned = 0; + if (!DeviceIoControl(tt->hand, OVPN_IOCTL_GET_PEER_STATS, &ps, sizeof(ps), &peer_stats, sizeof(peer_stats), + &bytes_returned, NULL)) + { + if (GetLastError() == ERROR_INVALID_FUNCTION) + { + /* are we using the old driver? */ + return dco_get_peer_stats_fallback(c, raise_sigusr1_on_err); + } + + msg(M_WARN | M_ERRNO, "%s: DeviceIoControl(OVPN_IOCTL_GET_PEER_STATS) failed", __func__); + return -1; + } + + if (bytes_returned != sizeof(OVPN_PEER_STATS)) + { + msg(M_WARN | M_ERRNO, "%s: DeviceIoControl(OVPN_IOCTL_GET_PEER_STATS) returned invalid size", __func__); + return -1; + } + + c->c2.dco_read_bytes = peer_stats.LinkRxBytes; + c->c2.dco_write_bytes = peer_stats.LinkTxBytes; + c->c2.tun_read_bytes = peer_stats.VpnRxBytes; + c->c2.tun_write_bytes = peer_stats.VpnTxBytes; + + return 0; +} + void dco_event_set(dco_context_t *dco, struct event_set *es, void *arg) { diff --git a/src/openvpn/dco_win.h b/src/openvpn/dco_win.h index a7f4865a5..4f3f0288c 100644 --- a/src/openvpn/dco_win.h +++ b/src/openvpn/dco_win.h @@ -57,6 +57,8 @@ struct dco_context uint64_t dco_read_bytes; uint64_t dco_write_bytes; + + struct context *c; }; typedef struct dco_context dco_context_t; diff --git a/src/openvpn/ovpn_dco_win.h b/src/openvpn/ovpn_dco_win.h index baf721492..9e1378a81 100644 --- a/src/openvpn/ovpn_dco_win.h +++ b/src/openvpn/ovpn_dco_win.h @@ -83,6 +83,14 @@ typedef struct _OVPN_STATS { LONG64 TunBytesReceived; } OVPN_STATS, * POVPN_STATS; +typedef struct _OVPN_PEER_STATS { + int PeerId; + LONG64 LinkRxBytes; + LONG64 LinkTxBytes; + LONG64 VpnRxBytes; + LONG64 VpnTxBytes; +} OVPN_PEER_STATS, * POVPN_PEER_STATS; + typedef enum _OVPN_KEY_SLOT { OVPN_KEY_SLOT_PRIMARY, OVPN_KEY_SLOT_SECONDARY @@ -185,6 +193,10 @@ typedef struct _OVPN_MP_IROUTE { int IPv6; } OVPN_MP_IROUTE, * POVPN_MP_IROUTE; +typedef struct _OVPN_GET_PEER_STATS { + int PeerId; // -1 for all peers stats +} OVPN_GET_PEER_STATS, * POVPN_GET_PEER_STATS; + #define OVPN_IOCTL_NEW_PEER CTL_CODE(FILE_DEVICE_UNKNOWN, 1, METHOD_BUFFERED, FILE_ANY_ACCESS) #define OVPN_IOCTL_GET_STATS CTL_CODE(FILE_DEVICE_UNKNOWN, 2, METHOD_BUFFERED, FILE_ANY_ACCESS) #define OVPN_IOCTL_NEW_KEY CTL_CODE(FILE_DEVICE_UNKNOWN, 3, METHOD_BUFFERED, FILE_ANY_ACCESS) @@ -207,3 +219,5 @@ typedef struct _OVPN_MP_IROUTE { #define OVPN_IOCTL_MP_ADD_IROUTE CTL_CODE(FILE_DEVICE_UNKNOWN, 17, METHOD_BUFFERED, FILE_ANY_ACCESS) #define OVPN_IOCTL_MP_DEL_IROUTE CTL_CODE(FILE_DEVICE_UNKNOWN, 18, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define OVPN_IOCTL_GET_PEER_STATS CTL_CODE(FILE_DEVICE_UNKNOWN, 19, METHOD_BUFFERED, FILE_ANY_ACCESS) -- 2.47.3 From da309c1e8bed7b9c25e800499400c8ad908276db Mon Sep 17 00:00:00 2001 From: Lev Stipakov Date: Tue, 2 Sep 2025 12:36:01 +0200 Subject: [PATCH 07/16] Refactor management bytecount tracking There are few issues with it: - when using DCO, the server part doesn't output BYTECOUNT_CLI since process_incoming_link_part1/process_outgoing_link are not called - when using DCO, the server part applies bytecount timer to the each connection, unneccessary making too many calls to the kernel and also uses incorrect BYTECOUNT output. - client part outputs counters using timer, server part utilizes traffic activity -> inconsistency Following changes have been made: - Use timer to output counters in client and server mode. Code which deals with bytecount on traffic activity has been removed. This unifies DCO and non-DCO, as well as client and server mode - In server mode, peers stats are fetched with the single ioctl call - Per-packet stats are not persisted anymore in the client mode during traffic activity. Instead cumulative stats (including DCO stats) are persisted when the session closes. GitHub: closes OpenVPN/openvpn#820 Change-Id: I43a93f0d84f01fd808a64115e1b8c3b806706491 Signed-off-by: Lev Stipakov Acked-by: Gert Doering Message-Id: <20250902103606.22181-1-gert@greenie.muc.de> URL: https://sourceforge.net/p/openvpn/mailman/message/59228150/ Signed-off-by: Gert Doering --- src/openvpn/forward.c | 18 +--------- src/openvpn/manage.c | 81 ++++++++++++++++++++++++++++++++----------- src/openvpn/manage.h | 31 ++--------------- src/openvpn/multi.c | 7 ++++ 4 files changed, 70 insertions(+), 67 deletions(-) diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c index 75ca9d5c5..03b6a0c6b 100644 --- a/src/openvpn/forward.c +++ b/src/openvpn/forward.c @@ -818,7 +818,7 @@ process_coarse_timers(struct context *c) #ifdef ENABLE_MANAGEMENT if (management) { - management_check_bytecount(c, management, &c->c2.timeval); + management_check_bytecount_client(c, management, &c->c2.timeval); } #endif /* ENABLE_MANAGEMENT */ } @@ -998,14 +998,6 @@ process_incoming_link_part1(struct context *c, struct link_socket_info *lsi, boo } #endif c->c2.original_recv_size = c->c2.buf.len; -#ifdef ENABLE_MANAGEMENT - if (management) - { - management_bytes_client(management, c->c2.buf.len, 0); - management_bytes_server(management, &c->c2.link_read_bytes, &c->c2.link_write_bytes, - &c->c2.mda_context); - } -#endif } else { @@ -1822,14 +1814,6 @@ process_outgoing_link(struct context *c, struct link_socket *sock) { mmap_stats->link_write_bytes = link_write_bytes_global; } -#endif -#ifdef ENABLE_MANAGEMENT - if (management) - { - management_bytes_client(management, 0, size); - management_bytes_server(management, &c->c2.link_read_bytes, - &c->c2.link_write_bytes, &c->c2.mda_context); - } #endif } } diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c index aed04f54b..52b0e5e25 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -41,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "multi.h" #include "memdbg.h" @@ -517,29 +518,27 @@ man_bytecount(struct management *man, const int update_seconds) } static void -man_bytecount_output_client(struct management *man, counter_type dco_read_bytes, - counter_type dco_write_bytes) +man_bytecount_output_client(counter_type bytes_in_total, counter_type bytes_out_total) { char in[32]; char out[32]; /* do in a roundabout way to work around possible mingw or mingw-glibc bug */ - snprintf(in, sizeof(in), counter_format, man->persist.bytes_in + dco_read_bytes); - snprintf(out, sizeof(out), counter_format, man->persist.bytes_out + dco_write_bytes); + snprintf(in, sizeof(in), counter_format, bytes_in_total); + snprintf(out, sizeof(out), counter_format, bytes_out_total); msg(M_CLIENT, ">BYTECOUNT:%s,%s", in, out); } -void -man_bytecount_output_server(const counter_type *bytes_in_total, const counter_type *bytes_out_total, +static void +man_bytecount_output_server(const counter_type bytes_in_total, const counter_type bytes_out_total, struct man_def_auth_context *mdac) { char in[32]; char out[32]; /* do in a roundabout way to work around possible mingw or mingw-glibc bug */ - snprintf(in, sizeof(in), counter_format, *bytes_in_total); - snprintf(out, sizeof(out), counter_format, *bytes_out_total); + snprintf(in, sizeof(in), counter_format, bytes_in_total); + snprintf(out, sizeof(out), counter_format, bytes_out_total); msg(M_CLIENT, ">BYTECOUNT_CLI:%lu,%s,%s", mdac->cid, in, out); - mdac->bytecount_last_update = now; } static void @@ -4065,42 +4064,82 @@ management_sleep(const int n) } void -management_check_bytecount(struct context *c, struct management *man, struct timeval *timeval) +management_check_bytecount_client(struct context *c, struct management *man, struct timeval *timeval) { - if (event_timeout_trigger(&man->connection.bytecount_update_interval, timeval, ETT_DEFAULT)) + if (man->persist.callback.flags & MCF_SERVER) { - counter_type dco_read_bytes = 0; - counter_type dco_write_bytes = 0; + return; + } + if (event_timeout_trigger(&man->connection.bytecount_update_interval, timeval, ETT_DEFAULT)) + { if (dco_enabled(&c->options)) { if (dco_get_peer_stats(c, true) < 0) { return; } + } + + man_bytecount_output_client(c->c2.dco_read_bytes + man->persist.bytes_in + c->c2.link_read_bytes, + c->c2.dco_write_bytes + man->persist.bytes_out + c->c2.link_write_bytes); + } +} + +void +management_check_bytecount_server(struct multi_context *multi) +{ + if (!(management->persist.callback.flags & MCF_SERVER)) + { + return; + } - dco_read_bytes = c->c2.dco_read_bytes; - dco_write_bytes = c->c2.dco_write_bytes; + struct timeval null; + CLEAR(null); + if (event_timeout_trigger(&management->connection.bytecount_update_interval, &null, ETT_DEFAULT)) + { + /* fetch counters from dco */ + if (dco_enabled(&multi->top.options)) + { + if (dco_get_peer_stats_multi(&multi->top.c1.tuntap->dco, true) < 0) + { + return; + } } - if (!(man->persist.callback.flags & MCF_SERVER)) + /* iterate over peers and report counters for each connected peer */ + struct hash_iterator hi; + struct hash_element *he; + hash_iterator_init(multi->hash, &hi); + while ((he = hash_iterator_next(&hi))) { - man_bytecount_output_client(man, dco_read_bytes, dco_write_bytes); + struct multi_instance *mi = (struct multi_instance *)he->value; + struct context_2 *c2 = &mi->context.c2; + + if ((c2->mda_context.flags & (DAF_CONNECTION_ESTABLISHED | DAF_CONNECTION_CLOSED)) == DAF_CONNECTION_ESTABLISHED) + { + man_bytecount_output_server(c2->dco_read_bytes + c2->link_read_bytes, c2->dco_write_bytes + c2->link_write_bytes, &c2->mda_context); + } } + hash_iterator_free(&hi); } } -/* DCO resets stats on reconnect. Since client expects stats - * to be preserved across reconnects, we need to save DCO +/* context_2 stats are reset on reconnect. Since client expects stats + * to be preserved across reconnects, we need to save context_2 * stats before tearing the tunnel down. */ void man_persist_client_stats(struct management *man, struct context *c) { - /* no need to raise SIGUSR1 since we are already closing the instance */ + man->persist.bytes_in += c->c2.link_read_bytes; + man->persist.bytes_out += c->c2.link_write_bytes; + + /* no need to raise SIGUSR1 on error since we are already closing the instance */ if (dco_enabled(&c->options) && (dco_get_peer_stats(c, false) == 0)) { - management_bytes_client(man, c->c2.dco_read_bytes, c->c2.dco_write_bytes); + man->persist.bytes_in += c->c2.dco_read_bytes; + man->persist.bytes_out += c->c2.dco_write_bytes; } } diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index e9e1f95ef..0b064cfe5 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -70,8 +70,6 @@ struct man_def_auth_context unsigned int flags; unsigned int mda_key_id_counter; - - time_t bytecount_last_update; }; /* @@ -492,34 +490,9 @@ void management_auth_token(struct management *man, const char *token); * These functions drive the bytecount in/out counters. */ -void management_check_bytecount(struct context *c, struct management *man, struct timeval *timeval); - -static inline void -management_bytes_client(struct management *man, const int size_in, const int size_out) -{ - if (!(man->persist.callback.flags & MCF_SERVER)) - { - man->persist.bytes_in += size_in; - man->persist.bytes_out += size_out; - } -} +void management_check_bytecount_client(struct context *c, struct management *man, struct timeval *timeval); -void man_bytecount_output_server(const counter_type *bytes_in_total, - const counter_type *bytes_out_total, - struct man_def_auth_context *mdac); - -static inline void -management_bytes_server(struct management *man, const counter_type *bytes_in_total, - const counter_type *bytes_out_total, struct man_def_auth_context *mdac) -{ - if (man->connection.bytecount_update_seconds > 0 - && now >= mdac->bytecount_last_update + man->connection.bytecount_update_seconds - && (mdac->flags & (DAF_CONNECTION_ESTABLISHED | DAF_CONNECTION_CLOSED)) - == DAF_CONNECTION_ESTABLISHED) - { - man_bytecount_output_server(bytes_in_total, bytes_out_total, mdac); - } -} +void management_check_bytecount_server(struct multi_context *multi); void man_persist_client_stats(struct management *man, struct context *c); diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index e1ce32ab0..f1abdbe85 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -3812,6 +3812,13 @@ multi_process_per_second_timers_dowork(struct multi_context *m) { check_stale_routes(m); } + +#ifdef ENABLE_MANAGEMENT + if (management) + { + management_check_bytecount_server(m); + } +#endif /* ENABLE_MANAGEMENT */ } static void -- 2.47.3 From 3bc0b2d0aea742640a1acf97fc4b41726b88ce96 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Tue, 2 Sep 2025 18:00:44 +0200 Subject: [PATCH 08/16] management: resync timer on bytecount interval change coarse_timer_wakeup tracks when the next timer-driven task will occur. If a user issues `bytecount n` via the management interface, but the next scheduled wakeup is more than n seconds away, bandwidth logging will be delayed until that timer fires. To ensure timely logging, reset the timer whenever a new `bytecount` command is received. This guarantees that logging begins exactly n seconds after the command, matching the user-defined interval. Change-Id: Ic0035d52e0ea123398318870d2f4d21af927a602 Signed-off-by: Ralf Lici Acked-by: Gert Doering Message-Id: <20250902160050.18640-1-gert@greenie.muc.de> URL: https://sourceforge.net/p/openvpn/mailman/message/59228306/ Signed-off-by: Gert Doering --- src/openvpn/manage.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c index 52b0e5e25..422aa0bc9 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -514,6 +514,28 @@ man_bytecount(struct management *man, const int update_seconds) man->connection.bytecount_update_seconds = 0; event_timeout_clear(&man->connection.bytecount_update_interval); } + + /* The newly received bytecount interval may be sooner than the existing + * coarse timer wakeup. Reset the timer to ensure it fires at the correct, + * earlier time. + */ + if (man->persist.callback.arg) + { + struct context *c; + + if (man->settings.flags & MF_SERVER) + { + struct multi_context *m = man->persist.callback.arg; + c = &m->top; + } + else + { + c = man->persist.callback.arg; + } + + reset_coarse_timers(c); + } + msg(M_CLIENT, "SUCCESS: bytecount interval changed"); } -- 2.47.3 From 9e9ba09adbe08e93d91d8340efe63bae978f5f34 Mon Sep 17 00:00:00 2001 From: Gianmarco De Gregori Date: Tue, 2 Sep 2025 13:59:49 +0200 Subject: [PATCH 09/16] dco: avoid printing mi prefix on debug messages On messages printed for async DCO events, the currently-set mi prefix does not (always) belong to the peer that the async messages refer to, creating confusion. To avoid this, the M_NOIPREFIX flag is now used along with msglevel. Change-Id: I84a73d625c79d6a6a19122e48c91960dbe01ec49 Signed-off-by: Gianmarco De Gregori Acked-by: Gert Doering Message-Id: <20250902115954.29021-1-gert@greenie.muc.de> URL: https://sourceforge.net/p/openvpn/mailman/message/59228149/ Signed-off-by: Gert Doering --- src/openvpn/dco_linux.c | 20 ++++++++++---------- src/openvpn/sig.c | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/openvpn/dco_linux.c b/src/openvpn/dco_linux.c index a3907feed..6115d5175 100644 --- a/src/openvpn/dco_linux.c +++ b/src/openvpn/dco_linux.c @@ -548,7 +548,7 @@ nla_put_failure: int dco_del_peer(dco_context_t *dco, unsigned int peerid) { - msg(D_DCO_DEBUG, "%s: peer-id %d", __func__, peerid); + msg(D_DCO_DEBUG | M_NOIPREFIX, "%s: peer-id %d", __func__, peerid); struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_PEER_DEL); if (!nl_msg) @@ -868,7 +868,7 @@ ovpn_handle_peer(dco_context_t *dco, struct nlattr *attrs[]) uint32_t peer_id = nla_get_u32(tb_peer[OVPN_A_PEER_ID]); struct context_2 *c2; - msg(D_DCO_DEBUG, "%s: parsing message for peer %u...", __func__, peer_id); + msg(D_DCO_DEBUG | M_NOIPREFIX, "%s: parsing message for peer %u...", __func__, peer_id); if (dco->ifmode == OVPN_MODE_P2P) { @@ -890,7 +890,7 @@ ovpn_handle_peer(dco_context_t *dco, struct nlattr *attrs[]) struct multi_instance *mi = dco->c->multi->instances[peer_id]; if (!mi) { - msg(M_WARN, "%s: received data for a non-existing peer %u", __func__, peer_id); + msg(M_WARN | M_NOIPREFIX, "%s: received data for a non-existing peer %u", __func__, peer_id); return NL_SKIP; } @@ -934,32 +934,32 @@ ovpn_handle_peer_del_ntf(dco_context_t *dco, struct nlattr *attrs[]) if (!attrs[OVPN_A_PEER]) { - msg(D_DCO, "ovpn-dco: no peer in PEER_DEL_NTF message"); + msg(D_DCO | M_NOIPREFIX, "ovpn-dco: no peer in PEER_DEL_NTF message"); return NL_STOP; } struct nlattr *dp_attrs[OVPN_A_PEER_MAX + 1]; if (nla_parse_nested(dp_attrs, OVPN_A_PEER_MAX, attrs[OVPN_A_PEER], NULL)) { - msg(D_DCO, "ovpn-dco: can't parse peer in PEER_DEL_NTF messsage"); + msg(D_DCO | M_NOIPREFIX, "ovpn-dco: can't parse peer in PEER_DEL_NTF messsage"); return NL_STOP; } if (!dp_attrs[OVPN_A_PEER_DEL_REASON]) { - msg(D_DCO, "ovpn-dco: no reason in PEER_DEL_NTF message"); + msg(D_DCO | M_NOIPREFIX, "ovpn-dco: no reason in PEER_DEL_NTF message"); return NL_STOP; } if (!dp_attrs[OVPN_A_PEER_ID]) { - msg(D_DCO, "ovpn-dco: no peer-id in PEER_DEL_NTF message"); + msg(D_DCO | M_NOIPREFIX, "ovpn-dco: no peer-id in PEER_DEL_NTF message"); return NL_STOP; } int reason = nla_get_u32(dp_attrs[OVPN_A_PEER_DEL_REASON]); unsigned int peerid = nla_get_u32(dp_attrs[OVPN_A_PEER_ID]); - msg(D_DCO_DEBUG, "ovpn-dco: received CMD_PEER_DEL_NTF, ifindex: %d, peer-id %u, reason: %d", + msg(D_DCO_DEBUG | M_NOIPREFIX, "ovpn-dco: received CMD_PEER_DEL_NTF, ifindex: %d, peer-id %u, reason: %d", dco->ifindex, peerid, reason); dco->dco_message_peer_id = peerid; dco->dco_del_peer_reason = reason; @@ -1065,7 +1065,7 @@ ovpn_handle_msg(struct nl_msg *msg, void *arg) struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *gnlh = genlmsg_hdr(nlh); - msg(D_DCO_DEBUG, "ovpn-dco: received netlink message type=%u cmd=%u flags=%#.4x", + msg(D_DCO_DEBUG | M_NOIPREFIX, "ovpn-dco: received netlink message type=%u cmd=%u flags=%#.4x", nlh->nlmsg_type, gnlh->cmd, nlh->nlmsg_flags); /* if we get a message from the NLCTRL family, it means @@ -1148,7 +1148,7 @@ dco_get_peer(dco_context_t *dco, int peer_id, const bool raise_sigusr1_on_err) return 0; } - msg(D_DCO_DEBUG, "%s: peer-id %d", __func__, peer_id); + msg(D_DCO_DEBUG | M_NOIPREFIX, "%s: peer-id %d", __func__, peer_id); struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_PEER_GET); struct nlattr *attr = nla_nest_start(nl_msg, OVPN_A_PEER); diff --git a/src/openvpn/sig.c b/src/openvpn/sig.c index 5f5a80884..ecea6353e 100644 --- a/src/openvpn/sig.c +++ b/src/openvpn/sig.c @@ -239,7 +239,7 @@ register_signal(struct signal_info *si, int signum, const char *signal_text) { si->source = SIG_SOURCE_CONNECTION_FAILED; } - msg(D_SIGNAL_DEBUG, "register signal: %s (%s)", signal_name(signum, true), signal_text); + msg(D_SIGNAL_DEBUG | M_NOIPREFIX, "register signal: %s (%s)", signal_name(signum, true), signal_text); } else { -- 2.47.3 From 14ced61eef89797e606c3b7600adc4da83a336f4 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Tue, 2 Sep 2025 18:45:15 +0200 Subject: [PATCH 10/16] dco_linux: validate tun interface before fetching stats If dco_get_peer_stats() is called with an uninitialized c->c1.tuntap it results in a segfault. This issue happens when a client who has not connected to any server: - has --management and exits, - has --management and a management interface client issues either `bytecount` or `status` or - if SIGUSR2 is sent to it. Add a check to ensure the tun interface was set up before attempting to retrieve peer statistics. Change-Id: I40c11864745cc1619cb9cbf490b168f90feb5eac Signed-off-by: Ralf Lici Acked-by: Gert Doering Message-Id: <20250902164521.23145-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32768.html Signed-off-by: Gert Doering --- src/openvpn/dco_linux.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/openvpn/dco_linux.c b/src/openvpn/dco_linux.c index 6115d5175..40674e7f4 100644 --- a/src/openvpn/dco_linux.c +++ b/src/openvpn/dco_linux.c @@ -1139,6 +1139,8 @@ dco_do_read(dco_context_t *dco) static int dco_get_peer(dco_context_t *dco, int peer_id, const bool raise_sigusr1_on_err) { + ASSERT(dco); + /* peer_id == -1 means "dump all peers", but this is allowed in MP mode only. * If it happens in P2P mode it means that the DCO peer was deleted and we * can simply bail out @@ -1182,6 +1184,11 @@ nla_put_failure: int dco_get_peer_stats(struct context *c, const bool raise_sigusr1_on_err) { + if (!c->c1.tuntap || c->c1.tuntap->dco.ifindex == 0) + { + return -1; + } + return dco_get_peer(&c->c1.tuntap->dco, c->c2.tls_multi->dco_peer_id, raise_sigusr1_on_err); } -- 2.47.3 From fccdb21733d2826bcdc080fdfa93b0283b5231b8 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Tue, 2 Sep 2025 18:35:09 +0200 Subject: [PATCH 11/16] management: stop bytecount on client disconnection When a management interface client requests periodic bytecount notifications, openvpn continues to emit them even after the client has disconnected. Additionally, upon reconnecting, the client starts receiving these notifications without having issued a new bytecount command. Stop the periodic bytecount operation when the management interface client disconnects, preventing unnecessary stats polling when using DCO and ensuring that clients only receive notifications they have explicitly requested. Change-Id: I1474d232278433d097baf85352dfc9a79853bad1 Signed-off-by: Ralf Lici Acked-by: Gert Doering Message-Id: <20250902163514.22339-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32765.html Signed-off-by: Gert Doering --- src/openvpn/manage.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c index 422aa0bc9..5b2a7deb8 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -500,6 +500,13 @@ man_status(struct management *man, const int version, struct status_output *so) } } +static void +man_bytecount_stop(struct management *man) +{ + man->connection.bytecount_update_seconds = 0; + event_timeout_clear(&man->connection.bytecount_update_interval); +} + static void man_bytecount(struct management *man, const int update_seconds) { @@ -511,8 +518,7 @@ man_bytecount(struct management *man, const int update_seconds) } else { - man->connection.bytecount_update_seconds = 0; - event_timeout_clear(&man->connection.bytecount_update_interval); + man_bytecount_stop(man); } /* The newly received bytecount interval may be sooner than the existing @@ -1992,6 +1998,7 @@ man_reset_client_socket(struct management *man, const bool exiting) { if (socket_defined(man->connection.sd_cli)) { + man_bytecount_stop(man); #ifdef _WIN32 man_stop_ne32(man); #endif -- 2.47.3 From a01c909c3be5c06c9be06540f5c3aa04b449e9c9 Mon Sep 17 00:00:00 2001 From: Frank Lichtenheld Date: Tue, 2 Sep 2025 16:46:50 +0200 Subject: [PATCH 12/16] options: Introduce atoi_constrained and review usages of atoi_warn This is a more powerful version of atoi_warn that can - check minimum and maximum values - report error seperately from parsed value This can be used to simplify a lot of option parsing. Change-Id: Ibc7526d59c1de17a0f9d8ed88f75c6f070ab11e7 Signed-off-by: Frank Lichtenheld Acked-by: Arne Schwabe Message-Id: <20250902144657.11854-1-gert@greenie.muc.de> URL: https://sourceforge.net/p/openvpn/mailman/message/59228172/ Signed-off-by: Gert Doering --- src/openvpn/options.c | 148 ++++++++------------------- src/openvpn/options_util.c | 31 ++++++ src/openvpn/options_util.h | 14 ++- tests/unit_tests/openvpn/test_misc.c | 51 +++++++++ 4 files changed, 137 insertions(+), 107 deletions(-) diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 74946a486..a268b928c 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -6411,16 +6411,12 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, } else if (streq(p[0], "management-log-cache") && p[1] && !p[2]) { - int cache; - VERIFY_PERMISSION(OPT_P_GENERAL); - cache = atoi_warn(p[1], msglevel); - if (cache < 1) + if (!atoi_constrained(p[1], &options->management_log_history_cache, + p[0], 1, INT_MAX, msglevel)) { - msg(msglevel, "--management-log-cache parameter is out of range"); goto err; } - options->management_log_history_cache = cache; } #endif /* ifdef ENABLE_MANAGEMENT */ #ifdef ENABLE_PLUGIN @@ -6969,16 +6965,11 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, } else if (streq(p[0], "status-version") && p[1] && !p[2]) { - int version; - VERIFY_PERMISSION(OPT_P_GENERAL); - version = atoi_warn(p[1], msglevel); - if (version < 1 || version > 3) + if (!atoi_constrained(p[1], &options->status_file_version, p[0], 1, 3, msglevel)) { - msg(msglevel, "--status-version must be 1 to 3"); goto err; } - options->status_file_version = version; } else if (streq(p[0], "remap-usr1") && p[1] && !p[2]) { @@ -7151,16 +7142,11 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, } else if (streq(p[0], "shaper") && p[1] && !p[2]) { - int shaper; - VERIFY_PERMISSION(OPT_P_SHAPER); - shaper = atoi_warn(p[1], msglevel); - if (shaper < SHAPER_MIN || shaper > SHAPER_MAX) + if (!atoi_constrained(p[1], &options->shaper, p[0], SHAPER_MIN, SHAPER_MAX, msglevel)) { - msg(msglevel, "Bad shaper value, must be between %d and %d", SHAPER_MIN, SHAPER_MAX); goto err; } - options->shaper = shaper; } else if (streq(p[0], "port") && p[1] && !p[2]) { @@ -7739,7 +7725,11 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, else if (streq(p[0], "script-security") && p[1] && !p[2]) { VERIFY_PERMISSION(OPT_P_GENERAL); - script_security_set(atoi_warn(p[1], msglevel)); + int security; + if (atoi_constrained(p[1], &security, p[0], SSEC_NONE, SSEC_PW_ENV, msglevel)) + { + script_security_set(security); + } } else if (streq(p[0], "mssfix") && !p[3]) { @@ -7959,11 +7949,9 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, int real, virtual; VERIFY_PERMISSION(OPT_P_GENERAL); - real = atoi_warn(p[1], msglevel); - virtual = atoi_warn(p[2], msglevel); - if (real < 1 || virtual < 1) + if (!atoi_constrained(p[1], &real, "hash-size real", 1, INT_MAX, msglevel) + || !atoi_constrained(p[2], &virtual, "hash-size virtual", 1, INT_MAX, msglevel)) { - msg(msglevel, "--hash-size sizes must be >= 1 (preferably a power of 2)"); goto err; } options->real_hash_size = real; @@ -7974,11 +7962,9 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, int cf_max, cf_per; VERIFY_PERMISSION(OPT_P_GENERAL); - cf_max = atoi_warn(p[1], msglevel); - cf_per = atoi_warn(p[2], msglevel); - if (cf_max < 0 || cf_per < 0) + if (!atoi_constrained(p[1], &cf_max, "connect-freq n", 1, INT_MAX, msglevel) + || !atoi_constrained(p[2], &cf_per, "connect-freq seconds", 1, INT_MAX, msglevel)) { - msg(msglevel, "--connect-freq parms must be > 0"); goto err; } options->cf_max = cf_max; @@ -7986,15 +7972,12 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, } else if (streq(p[0], "connect-freq-initial") && p[1] && p[2] && !p[3]) { - long cf_max, cf_per; + int cf_max, cf_per; VERIFY_PERMISSION(OPT_P_GENERAL); - char *e1, *e2; - cf_max = strtol(p[1], &e1, 10); - cf_per = strtol(p[2], &e2, 10); - if (cf_max < 0 || cf_per < 0 || *e1 != '\0' || *e2 != '\0') + if (!atoi_constrained(p[1], &cf_max, "connect-freq-initial n", 1, INT_MAX, msglevel) + || !atoi_constrained(p[2], &cf_per, "connect-freq-initial seconds", 1, INT_MAX, msglevel)) { - msg(msglevel, "--connect-freq-initial parameters must be integers and >= 0"); goto err; } options->cf_initial_max = cf_max; @@ -8002,21 +7985,11 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, } else if (streq(p[0], "max-clients") && p[1] && !p[2]) { - int max_clients; - VERIFY_PERMISSION(OPT_P_GENERAL); - max_clients = atoi_warn(p[1], msglevel); - if (max_clients < 0) + if (!atoi_constrained(p[1], &options->max_clients, p[0], 1, MAX_PEER_ID, msglevel)) { - msg(msglevel, "--max-clients must be at least 1"); goto err; } - if (max_clients >= MAX_PEER_ID) /* max peer-id value */ - { - msg(msglevel, "--max-clients must be less than %d", MAX_PEER_ID); - goto err; - } - options->max_clients = max_clients; } else if (streq(p[0], "max-routes-per-client") && p[1] && !p[2]) { @@ -8188,27 +8161,13 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, } else if (streq(p[0], "bcast-buffers") && p[1] && !p[2]) { - int n_bcast_buf; - VERIFY_PERMISSION(OPT_P_GENERAL); - n_bcast_buf = atoi_warn(p[1], msglevel); - if (n_bcast_buf < 1) - { - msg(msglevel, "--bcast-buffers parameter must be > 0"); - } - options->n_bcast_buf = n_bcast_buf; + atoi_constrained(p[1], &options->n_bcast_buf, p[0], 1, INT_MAX, msglevel); } else if (streq(p[0], "tcp-queue-limit") && p[1] && !p[2]) { - int tcp_queue_limit; - VERIFY_PERMISSION(OPT_P_GENERAL); - tcp_queue_limit = atoi_warn(p[1], msglevel); - if (tcp_queue_limit < 1) - { - msg(msglevel, "--tcp-queue-limit parameter must be > 0"); - } - options->tcp_queue_limit = tcp_queue_limit; + atoi_constrained(p[1], &options->tcp_queue_limit, p[0], 1, INT_MAX, msglevel); } #if PORT_SHARE else if (streq(p[0], "port-share") && p[1] && p[2] && !p[4]) @@ -8354,21 +8313,24 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, int ageing_time, check_interval; VERIFY_PERMISSION(OPT_P_GENERAL); - ageing_time = atoi_warn(p[1], msglevel); + if (!atoi_constrained(p[1], &ageing_time, "stale-routes-check age", 1, INT_MAX, msglevel)) + { + goto err; + } + if (p[2]) { - check_interval = atoi_warn(p[2], msglevel); + if (!atoi_constrained(p[2], &check_interval, + "stale-routes-check interval", 1, INT_MAX, msglevel)) + { + goto err; + } } else { check_interval = ageing_time; } - if (ageing_time < 1 || check_interval < 1) - { - msg(msglevel, "--stale-routes-check aging time and check interval must be >= 1"); - goto err; - } options->stale_routes_ageing_time = ageing_time; options->stale_routes_check_interval = check_interval; } @@ -8386,7 +8348,7 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, else if (streq(p[0], "push-continuation") && p[1] && !p[2]) { VERIFY_PERMISSION(OPT_P_PULL_MODE); - options->push_continuation = atoi_warn(p[1], msglevel); + atoi_constrained(p[1], &options->push_continuation, p[0], 0, 2, msglevel); } else if (streq(p[0], "auth-user-pass") && !p[2]) { @@ -8505,33 +8467,23 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, { if (!streq(p[2], "default")) { - int offset = atoi_warn(p[2], msglevel); + int offset; - if (!(offset > -256 && offset < 256)) + if (!atoi_constrained(p[2], &offset, "ip-win32 offset", -256, 256, msglevel)) { - msg(msglevel, - "--ip-win32 dynamic [offset] [lease-time]: offset (%d) must be > -256 and < 256", - offset); goto err; } - to->dhcp_masq_custom_offset = true; to->dhcp_masq_offset = offset; } if (p[3]) { - const int min_lease = 30; - int lease_time; - lease_time = atoi_warn(p[3], msglevel); - if (lease_time < min_lease) + if (!atoi_constrained(p[3], &to->dhcp_lease_time, + "ip-win32 lease time", 30, INT_MAX, msglevel)) { - msg(msglevel, - "--ip-win32 dynamic [offset] [lease-time]: lease time parameter (%d) must be at least %d seconds", - lease_time, min_lease); goto err; } - to->dhcp_lease_time = lease_time; } } } @@ -8629,8 +8581,7 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, } else if (streq(p[1], "NBT") && p[2] && !p[3]) { - int t; - t = atoi_warn(p[2], msglevel); + int t = atoi_warn(p[2], msglevel); if (!(t == 1 || t == 2 || t == 4 || t == 8)) { msg(msglevel, "--dhcp-option NBT: parameter (%d) must be 1, 2, 4, or 8", t); @@ -8704,15 +8655,11 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, } else if (streq(p[0], "tap-sleep") && p[1] && !p[2]) { - int s; VERIFY_PERMISSION(OPT_P_DHCPDNS); - s = atoi_warn(p[1], msglevel); - if (s < 0 || s >= 256) + if (!atoi_constrained(p[1], &options->tuntap_options.tap_sleep, p[0], 0, 255, msglevel)) { - msg(msglevel, "--tap-sleep parameter must be between 0 and 255"); goto err; } - options->tuntap_options.tap_sleep = s; } else if (streq(p[0], "dhcp-renew") && !p[1]) { @@ -9152,30 +9099,19 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, VERIFY_PERMISSION(OPT_P_GENERAL); if (p[1]) { - int replay_window; - - replay_window = atoi_warn(p[1], msglevel); - if (!(MIN_SEQ_BACKTRACK <= replay_window && replay_window <= MAX_SEQ_BACKTRACK)) + if (!atoi_constrained(p[1], &options->replay_window, "replay-window windows size", + MIN_SEQ_BACKTRACK, MAX_SEQ_BACKTRACK, msglevel)) { - msg(msglevel, "replay-window window size parameter (%d) must be between %d and %d", - replay_window, MIN_SEQ_BACKTRACK, MAX_SEQ_BACKTRACK); goto err; } - options->replay_window = replay_window; if (p[2]) { - int replay_time; - - replay_time = atoi_warn(p[2], msglevel); - if (!(MIN_TIME_BACKTRACK <= replay_time && replay_time <= MAX_TIME_BACKTRACK)) + if (!atoi_constrained(p[2], &options->replay_time, "replay-window time window", + MIN_TIME_BACKTRACK, MAX_TIME_BACKTRACK, msglevel)) { - msg(msglevel, - "replay-window time window parameter (%d) must be between %d and %d", - replay_time, MIN_TIME_BACKTRACK, MAX_TIME_BACKTRACK); goto err; } - options->replay_time = replay_time; } } else @@ -9771,7 +9707,7 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, else if (!p[2]) { char *endp = NULL; - int i = strtol(provider, &endp, 10); + long i = strtol(provider, &endp, 10); if (*endp == 0) { @@ -9842,7 +9778,7 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, else if (streq(p[0], "pkcs11-pin-cache") && p[1] && !p[2]) { VERIFY_PERMISSION(OPT_P_GENERAL); - options->pkcs11_pin_cache_period = atoi_warn(p[1], msglevel); + options->pkcs11_pin_cache_period = positive_atoi(p[1], msglevel); } else if (streq(p[0], "pkcs11-id") && p[1] && !p[2]) { diff --git a/src/openvpn/options_util.c b/src/openvpn/options_util.c index c3938a7dc..be1eefcac 100644 --- a/src/openvpn/options_util.c +++ b/src/openvpn/options_util.c @@ -146,6 +146,37 @@ atoi_warn(const char *str, int msglevel) return (int)i; } +bool +atoi_constrained(const char *str, int *value, const char *name, int min, int max, int msglevel) +{ + ASSERT(min < max); + + char *endptr; + long long i = strtoll(str, &endptr, 10); + if (i < INT_MIN || *endptr != '\0' || i > INT_MAX) + { + msg(msglevel, "%s: Cannot parse '%s' as integer", name, str); + return false; + } + if (i < min || i > max) + { + if (max == INT_MAX) /* nicer message for common case */ + { + msg(msglevel, "%s: Must be an integer >= %d, not %lld", + name, min, i); + } + else + { + msg(msglevel, "%s: Must be an integer between %d and %d, not %lld", + name, min, max, i); + } + return false; + } + + *value = i; + return true; +} + static const char *updatable_options[] = { "block-ipv6", "block-outside-dns", "dhcp-option", "dns", "ifconfig", "ifconfig-ipv6", diff --git a/src/openvpn/options_util.h b/src/openvpn/options_util.h index 6f81b1ef2..9eca7b6fd 100644 --- a/src/openvpn/options_util.h +++ b/src/openvpn/options_util.h @@ -41,10 +41,22 @@ int positive_atoi(const char *str, int msglevel); /** * Converts a str to an integer if the string can be represented as an - * integer number. Otherwise print a warning with msglevel and return 0 + * integer number. Otherwise print a warning with \p msglevel and return 0 */ int atoi_warn(const char *str, int msglevel); +/** + * Converts a str to an integer if the string can be represented as an + * integer number and is between \p min and \p max. + * The integer is stored in \p value. + * On error, print a warning with \p msglevel using \p name. \p value is + * not changed on error. + * + * @return \c true if the integer has been parsed and stored in value, \c false otherwise + */ +bool atoi_constrained(const char *str, int *value, const char *name, int min, int max, + int msglevel); + /** * Filter an option line by all pull filters. * diff --git a/tests/unit_tests/openvpn/test_misc.c b/tests/unit_tests/openvpn/test_misc.c index 1857922f6..c3f80dc74 100644 --- a/tests/unit_tests/openvpn/test_misc.c +++ b/tests/unit_tests/openvpn/test_misc.c @@ -351,6 +351,14 @@ test_atoi_variants(void **state) assert_int_equal(atoi_warn("0", msglevel), 0); assert_int_equal(atoi_warn("-1194", msglevel), -1194); + int parameter = 0; + assert_true(atoi_constrained("1234", ¶meter, "test", 0, INT_MAX, msglevel)); + assert_int_equal(parameter, 1234); + assert_true(atoi_constrained("0", ¶meter, "test", -1, 0, msglevel)); + assert_int_equal(parameter, 0); + assert_true(atoi_constrained("-1194", ¶meter, "test", INT_MIN, INT_MAX, msglevel)); + assert_int_equal(parameter, -1194); + CLEAR(mock_msg_buf); assert_int_equal(positive_atoi("-1234", msglevel), 0); assert_string_equal(mock_msg_buf, "Cannot parse argument '-1234' as non-negative integer"); @@ -364,6 +372,12 @@ test_atoi_variants(void **state) assert_int_equal(atoi_warn("2147483653", msglevel), 0); assert_string_equal(mock_msg_buf, "Cannot parse argument '2147483653' as integer"); + CLEAR(mock_msg_buf); + parameter = -42; + assert_false(atoi_constrained("2147483653", ¶meter, "test", 0, INT_MAX, msglevel)); + assert_string_equal(mock_msg_buf, "test: Cannot parse '2147483653' as integer"); + assert_int_equal(parameter, -42); + CLEAR(mock_msg_buf); assert_int_equal(positive_atoi("foo77", msglevel), 0); assert_string_equal(mock_msg_buf, "Cannot parse argument 'foo77' as non-negative integer"); @@ -372,6 +386,18 @@ test_atoi_variants(void **state) assert_int_equal(positive_atoi("77foo", msglevel), 0); assert_string_equal(mock_msg_buf, "Cannot parse argument '77foo' as non-negative integer"); + CLEAR(mock_msg_buf); + parameter = -42; + assert_false(atoi_constrained("foo77", ¶meter, "test", 0, INT_MAX, msglevel)); + assert_string_equal(mock_msg_buf, "test: Cannot parse 'foo77' as integer"); + assert_int_equal(parameter, -42); + + CLEAR(mock_msg_buf); + parameter = -42; + assert_false(atoi_constrained("77foo", ¶meter, "test", 0, INT_MAX, msglevel)); + assert_string_equal(mock_msg_buf, "test: Cannot parse '77foo' as integer"); + assert_int_equal(parameter, -42); + CLEAR(mock_msg_buf); assert_int_equal(atoi_warn("foo77", msglevel), 0); assert_string_equal(mock_msg_buf, "Cannot parse argument 'foo77' as integer"); @@ -380,6 +406,31 @@ test_atoi_variants(void **state) assert_int_equal(atoi_warn("77foo", msglevel), 0); assert_string_equal(mock_msg_buf, "Cannot parse argument '77foo' as integer"); + /* special tests for _constrained */ + CLEAR(mock_msg_buf); + parameter = -42; + assert_false(atoi_constrained("77", ¶meter, "test", 0, 76, msglevel)); + assert_string_equal(mock_msg_buf, "test: Must be an integer between 0 and 76, not 77"); + assert_int_equal(parameter, -42); + + CLEAR(mock_msg_buf); + parameter = -42; + assert_false(atoi_constrained("-77", ¶meter, "test", -76, 76, msglevel)); + assert_string_equal(mock_msg_buf, "test: Must be an integer between -76 and 76, not -77"); + assert_int_equal(parameter, -42); + + CLEAR(mock_msg_buf); + parameter = -42; + assert_false(atoi_constrained("-77", ¶meter, "test", 0, INT_MAX, msglevel)); + assert_string_equal(mock_msg_buf, "test: Must be an integer >= 0, not -77"); + assert_int_equal(parameter, -42); + + CLEAR(mock_msg_buf); + parameter = -42; + assert_false(atoi_constrained("0", ¶meter, "test", 1, INT_MAX, msglevel)); + assert_string_equal(mock_msg_buf, "test: Must be an integer >= 1, not 0"); + assert_int_equal(parameter, -42); + mock_set_debug_level(saved_log_level); } -- 2.47.3 From 80c5cdef00fdef89738df1c17441f0bb987ccc97 Mon Sep 17 00:00:00 2001 From: Frank Lichtenheld Date: Tue, 26 Aug 2025 20:41:38 +0200 Subject: [PATCH 13/16] ssl_openssl: Fix type of sslopts argument to SSL_CTX_set_options The argument changed type in OpenSSL 3.0. Change-Id: Ia5e0aad8a97d38f8d309a29ecfe3c578edff9595 Signed-off-by: Frank Lichtenheld Acked-by: Arne Schwabe Message-Id: <20250826184148.21534-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32690.html Signed-off-by: Gert Doering --- src/openvpn/ssl_openssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c index 1948d1288..aa1ac11b7 100644 --- a/src/openvpn/ssl_openssl.c +++ b/src/openvpn/ssl_openssl.c @@ -308,7 +308,7 @@ tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags) ASSERT(NULL != ctx); /* process SSL options */ - long sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET; + uint64_t sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET; #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE sslopt |= SSL_OP_CIPHER_SERVER_PREFERENCE; #endif -- 2.47.3 From c598efc405b7a47ae66f7f78e455e2902b76ce88 Mon Sep 17 00:00:00 2001 From: Marco Baffo Date: Wed, 3 Sep 2025 18:48:20 +0200 Subject: [PATCH 14/16] PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages Using the management interface you can now target one or more clients (via broadcast or via cid) and send a PUSH_UPDATE control message to update some options. See doc/management-notes.txt for details. Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Signed-off-by: Marco Baffo Acked-by: Gert Doering Message-Id: <20250903164826.13284-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32807.html Signed-off-by: Gert Doering --- CMakeLists.txt | 7 +- doc/management-notes.txt | 29 ++ src/openvpn/manage.c | 63 +++- src/openvpn/manage.h | 3 +- src/openvpn/multi.c | 48 ++- src/openvpn/multi.h | 7 + src/openvpn/options.c | 1 - src/openvpn/options_util.c | 4 +- src/openvpn/push.c | 6 +- src/openvpn/push.h | 37 ++- src/openvpn/push_util.c | 269 ++++++++++++++++- tests/unit_tests/openvpn/Makefile.am | 3 +- .../unit_tests/openvpn/test_push_update_msg.c | 282 +++++++++++++++++- 13 files changed, 741 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 35513e9c3..f027b0103 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -874,9 +874,10 @@ if (BUILD_TESTING) target_sources(test_push_update_msg PRIVATE tests/unit_tests/openvpn/mock_msg.c tests/unit_tests/openvpn/mock_get_random.c - src/openvpn/push_util.c - src/openvpn/options_util.c - src/openvpn/otime.c + src/openvpn/push_util.c + src/openvpn/options_util.c + src/openvpn/otime.c + src/openvpn/list.c ) if (TARGET test_argv) diff --git a/doc/management-notes.txt b/doc/management-notes.txt index f1d2930f8..1a5c311cc 100644 --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -1028,6 +1028,35 @@ This capability is intended to allow the use of certificates stored outside of the filesystem (e.g. in Mac OS X Keychain) with OpenVPN via the management interface. +COMMAND -- push-update-broad (OpenVPN 2.7 or higher) +---------------------------------------------------- +Send a message to every connected client to update options at runtime. +The updatable options are: "block-ipv6", "block-outside-dns", "dhcp-option", +"dns", "ifconfig", "ifconfig-ipv6", "redirect-gateway", "redirect-private", +"route", "route-gateway", "route-ipv6", "route-metric", "topology", +"tun-mtu", "keepalive". When a valid option is pushed, the receiving client will +delete every previous value and set new value, so the update of the option will +not be incremental even when theoretically possible (ex. with "redirect-gateway"). +The '-' symbol in front of an option means the option should be removed. +When an option is used with '-', it cannot take any parameter. +The '?' symbol in front of an option means the option's update is optional +so if the client do not support it, that option will just be ignored without +making fail the entire command. The '-' and '?' symbols can be used together. + +Option Format Ex. + `-?option`, `-option`, `?option parameters` are valid formats, + `?-option` is not a valid format. + +Example + push-update-broad "route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400" + +COMMAND -- push-update-cid (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but you must target a single client using client id. + +Example + push-update-cid 42 "route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400" + OUTPUT FORMAT ------------- diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c index 5b2a7deb8..114e4c65f 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -41,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "push.h" #include "multi.h" #include "memdbg.h" @@ -132,8 +133,10 @@ man_help(void) msg(M_CLIENT, "test n : Produce n lines of output for testing/debugging."); msg(M_CLIENT, "username type u : Enter username u for a queried OpenVPN username."); msg(M_CLIENT, "verb [n] : Set log verbosity level to n, or show if n is absent."); - msg(M_CLIENT, - "version [n] : Set client's version to n or show current version of daemon."); + msg(M_CLIENT, "version [n] : Set client's version to n or show current version of daemon."); + msg(M_CLIENT, "push-update-broad options : Broadcast a message to update the specified options."); + msg(M_CLIENT, " Ex. push-update-broad \"route something, -dns\""); + msg(M_CLIENT, "push-update-cid CID options : Send an update message to the client identified by CID."); msg(M_CLIENT, "END"); } @@ -1332,6 +1335,48 @@ set_client_version(struct management *man, const char *version) } } +static void +man_push_update(struct management *man, const char **p, const push_update_type type) +{ + bool status = false; + + if (type == UPT_BROADCAST) + { + if (!man->persist.callback.push_update_broadcast) + { + man_command_unsupported("push-update-broad"); + return; + } + + status = (*man->persist.callback.push_update_broadcast)(man->persist.callback.arg, p[1]); + } + else if (type == UPT_BY_CID) + { + if (!man->persist.callback.push_update_by_cid) + { + man_command_unsupported("push-update-cid"); + return; + } + + unsigned long cid = 0; + + if (!parse_cid(p[1], &cid)) + { + msg(M_CLIENT, "ERROR: push-update-cid fail during cid parsing"); + return; + } + + status = (*man->persist.callback.push_update_by_cid)(man->persist.callback.arg, cid, p[2]); + } + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update command succeeded"); + return; + } + msg(M_CLIENT, "ERROR: push-update command failed"); +} + static void man_dispatch_command(struct management *man, struct status_output *so, const char **p, const int nparms) @@ -1655,6 +1700,20 @@ man_dispatch_command(struct management *man, struct status_output *so, const cha man_remote(man, p); } } + else if (streq(p[0], "push-update-broad")) + { + if (man_need(man, p, 1, 0)) + { + man_push_update(man, p, UPT_BROADCAST); + } + } + else if (streq(p[0], "push-update-cid")) + { + if (man_need(man, p, 2, 0)) + { + man_push_update(man, p, UPT_BY_CID); + } + } #if 1 else if (streq(p[0], "test")) { diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index 0b064cfe5..0bce25e50 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -43,7 +43,6 @@ #define MF_EXTERNAL_KEY_PSSPAD (1u << 16) #define MF_EXTERNAL_KEY_DIGEST (1u << 17) - #ifdef ENABLE_MANAGEMENT #include "misc.h" @@ -197,6 +196,8 @@ struct management_callback #endif unsigned int (*remote_entry_count)(void *arg); bool (*remote_entry_get)(void *arg, unsigned int index, char **remote); + bool (*push_update_broadcast)(void *arg, const char *options); + bool (*push_update_by_cid)(void *arg, unsigned long cid, const char *options); }; /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index f1abdbe85..85975ff59 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -3996,7 +3996,7 @@ management_delete_event(void *arg, event_t event) } } -static struct multi_instance * +struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { if (m) @@ -4137,6 +4137,8 @@ init_management_callback_multi(struct multi_context *m) cb.client_auth = management_client_auth; cb.client_pending_auth = management_client_pending_auth; cb.get_peer_info = management_get_peer_info; + cb.push_update_broadcast = management_callback_send_push_update_broadcast; + cb.push_update_by_cid = management_callback_send_push_update_by_cid; management_set_callback(management, &cb); } #endif /* ifdef ENABLE_MANAGEMENT */ @@ -4261,3 +4263,47 @@ tunnel_server(struct context *top) multi_top_free(&multi); close_instance(top); } + +/** + * Update the vhash with new IP/IPv6 addresses in the multi_context when a + * push-update message containing ifconfig/ifconfig-ipv6 options is sent + * from the server. This function should be called after a push-update + * and old_ip/old_ipv6 are the previous addresses of the client in + * ctx->options.ifconfig_local and ctx->options.ifconfig_ipv6_local. + */ +void +update_vhash(struct multi_context *m, struct multi_instance *mi, const char *old_ip, const char *old_ipv6) +{ + struct in_addr addr; + struct in6_addr new_ipv6; + + if ((mi->context.options.ifconfig_local && (!old_ip || strcmp(old_ip, mi->context.options.ifconfig_local))) + && inet_pton(AF_INET, mi->context.options.ifconfig_local, &addr) == 1) + { + in_addr_t new_ip = ntohl(addr.s_addr); + + /* Add new IP */ + multi_learn_in_addr_t(m, mi, new_ip, -1, true); + } + + /* TO DO: + * else if (old_ip) + * { + * // remove old IP + * } + */ + + if ((mi->context.options.ifconfig_ipv6_local && (!old_ipv6 || strcmp(old_ipv6, mi->context.options.ifconfig_ipv6_local))) + && inet_pton(AF_INET6, mi->context.options.ifconfig_ipv6_local, &new_ipv6) == 1) + { + /* Add new IPv6 */ + multi_learn_in6_addr(m, mi, new_ipv6, -1, true); + } + + /* TO DO: + * else if (old_ipv6) + * { + * // remove old IPv6 + * } + */ +} diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index e87e46557..087c0e6c0 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -686,5 +686,12 @@ multi_set_pending(struct multi_context *m, struct multi_instance *mi) */ void multi_assign_peer_id(struct multi_context *m, struct multi_instance *mi); +#ifdef ENABLE_MANAGEMENT +struct multi_instance * +lookup_by_cid(struct multi_context *m, const unsigned long cid); +#endif + +void +update_vhash(struct multi_context *m, struct multi_instance *mi, const char *old_ip, const char *old_ipv6); #endif /* MULTI_H */ diff --git a/src/openvpn/options.c b/src/openvpn/options.c index a268b928c..0616a1760 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -5488,7 +5488,6 @@ apply_push_options(struct context *c, struct options *options, struct buffer *bu { continue; /* Ignoring this option */ } - throw_signal_soft(SIGUSR1, "Offending option received from server"); return false; /* Cause push/pull error and stop push processing */ } diff --git a/src/openvpn/options_util.c b/src/openvpn/options_util.c index be1eefcac..28f1f9e21 100644 --- a/src/openvpn/options_util.c +++ b/src/openvpn/options_util.c @@ -236,11 +236,11 @@ check_push_update_option_flags(char *line, int *i, unsigned int *flags) { if (*flags & PUSH_OPT_OPTIONAL) { - msg(D_PUSH, "Pushed option is not updatable: '%s'. Ignoring.", line); + msg(D_PUSH, "Pushed dispensable option is not updatable: '%s'. Ignoring.", line); } else { - msg(M_WARN, "Pushed option is not updatable: '%s'. Restarting.", line); + msg(M_WARN, "Pushed option is not updatable: '%s'.", line); return false; } } diff --git a/src/openvpn/push.c b/src/openvpn/push.c index 4f6adfce4..1ea7ed99f 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -1073,6 +1073,10 @@ process_incoming_push_reply(struct context *c, unsigned int permission_mask, break; } } + else + { + throw_signal_soft(SIGUSR1, "Offending option received from server"); + } } else if (ch == '\0') { @@ -1100,7 +1104,7 @@ process_incoming_push_msg(struct context *c, const struct buffer *buffer, } else if (honor_received_options && buf_string_compare_advance(&buf, push_update_cmd)) { - return process_incoming_push_update(c, permission_mask, option_types_found, &buf); + return process_incoming_push_update(c, permission_mask, option_types_found, &buf, false); } else { diff --git a/src/openvpn/push.h b/src/openvpn/push.h index 22b940f39..8ffd0c298 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -41,6 +41,15 @@ #define PUSH_OPT_TO_REMOVE (1 << 0) #define PUSH_OPT_OPTIONAL (1 << 1) +#ifdef ENABLE_MANAGEMENT +/* Push-update message sender modes */ +typedef enum +{ + UPT_BROADCAST = 0, + UPT_BY_CID = 1 +} push_update_type; +#endif + int process_incoming_push_request(struct context *c); /** @@ -56,6 +65,7 @@ int process_incoming_push_request(struct context *c); * @param option_types_found A pointer to a variable that will be filled with the types of options * found in the message. * @param buf A buffer containing the received message. + * @param msg_sender A boolean indicating if function is called by the message sender (server). * * @return * - `PUSH_MSG_UPDATE`: The message was processed successfully, and the updates were applied. @@ -65,7 +75,8 @@ int process_incoming_push_request(struct context *c); */ int process_incoming_push_update(struct context *c, unsigned int permission_mask, - unsigned int *option_types_found, struct buffer *buf); + unsigned int *option_types_found, struct buffer *buf, + bool msg_sender); int process_incoming_push_msg(struct context *c, const struct buffer *buffer, bool honor_received_options, unsigned int permission_mask, @@ -127,4 +138,28 @@ void send_push_reply_auth_token(struct tls_multi *multi); */ void receive_auth_pending(struct context *c, const struct buffer *buffer); +#ifdef ENABLE_MANAGEMENT +/** + * @brief A function to send a PUSH_UPDATE control message from server to client(s). + * + * @param m the multi_context, contains all the clients connected to this server. + * @param target the target to which to send the message. It should be: + * `NULL` if `type == UPT_BROADCAST`, + * a `mroute_addr *` if `type == UPT_BY_ADDR`, + * a `char *` if `type == UPT_BY_CN`, + * an `unsigned long *` if `type == UPT_BY_CID`. + * @param msg a string containing the options to send. + * @param type the way to address the message (broadcast, by cid, by cn, by address). + * @param push_bundle_size the maximum size of a bundle of pushed option. Just use PUSH_BUNDLE_SIZE macro. + * @return the number of clients to which the message was sent. + */ +int +send_push_update(struct multi_context *m, const void *target, const char *msg, const push_update_type type, const int push_bundle_size); + +bool management_callback_send_push_update_broadcast(void *arg, const char *options); + +bool management_callback_send_push_update_by_cid(void *arg, unsigned long cid, const char *options); + +#endif /* ifdef ENABLE_MANAGEMENT*/ + #endif /* ifndef PUSH_H */ diff --git a/src/openvpn/push_util.c b/src/openvpn/push_util.c index 0862a7433..bd3992be6 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -3,10 +3,16 @@ #endif #include "push.h" +#include "buffer.h" + +#ifdef ENABLE_MANAGEMENT +#include "multi.h" +#endif int process_incoming_push_update(struct context *c, unsigned int permission_mask, - unsigned int *option_types_found, struct buffer *buf) + unsigned int *option_types_found, struct buffer *buf, + bool msg_sender) { int ret = PUSH_MSG_ERROR; const uint8_t ch = buf_read_u8(buf); @@ -27,6 +33,10 @@ process_incoming_push_update(struct context *c, unsigned int permission_mask, break; } } + else if (!msg_sender) + { + throw_signal_soft(SIGUSR1, "Offending option received from server"); + } } else if (ch == '\0') { @@ -35,3 +45,260 @@ process_incoming_push_update(struct context *c, unsigned int permission_mask, return ret; } + +#ifdef ENABLE_MANAGEMENT +/** + * Return index of last `,` or `0` if it didn't find any. + * If there is a comma at index `0` it's an error anyway + */ +static int +find_first_comma_of_next_bundle(const char *str, int ix) +{ + while (ix > 0) + { + if (str[ix] == ',') + { + return ix; + } + ix--; + } + return 0; +} + +/* Allocate memory and assemble the final message */ +static struct buffer +forge_msg(const char *src, const char *continuation, struct gc_arena *gc) +{ + int src_len = strlen(src); + int con_len = continuation ? strlen(continuation) : 0; + struct buffer buf = alloc_buf_gc(src_len + sizeof(push_update_cmd) + con_len + 2, gc); + + buf_printf(&buf, "%s,%s%s", push_update_cmd, src, continuation ? continuation : ""); + + return buf; +} + +static char * +gc_strdup(const char *src, struct gc_arena *gc) +{ + char *ret = gc_malloc((strlen(src) + 1) * sizeof(char), true, gc); + + strcpy(ret, src); + return ret; +} + +/* It split the messagge (if necessay) and fill msgs with the message chunks. + * Return `false` on failure an `true` on success. + */ +static bool +message_splitter(const char *s, struct buffer *msgs, struct gc_arena *gc, const int safe_cap) +{ + if (!s || !*s) + { + return false; + } + + char *str = gc_strdup(s, gc); + int i = 0; + int im = 0; + + while (*str) + { + /* + ',' - '/0' */ + if (strlen(str) > safe_cap) + { + int ci = find_first_comma_of_next_bundle(str, safe_cap); + if (!ci) + { + /* if no commas were found go to fail, do not send any message */ + return false; + } + str[ci] = '\0'; + /* copy from i to (ci -1) */ + msgs[im] = forge_msg(str, ",push-continuation 2", gc); + i = ci + 1; + } + else + { + if (im) + { + msgs[im] = forge_msg(str, ",push-continuation 1", gc); + } + else + { + msgs[im] = forge_msg(str, NULL, gc); + } + i = strlen(str); + } + str = &str[i]; + im++; + } + return true; +} + +/* send the message(s) prepared to one single client */ +static bool +send_single_push_update(struct context *c, struct buffer *msgs, unsigned int *option_types_found) +{ + if (!msgs[0].data || !*(msgs[0].data)) + { + return false; + } + int i = -1; + + while (msgs[++i].data && *(msgs[i].data)) + { + if (!send_control_channel_string(c, BSTR(&msgs[i]), D_PUSH)) + { + return false; + } + + /* After sending the control message, we update the options + * server-side in the client's context so pushed options like + * ifconfig/ifconfig-ipv6 can actually work. + * If we don't do that, packets arriving from the client with the + * new address will be rejected and packets for the new address + * will not be routed towards the client. + * For the same reason we later update the vhash too in + * `send_push_update()` function. + */ + buf_string_compare_advance(&msgs[i], push_update_cmd); + if (process_incoming_push_update(c, pull_permission_mask(c), option_types_found, &msgs[i], true) == PUSH_MSG_ERROR) + { + msg(M_WARN, "Failed to process push update message sent to client ID: %u", + c->c2.tls_multi ? c->c2.tls_multi->peer_id : UINT32_MAX); + continue; + } + c->options.push_option_types_found |= *option_types_found; + if (!options_postprocess_pull(&c->options, c->c2.es)) + { + msg(M_WARN, "Failed to post-process push update message sent to client ID: %u", + c->c2.tls_multi ? c->c2.tls_multi->peer_id : UINT32_MAX); + } + } + return true; +} + +int +send_push_update(struct multi_context *m, const void *target, const char *msg, const push_update_type type, const int push_bundle_size) +{ + if (!msg || !*msg || !m + || (!target && type != UPT_BROADCAST)) + { + return -EINVAL; + } + + struct gc_arena gc = gc_new(); + /* extra space for possible trailing ifconfig and push-continuation */ + const int extra = 84 + sizeof(push_update_cmd); + /* push_bundle_size is the maximum size of a message, so if the message + * we want to send exceeds that size we have to split it into smaller messages */ + const int safe_cap = push_bundle_size - extra; + int msgs_num = (strlen(msg) / safe_cap) + ((strlen(msg) % safe_cap) != 0); + struct buffer *msgs = gc_malloc((msgs_num + 1) * sizeof(struct buffer), true, &gc); + + unsigned int option_types_found = 0; + + msgs[msgs_num].data = NULL; + if (!message_splitter(msg, msgs, &gc, safe_cap)) + { + gc_free(&gc); + return -EINVAL; + } + + if (type == UPT_BY_CID) + { + struct multi_instance *mi = lookup_by_cid(m, *((unsigned long *)target)); + + if (!mi) + { + return -ENOENT; + } + + const char *old_ip = mi->context.options.ifconfig_local; + const char *old_ipv6 = mi->context.options.ifconfig_ipv6_local; + if (!mi->halt + && send_single_push_update(&mi->context, msgs, &option_types_found)) + { + if (option_types_found & OPT_P_UP) + { + update_vhash(m, mi, old_ip, old_ipv6); + } + gc_free(&gc); + return 1; + } + else + { + gc_free(&gc); + return 0; + } + } + + int count = 0; + struct hash_iterator hi; + const struct hash_element *he; + + hash_iterator_init(m->iter, &hi); + while ((he = hash_iterator_next(&hi))) + { + struct multi_instance *curr_mi = he->value; + + if (curr_mi->halt) + { + continue; + } + + /* Type is UPT_BROADCAST so we update every client */ + option_types_found = 0; + const char *old_ip = curr_mi->context.options.ifconfig_local; + const char *old_ipv6 = curr_mi->context.options.ifconfig_ipv6_local; + if (!send_single_push_update(&curr_mi->context, msgs, &option_types_found)) + { + msg(M_CLIENT, "ERROR: Peer ID: %u has not been updated", + curr_mi->context.c2.tls_multi ? curr_mi->context.c2.tls_multi->peer_id : UINT32_MAX); + continue; + } + if (option_types_found & OPT_P_UP) + { + update_vhash(m, curr_mi, old_ip, old_ipv6); + } + count++; + } + + hash_iterator_free(&hi); + gc_free(&gc); + return count; +} + +#define RETURN_UPDATE_STATUS(n_sent) \ + do \ + { \ + if ((n_sent) > 0) \ + { \ + msg(M_CLIENT, "SUCCESS: %d client(s) updated", (n_sent)); \ + return true; \ + } \ + else \ + { \ + msg(M_CLIENT, "ERROR: no client updated"); \ + return false; \ + } \ + } while (0) + + +bool +management_callback_send_push_update_broadcast(void *arg, const char *options) +{ + int n_sent = send_push_update(arg, NULL, options, UPT_BROADCAST, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} + +bool +management_callback_send_push_update_by_cid(void *arg, unsigned long cid, const char *options) +{ + int n_sent = send_push_update(arg, &cid, options, UPT_BY_CID, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} +#endif /* ifdef ENABLE_MANAGEMENT */ diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index 7a7fec7fd..ec8cc69a0 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -337,7 +337,8 @@ push_update_msg_testdriver_SOURCES = test_push_update_msg.c \ $(top_srcdir)/src/openvpn/platform.c \ $(top_srcdir)/src/openvpn/push_util.c \ $(top_srcdir)/src/openvpn/options_util.c \ - $(top_srcdir)/src/openvpn/otime.c + $(top_srcdir)/src/openvpn/otime.c \ + $(top_srcdir)/src/openvpn/list.c socket_testdriver_CFLAGS = \ -I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \ diff --git a/tests/unit_tests/openvpn/test_push_update_msg.c b/tests/unit_tests/openvpn/test_push_update_msg.c index 0f4ad4140..87329b14f 100644 --- a/tests/unit_tests/openvpn/test_push_update_msg.c +++ b/tests/unit_tests/openvpn/test_push_update_msg.c @@ -8,9 +8,16 @@ #include #include "push.h" #include "options_util.h" +#include "multi.h" /* mocks */ +void +throw_signal_soft(const int signum, const char *signal_text) +{ + msg(M_WARN, "Offending option received from server"); +} + unsigned int pull_permission_mask(const struct context *c) { @@ -21,6 +28,18 @@ pull_permission_mask(const struct context *c) return flags; } +void +update_vhash(struct multi_context *m, struct multi_instance *mi, const char *old_ip, const char *old_ipv6) +{ + return; +} + +bool +options_postprocess_pull(struct options *options, struct env_set *es) +{ + return true; +} + bool apply_push_options(struct context *c, struct options *options, struct buffer *buf, unsigned int permission_mask, unsigned int *option_types_found, @@ -48,7 +67,6 @@ apply_push_options(struct context *c, struct options *options, struct buffer *bu { continue; /* Ignoring this option */ } - msg(M_WARN, "Offending option received from server"); return false; /* Cause push/pull error and stop push processing */ } } @@ -77,7 +95,7 @@ process_incoming_push_msg(struct context *c, const struct buffer *buffer, } else if (honor_received_options && buf_string_compare_advance(&buf, push_update_cmd)) { - return process_incoming_push_update(c, permission_mask, option_types_found, &buf); + return process_incoming_push_update(c, permission_mask, option_types_found, &buf, false); } else { @@ -85,6 +103,49 @@ process_incoming_push_msg(struct context *c, const struct buffer *buffer, } } +const char * +tls_common_name(const struct tls_multi *multi, const bool null) +{ + return NULL; +} + +#ifndef ENABLE_MANAGEMENT +bool +send_control_channel_string(struct context *c, const char *str, int msglevel) +{ + return true; +} +#else /* ifndef ENABLE_MANAGEMENT */ +char **res; +int i; + +bool +send_control_channel_string(struct context *c, const char *str, int msglevel) +{ + if (res && res[i] && strcmp(res[i], str)) + { + printf("\n\nexpected: %s\n\n actual: %s\n\n", res[i], str); + return false; + } + i++; + return true; +} + +struct multi_instance * +lookup_by_cid(struct multi_context *m, const unsigned long cid) +{ + return *(m->instances); +} + +bool +mroute_extract_openvpn_sockaddr(struct mroute_addr *addr, + const struct openvpn_sockaddr *osaddr, + bool use_port) +{ + return true; +} +#endif /* ifndef ENABLE_MANAGEMENT */ + /* tests */ static void @@ -120,7 +181,6 @@ test_incoming_push_message_error1(void **state) free_buf(&buf); } - static void test_incoming_push_message_error2(void **state) { @@ -219,6 +279,207 @@ test_incoming_push_message_mix2(void **state) free_buf(&buf); } +#ifdef ENABLE_MANAGEMENT +char *r0[] = { + "PUSH_UPDATE,redirect-gateway local,route 192.168.1.0 255.255.255.0" +}; +char *r1[] = { + "PUSH_UPDATE,-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2", + "PUSH_UPDATE, akakakakakakakakakakakaf, dhcp-option DNS 8.8.8.8,redirect-gateway local,push-continuation 2", + "PUSH_UPDATE,route 192.168.1.0 255.255.255.0,push-continuation 1" +}; +char *r3[] = { + "PUSH_UPDATE,,," +}; +char *r4[] = { + "PUSH_UPDATE,-dhcp-option, blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2", + "PUSH_UPDATE, akakakakakakakakakakakaf,dhcp-option DNS 8.8.8.8, redirect-gateway local,push-continuation 2", + "PUSH_UPDATE, route 192.168.1.0 255.255.255.0,,push-continuation 1" +}; +char *r5[] = { + "PUSH_UPDATE,,-dhcp-option, blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2", + "PUSH_UPDATE, akakakakakakakakakakakaf,dhcp-option DNS 8.8.8.8, redirect-gateway local,push-continuation 2", + "PUSH_UPDATE, route 192.168.1.0 255.255.255.0,push-continuation 1" +}; +char *r6[] = { + "PUSH_UPDATE,-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2", + "PUSH_UPDATE, akakakakakakakakakakakaf, dhcp-option DNS 8.8.8.8, redirect-gateway 10.10.10.10,,push-continuation 2", + "PUSH_UPDATE, route 192.168.1.0 255.255.255.0,,push-continuation 1" +}; +char *r7[] = { + "PUSH_UPDATE,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,push-continuation 2", + "PUSH_UPDATE,,,,,,,,,,,,,,,,,,,push-continuation 1" +}; +char *r8[] = { + "PUSH_UPDATE,-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2", + "PUSH_UPDATE, akakakakakakakakakakakaf, dhcp-option DNS 8.8.8.8,redirect-gateway\n local,push-continuation 2", + "PUSH_UPDATE,route 192.168.1.0 255.255.255.0\n\n\n,push-continuation 1" +}; +char *r9[] = { + "PUSH_UPDATE,," +}; + + +const char *msg0 = "redirect-gateway local,route 192.168.1.0 255.255.255.0"; +const char *msg1 = "-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf," + " akakakakakakakakakakakaf, dhcp-option DNS 8.8.8.8,redirect-gateway local,route 192.168.1.0 255.255.255.0"; +const char *msg2 = ""; +const char *msg3 = ",,"; +const char *msg4 = "-dhcp-option, blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf," + " akakakakakakakakakakakaf,dhcp-option DNS 8.8.8.8, redirect-gateway local, route 192.168.1.0 255.255.255.0,"; +const char *msg5 = ",-dhcp-option, blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf," + " akakakakakakakakakakakaf,dhcp-option DNS 8.8.8.8, redirect-gateway local, route 192.168.1.0 255.255.255.0"; +const char *msg6 = "-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf, akakakakakakakakakakakaf," + " dhcp-option DNS 8.8.8.8, redirect-gateway 10.10.10.10,, route 192.168.1.0 255.255.255.0,"; +const char *msg7 = ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"; +const char *msg8 = "-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf, akakakakakakakakakakakaf," + " dhcp-option DNS 8.8.8.8,redirect-gateway\n local,route 192.168.1.0 255.255.255.0\n\n\n"; +const char *msg9 = ","; + +const char *msg10 = "abandon ability able about above absent absorb abstract absurd abuse access accident account accuse achieve" + "acid acoustic acquire across act action actor actress actual adapt add addict address adjust" + "baby bachelor bacon badge bag balance balcony ball bamboo banana banner bar barely bargain barrel base basic" + "basket battle beach bean beauty because become beef before begin behave behind" + "cabbage cabin cable cactus cage cake call calm camera camp can canal cancel candy cannon canoe canvas canyon" + "capable capital captain car carbon card cargo carpet carry cart case" + "daisy damage damp dance danger daring dash daughter dawn day deal debate debris decade december decide decline" + "decorate decrease deer defense define defy degree delay deliver demand demise denial"; + +#define PUSH_BUNDLE_SIZE_TEST 184 + +static void +test_send_push_msg0(void **state) +{ + i = 0; + res = r0; + struct multi_context *m = *state; + const unsigned long cid = 0; + assert_int_equal(send_push_update(m, &cid, msg0, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1); +} +static void +test_send_push_msg1(void **state) +{ + i = 0; + res = r1; + struct multi_context *m = *state; + const unsigned long cid = 0; + assert_int_equal(send_push_update(m, &cid, msg1, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1); +} + +static void +test_send_push_msg2(void **state) +{ + i = 0; + res = NULL; + struct multi_context *m = *state; + const unsigned long cid = 0; + assert_int_equal(send_push_update(m, &cid, msg2, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), -EINVAL); +} + +static void +test_send_push_msg3(void **state) +{ + i = 0; + res = r3; + struct multi_context *m = *state; + const unsigned long cid = 0; + assert_int_equal(send_push_update(m, &cid, msg3, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1); +} + +static void +test_send_push_msg4(void **state) +{ + i = 0; + res = r4; + struct multi_context *m = *state; + const unsigned long cid = 0; + assert_int_equal(send_push_update(m, &cid, msg4, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1); +} + +static void +test_send_push_msg5(void **state) +{ + i = 0; + res = r5; + struct multi_context *m = *state; + const unsigned long cid = 0; + assert_int_equal(send_push_update(m, &cid, msg5, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1); +} + +static void +test_send_push_msg6(void **state) +{ + i = 0; + res = r6; + struct multi_context *m = *state; + const unsigned long cid = 0; + assert_int_equal(send_push_update(m, &cid, msg6, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1); +} + +static void +test_send_push_msg7(void **state) +{ + i = 0; + res = r7; + struct multi_context *m = *state; + const unsigned long cid = 0; + assert_int_equal(send_push_update(m, &cid, msg7, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1); +} + +static void +test_send_push_msg8(void **state) +{ + i = 0; + res = r8; + struct multi_context *m = *state; + const unsigned long cid = 0; + assert_int_equal(send_push_update(m, &cid, msg8, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1); +} + +static void +test_send_push_msg9(void **state) +{ + i = 0; + res = r9; + struct multi_context *m = *state; + const unsigned long cid = 0; + assert_int_equal(send_push_update(m, &cid, msg9, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1); +} + +static void +test_send_push_msg10(void **state) +{ + i = 0; + res = NULL; + struct multi_context *m = *state; + const unsigned long cid = 0; + assert_int_equal(send_push_update(m, &cid, msg10, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), -EINVAL); +} + +#undef PUSH_BUNDLE_SIZE_TEST + +static int +setup2(void **state) +{ + struct multi_context *m = calloc(1, sizeof(struct multi_context)); + m->instances = calloc(1, sizeof(struct multi_instance *)); + struct multi_instance *mi = calloc(1, sizeof(struct multi_instance)); + *(m->instances) = mi; + *state = m; + return 0; +} + +static int +teardown2(void **state) +{ + struct multi_context *m = *state; + free(*(m->instances)); + free(m->instances); + free(m); + return 0; +} +#endif /* ifdef ENABLE_MANAGEMENT */ + static int setup(void **state) { @@ -249,7 +510,20 @@ main(void) cmocka_unit_test_setup_teardown(test_incoming_push_message_1, setup, teardown), cmocka_unit_test_setup_teardown(test_incoming_push_message_bad_format, setup, teardown), cmocka_unit_test_setup_teardown(test_incoming_push_message_mix, setup, teardown), - cmocka_unit_test_setup_teardown(test_incoming_push_message_mix2, setup, teardown) + cmocka_unit_test_setup_teardown(test_incoming_push_message_mix2, setup, teardown), +#ifdef ENABLE_MANAGEMENT + cmocka_unit_test_setup_teardown(test_send_push_msg0, setup2, teardown2), + cmocka_unit_test_setup_teardown(test_send_push_msg1, setup2, teardown2), + cmocka_unit_test_setup_teardown(test_send_push_msg2, setup2, teardown2), + cmocka_unit_test_setup_teardown(test_send_push_msg3, setup2, teardown2), + cmocka_unit_test_setup_teardown(test_send_push_msg4, setup2, teardown2), + cmocka_unit_test_setup_teardown(test_send_push_msg5, setup2, teardown2), + cmocka_unit_test_setup_teardown(test_send_push_msg6, setup2, teardown2), + cmocka_unit_test_setup_teardown(test_send_push_msg7, setup2, teardown2), + cmocka_unit_test_setup_teardown(test_send_push_msg8, setup2, teardown2), + cmocka_unit_test_setup_teardown(test_send_push_msg9, setup2, teardown2), + cmocka_unit_test_setup_teardown(test_send_push_msg10, setup2, teardown2) +#endif }; return cmocka_run_group_tests(tests, NULL, NULL); -- 2.47.3 From 1e7b9a0fb021f0a64e76369f4efd2001d50ef42b Mon Sep 17 00:00:00 2001 From: Gert Doering Date: Wed, 3 Sep 2025 19:03:39 +0200 Subject: [PATCH 15/16] OpenVPN Release 2.7_beta1 version.m4, ChangeLog, Changes.rst Changes.rst has not received an "2.7_beta1" section - it has the "highlevel" overview of what is new in 2.7, but for alpha/beta releases it's better to look at git log to see what has been added/fixed. New features alpha3 -> beta1 are - a large number of signed/unsigned related warnings have been fixed - bugfixes in --dns-updown script for linux systems using resolvconf - rewrite of the management interface "bytecount" infastructure to better interact with DCO - PUSH_UPDATE server support (via management interface) - introduction of route_redirect_gateway_ipv4 and _ipv6 env variables - speeding up t_client tests by reducing per-test startup delay 3s -> 1s The biggest noticeable difference in beta1 is the reformatting using clang-format, leaving uncrustify as that wasn't stable across versions. Signed-off-by: Gert Doering --- ChangeLog | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Changes.rst | 30 +++++++++++++++++++++++++ version.m4 | 2 +- 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 722486a66..f89c784cb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,71 @@ OpenVPN ChangeLog Copyright (C) 2002-2025 OpenVPN Inc +2025.09.04 -- Version 2.7_beta1 + +Arne Schwabe (1): + Check message id/acked ids too when doing sessionid cookie checks + +Frank Lichtenheld (27): + Update text of GPL to latest version from FSF + Update GPL header in all source files to current recommended version + Define a .clang-format file for the project + Disable clang-format for some code parts + Update git-pre-commit-uncrustify.sh to handle clang-format + GHA: enable -Werror for mbedTLS v3 and AWS LC builds + Reformat the whole project with clang-format + Fix build error with clang-cl on latest Windows SDK + clang-format: Switch to ColumnLimit 0 + Add clang-format reformat commit to .git-blame-ignore-revs + Remove uncrustify config and reformat-all.sh + buffer: remove unused function buf_write_alloc_prepend + t_client.sh: Do not wait 3 seconds for OpenVPN to come up + Collect trivial conversion fixes + options: Fix --hash-size virtual argument + Clean up documentation for --tun-mtu-max + comp: Make sure comp flags are treated as unsigned + crypto: Make sure crypto flags are treated as unsigned + options: Make sure option types are treated as unsigned + route: Make sure various route flags are treated as unsigned + socket: Create socket_util with non-socket functions + Add new unit test module test_socket + socket_util: Clean up conversion warnings in add_in6_addr + manage: Make sure various management flags are treated as unsigned + forward: Make sure pip flags are treated as unsigned + options: Introduce atoi_constrained and review usages of atoi_warn + ssl_openssl: Fix type of sslopts argument to SSL_CTX_set_options + +Gert Doering (3): + Remove use of 'dh dh2048.pem' from sample configs, remove 'dh2048.pem' file + Introduce env variables to communicate desired gateway redirection to NM. + OpenVPN Release 2.7_beta1 + +Gianmarco De Gregori (1): + dco: avoid printing mi prefix on debug messages + +Heiko Hund (1): + dns: fix systemd dns-updown script + +Ilia Shipitsin (1): + GHA: limit 'Deploy Doxygen documentation' to main repo only + +Lev Stipakov (3): + Log setting DNS via NRPT + dco-win: add support for multipeer stats + Refactor management bytecount tracking + +Marco Baffo (1): + PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages + +Ralf Lici (3): + management: resync timer on bytecount interval change + dco_linux: validate tun interface before fetching stats + management: stop bytecount on client disconnection + +Samuli Seppänen (2): + Add sample FFDH parameters file and use that in t_server_null tests + + 2025.07.31 -- Version 2.7_alpha3 Antonio Quartulli (10): diff --git a/Changes.rst b/Changes.rst index 1bc5a8e51..e27294ece 100644 --- a/Changes.rst +++ b/Changes.rst @@ -131,11 +131,23 @@ PUSH_UPDATE client support implementation for OpenVPN 2.x is still under development. See also: https://openvpn.github.io/openvpn-rfc/openvpn-wire-protocol.html +PUSH_UPDATE server support (minimal) + new management interface commands ``push-update-broad`` and + ``push-update-cid`` to send PUSH_UPDATE option updates to all + clients ("there is a new DNS server") or only a specific client ID + ("privileges have changed, here's a new IP address"). See + doc/management-notes.txt + Support for user-defined routing tables on Linux see the ``--route-table`` option in the manpage PQE support for WolfSSL +Two new environment variables have been introduced to communicate desired + default gateway redirection to plugins like Network Manager, + ``route_redirect_gateway_ipv4`` and ``route_redirect_gateway_ipv6``. + See the "Environmental Variables" section in the man page + Deprecated features ------------------- @@ -235,6 +247,24 @@ User-visible Changes server pushes DCO incompatible options), use the ``--disable-dco`` option. +- Apply more checks to incoming TLS handshake packets before creating + new state - namely, verify message ID / acked ID for "valid range for + an initial packet". This fixes a problem with clients that float + very early but send control channel packet from the pre-float IP + (Github: OpenVPN/openvpn#704). + +- Use of ``--dh dh2048.pem`` in all sample configs has been replaced + with ``--dh none``. The ``dh2048.pem`` file has been removed, and + has been replaced with ``ffdhe2048.pem`` for the benefit of the + t_server_null test (to test all variants of ``--dh``). + +- the startup delay in ``t_client.sh`` has been reduced from 3s to 1s, + making a noticeable difference for setups with many tests. + +- changed from using ``uncrustify`` for code formatting and pre-commit checks + to ``clang-format``. This reformatted quite a bit of code, and requires + that regular committers change their pre-commit checks accordingly. + Overview of changes in 2.6 ========================== diff --git a/version.m4 b/version.m4 index 601b7e25b..8594535e5 100644 --- a/version.m4 +++ b/version.m4 @@ -3,7 +3,7 @@ define([PRODUCT_NAME], [OpenVPN]) define([PRODUCT_TARNAME], [openvpn]) define([PRODUCT_VERSION_MAJOR], [2]) define([PRODUCT_VERSION_MINOR], [7]) -define([PRODUCT_VERSION_PATCH], [_alpha3]) +define([PRODUCT_VERSION_PATCH], [_beta1]) m4_append([PRODUCT_VERSION], [PRODUCT_VERSION_MAJOR]) m4_append([PRODUCT_VERSION], [PRODUCT_VERSION_MINOR], [[.]]) m4_append([PRODUCT_VERSION], [PRODUCT_VERSION_PATCH], [[]]) -- 2.47.3 From 88f8edbf7545dc7913d031ea12c4bae5250bb766 Mon Sep 17 00:00:00 2001 From: Gert Doering Date: Sun, 7 Sep 2025 23:12:46 +0200 Subject: [PATCH 16/16] replace assert() calls with ASSERT() OpenVPN's ASSERT() macro will do a bit more than the standard-libc assert() call, namely print out which function and what expression failed, before calling _exit(1). Also, it can not be accidentially compiled-away (-DNDEBUG). Use of ASSERT() is generally only advised in cases of "this must not happen, but if it does, it's a programming or state corruption error that we must know about". Use of assert() is lacking the extra debug info, and as such, not advised at all. Change-Id: I6480d6f741c2368a0d951004b91167d5943f8f9d Signed-off-by: Gert Doering Acked-by: mandree Message-Id: <20250907211252.23924-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32824.html Signed-off-by: Gert Doering --- src/openvpn/dco_freebsd.c | 4 ++-- src/openvpn/init.c | 6 +++--- src/openvpn/options.c | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/openvpn/dco_freebsd.c b/src/openvpn/dco_freebsd.c index 931f9f681..65303cd4d 100644 --- a/src/openvpn/dco_freebsd.c +++ b/src/openvpn/dco_freebsd.c @@ -100,7 +100,7 @@ nvlist_to_sockaddr(const nvlist_t *nvl, struct sockaddr_storage *ss) in->sin_len = sizeof(*in); data = nvlist_get_binary(nvl, "address", &len); - assert(len == sizeof(in->sin_addr)); + ASSERT(len == sizeof(in->sin_addr)); memcpy(&in->sin_addr, data, sizeof(in->sin_addr)); in->sin_port = nvlist_get_number(nvl, "port"); break; @@ -114,7 +114,7 @@ nvlist_to_sockaddr(const nvlist_t *nvl, struct sockaddr_storage *ss) in6->sin6_len = sizeof(*in6); data = nvlist_get_binary(nvl, "address", &len); - assert(len == sizeof(in6->sin6_addr)); + ASSERT(len == sizeof(in6->sin6_addr)); memcpy(&in6->sin6_addr, data, sizeof(in6->sin6_addr)); in6->sin6_port = nvlist_get_number(nvl, "port"); diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 39ea8e4a4..2821cd445 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -319,7 +319,7 @@ management_callback_send_cc_message(void *arg, const char *command, const char * static unsigned int management_callback_remote_entry_count(void *arg) { - assert(arg); + ASSERT(arg); struct context *c = (struct context *)arg; struct connection_list *l = c->options.connection_list; @@ -329,8 +329,8 @@ management_callback_remote_entry_count(void *arg) static bool management_callback_remote_entry_get(void *arg, unsigned int index, char **remote) { - assert(arg); - assert(remote); + ASSERT(arg); + ASSERT(remote); struct context *c = (struct context *)arg; struct connection_list *l = c->options.connection_list; diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 0616a1760..6858a6902 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -3486,9 +3486,9 @@ tuntap_options_postprocess_dns(struct options *o) { /* Copy --dhcp-options to tuntap_options */ struct dhcp_options *dhcp = &dns->from_dhcp; - assert(sizeof(dhcp->dns) == sizeof(tt->dns)); - assert(sizeof(dhcp->dns6) == sizeof(tt->dns6)); - assert(sizeof(dhcp->domain_search_list) == sizeof(tt->domain_search_list)); + ASSERT(sizeof(dhcp->dns) == sizeof(tt->dns)); + ASSERT(sizeof(dhcp->dns6) == sizeof(tt->dns6)); + ASSERT(sizeof(dhcp->domain_search_list) == sizeof(tt->domain_search_list)); tt->domain = dhcp->domain; tt->dns_len = dhcp->dns_len; -- 2.47.3