From: mrbff (C. Review) <ge...@op...> - 2025-01-16 12:11:54
|
Attention is currently required from: flichtenheld, plaisthos. Hello plaisthos, flichtenheld, I'd like you to do a code review. Please visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email to review the following change. Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... 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, via cid, via common name, via address) and send a PUSH_UPDATE control message to update some options. Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Signed-off-by: Marco Baffo <ma...@ma...> --- M CMakeLists.txt M src/openvpn/manage.c M src/openvpn/manage.h M src/openvpn/multi.c M src/openvpn/multi.h M src/openvpn/push.h M src/openvpn/push_util.c M tests/unit_tests/openvpn/Makefile.am M tests/unit_tests/openvpn/test_push_update_msg.c 9 files changed, 729 insertions(+), 6 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/69/869/1 diff --git a/CMakeLists.txt b/CMakeLists.txt index dec3db4..a44134a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -818,6 +818,7 @@ 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/src/openvpn/manage.c b/src/openvpn/manage.c index 0c77f85..0bbf93c 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -24,7 +24,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif - #include "syshead.h" #ifdef ENABLE_MANAGEMENT @@ -42,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "push.h" #include "memdbg.h" @@ -124,6 +124,11 @@ 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, "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, "push-update-cn CN options : Send an update message to the client(s) with the specified Common Name."); + msg(M_CLIENT, "push-update-addr ip port options : Send an update message to the client(s) connecting from the provided address."); msg(M_CLIENT, "END"); } @@ -1327,6 +1332,154 @@ } static void +man_push_update(struct management *man, const char **p, const push_update_type type) +{ + if (type == UPT_BROADCAST) + { + if (!man->persist.callback.push_update_broadcast) + { + man_command_unsupported("push-update-broad"); + return; + } + + const bool status = (*man->persist.callback.push_update_broadcast)(man->persist.callback.arg, p[1]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-broad command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-broad command failed"); + } + } + 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; + } + + const bool status = (*man->persist.callback.push_update_by_cid)(man->persist.callback.arg, cid, p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cid command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cid command failed"); + } + } + else if (type == UPT_BY_CN) + { + if (!man->persist.callback.push_update_by_cn) + { + man_command_unsupported("push-update-cn"); + return; + } + + const bool status = (*man->persist.callback.push_update_by_cn)(man->persist.callback.arg, p[1], p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cn command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cn command failed"); + } + } + else if (type == UPT_BY_ADDR) + { + if (!man->persist.callback.push_update_by_addr) + { + man_command_unsupported("push-update-addr"); + return; + } + + const char *ip_str = p[1]; + const char *port_str = p[2]; + const char *options = p[3]; + + if (!strlen(ip_str) || !strlen(port_str)) + { + msg(M_CLIENT, "ERROR: push-update-addr parse"); + return; + } + + struct addrinfo *res = NULL; + int port = atoi(port_str); + + if (port < 1 || port > 65535) + { + msg(M_CLIENT, "ERROR: port number is out of range: %s", port_str); + return; + } + + int status = openvpn_getaddrinfo(GETADDR_MSG_VIRT_OUT, ip_str, port_str, 0, NULL, AF_UNSPEC, &res); + + if (status != 0 || !res) + { + msg(M_CLIENT, "ERROR: error resolving address: %s (%s)", ip_str, gai_strerror(status)); + return; + } + + struct addrinfo *rp; + bool found_client = false; + + /* Iterate through resolved addresses */ + for (rp = res; rp != NULL; rp = rp->ai_next) + { + struct openvpn_sockaddr saddr; + struct mroute_addr maddr; + + CLEAR(saddr); + switch (rp->ai_family) + { + case AF_INET: + saddr.addr.in4 = *((struct sockaddr_in *)rp->ai_addr); + break; + + case AF_INET6: + saddr.addr.in6 = *((struct sockaddr_in6 *)rp->ai_addr); + break; + + default: + continue; + } + + if (!mroute_extract_openvpn_sockaddr(&maddr, &saddr, true)) + { + continue; + } + + if ((*man->persist.callback.push_update_by_addr)(man->persist.callback.arg, &maddr, options)) + { + msg(M_CLIENT, "SUCCESS: push-update sent to %s:%d", ip_str, port); + found_client = true; + break; + } + } + + if (!found_client) + { + msg(M_CLIENT, "ERROR: no client found at address %s:%d", ip_str, port); + } + freeaddrinfo(res); + } +} + +static void man_dispatch_command(struct management *man, struct status_output *so, const char **p, const int nparms) { struct gc_arena gc = gc_new(); @@ -1648,6 +1801,34 @@ 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); + } + } + else if (streq(p[0], "push-update-cn")) + { + if (man_need(man, p, 2, 0)) + { + man_push_update(man, p, UPT_BY_CN); + } + } + else if (streq(p[0], "push-update-addr")) + { + if (man_need(man, p, 3, 0)) + { + man_push_update(man, p, UPT_BY_ADDR); + } + } #if 1 else if (streq(p[0], "test")) { diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index f501543..8f463c6 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -44,7 +44,6 @@ #define MF_EXTERNAL_KEY_PSSPAD (1<<16) #define MF_EXTERNAL_KEY_DIGEST (1<<17) - #ifdef ENABLE_MANAGEMENT #include "misc.h" @@ -205,6 +204,10 @@ #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); + bool (*push_update_by_cn)(void *arg, const char *cn, const char *options); + bool (*push_update_by_addr)(void *arg, const struct mroute_addr *maddr, const char *options); }; /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 5b693f9..6a9ce83 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -3981,7 +3981,7 @@ } } -static struct multi_instance * +struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { if (m) @@ -4129,6 +4129,10 @@ 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; + cb.push_update_by_cn = management_callback_send_push_update_by_cn; + cb.push_update_by_addr = management_callback_send_push_update_by_addr; management_set_callback(management, &cb); } #endif /* ifdef ENABLE_MANAGEMENT */ diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 9b6834a..4bb6e21 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -714,5 +714,10 @@ */ 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 #endif /* MULTI_H */ diff --git a/src/openvpn/push.h b/src/openvpn/push.h index b930c6c..112a2a5 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -42,6 +42,16 @@ #define PUSH_OPT_TO_REMOVE (1<<0) #define PUSH_OPT_OPTIONAL (1<<1) +/* Push-update message sender modes */ +typedef enum { + UPT_BROADCAST = 0, + UPT_BY_ADDR = 1, + UPT_BY_CN = 2, +#ifdef ENABLE_MANAGEMENT + UPT_BY_CID = 3 +#endif +} push_update_type; + int process_incoming_push_request(struct context *c); /** @@ -135,4 +145,33 @@ void receive_auth_pending(struct context *c, const struct buffer *buffer); +/** + * @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); + +#ifdef ENABLE_MANAGEMENT + +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); + +bool management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options); + +bool management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, 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 b4d1e8b..69d7a9c 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -3,6 +3,8 @@ #endif #include "push.h" +#include "multi.h" +#include "ssl_verify.h" int process_incoming_push_update(struct context *c, @@ -42,3 +44,247 @@ return ret; } + +/** + * 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; +} + +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(char *str, char **mesgs, struct gc_arena *gc, const int safe_cap) +{ + if (!str || !*str) + { + return false; + } + + int i = 0; + int im = 0; + + while (*str) + { + /* sizeof(push_update_cmd) + ',' - '/0' */ + if (strlen(str) + sizeof(push_update_cmd) > safe_cap) + { + int ci = find_first_comma_of_next_bundle(str, safe_cap - sizeof(push_update_cmd)); + 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) */ + mesgs[im] = gc_strdup(str, gc); + i = ci + 1; + } + else + { + mesgs[im] = gc_strdup(str, gc); + i = strlen(str); + } + str = &str[i]; + im++; + } + return true; +} + +/* It actually send the already divided messagge to one single client */ +static bool +send_single_push_update(struct context *c, char **msgs, struct buffer *buf, struct gc_arena *gc, const int push_bundle_size) +{ + if (!msgs[0] || !*msgs[0]) + { + return false; + } + else if (!msgs[1] || !*msgs[1]) + { + buf_printf(buf, "%s%c%s", push_update_cmd, ',', msgs[0]); + if (!send_control_channel_string(c, BSTR(buf), D_PUSH)) + { + return false; + } + } + else + { + int i = 0; + while (msgs[i] && *msgs[i]) + { + if (msgs[i+1]) + { + buf_printf(buf, "%s%c%s%s", push_update_cmd, ',', msgs[i], ",push-continuation 2"); + } + else + { + buf_printf(buf, "%s%c%s%s", push_update_cmd, ',', msgs[i], ",push-continuation 1"); + } + + if (!send_control_channel_string(c, BSTR(buf), D_PUSH)) + { + return false; + } + *buf = alloc_buf_gc(push_bundle_size, gc); + i++; + } + } + 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(); + struct buffer buf = alloc_buf_gc(push_bundle_size, &gc); + /* extra space for possible trailing ifconfig and push-continuation */ + const int extra = 84; + /* 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 = BCAP(&buf) - extra; + int mexnum = (strlen(msg) / (safe_cap - sizeof(push_update_cmd))) + 1; + char **msgs = gc_malloc(sizeof(char *) * (mexnum + 1), true, &gc); + + msgs[mexnum] = NULL; + if (!message_splitter(gc_strdup(msg, &gc), msgs, &gc, safe_cap)) + { + gc_free(&gc); + return -EINVAL; + } + +#ifdef ENABLE_MANAGEMENT + if (type == UPT_BY_CID) + { + struct multi_instance *mi = lookup_by_cid(m, *((unsigned long *)target)); + + if (!mi) + { + return -ENOENT; + } + if (!mi->halt + && send_single_push_update(&mi->context, msgs, &buf, &gc, push_bundle_size)) + { + gc_free(&gc); + return 1; + } + } +#endif + + 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; + } + if (type == UPT_BY_ADDR && !mroute_addr_equal(target, &curr_mi->real)) + { + continue; + } + else if (type == UPT_BY_CN) + { + const char *curr_cn = tls_common_name(curr_mi->context.c2.tls_multi, false); + if (strcmp(curr_cn, target)) + { + continue; + } + } + /* Either we found a matching client or type is UPT_BROADCAST so we update every client */ + if (!send_single_push_update(&curr_mi->context, msgs, &buf, &gc, push_bundle_size)) + { + 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; + } + count++; + } + + hash_iterator_free(&hi); + gc_free(&gc); + return count; +} + +#ifdef ENABLE_MANAGEMENT +#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 ret = send_push_update(arg, &cid, options, UPT_BY_CID, PUSH_BUNDLE_SIZE); + + if (ret == -ENOENT) + { + msg(M_CLIENT, "ERROR: no client found with CID: %lu", cid); + } + + return (ret > 0); +} + +bool +management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options) +{ + int n_sent = send_push_update(arg, cn, options, UPT_BY_CN, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} + +bool +management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, const char *options) +{ + int n_sent = send_push_update(arg, maddr, options, UPT_BY_ADDR, 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 651c5f6..529b77b 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -325,4 +325,5 @@ $(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 \ + $(top_srcdir)/src/openvpn/list.c \ No newline at end of file diff --git a/tests/unit_tests/openvpn/test_push_update_msg.c b/tests/unit_tests/openvpn/test_push_update_msg.c index d0876bc..1a9e093 100644 --- a/tests/unit_tests/openvpn/test_push_update_msg.c +++ b/tests/unit_tests/openvpn/test_push_update_msg.c @@ -8,6 +8,7 @@ #include <cmocka.h> #include "push.h" #include "options_util.h" +#include "multi.h" /* mocks */ @@ -94,6 +95,48 @@ } } +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)) + { + 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 @@ -124,7 +167,6 @@ free_buf(&buf); } - static void test_incoming_push_message_error2(void **state) { @@ -209,6 +251,194 @@ 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 = "Voilà! In view, a humble vaudevillian veteran cast vicariously as both victim and villain by the vicissitudes of Fate. This visage no mere veneer of vanity is a vestige of the vox populi now vacant vanished. However this valorous visitation of a by-gone vexation stands vivified and has vowed to vanquish these venal and virulent vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The only verdict is vengeance; a vendetta held as a votive not in vain for the value and veracity of such shall one day vindicate the vigilant and the virtuous. Verily this vichyssoise of verbiage veers most verbose so let me simply add that it is my very good honor to meet you and you may call me V."; + +#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) { @@ -238,7 +468,20 @@ 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); -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 1 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-MessageType: newchange |
From: mrbff (C. Review) <ge...@op...> - 2025-01-22 15:31:22
|
Attention is currently required from: flichtenheld, plaisthos. Hello flichtenheld, plaisthos, I'd like you to reexamine a change. Please visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email to look at the new patch set (#3). Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... 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, via cid, via common name, via address) and send a PUSH_UPDATE control message to update some options. Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Signed-off-by: Marco Baffo <ma...@ma...> --- M CMakeLists.txt M src/openvpn/manage.c M src/openvpn/manage.h M src/openvpn/multi.c M src/openvpn/multi.h M src/openvpn/push.h M src/openvpn/push_util.c M tests/unit_tests/openvpn/Makefile.am M tests/unit_tests/openvpn/test_push_update_msg.c 9 files changed, 730 insertions(+), 6 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/69/869/3 diff --git a/CMakeLists.txt b/CMakeLists.txt index dec3db4..a44134a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -818,6 +818,7 @@ 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/src/openvpn/manage.c b/src/openvpn/manage.c index 0c77f85..0bbf93c 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -24,7 +24,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif - #include "syshead.h" #ifdef ENABLE_MANAGEMENT @@ -42,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "push.h" #include "memdbg.h" @@ -124,6 +124,11 @@ 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, "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, "push-update-cn CN options : Send an update message to the client(s) with the specified Common Name."); + msg(M_CLIENT, "push-update-addr ip port options : Send an update message to the client(s) connecting from the provided address."); msg(M_CLIENT, "END"); } @@ -1327,6 +1332,154 @@ } static void +man_push_update(struct management *man, const char **p, const push_update_type type) +{ + if (type == UPT_BROADCAST) + { + if (!man->persist.callback.push_update_broadcast) + { + man_command_unsupported("push-update-broad"); + return; + } + + const bool status = (*man->persist.callback.push_update_broadcast)(man->persist.callback.arg, p[1]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-broad command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-broad command failed"); + } + } + 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; + } + + const bool status = (*man->persist.callback.push_update_by_cid)(man->persist.callback.arg, cid, p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cid command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cid command failed"); + } + } + else if (type == UPT_BY_CN) + { + if (!man->persist.callback.push_update_by_cn) + { + man_command_unsupported("push-update-cn"); + return; + } + + const bool status = (*man->persist.callback.push_update_by_cn)(man->persist.callback.arg, p[1], p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cn command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cn command failed"); + } + } + else if (type == UPT_BY_ADDR) + { + if (!man->persist.callback.push_update_by_addr) + { + man_command_unsupported("push-update-addr"); + return; + } + + const char *ip_str = p[1]; + const char *port_str = p[2]; + const char *options = p[3]; + + if (!strlen(ip_str) || !strlen(port_str)) + { + msg(M_CLIENT, "ERROR: push-update-addr parse"); + return; + } + + struct addrinfo *res = NULL; + int port = atoi(port_str); + + if (port < 1 || port > 65535) + { + msg(M_CLIENT, "ERROR: port number is out of range: %s", port_str); + return; + } + + int status = openvpn_getaddrinfo(GETADDR_MSG_VIRT_OUT, ip_str, port_str, 0, NULL, AF_UNSPEC, &res); + + if (status != 0 || !res) + { + msg(M_CLIENT, "ERROR: error resolving address: %s (%s)", ip_str, gai_strerror(status)); + return; + } + + struct addrinfo *rp; + bool found_client = false; + + /* Iterate through resolved addresses */ + for (rp = res; rp != NULL; rp = rp->ai_next) + { + struct openvpn_sockaddr saddr; + struct mroute_addr maddr; + + CLEAR(saddr); + switch (rp->ai_family) + { + case AF_INET: + saddr.addr.in4 = *((struct sockaddr_in *)rp->ai_addr); + break; + + case AF_INET6: + saddr.addr.in6 = *((struct sockaddr_in6 *)rp->ai_addr); + break; + + default: + continue; + } + + if (!mroute_extract_openvpn_sockaddr(&maddr, &saddr, true)) + { + continue; + } + + if ((*man->persist.callback.push_update_by_addr)(man->persist.callback.arg, &maddr, options)) + { + msg(M_CLIENT, "SUCCESS: push-update sent to %s:%d", ip_str, port); + found_client = true; + break; + } + } + + if (!found_client) + { + msg(M_CLIENT, "ERROR: no client found at address %s:%d", ip_str, port); + } + freeaddrinfo(res); + } +} + +static void man_dispatch_command(struct management *man, struct status_output *so, const char **p, const int nparms) { struct gc_arena gc = gc_new(); @@ -1648,6 +1801,34 @@ 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); + } + } + else if (streq(p[0], "push-update-cn")) + { + if (man_need(man, p, 2, 0)) + { + man_push_update(man, p, UPT_BY_CN); + } + } + else if (streq(p[0], "push-update-addr")) + { + if (man_need(man, p, 3, 0)) + { + man_push_update(man, p, UPT_BY_ADDR); + } + } #if 1 else if (streq(p[0], "test")) { diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index f501543..8f463c6 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -44,7 +44,6 @@ #define MF_EXTERNAL_KEY_PSSPAD (1<<16) #define MF_EXTERNAL_KEY_DIGEST (1<<17) - #ifdef ENABLE_MANAGEMENT #include "misc.h" @@ -205,6 +204,10 @@ #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); + bool (*push_update_by_cn)(void *arg, const char *cn, const char *options); + bool (*push_update_by_addr)(void *arg, const struct mroute_addr *maddr, const char *options); }; /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 5b693f9..6a9ce83 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -3981,7 +3981,7 @@ } } -static struct multi_instance * +struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { if (m) @@ -4129,6 +4129,10 @@ 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; + cb.push_update_by_cn = management_callback_send_push_update_by_cn; + cb.push_update_by_addr = management_callback_send_push_update_by_addr; management_set_callback(management, &cb); } #endif /* ifdef ENABLE_MANAGEMENT */ diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 9b6834a..4bb6e21 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -714,5 +714,10 @@ */ 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 #endif /* MULTI_H */ diff --git a/src/openvpn/push.h b/src/openvpn/push.h index b930c6c..112a2a5 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -42,6 +42,16 @@ #define PUSH_OPT_TO_REMOVE (1<<0) #define PUSH_OPT_OPTIONAL (1<<1) +/* Push-update message sender modes */ +typedef enum { + UPT_BROADCAST = 0, + UPT_BY_ADDR = 1, + UPT_BY_CN = 2, +#ifdef ENABLE_MANAGEMENT + UPT_BY_CID = 3 +#endif +} push_update_type; + int process_incoming_push_request(struct context *c); /** @@ -135,4 +145,33 @@ void receive_auth_pending(struct context *c, const struct buffer *buffer); +/** + * @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); + +#ifdef ENABLE_MANAGEMENT + +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); + +bool management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options); + +bool management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, 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 b4d1e8b..c84426d 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -3,6 +3,8 @@ #endif #include "push.h" +#include "multi.h" +#include "ssl_verify.h" int process_incoming_push_update(struct context *c, @@ -42,3 +44,248 @@ return ret; } + +/** + * 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; +} + +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(char *str, char **mesgs, struct gc_arena *gc, const int safe_cap) +{ + if (!str || !*str) + { + return false; + } + + int i = 0; + int im = 0; + + while (*str) + { + /* sizeof(push_update_cmd) + ',' - '/0' */ + if (strlen(str) + sizeof(push_update_cmd) > safe_cap) + { + int ci = find_first_comma_of_next_bundle(str, safe_cap - sizeof(push_update_cmd)); + 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) */ + mesgs[im] = gc_strdup(str, gc); + i = ci + 1; + } + else + { + mesgs[im] = gc_strdup(str, gc); + i = strlen(str); + } + str = &str[i]; + im++; + } + return true; +} + +/* It actually send the already divided messagge to one single client */ +static bool +send_single_push_update(struct context *c, char **msgs, struct buffer *buf, struct gc_arena *gc, const int push_bundle_size) +{ + if (!msgs[0] || !*msgs[0]) + { + return false; + } + else if (!msgs[1] || !*msgs[1]) + { + buf_printf(buf, "%s%c%s", push_update_cmd, ',', msgs[0]); + if (!send_control_channel_string(c, BSTR(buf), D_PUSH)) + { + return false; + } + } + else + { + int i = 0; + while (msgs[i] && *msgs[i]) + { + if (msgs[i+1]) + { + buf_printf(buf, "%s%c%s%s", push_update_cmd, ',', msgs[i], ",push-continuation 2"); + } + else + { + buf_printf(buf, "%s%c%s%s", push_update_cmd, ',', msgs[i], ",push-continuation 1"); + } + + if (!send_control_channel_string(c, BSTR(buf), D_PUSH)) + { + return false; + } + *buf = alloc_buf_gc(push_bundle_size, gc); + i++; + } + } + 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(); + struct buffer buf = alloc_buf_gc(push_bundle_size, &gc); + /* extra space for possible trailing ifconfig and push-continuation */ + const int extra = 84; + /* 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 = BCAP(&buf) - extra; + int mexnum = (strlen(msg) / (safe_cap - sizeof(push_update_cmd))) + 1; + char **msgs = gc_malloc(sizeof(char *) * (mexnum + 1), true, &gc); + + msgs[mexnum] = NULL; + if (!message_splitter(gc_strdup(msg, &gc), msgs, &gc, safe_cap)) + { + gc_free(&gc); + return -EINVAL; + } + +#ifdef ENABLE_MANAGEMENT + if (type == UPT_BY_CID) + { + struct multi_instance *mi = lookup_by_cid(m, *((unsigned long *)target)); + + if (!mi) + { + return -ENOENT; + } + if (!mi->halt + && send_single_push_update(&mi->context, msgs, &buf, &gc, push_bundle_size)) + { + gc_free(&gc); + return 1; + } + } +#endif + + 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; + + buf_clear(&buf); + if (curr_mi->halt) + { + continue; + } + if (type == UPT_BY_ADDR && !mroute_addr_equal(target, &curr_mi->real)) + { + continue; + } + else if (type == UPT_BY_CN) + { + const char *curr_cn = tls_common_name(curr_mi->context.c2.tls_multi, false); + if (strcmp(curr_cn, target)) + { + continue; + } + } + /* Either we found a matching client or type is UPT_BROADCAST so we update every client */ + if (!send_single_push_update(&curr_mi->context, msgs, &buf, &gc, push_bundle_size)) + { + 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; + } + count++; + } + + hash_iterator_free(&hi); + gc_free(&gc); + return count; +} + +#ifdef ENABLE_MANAGEMENT +#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 ret = send_push_update(arg, &cid, options, UPT_BY_CID, PUSH_BUNDLE_SIZE); + + if (ret == -ENOENT) + { + msg(M_CLIENT, "ERROR: no client found with CID: %lu", cid); + } + + return (ret > 0); +} + +bool +management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options) +{ + int n_sent = send_push_update(arg, cn, options, UPT_BY_CN, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} + +bool +management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, const char *options) +{ + int n_sent = send_push_update(arg, maddr, options, UPT_BY_ADDR, 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 651c5f6..529b77b 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -325,4 +325,5 @@ $(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 \ + $(top_srcdir)/src/openvpn/list.c \ No newline at end of file diff --git a/tests/unit_tests/openvpn/test_push_update_msg.c b/tests/unit_tests/openvpn/test_push_update_msg.c index d0876bc..1a9e093 100644 --- a/tests/unit_tests/openvpn/test_push_update_msg.c +++ b/tests/unit_tests/openvpn/test_push_update_msg.c @@ -8,6 +8,7 @@ #include <cmocka.h> #include "push.h" #include "options_util.h" +#include "multi.h" /* mocks */ @@ -94,6 +95,48 @@ } } +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)) + { + 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 @@ -124,7 +167,6 @@ free_buf(&buf); } - static void test_incoming_push_message_error2(void **state) { @@ -209,6 +251,194 @@ 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 = "Voilà! In view, a humble vaudevillian veteran cast vicariously as both victim and villain by the vicissitudes of Fate. This visage no mere veneer of vanity is a vestige of the vox populi now vacant vanished. However this valorous visitation of a by-gone vexation stands vivified and has vowed to vanquish these venal and virulent vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The only verdict is vengeance; a vendetta held as a votive not in vain for the value and veracity of such shall one day vindicate the vigilant and the virtuous. Verily this vichyssoise of verbiage veers most verbose so let me simply add that it is my very good honor to meet you and you may call me V."; + +#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) { @@ -238,7 +468,20 @@ 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); -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 3 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-MessageType: newpatchset |
From: mrbff (C. Review) <ge...@op...> - 2025-01-23 15:17:10
|
Attention is currently required from: flichtenheld, plaisthos. Hello flichtenheld, plaisthos, I'd like you to reexamine a change. Please visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email to look at the new patch set (#4). Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... 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, via cid, via common name, via address) and send a PUSH_UPDATE control message to update some options. Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Signed-off-by: Marco Baffo <ma...@ma...> --- M CMakeLists.txt M src/openvpn/manage.c M src/openvpn/manage.h M src/openvpn/multi.c M src/openvpn/multi.h M src/openvpn/push.h M src/openvpn/push_util.c M tests/unit_tests/openvpn/Makefile.am M tests/unit_tests/openvpn/test_push_update_msg.c 9 files changed, 740 insertions(+), 6 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/69/869/4 diff --git a/CMakeLists.txt b/CMakeLists.txt index dec3db4..a44134a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -818,6 +818,7 @@ 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/src/openvpn/manage.c b/src/openvpn/manage.c index 0c77f85..0bbf93c 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -24,7 +24,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif - #include "syshead.h" #ifdef ENABLE_MANAGEMENT @@ -42,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "push.h" #include "memdbg.h" @@ -124,6 +124,11 @@ 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, "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, "push-update-cn CN options : Send an update message to the client(s) with the specified Common Name."); + msg(M_CLIENT, "push-update-addr ip port options : Send an update message to the client(s) connecting from the provided address."); msg(M_CLIENT, "END"); } @@ -1327,6 +1332,154 @@ } static void +man_push_update(struct management *man, const char **p, const push_update_type type) +{ + if (type == UPT_BROADCAST) + { + if (!man->persist.callback.push_update_broadcast) + { + man_command_unsupported("push-update-broad"); + return; + } + + const bool status = (*man->persist.callback.push_update_broadcast)(man->persist.callback.arg, p[1]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-broad command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-broad command failed"); + } + } + 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; + } + + const bool status = (*man->persist.callback.push_update_by_cid)(man->persist.callback.arg, cid, p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cid command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cid command failed"); + } + } + else if (type == UPT_BY_CN) + { + if (!man->persist.callback.push_update_by_cn) + { + man_command_unsupported("push-update-cn"); + return; + } + + const bool status = (*man->persist.callback.push_update_by_cn)(man->persist.callback.arg, p[1], p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cn command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cn command failed"); + } + } + else if (type == UPT_BY_ADDR) + { + if (!man->persist.callback.push_update_by_addr) + { + man_command_unsupported("push-update-addr"); + return; + } + + const char *ip_str = p[1]; + const char *port_str = p[2]; + const char *options = p[3]; + + if (!strlen(ip_str) || !strlen(port_str)) + { + msg(M_CLIENT, "ERROR: push-update-addr parse"); + return; + } + + struct addrinfo *res = NULL; + int port = atoi(port_str); + + if (port < 1 || port > 65535) + { + msg(M_CLIENT, "ERROR: port number is out of range: %s", port_str); + return; + } + + int status = openvpn_getaddrinfo(GETADDR_MSG_VIRT_OUT, ip_str, port_str, 0, NULL, AF_UNSPEC, &res); + + if (status != 0 || !res) + { + msg(M_CLIENT, "ERROR: error resolving address: %s (%s)", ip_str, gai_strerror(status)); + return; + } + + struct addrinfo *rp; + bool found_client = false; + + /* Iterate through resolved addresses */ + for (rp = res; rp != NULL; rp = rp->ai_next) + { + struct openvpn_sockaddr saddr; + struct mroute_addr maddr; + + CLEAR(saddr); + switch (rp->ai_family) + { + case AF_INET: + saddr.addr.in4 = *((struct sockaddr_in *)rp->ai_addr); + break; + + case AF_INET6: + saddr.addr.in6 = *((struct sockaddr_in6 *)rp->ai_addr); + break; + + default: + continue; + } + + if (!mroute_extract_openvpn_sockaddr(&maddr, &saddr, true)) + { + continue; + } + + if ((*man->persist.callback.push_update_by_addr)(man->persist.callback.arg, &maddr, options)) + { + msg(M_CLIENT, "SUCCESS: push-update sent to %s:%d", ip_str, port); + found_client = true; + break; + } + } + + if (!found_client) + { + msg(M_CLIENT, "ERROR: no client found at address %s:%d", ip_str, port); + } + freeaddrinfo(res); + } +} + +static void man_dispatch_command(struct management *man, struct status_output *so, const char **p, const int nparms) { struct gc_arena gc = gc_new(); @@ -1648,6 +1801,34 @@ 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); + } + } + else if (streq(p[0], "push-update-cn")) + { + if (man_need(man, p, 2, 0)) + { + man_push_update(man, p, UPT_BY_CN); + } + } + else if (streq(p[0], "push-update-addr")) + { + if (man_need(man, p, 3, 0)) + { + man_push_update(man, p, UPT_BY_ADDR); + } + } #if 1 else if (streq(p[0], "test")) { diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index f501543..8f463c6 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -44,7 +44,6 @@ #define MF_EXTERNAL_KEY_PSSPAD (1<<16) #define MF_EXTERNAL_KEY_DIGEST (1<<17) - #ifdef ENABLE_MANAGEMENT #include "misc.h" @@ -205,6 +204,10 @@ #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); + bool (*push_update_by_cn)(void *arg, const char *cn, const char *options); + bool (*push_update_by_addr)(void *arg, const struct mroute_addr *maddr, const char *options); }; /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 5b693f9..6a9ce83 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -3981,7 +3981,7 @@ } } -static struct multi_instance * +struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { if (m) @@ -4129,6 +4129,10 @@ 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; + cb.push_update_by_cn = management_callback_send_push_update_by_cn; + cb.push_update_by_addr = management_callback_send_push_update_by_addr; management_set_callback(management, &cb); } #endif /* ifdef ENABLE_MANAGEMENT */ diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 9b6834a..4bb6e21 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -714,5 +714,10 @@ */ 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 #endif /* MULTI_H */ diff --git a/src/openvpn/push.h b/src/openvpn/push.h index b930c6c..112a2a5 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -42,6 +42,16 @@ #define PUSH_OPT_TO_REMOVE (1<<0) #define PUSH_OPT_OPTIONAL (1<<1) +/* Push-update message sender modes */ +typedef enum { + UPT_BROADCAST = 0, + UPT_BY_ADDR = 1, + UPT_BY_CN = 2, +#ifdef ENABLE_MANAGEMENT + UPT_BY_CID = 3 +#endif +} push_update_type; + int process_incoming_push_request(struct context *c); /** @@ -135,4 +145,33 @@ void receive_auth_pending(struct context *c, const struct buffer *buffer); +/** + * @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); + +#ifdef ENABLE_MANAGEMENT + +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); + +bool management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options); + +bool management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, 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 b4d1e8b..d79bf98 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -3,6 +3,8 @@ #endif #include "push.h" +#include "multi.h" +#include "ssl_verify.h" int process_incoming_push_update(struct context *c, @@ -42,3 +44,257 @@ return ret; } + +/** + * 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 asseble the final message */ +static char * +forge_msg(const char *src, const char *continuation, struct gc_arena *gc) +{ + int src_len = strlen(src); + int con_len = continuation ? strlen(continuation) : 0; + char *ret = gc_malloc((src_len + sizeof(push_update_cmd) + con_len + 2) * sizeof(char), true, gc); + int i = sizeof(push_update_cmd) -1; + + strcpy(ret, push_update_cmd); + ret[i++] = ','; + strcpy(&ret[i], src); + if (continuation) + { + i += src_len; + strcpy(&ret[i], continuation); + } + return ret; +} + +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(char *str, char **msgs, struct gc_arena *gc, const int safe_cap) +{ + if (!str || !*str) + { + return false; + } + + 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; +} + +/* It actually send the already divided messagge to one single client */ +static bool +send_single_push_update(struct context *c, char **msgs) +{ + if (!msgs[0] || !*msgs[0]) + { + return false; + } + int i = 0; + while (msgs[i] && *msgs[i]) + { + if (!send_control_channel_string(c, msgs[i], D_PUSH)) + { + return false; + } + i++; + } + 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); + char **msgs = gc_malloc(sizeof(char *) * (msgs_num + 1), true, &gc); + + msgs[msgs_num] = NULL; + if (!message_splitter(gc_strdup(msg, &gc), msgs, &gc, safe_cap)) + { + gc_free(&gc); + return -EINVAL; + } + +#ifdef ENABLE_MANAGEMENT + if (type == UPT_BY_CID) + { + struct multi_instance *mi = lookup_by_cid(m, *((unsigned long *)target)); + + if (!mi) + { + return -ENOENT; + } + if (!mi->halt + && send_single_push_update(&mi->context, msgs)) + { + gc_free(&gc); + return 1; + } + else + { + gc_free(&gc); + return 0; + } + } +#endif /* ifdef ENABLE_MANAGEMENT */ + + 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; + } + if (type == UPT_BY_ADDR && !mroute_addr_equal(target, &curr_mi->real)) + { + continue; + } + else if (type == UPT_BY_CN) + { + const char *curr_cn = tls_common_name(curr_mi->context.c2.tls_multi, false); + if (strcmp(curr_cn, target)) + { + continue; + } + } + /* Either we found a matching client or type is UPT_BROADCAST so we update every client */ + if (!send_single_push_update(&curr_mi->context, msgs)) + { + 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; + } + count++; + } + + hash_iterator_free(&hi); + gc_free(&gc); + return count; +} + +#ifdef ENABLE_MANAGEMENT +#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 ret = send_push_update(arg, &cid, options, UPT_BY_CID, PUSH_BUNDLE_SIZE); + + if (ret == -ENOENT) + { + msg(M_CLIENT, "ERROR: no client found with CID: %lu", cid); + } + + return (ret > 0); +} + +bool +management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options) +{ + int n_sent = send_push_update(arg, cn, options, UPT_BY_CN, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} + +bool +management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, const char *options) +{ + int n_sent = send_push_update(arg, maddr, options, UPT_BY_ADDR, 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 651c5f6..529b77b 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -325,4 +325,5 @@ $(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 \ + $(top_srcdir)/src/openvpn/list.c \ No newline at end of file diff --git a/tests/unit_tests/openvpn/test_push_update_msg.c b/tests/unit_tests/openvpn/test_push_update_msg.c index d0876bc..c29ac42 100644 --- a/tests/unit_tests/openvpn/test_push_update_msg.c +++ b/tests/unit_tests/openvpn/test_push_update_msg.c @@ -8,6 +8,7 @@ #include <cmocka.h> #include "push.h" #include "options_util.h" +#include "multi.h" /* mocks */ @@ -94,6 +95,49 @@ } } +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 @@ -124,7 +168,6 @@ free_buf(&buf); } - static void test_incoming_push_message_error2(void **state) { @@ -209,6 +252,194 @@ 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 = "Voilà! In view, a humble vaudevillian veteran cast vicariously as both victim and villain by the vicissitudes of Fate. This visage no mere veneer of vanity is a vestige of the vox populi now vacant vanished. However this valorous visitation of a by-gone vexation stands vivified and has vowed to vanquish these venal and virulent vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The only verdict is vengeance; a vendetta held as a votive not in vain for the value and veracity of such shall one day vindicate the vigilant and the virtuous. Verily this vichyssoise of verbiage veers most verbose so let me simply add that it is my very good honor to meet you and you may call me V."; + +#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) { @@ -238,7 +469,20 @@ 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); -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 4 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-MessageType: newpatchset |
From: cron2 (C. Review) <ge...@op...> - 2025-05-18 22:18:31
|
Attention is currently required from: flichtenheld, mrbff, plaisthos. cron2 has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/869?usp=email ) Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... Patch Set 5: Code-Review-1 (1 comment) Patchset: PS5: This needs a patch to doc/management-notes.txt and the super-long text blob in test_push_update_msg.c needs to be wrapped to multiple lines ``` "foo bar " "and so on " "the saga goes" ``` -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 5 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: mrbff <ma...@ma...> Gerrit-Comment-Date: Sun, 18 May 2025 22:18:22 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: Yes Gerrit-MessageType: comment |
From: mrbff (C. Review) <ge...@op...> - 2025-05-20 09:39:10
|
Attention is currently required from: cron2, flichtenheld, mrbff, plaisthos. Hello cron2, flichtenheld, plaisthos, I'd like you to reexamine a change. Please visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email to look at the new patch set (#6). The following approvals got outdated and were removed: Code-Review-1 by cron2 Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... 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, via cid, via common name, via address) and send a PUSH_UPDATE control message to update some options. Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Signed-off-by: Marco Baffo <ma...@ma...> --- M CMakeLists.txt M doc/management-notes.txt M src/openvpn/manage.c M src/openvpn/manage.h M src/openvpn/multi.c M src/openvpn/multi.h M src/openvpn/push.h M src/openvpn/push_util.c M tests/unit_tests/openvpn/Makefile.am M tests/unit_tests/openvpn/test_push_update_msg.c 10 files changed, 796 insertions(+), 6 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/69/869/6 diff --git a/CMakeLists.txt b/CMakeLists.txt index f597a89..185a312 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -832,6 +832,7 @@ 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 f1d2930..58393da 100644 --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -1028,6 +1028,51 @@ 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" + +COMMAND -- push-update-cn (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target the clients based on the provided common name +(usually just one client per common name is permitted except if "duplicate-cn" option is used). + +Example + push-update-cid Client0 "route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400" + +COMMAND -- push-update-addr (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target only the client(s) connecting from the +provided address (real address). Support both IPv4 and IPv6. + +Example + push-update-addr 9.9.9.9 1234 "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 0e73942..43d2097 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -24,7 +24,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif - #include "syshead.h" #ifdef ENABLE_MANAGEMENT @@ -42,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "push.h" #include "memdbg.h" @@ -124,6 +124,11 @@ 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, "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, "push-update-cn CN options : Send an update message to the client(s) with the specified Common Name."); + msg(M_CLIENT, "push-update-addr ip port options : Send an update message to the client(s) connecting from the provided address."); msg(M_CLIENT, "END"); } @@ -1334,6 +1339,154 @@ } static void +man_push_update(struct management *man, const char **p, const push_update_type type) +{ + if (type == UPT_BROADCAST) + { + if (!man->persist.callback.push_update_broadcast) + { + man_command_unsupported("push-update-broad"); + return; + } + + const bool status = (*man->persist.callback.push_update_broadcast)(man->persist.callback.arg, p[1]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-broad command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-broad command failed"); + } + } + 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; + } + + const bool status = (*man->persist.callback.push_update_by_cid)(man->persist.callback.arg, cid, p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cid command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cid command failed"); + } + } + else if (type == UPT_BY_CN) + { + if (!man->persist.callback.push_update_by_cn) + { + man_command_unsupported("push-update-cn"); + return; + } + + const bool status = (*man->persist.callback.push_update_by_cn)(man->persist.callback.arg, p[1], p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cn command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cn command failed"); + } + } + else if (type == UPT_BY_ADDR) + { + if (!man->persist.callback.push_update_by_addr) + { + man_command_unsupported("push-update-addr"); + return; + } + + const char *ip_str = p[1]; + const char *port_str = p[2]; + const char *options = p[3]; + + if (!strlen(ip_str) || !strlen(port_str)) + { + msg(M_CLIENT, "ERROR: push-update-addr parse"); + return; + } + + struct addrinfo *res = NULL; + int port = atoi(port_str); + + if (port < 1 || port > 65535) + { + msg(M_CLIENT, "ERROR: port number is out of range: %s", port_str); + return; + } + + int status = openvpn_getaddrinfo(GETADDR_MSG_VIRT_OUT, ip_str, port_str, 0, NULL, AF_UNSPEC, &res); + + if (status != 0 || !res) + { + msg(M_CLIENT, "ERROR: error resolving address: %s (%s)", ip_str, gai_strerror(status)); + return; + } + + struct addrinfo *rp; + bool found_client = false; + + /* Iterate through resolved addresses */ + for (rp = res; rp != NULL; rp = rp->ai_next) + { + struct openvpn_sockaddr saddr; + struct mroute_addr maddr; + + CLEAR(saddr); + switch (rp->ai_family) + { + case AF_INET: + saddr.addr.in4 = *((struct sockaddr_in *)rp->ai_addr); + break; + + case AF_INET6: + saddr.addr.in6 = *((struct sockaddr_in6 *)rp->ai_addr); + break; + + default: + continue; + } + + if (!mroute_extract_openvpn_sockaddr(&maddr, &saddr, true)) + { + continue; + } + + if ((*man->persist.callback.push_update_by_addr)(man->persist.callback.arg, &maddr, options)) + { + msg(M_CLIENT, "SUCCESS: push-update sent to %s:%d", ip_str, port); + found_client = true; + break; + } + } + + if (!found_client) + { + msg(M_CLIENT, "ERROR: no client found at address %s:%d", ip_str, port); + } + freeaddrinfo(res); + } +} + +static void man_dispatch_command(struct management *man, struct status_output *so, const char **p, const int nparms) { struct gc_arena gc = gc_new(); @@ -1655,6 +1808,34 @@ 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); + } + } + else if (streq(p[0], "push-update-cn")) + { + if (man_need(man, p, 2, 0)) + { + man_push_update(man, p, UPT_BY_CN); + } + } + else if (streq(p[0], "push-update-addr")) + { + if (man_need(man, p, 3, 0)) + { + man_push_update(man, p, UPT_BY_ADDR); + } + } #if 1 else if (streq(p[0], "test")) { diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index 02ceb82..aab0760 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -44,7 +44,6 @@ #define MF_EXTERNAL_KEY_PSSPAD (1<<16) #define MF_EXTERNAL_KEY_DIGEST (1<<17) - #ifdef ENABLE_MANAGEMENT #include "misc.h" @@ -205,6 +204,10 @@ #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); + bool (*push_update_by_cn)(void *arg, const char *cn, const char *options); + bool (*push_update_by_addr)(void *arg, const struct mroute_addr *maddr, const char *options); }; /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index ec04369..2a8b725 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -4062,7 +4062,7 @@ } } -static struct multi_instance * +struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { if (m) @@ -4210,6 +4210,10 @@ 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; + cb.push_update_by_cn = management_callback_send_push_update_by_cn; + cb.push_update_by_addr = management_callback_send_push_update_by_addr; management_set_callback(management, &cb); } #endif /* ifdef ENABLE_MANAGEMENT */ diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index e14ad60..b78bfc9 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -710,5 +710,10 @@ */ 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 #endif /* MULTI_H */ diff --git a/src/openvpn/push.h b/src/openvpn/push.h index 4643c36..6127012 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -42,6 +42,16 @@ #define PUSH_OPT_TO_REMOVE (1<<0) #define PUSH_OPT_OPTIONAL (1<<1) +/* Push-update message sender modes */ +typedef enum { + UPT_BROADCAST = 0, + UPT_BY_ADDR = 1, + UPT_BY_CN = 2, +#ifdef ENABLE_MANAGEMENT + UPT_BY_CID = 3 +#endif +} push_update_type; + int process_incoming_push_request(struct context *c); /** @@ -134,4 +144,33 @@ void receive_auth_pending(struct context *c, const struct buffer *buffer); +/** + * @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); + +#ifdef ENABLE_MANAGEMENT + +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); + +bool management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options); + +bool management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, 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 b4d1e8b..d79bf98 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -3,6 +3,8 @@ #endif #include "push.h" +#include "multi.h" +#include "ssl_verify.h" int process_incoming_push_update(struct context *c, @@ -42,3 +44,257 @@ return ret; } + +/** + * 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 asseble the final message */ +static char * +forge_msg(const char *src, const char *continuation, struct gc_arena *gc) +{ + int src_len = strlen(src); + int con_len = continuation ? strlen(continuation) : 0; + char *ret = gc_malloc((src_len + sizeof(push_update_cmd) + con_len + 2) * sizeof(char), true, gc); + int i = sizeof(push_update_cmd) -1; + + strcpy(ret, push_update_cmd); + ret[i++] = ','; + strcpy(&ret[i], src); + if (continuation) + { + i += src_len; + strcpy(&ret[i], continuation); + } + return ret; +} + +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(char *str, char **msgs, struct gc_arena *gc, const int safe_cap) +{ + if (!str || !*str) + { + return false; + } + + 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; +} + +/* It actually send the already divided messagge to one single client */ +static bool +send_single_push_update(struct context *c, char **msgs) +{ + if (!msgs[0] || !*msgs[0]) + { + return false; + } + int i = 0; + while (msgs[i] && *msgs[i]) + { + if (!send_control_channel_string(c, msgs[i], D_PUSH)) + { + return false; + } + i++; + } + 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); + char **msgs = gc_malloc(sizeof(char *) * (msgs_num + 1), true, &gc); + + msgs[msgs_num] = NULL; + if (!message_splitter(gc_strdup(msg, &gc), msgs, &gc, safe_cap)) + { + gc_free(&gc); + return -EINVAL; + } + +#ifdef ENABLE_MANAGEMENT + if (type == UPT_BY_CID) + { + struct multi_instance *mi = lookup_by_cid(m, *((unsigned long *)target)); + + if (!mi) + { + return -ENOENT; + } + if (!mi->halt + && send_single_push_update(&mi->context, msgs)) + { + gc_free(&gc); + return 1; + } + else + { + gc_free(&gc); + return 0; + } + } +#endif /* ifdef ENABLE_MANAGEMENT */ + + 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; + } + if (type == UPT_BY_ADDR && !mroute_addr_equal(target, &curr_mi->real)) + { + continue; + } + else if (type == UPT_BY_CN) + { + const char *curr_cn = tls_common_name(curr_mi->context.c2.tls_multi, false); + if (strcmp(curr_cn, target)) + { + continue; + } + } + /* Either we found a matching client or type is UPT_BROADCAST so we update every client */ + if (!send_single_push_update(&curr_mi->context, msgs)) + { + 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; + } + count++; + } + + hash_iterator_free(&hi); + gc_free(&gc); + return count; +} + +#ifdef ENABLE_MANAGEMENT +#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 ret = send_push_update(arg, &cid, options, UPT_BY_CID, PUSH_BUNDLE_SIZE); + + if (ret == -ENOENT) + { + msg(M_CLIENT, "ERROR: no client found with CID: %lu", cid); + } + + return (ret > 0); +} + +bool +management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options) +{ + int n_sent = send_push_update(arg, cn, options, UPT_BY_CN, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} + +bool +management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, const char *options) +{ + int n_sent = send_push_update(arg, maddr, options, UPT_BY_ADDR, 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 6fb7843..ef9c359 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -331,4 +331,5 @@ $(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 \ + $(top_srcdir)/src/openvpn/list.c \ No newline at end of file diff --git a/tests/unit_tests/openvpn/test_push_update_msg.c b/tests/unit_tests/openvpn/test_push_update_msg.c index d0876bc..549520b 100644 --- a/tests/unit_tests/openvpn/test_push_update_msg.c +++ b/tests/unit_tests/openvpn/test_push_update_msg.c @@ -8,6 +8,7 @@ #include <cmocka.h> #include "push.h" #include "options_util.h" +#include "multi.h" /* mocks */ @@ -94,6 +95,49 @@ } } +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 @@ -124,7 +168,6 @@ free_buf(&buf); } - static void test_incoming_push_message_error2(void **state) { @@ -209,6 +252,205 @@ 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 = "Voilà! In view, a humble vaudevillian veteran cast vicariously as both victim and villain by the vicissitudes" + " of Fate. This visage no mere veneer of vanity is a vestige of the vox populi now vacant vanished. However this" + " valorous visitation of a by-gone vexation stands vivified and has vowed to vanquish these venal and virulent" + " vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The only" + " verdict is vengeance; a vendetta held as a votive not in vain for the value and veracity of such shall one" + " day vindicate the vigilant and the virtuous. Verily this vichyssoise of verbiage veers most verbose so let" + " me simply add that it is my very good honor to meet you and you may call me V."; + +#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) { @@ -238,7 +480,20 @@ 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); -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 6 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: mrbff <ma...@ma...> Gerrit-MessageType: newpatchset |
From: stipa (C. Review) <ge...@op...> - 2025-07-03 13:19:43
|
Attention is currently required from: cron2, flichtenheld, mrbff, plaisthos. stipa has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/869?usp=email ) Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... Patch Set 7: Code-Review-1 (1 comment) Patchset: PS7: I did some testing and indeed I was able to send PUSH_UPDATE messages to the client. However it doesn't change the state on the server - for example, after pushing new client IP address we got broken tunnel because server rejects packets with unknown source address. Are there plans to implement the second part too in 2.7 or later? At the moment this implementation is not that useful. -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 7 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: mrbff <ma...@ma...> Gerrit-Comment-Date: Thu, 03 Jul 2025 13:19:28 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: Yes Gerrit-MessageType: comment |
From: mrbff (C. Review) <ge...@op...> - 2025-07-07 17:16:14
|
Attention is currently required from: cron2, flichtenheld, mrbff, plaisthos, stipa. Hello cron2, flichtenheld, plaisthos, stipa, I'd like you to reexamine a change. Please visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email to look at the new patch set (#8). The following approvals got outdated and were removed: Code-Review-1 by stipa Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... 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, via cid, via common name, via address) and send a PUSH_UPDATE control message to update some options. Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Signed-off-by: Marco Baffo <ma...@ma...> --- M CMakeLists.txt M doc/management-notes.txt M src/openvpn/manage.c M src/openvpn/manage.h M src/openvpn/multi.c M src/openvpn/multi.h M src/openvpn/push.h M src/openvpn/push_util.c M tests/unit_tests/openvpn/Makefile.am M tests/unit_tests/openvpn/test_push_update_msg.c 10 files changed, 824 insertions(+), 6 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/69/869/8 diff --git a/CMakeLists.txt b/CMakeLists.txt index 54cf503..1381e03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -861,6 +861,7 @@ 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 f1d2930..58393da 100644 --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -1028,6 +1028,51 @@ 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" + +COMMAND -- push-update-cn (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target the clients based on the provided common name +(usually just one client per common name is permitted except if "duplicate-cn" option is used). + +Example + push-update-cid Client0 "route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400" + +COMMAND -- push-update-addr (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target only the client(s) connecting from the +provided address (real address). Support both IPv4 and IPv6. + +Example + push-update-addr 9.9.9.9 1234 "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 8836e79..251b076 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -24,7 +24,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif - #include "syshead.h" #ifdef ENABLE_MANAGEMENT @@ -42,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "push.h" #include "memdbg.h" @@ -124,6 +124,11 @@ 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, "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, "push-update-cn CN options : Send an update message to the client(s) with the specified Common Name."); + msg(M_CLIENT, "push-update-addr ip port options : Send an update message to the client(s) connecting from the provided address."); msg(M_CLIENT, "END"); } @@ -1335,6 +1340,154 @@ } static void +man_push_update(struct management *man, const char **p, const push_update_type type) +{ + if (type == UPT_BROADCAST) + { + if (!man->persist.callback.push_update_broadcast) + { + man_command_unsupported("push-update-broad"); + return; + } + + const bool status = (*man->persist.callback.push_update_broadcast)(man->persist.callback.arg, p[1]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-broad command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-broad command failed"); + } + } + 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; + } + + const bool status = (*man->persist.callback.push_update_by_cid)(man->persist.callback.arg, cid, p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cid command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cid command failed"); + } + } + else if (type == UPT_BY_CN) + { + if (!man->persist.callback.push_update_by_cn) + { + man_command_unsupported("push-update-cn"); + return; + } + + const bool status = (*man->persist.callback.push_update_by_cn)(man->persist.callback.arg, p[1], p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cn command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cn command failed"); + } + } + else if (type == UPT_BY_ADDR) + { + if (!man->persist.callback.push_update_by_addr) + { + man_command_unsupported("push-update-addr"); + return; + } + + const char *ip_str = p[1]; + const char *port_str = p[2]; + const char *options = p[3]; + + if (!strlen(ip_str) || !strlen(port_str)) + { + msg(M_CLIENT, "ERROR: push-update-addr parse"); + return; + } + + struct addrinfo *res = NULL; + int port = atoi(port_str); + + if (port < 1 || port > 65535) + { + msg(M_CLIENT, "ERROR: port number is out of range: %s", port_str); + return; + } + + int status = openvpn_getaddrinfo(GETADDR_MSG_VIRT_OUT, ip_str, port_str, 0, NULL, AF_UNSPEC, &res); + + if (status != 0 || !res) + { + msg(M_CLIENT, "ERROR: error resolving address: %s (%s)", ip_str, gai_strerror(status)); + return; + } + + struct addrinfo *rp; + bool found_client = false; + + /* Iterate through resolved addresses */ + for (rp = res; rp != NULL; rp = rp->ai_next) + { + struct openvpn_sockaddr saddr; + struct mroute_addr maddr; + + CLEAR(saddr); + switch (rp->ai_family) + { + case AF_INET: + saddr.addr.in4 = *((struct sockaddr_in *)rp->ai_addr); + break; + + case AF_INET6: + saddr.addr.in6 = *((struct sockaddr_in6 *)rp->ai_addr); + break; + + default: + continue; + } + + if (!mroute_extract_openvpn_sockaddr(&maddr, &saddr, true)) + { + continue; + } + + if ((*man->persist.callback.push_update_by_addr)(man->persist.callback.arg, &maddr, options)) + { + msg(M_CLIENT, "SUCCESS: push-update sent to %s:%d", ip_str, port); + found_client = true; + break; + } + } + + if (!found_client) + { + msg(M_CLIENT, "ERROR: no client found at address %s:%d", ip_str, port); + } + freeaddrinfo(res); + } +} + +static void man_dispatch_command(struct management *man, struct status_output *so, const char **p, const int nparms) { struct gc_arena gc = gc_new(); @@ -1656,6 +1809,34 @@ 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); + } + } + else if (streq(p[0], "push-update-cn")) + { + if (man_need(man, p, 2, 0)) + { + man_push_update(man, p, UPT_BY_CN); + } + } + else if (streq(p[0], "push-update-addr")) + { + if (man_need(man, p, 3, 0)) + { + man_push_update(man, p, UPT_BY_ADDR); + } + } #if 1 else if (streq(p[0], "test")) { diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index eb19a4e..fd7cb11 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -44,7 +44,6 @@ #define MF_EXTERNAL_KEY_PSSPAD (1<<16) #define MF_EXTERNAL_KEY_DIGEST (1<<17) - #ifdef ENABLE_MANAGEMENT #include "misc.h" @@ -205,6 +204,10 @@ #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); + bool (*push_update_by_cn)(void *arg, const char *cn, const char *options); + bool (*push_update_by_addr)(void *arg, const struct mroute_addr *maddr, const char *options); }; /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 7f0d890..3daf358 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -4072,7 +4072,7 @@ } } -static struct multi_instance * +struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { if (m) @@ -4220,6 +4220,10 @@ 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; + cb.push_update_by_cn = management_callback_send_push_update_by_cn; + cb.push_update_by_addr = management_callback_send_push_update_by_addr; management_set_callback(management, &cb); } #endif /* ifdef ENABLE_MANAGEMENT */ diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 40f7519..1e965a4 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -710,5 +710,10 @@ */ 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 #endif /* MULTI_H */ diff --git a/src/openvpn/push.h b/src/openvpn/push.h index 18dfcd8..e67c3ac 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -42,6 +42,16 @@ #define PUSH_OPT_TO_REMOVE (1<<0) #define PUSH_OPT_OPTIONAL (1<<1) +/* Push-update message sender modes */ +typedef enum { + UPT_BROADCAST = 0, + UPT_BY_ADDR = 1, + UPT_BY_CN = 2, +#ifdef ENABLE_MANAGEMENT + UPT_BY_CID = 3 +#endif +} push_update_type; + int process_incoming_push_request(struct context *c); /** @@ -134,4 +144,33 @@ void receive_auth_pending(struct context *c, const struct buffer *buffer); +/** + * @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); + +#ifdef ENABLE_MANAGEMENT + +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); + +bool management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options); + +bool management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, 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 b4d1e8b..28b303e 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -3,6 +3,8 @@ #endif #include "push.h" +#include "multi.h" +#include "ssl_verify.h" int process_incoming_push_update(struct context *c, @@ -42,3 +44,279 @@ return ret; } + +/** + * 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 asseble the final message */ +static char * +forge_msg(const char *src, const char *continuation, struct gc_arena *gc) +{ + int src_len = strlen(src); + int con_len = continuation ? strlen(continuation) : 0; + char *ret = gc_malloc((src_len + sizeof(push_update_cmd) + con_len + 2) * sizeof(char), true, gc); + int i = sizeof(push_update_cmd) -1; + + strcpy(ret, push_update_cmd); + ret[i++] = ','; + strcpy(&ret[i], src); + if (continuation) + { + i += src_len; + strcpy(&ret[i], continuation); + } + return ret; +} + +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(char *str, char **msgs, struct gc_arena *gc, const int safe_cap) +{ + if (!str || !*str) + { + return false; + } + + 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; +} + +/* It actually send the already divided messagge to one single client */ +static bool +send_single_push_update(struct context *c, char **msgs) +{ + if (!msgs[0] || !*msgs[0]) + { + return false; + } + int i = 0; + struct gc_arena gc = gc_new(); + + while (msgs[i] && *msgs[i]) + { + unsigned int option_types_found = 0; + struct buffer buf = alloc_buf_gc(strlen(msgs[i]), &gc); + + buf_write(&buf, msgs[i], strlen(msgs[i])); + if (!send_control_channel_string(c, msgs[i], D_PUSH)) + { + return false; + } + i++; + + /* After sending the control message, we update the options server-side in the client's context */ + buf_string_compare_advance(&buf, push_update_cmd); + if (process_incoming_push_update(c, pull_permission_mask(c), &option_types_found, &buf) == 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); + } + } + gc_free(&gc); + 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); + char **msgs = gc_malloc(sizeof(char *) * (msgs_num + 1), true, &gc); + + msgs[msgs_num] = NULL; + if (!message_splitter(gc_strdup(msg, &gc), msgs, &gc, safe_cap)) + { + gc_free(&gc); + return -EINVAL; + } + +#ifdef ENABLE_MANAGEMENT + if (type == UPT_BY_CID) + { + struct multi_instance *mi = lookup_by_cid(m, *((unsigned long *)target)); + + if (!mi) + { + return -ENOENT; + } + if (!mi->halt + && send_single_push_update(&mi->context, msgs)) + { + gc_free(&gc); + return 1; + } + else + { + gc_free(&gc); + return 0; + } + } +#endif /* ifdef ENABLE_MANAGEMENT */ + + 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; + } + if (type == UPT_BY_ADDR && !mroute_addr_equal(target, &curr_mi->real)) + { + continue; + } + else if (type == UPT_BY_CN) + { + const char *curr_cn = tls_common_name(curr_mi->context.c2.tls_multi, false); + if (strcmp(curr_cn, target)) + { + continue; + } + } + /* Either we found a matching client or type is UPT_BROADCAST so we update every client */ + if (!send_single_push_update(&curr_mi->context, msgs)) + { + 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; + } + count++; + } + + hash_iterator_free(&hi); + gc_free(&gc); + return count; +} + +#ifdef ENABLE_MANAGEMENT +#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 ret = send_push_update(arg, &cid, options, UPT_BY_CID, PUSH_BUNDLE_SIZE); + + if (ret == -ENOENT) + { + msg(M_CLIENT, "ERROR: no client found with CID: %lu", cid); + } + + return (ret > 0); +} + +bool +management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options) +{ + int n_sent = send_push_update(arg, cn, options, UPT_BY_CN, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} + +bool +management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, const char *options) +{ + int n_sent = send_push_update(arg, maddr, options, UPT_BY_ADDR, 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 b24e03c..9a40512 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -343,4 +343,5 @@ $(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 \ + $(top_srcdir)/src/openvpn/list.c \ No newline at end of file diff --git a/tests/unit_tests/openvpn/test_push_update_msg.c b/tests/unit_tests/openvpn/test_push_update_msg.c index d0876bc..8420510 100644 --- a/tests/unit_tests/openvpn/test_push_update_msg.c +++ b/tests/unit_tests/openvpn/test_push_update_msg.c @@ -8,6 +8,7 @@ #include <cmocka.h> #include "push.h" #include "options_util.h" +#include "multi.h" /* mocks */ @@ -37,6 +38,12 @@ } 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, @@ -94,6 +101,49 @@ } } +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 @@ -124,7 +174,6 @@ free_buf(&buf); } - static void test_incoming_push_message_error2(void **state) { @@ -209,6 +258,205 @@ 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 = "Voilà! In view, a humble vaudevillian veteran cast vicariously as both victim and villain by the vicissitudes" + " of Fate. This visage no mere veneer of vanity is a vestige of the vox populi now vacant vanished. However this" + " valorous visitation of a by-gone vexation stands vivified and has vowed to vanquish these venal and virulent" + " vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The only" + " verdict is vengeance; a vendetta held as a votive not in vain for the value and veracity of such shall one" + " day vindicate the vigilant and the virtuous. Verily this vichyssoise of verbiage veers most verbose so let" + " me simply add that it is my very good honor to meet you and you may call me V."; + +#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) { @@ -238,7 +486,20 @@ 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); -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 8 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: stipa <lst...@gm...> Gerrit-Attention: mrbff <ma...@ma...> Gerrit-MessageType: newpatchset |
From: mrbff (C. Review) <ge...@op...> - 2025-07-09 16:30:07
|
Attention is currently required from: cron2, flichtenheld, mrbff, plaisthos, stipa. Hello cron2, flichtenheld, plaisthos, stipa, I'd like you to reexamine a change. Please visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email to look at the new patch set (#9). Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... 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, via cid, via common name, via address) and send a PUSH_UPDATE control message to update some options. Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Signed-off-by: Marco Baffo <ma...@ma...> --- M CMakeLists.txt M doc/management-notes.txt M src/openvpn/manage.c M src/openvpn/manage.h M src/openvpn/multi.c M src/openvpn/multi.h M src/openvpn/push.h M src/openvpn/push_util.c M tests/unit_tests/openvpn/Makefile.am M tests/unit_tests/openvpn/test_push_update_msg.c 10 files changed, 895 insertions(+), 6 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/69/869/9 diff --git a/CMakeLists.txt b/CMakeLists.txt index 54cf503..1381e03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -861,6 +861,7 @@ 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 f1d2930..58393da 100644 --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -1028,6 +1028,51 @@ 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" + +COMMAND -- push-update-cn (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target the clients based on the provided common name +(usually just one client per common name is permitted except if "duplicate-cn" option is used). + +Example + push-update-cid Client0 "route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400" + +COMMAND -- push-update-addr (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target only the client(s) connecting from the +provided address (real address). Support both IPv4 and IPv6. + +Example + push-update-addr 9.9.9.9 1234 "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 8836e79..251b076 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -24,7 +24,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif - #include "syshead.h" #ifdef ENABLE_MANAGEMENT @@ -42,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "push.h" #include "memdbg.h" @@ -124,6 +124,11 @@ 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, "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, "push-update-cn CN options : Send an update message to the client(s) with the specified Common Name."); + msg(M_CLIENT, "push-update-addr ip port options : Send an update message to the client(s) connecting from the provided address."); msg(M_CLIENT, "END"); } @@ -1335,6 +1340,154 @@ } static void +man_push_update(struct management *man, const char **p, const push_update_type type) +{ + if (type == UPT_BROADCAST) + { + if (!man->persist.callback.push_update_broadcast) + { + man_command_unsupported("push-update-broad"); + return; + } + + const bool status = (*man->persist.callback.push_update_broadcast)(man->persist.callback.arg, p[1]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-broad command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-broad command failed"); + } + } + 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; + } + + const bool status = (*man->persist.callback.push_update_by_cid)(man->persist.callback.arg, cid, p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cid command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cid command failed"); + } + } + else if (type == UPT_BY_CN) + { + if (!man->persist.callback.push_update_by_cn) + { + man_command_unsupported("push-update-cn"); + return; + } + + const bool status = (*man->persist.callback.push_update_by_cn)(man->persist.callback.arg, p[1], p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cn command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cn command failed"); + } + } + else if (type == UPT_BY_ADDR) + { + if (!man->persist.callback.push_update_by_addr) + { + man_command_unsupported("push-update-addr"); + return; + } + + const char *ip_str = p[1]; + const char *port_str = p[2]; + const char *options = p[3]; + + if (!strlen(ip_str) || !strlen(port_str)) + { + msg(M_CLIENT, "ERROR: push-update-addr parse"); + return; + } + + struct addrinfo *res = NULL; + int port = atoi(port_str); + + if (port < 1 || port > 65535) + { + msg(M_CLIENT, "ERROR: port number is out of range: %s", port_str); + return; + } + + int status = openvpn_getaddrinfo(GETADDR_MSG_VIRT_OUT, ip_str, port_str, 0, NULL, AF_UNSPEC, &res); + + if (status != 0 || !res) + { + msg(M_CLIENT, "ERROR: error resolving address: %s (%s)", ip_str, gai_strerror(status)); + return; + } + + struct addrinfo *rp; + bool found_client = false; + + /* Iterate through resolved addresses */ + for (rp = res; rp != NULL; rp = rp->ai_next) + { + struct openvpn_sockaddr saddr; + struct mroute_addr maddr; + + CLEAR(saddr); + switch (rp->ai_family) + { + case AF_INET: + saddr.addr.in4 = *((struct sockaddr_in *)rp->ai_addr); + break; + + case AF_INET6: + saddr.addr.in6 = *((struct sockaddr_in6 *)rp->ai_addr); + break; + + default: + continue; + } + + if (!mroute_extract_openvpn_sockaddr(&maddr, &saddr, true)) + { + continue; + } + + if ((*man->persist.callback.push_update_by_addr)(man->persist.callback.arg, &maddr, options)) + { + msg(M_CLIENT, "SUCCESS: push-update sent to %s:%d", ip_str, port); + found_client = true; + break; + } + } + + if (!found_client) + { + msg(M_CLIENT, "ERROR: no client found at address %s:%d", ip_str, port); + } + freeaddrinfo(res); + } +} + +static void man_dispatch_command(struct management *man, struct status_output *so, const char **p, const int nparms) { struct gc_arena gc = gc_new(); @@ -1656,6 +1809,34 @@ 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); + } + } + else if (streq(p[0], "push-update-cn")) + { + if (man_need(man, p, 2, 0)) + { + man_push_update(man, p, UPT_BY_CN); + } + } + else if (streq(p[0], "push-update-addr")) + { + if (man_need(man, p, 3, 0)) + { + man_push_update(man, p, UPT_BY_ADDR); + } + } #if 1 else if (streq(p[0], "test")) { diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index eb19a4e..fd7cb11 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -44,7 +44,6 @@ #define MF_EXTERNAL_KEY_PSSPAD (1<<16) #define MF_EXTERNAL_KEY_DIGEST (1<<17) - #ifdef ENABLE_MANAGEMENT #include "misc.h" @@ -205,6 +204,10 @@ #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); + bool (*push_update_by_cn)(void *arg, const char *cn, const char *options); + bool (*push_update_by_addr)(void *arg, const struct mroute_addr *maddr, const char *options); }; /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 7f0d890..3a4851d 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -4072,7 +4072,7 @@ } } -static struct multi_instance * +struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { if (m) @@ -4220,6 +4220,10 @@ 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; + cb.push_update_by_cn = management_callback_send_push_update_by_cn; + cb.push_update_by_addr = management_callback_send_push_update_by_addr; management_set_callback(management, &cb); } #endif /* ifdef ENABLE_MANAGEMENT */ @@ -4344,3 +4348,51 @@ close_instance(top); } + +#ifdef ENABLE_MANAGEMENT +/** + * 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. + */ +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 (inet_pton(AF_INET, mi->context.options.ifconfig_local, &addr) == 1) + { + in_addr_t new_ip = addr.s_addr; + + if (mi->context.options.ifconfig_local && (!old_ip || strcmp(old_ip, mi->context.options.ifconfig_local))) + { + /* Add new, remove old if exist */ + multi_learn_in_addr_t(m, mi, new_ip, 0, true); + } + } + + /* TO DO: + * else if (old_ip && !mi->context.options.ifconfig_local) + * { + * // remove old ip + * } + */ + + if (inet_pton(AF_INET6, mi->context.options.ifconfig_ipv6_local, &new_ipv6) == 1) + { + if (mi->context.options.ifconfig_ipv6_local && (!old_ipv6 || strcmp(old_ipv6, mi->context.options.ifconfig_ipv6_local))) + { + /* Add new, remove old if exist */ + multi_learn_in6_addr(m, mi, new_ipv6, 0, true); + } + } + + /* TO DO: + * else if (old_ipv6 && !mi->context.options.ifconfig_ipv6_local) + * { + * // remove old IPv6 + * } + */ +} +#endif /* ifdef ENABLE_MANAGEMENT */ diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 40f7519..e0ed00f 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -710,5 +710,13 @@ */ 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); + +void +update_vhash(struct multi_context *m, struct multi_instance *mi, const char *old_ip, const char *old_ipv6); + +#endif #endif /* MULTI_H */ diff --git a/src/openvpn/push.h b/src/openvpn/push.h index 18dfcd8..e67c3ac 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -42,6 +42,16 @@ #define PUSH_OPT_TO_REMOVE (1<<0) #define PUSH_OPT_OPTIONAL (1<<1) +/* Push-update message sender modes */ +typedef enum { + UPT_BROADCAST = 0, + UPT_BY_ADDR = 1, + UPT_BY_CN = 2, +#ifdef ENABLE_MANAGEMENT + UPT_BY_CID = 3 +#endif +} push_update_type; + int process_incoming_push_request(struct context *c); /** @@ -134,4 +144,33 @@ void receive_auth_pending(struct context *c, const struct buffer *buffer); +/** + * @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); + +#ifdef ENABLE_MANAGEMENT + +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); + +bool management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options); + +bool management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, 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 b4d1e8b..1739510 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -3,6 +3,8 @@ #endif #include "push.h" +#include "multi.h" +#include "ssl_verify.h" int process_incoming_push_update(struct context *c, @@ -42,3 +44,293 @@ return ret; } + +/** + * 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 asseble the final message */ +static char * +forge_msg(const char *src, const char *continuation, struct gc_arena *gc) +{ + int src_len = strlen(src); + int con_len = continuation ? strlen(continuation) : 0; + char *ret = gc_malloc((src_len + sizeof(push_update_cmd) + con_len + 2) * sizeof(char), true, gc); + int i = sizeof(push_update_cmd) -1; + + strcpy(ret, push_update_cmd); + ret[i++] = ','; + strcpy(&ret[i], src); + if (continuation) + { + i += src_len; + strcpy(&ret[i], continuation); + } + return ret; +} + +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(char *str, char **msgs, struct gc_arena *gc, const int safe_cap) +{ + if (!str || !*str) + { + return false; + } + + 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; +} + +/* It actually send the already divided messagge to one single client */ +static bool +send_single_push_update(struct context *c, char **msgs, unsigned int *option_types_found) +{ + if (!msgs[0] || !*msgs[0]) + { + return false; + } + int i = 0; + struct gc_arena gc = gc_new(); + + while (msgs[i] && *msgs[i]) + { + struct buffer buf = alloc_buf_gc(strlen(msgs[i]), &gc); + + buf_write(&buf, msgs[i], strlen(msgs[i])); + if (!send_control_channel_string(c, msgs[i], D_PUSH)) + { + return false; + } + i++; + + /* After sending the control message, we update the options server-side in the client's context */ + buf_string_compare_advance(&buf, push_update_cmd); + if (process_incoming_push_update(c, pull_permission_mask(c), option_types_found, &buf) == 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); + } + } + gc_free(&gc); + 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); + char **msgs = gc_malloc(sizeof(char *) * (msgs_num + 1), true, &gc); + unsigned int option_types_found = 0; + + msgs[msgs_num] = NULL; + if (!message_splitter(gc_strdup(msg, &gc), msgs, &gc, safe_cap)) + { + gc_free(&gc); + return -EINVAL; + } + +#ifdef ENABLE_MANAGEMENT + 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; + } + } +#endif /* ifdef ENABLE_MANAGEMENT */ + + 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; + } + if (type == UPT_BY_ADDR && !mroute_addr_equal(target, &curr_mi->real)) + { + continue; + } + else if (type == UPT_BY_CN) + { + const char *curr_cn = tls_common_name(curr_mi->context.c2.tls_multi, false); + if (strcmp(curr_cn, target)) + { + continue; + } + } + /* Either we found a matching client or 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; +} + +#ifdef ENABLE_MANAGEMENT +#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 ret = send_push_update(arg, &cid, options, UPT_BY_CID, PUSH_BUNDLE_SIZE); + + if (ret == -ENOENT) + { + msg(M_CLIENT, "ERROR: no client found with CID: %lu", cid); + } + + return (ret > 0); +} + +bool +management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options) +{ + int n_sent = send_push_update(arg, cn, options, UPT_BY_CN, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} + +bool +management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, const char *options) +{ + int n_sent = send_push_update(arg, maddr, options, UPT_BY_ADDR, 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 b24e03c..9a40512 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -343,4 +343,5 @@ $(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 \ + $(top_srcdir)/src/openvpn/list.c \ No newline at end of file diff --git a/tests/unit_tests/openvpn/test_push_update_msg.c b/tests/unit_tests/openvpn/test_push_update_msg.c index d0876bc..c65dd08 100644 --- a/tests/unit_tests/openvpn/test_push_update_msg.c +++ b/tests/unit_tests/openvpn/test_push_update_msg.c @@ -8,6 +8,7 @@ #include <cmocka.h> #include "push.h" #include "options_util.h" +#include "multi.h" /* mocks */ @@ -37,6 +38,12 @@ } 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, @@ -94,6 +101,49 @@ } } +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 @@ -124,7 +174,6 @@ free_buf(&buf); } - static void test_incoming_push_message_error2(void **state) { @@ -209,6 +258,211 @@ 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 = "Voilà! In view, a humble vaudevillian veteran cast vicariously as both victim and villain by the vicissitudes" + " of Fate. This visage no mere veneer of vanity is a vestige of the vox populi now vacant vanished. However this" + " valorous visitation of a by-gone vexation stands vivified and has vowed to vanquish these venal and virulent" + " vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The only" + " verdict is vengeance; a vendetta held as a votive not in vain for the value and veracity of such shall one" + " day vindicate the vigilant and the virtuous. Verily this vichyssoise of verbiage veers most verbose so let" + " me simply add that it is my very good honor to meet you and you may call me V."; + +#define PUSH_BUNDLE_SIZE_TEST 184 + +void +update_vhash(struct multi_context *m, struct multi_instance *mi, const char *old_ip, const char *old_ipv6) +{ + /* Just a mock */ +} + +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) { @@ -238,7 +492,20 @@ 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); -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 9 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: stipa <lst...@gm...> Gerrit-Attention: mrbff <ma...@ma...> Gerrit-MessageType: newpatchset |
From: mrbff (C. Review) <ge...@op...> - 2025-07-10 08:11:34
|
Attention is currently required from: cron2, flichtenheld, mrbff, plaisthos, stipa. Hello cron2, flichtenheld, plaisthos, stipa, I'd like you to reexamine a change. Please visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email to look at the new patch set (#10). Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... 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, via cid, via common name, via address) and send a PUSH_UPDATE control message to update some options. Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Signed-off-by: Marco Baffo <ma...@ma...> --- M CMakeLists.txt M doc/management-notes.txt M src/openvpn/manage.c M src/openvpn/manage.h M src/openvpn/multi.c M src/openvpn/multi.h M src/openvpn/push.h M src/openvpn/push_util.c M tests/unit_tests/openvpn/Makefile.am M tests/unit_tests/openvpn/test_push_update_msg.c 10 files changed, 895 insertions(+), 6 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/69/869/10 diff --git a/CMakeLists.txt b/CMakeLists.txt index 54cf503..1381e03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -861,6 +861,7 @@ 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 f1d2930..58393da 100644 --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -1028,6 +1028,51 @@ 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" + +COMMAND -- push-update-cn (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target the clients based on the provided common name +(usually just one client per common name is permitted except if "duplicate-cn" option is used). + +Example + push-update-cid Client0 "route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400" + +COMMAND -- push-update-addr (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target only the client(s) connecting from the +provided address (real address). Support both IPv4 and IPv6. + +Example + push-update-addr 9.9.9.9 1234 "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 8836e79..251b076 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -24,7 +24,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif - #include "syshead.h" #ifdef ENABLE_MANAGEMENT @@ -42,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "push.h" #include "memdbg.h" @@ -124,6 +124,11 @@ 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, "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, "push-update-cn CN options : Send an update message to the client(s) with the specified Common Name."); + msg(M_CLIENT, "push-update-addr ip port options : Send an update message to the client(s) connecting from the provided address."); msg(M_CLIENT, "END"); } @@ -1335,6 +1340,154 @@ } static void +man_push_update(struct management *man, const char **p, const push_update_type type) +{ + if (type == UPT_BROADCAST) + { + if (!man->persist.callback.push_update_broadcast) + { + man_command_unsupported("push-update-broad"); + return; + } + + const bool status = (*man->persist.callback.push_update_broadcast)(man->persist.callback.arg, p[1]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-broad command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-broad command failed"); + } + } + 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; + } + + const bool status = (*man->persist.callback.push_update_by_cid)(man->persist.callback.arg, cid, p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cid command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cid command failed"); + } + } + else if (type == UPT_BY_CN) + { + if (!man->persist.callback.push_update_by_cn) + { + man_command_unsupported("push-update-cn"); + return; + } + + const bool status = (*man->persist.callback.push_update_by_cn)(man->persist.callback.arg, p[1], p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cn command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cn command failed"); + } + } + else if (type == UPT_BY_ADDR) + { + if (!man->persist.callback.push_update_by_addr) + { + man_command_unsupported("push-update-addr"); + return; + } + + const char *ip_str = p[1]; + const char *port_str = p[2]; + const char *options = p[3]; + + if (!strlen(ip_str) || !strlen(port_str)) + { + msg(M_CLIENT, "ERROR: push-update-addr parse"); + return; + } + + struct addrinfo *res = NULL; + int port = atoi(port_str); + + if (port < 1 || port > 65535) + { + msg(M_CLIENT, "ERROR: port number is out of range: %s", port_str); + return; + } + + int status = openvpn_getaddrinfo(GETADDR_MSG_VIRT_OUT, ip_str, port_str, 0, NULL, AF_UNSPEC, &res); + + if (status != 0 || !res) + { + msg(M_CLIENT, "ERROR: error resolving address: %s (%s)", ip_str, gai_strerror(status)); + return; + } + + struct addrinfo *rp; + bool found_client = false; + + /* Iterate through resolved addresses */ + for (rp = res; rp != NULL; rp = rp->ai_next) + { + struct openvpn_sockaddr saddr; + struct mroute_addr maddr; + + CLEAR(saddr); + switch (rp->ai_family) + { + case AF_INET: + saddr.addr.in4 = *((struct sockaddr_in *)rp->ai_addr); + break; + + case AF_INET6: + saddr.addr.in6 = *((struct sockaddr_in6 *)rp->ai_addr); + break; + + default: + continue; + } + + if (!mroute_extract_openvpn_sockaddr(&maddr, &saddr, true)) + { + continue; + } + + if ((*man->persist.callback.push_update_by_addr)(man->persist.callback.arg, &maddr, options)) + { + msg(M_CLIENT, "SUCCESS: push-update sent to %s:%d", ip_str, port); + found_client = true; + break; + } + } + + if (!found_client) + { + msg(M_CLIENT, "ERROR: no client found at address %s:%d", ip_str, port); + } + freeaddrinfo(res); + } +} + +static void man_dispatch_command(struct management *man, struct status_output *so, const char **p, const int nparms) { struct gc_arena gc = gc_new(); @@ -1656,6 +1809,34 @@ 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); + } + } + else if (streq(p[0], "push-update-cn")) + { + if (man_need(man, p, 2, 0)) + { + man_push_update(man, p, UPT_BY_CN); + } + } + else if (streq(p[0], "push-update-addr")) + { + if (man_need(man, p, 3, 0)) + { + man_push_update(man, p, UPT_BY_ADDR); + } + } #if 1 else if (streq(p[0], "test")) { diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index eb19a4e..fd7cb11 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -44,7 +44,6 @@ #define MF_EXTERNAL_KEY_PSSPAD (1<<16) #define MF_EXTERNAL_KEY_DIGEST (1<<17) - #ifdef ENABLE_MANAGEMENT #include "misc.h" @@ -205,6 +204,10 @@ #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); + bool (*push_update_by_cn)(void *arg, const char *cn, const char *options); + bool (*push_update_by_addr)(void *arg, const struct mroute_addr *maddr, const char *options); }; /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 7f0d890..67a7a79 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -4072,7 +4072,7 @@ } } -static struct multi_instance * +struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { if (m) @@ -4220,6 +4220,10 @@ 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; + cb.push_update_by_cn = management_callback_send_push_update_by_cn; + cb.push_update_by_addr = management_callback_send_push_update_by_addr; management_set_callback(management, &cb); } #endif /* ifdef ENABLE_MANAGEMENT */ @@ -4344,3 +4348,51 @@ 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 (inet_pton(AF_INET, mi->context.options.ifconfig_local, &addr) == 1) + { + in_addr_t new_ip = addr.s_addr; + + if (mi->context.options.ifconfig_local && (!old_ip || strcmp(old_ip, mi->context.options.ifconfig_local))) + { + /* Add new, remove old if exist */ + multi_learn_in_addr_t(m, mi, new_ip, 0, true); + } + } + + /* TO DO: + * else if (old_ip && !mi->context.options.ifconfig_local) + * { + * // remove old ip + * } + */ + + if (inet_pton(AF_INET6, mi->context.options.ifconfig_ipv6_local, &new_ipv6) == 1) + { + if (mi->context.options.ifconfig_ipv6_local && (!old_ipv6 || strcmp(old_ipv6, mi->context.options.ifconfig_ipv6_local))) + { + /* Add new, remove old if exist */ + multi_learn_in6_addr(m, mi, new_ipv6, 0, true); + } + } + + /* TO DO: + * else if (old_ipv6 && !mi->context.options.ifconfig_ipv6_local) + * { + * // remove old IPv6 + * } + */ +} diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 40f7519..4841075 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -710,5 +710,13 @@ */ 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/push.h b/src/openvpn/push.h index 18dfcd8..e67c3ac 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -42,6 +42,16 @@ #define PUSH_OPT_TO_REMOVE (1<<0) #define PUSH_OPT_OPTIONAL (1<<1) +/* Push-update message sender modes */ +typedef enum { + UPT_BROADCAST = 0, + UPT_BY_ADDR = 1, + UPT_BY_CN = 2, +#ifdef ENABLE_MANAGEMENT + UPT_BY_CID = 3 +#endif +} push_update_type; + int process_incoming_push_request(struct context *c); /** @@ -134,4 +144,33 @@ void receive_auth_pending(struct context *c, const struct buffer *buffer); +/** + * @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); + +#ifdef ENABLE_MANAGEMENT + +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); + +bool management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options); + +bool management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, 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 b4d1e8b..1739510 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -3,6 +3,8 @@ #endif #include "push.h" +#include "multi.h" +#include "ssl_verify.h" int process_incoming_push_update(struct context *c, @@ -42,3 +44,293 @@ return ret; } + +/** + * 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 asseble the final message */ +static char * +forge_msg(const char *src, const char *continuation, struct gc_arena *gc) +{ + int src_len = strlen(src); + int con_len = continuation ? strlen(continuation) : 0; + char *ret = gc_malloc((src_len + sizeof(push_update_cmd) + con_len + 2) * sizeof(char), true, gc); + int i = sizeof(push_update_cmd) -1; + + strcpy(ret, push_update_cmd); + ret[i++] = ','; + strcpy(&ret[i], src); + if (continuation) + { + i += src_len; + strcpy(&ret[i], continuation); + } + return ret; +} + +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(char *str, char **msgs, struct gc_arena *gc, const int safe_cap) +{ + if (!str || !*str) + { + return false; + } + + 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; +} + +/* It actually send the already divided messagge to one single client */ +static bool +send_single_push_update(struct context *c, char **msgs, unsigned int *option_types_found) +{ + if (!msgs[0] || !*msgs[0]) + { + return false; + } + int i = 0; + struct gc_arena gc = gc_new(); + + while (msgs[i] && *msgs[i]) + { + struct buffer buf = alloc_buf_gc(strlen(msgs[i]), &gc); + + buf_write(&buf, msgs[i], strlen(msgs[i])); + if (!send_control_channel_string(c, msgs[i], D_PUSH)) + { + return false; + } + i++; + + /* After sending the control message, we update the options server-side in the client's context */ + buf_string_compare_advance(&buf, push_update_cmd); + if (process_incoming_push_update(c, pull_permission_mask(c), option_types_found, &buf) == 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); + } + } + gc_free(&gc); + 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); + char **msgs = gc_malloc(sizeof(char *) * (msgs_num + 1), true, &gc); + unsigned int option_types_found = 0; + + msgs[msgs_num] = NULL; + if (!message_splitter(gc_strdup(msg, &gc), msgs, &gc, safe_cap)) + { + gc_free(&gc); + return -EINVAL; + } + +#ifdef ENABLE_MANAGEMENT + 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; + } + } +#endif /* ifdef ENABLE_MANAGEMENT */ + + 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; + } + if (type == UPT_BY_ADDR && !mroute_addr_equal(target, &curr_mi->real)) + { + continue; + } + else if (type == UPT_BY_CN) + { + const char *curr_cn = tls_common_name(curr_mi->context.c2.tls_multi, false); + if (strcmp(curr_cn, target)) + { + continue; + } + } + /* Either we found a matching client or 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; +} + +#ifdef ENABLE_MANAGEMENT +#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 ret = send_push_update(arg, &cid, options, UPT_BY_CID, PUSH_BUNDLE_SIZE); + + if (ret == -ENOENT) + { + msg(M_CLIENT, "ERROR: no client found with CID: %lu", cid); + } + + return (ret > 0); +} + +bool +management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options) +{ + int n_sent = send_push_update(arg, cn, options, UPT_BY_CN, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} + +bool +management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, const char *options) +{ + int n_sent = send_push_update(arg, maddr, options, UPT_BY_ADDR, 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 b24e03c..9a40512 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -343,4 +343,5 @@ $(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 \ + $(top_srcdir)/src/openvpn/list.c \ No newline at end of file diff --git a/tests/unit_tests/openvpn/test_push_update_msg.c b/tests/unit_tests/openvpn/test_push_update_msg.c index d0876bc..c65dd08 100644 --- a/tests/unit_tests/openvpn/test_push_update_msg.c +++ b/tests/unit_tests/openvpn/test_push_update_msg.c @@ -8,6 +8,7 @@ #include <cmocka.h> #include "push.h" #include "options_util.h" +#include "multi.h" /* mocks */ @@ -37,6 +38,12 @@ } 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, @@ -94,6 +101,49 @@ } } +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 @@ -124,7 +174,6 @@ free_buf(&buf); } - static void test_incoming_push_message_error2(void **state) { @@ -209,6 +258,211 @@ 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 = "Voilà! In view, a humble vaudevillian veteran cast vicariously as both victim and villain by the vicissitudes" + " of Fate. This visage no mere veneer of vanity is a vestige of the vox populi now vacant vanished. However this" + " valorous visitation of a by-gone vexation stands vivified and has vowed to vanquish these venal and virulent" + " vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The only" + " verdict is vengeance; a vendetta held as a votive not in vain for the value and veracity of such shall one" + " day vindicate the vigilant and the virtuous. Verily this vichyssoise of verbiage veers most verbose so let" + " me simply add that it is my very good honor to meet you and you may call me V."; + +#define PUSH_BUNDLE_SIZE_TEST 184 + +void +update_vhash(struct multi_context *m, struct multi_instance *mi, const char *old_ip, const char *old_ipv6) +{ + /* Just a mock */ +} + +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) { @@ -238,7 +492,20 @@ 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); -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 10 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: stipa <lst...@gm...> Gerrit-Attention: mrbff <ma...@ma...> Gerrit-MessageType: newpatchset |
From: flichtenheld (C. Review) <ge...@op...> - 2025-07-10 09:43:37
|
Attention is currently required from: cron2, mrbff, plaisthos, stipa. flichtenheld has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/869?usp=email ) Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... Patch Set 10: Code-Review-1 (1 comment) Patchset: PS10: Fails build with --disable-management -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 10 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: stipa <lst...@gm...> Gerrit-Attention: mrbff <ma...@ma...> Gerrit-Comment-Date: Thu, 10 Jul 2025 09:43:28 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: Yes Gerrit-MessageType: comment |
From: mrbff (C. Review) <ge...@op...> - 2025-07-10 13:28:40
|
Attention is currently required from: cron2, flichtenheld, mrbff, plaisthos, stipa. Hello cron2, flichtenheld, plaisthos, stipa, I'd like you to reexamine a change. Please visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email to look at the new patch set (#11). The following approvals got outdated and were removed: Code-Review-1 by flichtenheld Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... 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, via cid, via common name, via address) and send a PUSH_UPDATE control message to update some options. Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Signed-off-by: Marco Baffo <ma...@ma...> --- M CMakeLists.txt M doc/management-notes.txt M src/openvpn/manage.c M src/openvpn/manage.h M src/openvpn/multi.c M src/openvpn/multi.h M src/openvpn/push.h M src/openvpn/push_util.c M tests/unit_tests/openvpn/Makefile.am M tests/unit_tests/openvpn/test_push_update_msg.c 10 files changed, 893 insertions(+), 6 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/69/869/11 diff --git a/CMakeLists.txt b/CMakeLists.txt index 54cf503..1381e03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -861,6 +861,7 @@ 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 f1d2930..58393da 100644 --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -1028,6 +1028,51 @@ 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" + +COMMAND -- push-update-cn (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target the clients based on the provided common name +(usually just one client per common name is permitted except if "duplicate-cn" option is used). + +Example + push-update-cid Client0 "route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400" + +COMMAND -- push-update-addr (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target only the client(s) connecting from the +provided address (real address). Support both IPv4 and IPv6. + +Example + push-update-addr 9.9.9.9 1234 "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 8836e79..251b076 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -24,7 +24,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif - #include "syshead.h" #ifdef ENABLE_MANAGEMENT @@ -42,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "push.h" #include "memdbg.h" @@ -124,6 +124,11 @@ 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, "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, "push-update-cn CN options : Send an update message to the client(s) with the specified Common Name."); + msg(M_CLIENT, "push-update-addr ip port options : Send an update message to the client(s) connecting from the provided address."); msg(M_CLIENT, "END"); } @@ -1335,6 +1340,154 @@ } static void +man_push_update(struct management *man, const char **p, const push_update_type type) +{ + if (type == UPT_BROADCAST) + { + if (!man->persist.callback.push_update_broadcast) + { + man_command_unsupported("push-update-broad"); + return; + } + + const bool status = (*man->persist.callback.push_update_broadcast)(man->persist.callback.arg, p[1]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-broad command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-broad command failed"); + } + } + 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; + } + + const bool status = (*man->persist.callback.push_update_by_cid)(man->persist.callback.arg, cid, p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cid command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cid command failed"); + } + } + else if (type == UPT_BY_CN) + { + if (!man->persist.callback.push_update_by_cn) + { + man_command_unsupported("push-update-cn"); + return; + } + + const bool status = (*man->persist.callback.push_update_by_cn)(man->persist.callback.arg, p[1], p[2]); + + if (status) + { + msg(M_CLIENT, "SUCCESS: push-update-cn command succeeded"); + } + else + { + msg(M_CLIENT, "ERROR: push-update-cn command failed"); + } + } + else if (type == UPT_BY_ADDR) + { + if (!man->persist.callback.push_update_by_addr) + { + man_command_unsupported("push-update-addr"); + return; + } + + const char *ip_str = p[1]; + const char *port_str = p[2]; + const char *options = p[3]; + + if (!strlen(ip_str) || !strlen(port_str)) + { + msg(M_CLIENT, "ERROR: push-update-addr parse"); + return; + } + + struct addrinfo *res = NULL; + int port = atoi(port_str); + + if (port < 1 || port > 65535) + { + msg(M_CLIENT, "ERROR: port number is out of range: %s", port_str); + return; + } + + int status = openvpn_getaddrinfo(GETADDR_MSG_VIRT_OUT, ip_str, port_str, 0, NULL, AF_UNSPEC, &res); + + if (status != 0 || !res) + { + msg(M_CLIENT, "ERROR: error resolving address: %s (%s)", ip_str, gai_strerror(status)); + return; + } + + struct addrinfo *rp; + bool found_client = false; + + /* Iterate through resolved addresses */ + for (rp = res; rp != NULL; rp = rp->ai_next) + { + struct openvpn_sockaddr saddr; + struct mroute_addr maddr; + + CLEAR(saddr); + switch (rp->ai_family) + { + case AF_INET: + saddr.addr.in4 = *((struct sockaddr_in *)rp->ai_addr); + break; + + case AF_INET6: + saddr.addr.in6 = *((struct sockaddr_in6 *)rp->ai_addr); + break; + + default: + continue; + } + + if (!mroute_extract_openvpn_sockaddr(&maddr, &saddr, true)) + { + continue; + } + + if ((*man->persist.callback.push_update_by_addr)(man->persist.callback.arg, &maddr, options)) + { + msg(M_CLIENT, "SUCCESS: push-update sent to %s:%d", ip_str, port); + found_client = true; + break; + } + } + + if (!found_client) + { + msg(M_CLIENT, "ERROR: no client found at address %s:%d", ip_str, port); + } + freeaddrinfo(res); + } +} + +static void man_dispatch_command(struct management *man, struct status_output *so, const char **p, const int nparms) { struct gc_arena gc = gc_new(); @@ -1656,6 +1809,34 @@ 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); + } + } + else if (streq(p[0], "push-update-cn")) + { + if (man_need(man, p, 2, 0)) + { + man_push_update(man, p, UPT_BY_CN); + } + } + else if (streq(p[0], "push-update-addr")) + { + if (man_need(man, p, 3, 0)) + { + man_push_update(man, p, UPT_BY_ADDR); + } + } #if 1 else if (streq(p[0], "test")) { diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index eb19a4e..fd7cb11 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -44,7 +44,6 @@ #define MF_EXTERNAL_KEY_PSSPAD (1<<16) #define MF_EXTERNAL_KEY_DIGEST (1<<17) - #ifdef ENABLE_MANAGEMENT #include "misc.h" @@ -205,6 +204,10 @@ #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); + bool (*push_update_by_cn)(void *arg, const char *cn, const char *options); + bool (*push_update_by_addr)(void *arg, const struct mroute_addr *maddr, const char *options); }; /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 7f0d890..985af17 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -4072,7 +4072,7 @@ } } -static struct multi_instance * +struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { if (m) @@ -4220,6 +4220,10 @@ 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; + cb.push_update_by_cn = management_callback_send_push_update_by_cn; + cb.push_update_by_addr = management_callback_send_push_update_by_addr; management_set_callback(management, &cb); } #endif /* ifdef ENABLE_MANAGEMENT */ @@ -4344,3 +4348,47 @@ 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, remove old if exist */ + multi_learn_in_addr_t(m, mi, new_ip, 0, true); + } + + /* TO DO: + * else if (old_ip && !mi->context.options.ifconfig_local) + * { + * // 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, remove old if exist */ + multi_learn_in6_addr(m, mi, new_ipv6, 0, true); + } + + /* TO DO: + * else if (old_ipv6 && !mi->context.options.ifconfig_ipv6_local) + * { + * // remove old IPv6 + * } + */ +} diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 40f7519..4841075 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -710,5 +710,13 @@ */ 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/push.h b/src/openvpn/push.h index 18dfcd8..e67c3ac 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -42,6 +42,16 @@ #define PUSH_OPT_TO_REMOVE (1<<0) #define PUSH_OPT_OPTIONAL (1<<1) +/* Push-update message sender modes */ +typedef enum { + UPT_BROADCAST = 0, + UPT_BY_ADDR = 1, + UPT_BY_CN = 2, +#ifdef ENABLE_MANAGEMENT + UPT_BY_CID = 3 +#endif +} push_update_type; + int process_incoming_push_request(struct context *c); /** @@ -134,4 +144,33 @@ void receive_auth_pending(struct context *c, const struct buffer *buffer); +/** + * @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); + +#ifdef ENABLE_MANAGEMENT + +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); + +bool management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options); + +bool management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, 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 b4d1e8b..1739510 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -3,6 +3,8 @@ #endif #include "push.h" +#include "multi.h" +#include "ssl_verify.h" int process_incoming_push_update(struct context *c, @@ -42,3 +44,293 @@ return ret; } + +/** + * 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 asseble the final message */ +static char * +forge_msg(const char *src, const char *continuation, struct gc_arena *gc) +{ + int src_len = strlen(src); + int con_len = continuation ? strlen(continuation) : 0; + char *ret = gc_malloc((src_len + sizeof(push_update_cmd) + con_len + 2) * sizeof(char), true, gc); + int i = sizeof(push_update_cmd) -1; + + strcpy(ret, push_update_cmd); + ret[i++] = ','; + strcpy(&ret[i], src); + if (continuation) + { + i += src_len; + strcpy(&ret[i], continuation); + } + return ret; +} + +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(char *str, char **msgs, struct gc_arena *gc, const int safe_cap) +{ + if (!str || !*str) + { + return false; + } + + 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; +} + +/* It actually send the already divided messagge to one single client */ +static bool +send_single_push_update(struct context *c, char **msgs, unsigned int *option_types_found) +{ + if (!msgs[0] || !*msgs[0]) + { + return false; + } + int i = 0; + struct gc_arena gc = gc_new(); + + while (msgs[i] && *msgs[i]) + { + struct buffer buf = alloc_buf_gc(strlen(msgs[i]), &gc); + + buf_write(&buf, msgs[i], strlen(msgs[i])); + if (!send_control_channel_string(c, msgs[i], D_PUSH)) + { + return false; + } + i++; + + /* After sending the control message, we update the options server-side in the client's context */ + buf_string_compare_advance(&buf, push_update_cmd); + if (process_incoming_push_update(c, pull_permission_mask(c), option_types_found, &buf) == 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); + } + } + gc_free(&gc); + 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); + char **msgs = gc_malloc(sizeof(char *) * (msgs_num + 1), true, &gc); + unsigned int option_types_found = 0; + + msgs[msgs_num] = NULL; + if (!message_splitter(gc_strdup(msg, &gc), msgs, &gc, safe_cap)) + { + gc_free(&gc); + return -EINVAL; + } + +#ifdef ENABLE_MANAGEMENT + 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; + } + } +#endif /* ifdef ENABLE_MANAGEMENT */ + + 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; + } + if (type == UPT_BY_ADDR && !mroute_addr_equal(target, &curr_mi->real)) + { + continue; + } + else if (type == UPT_BY_CN) + { + const char *curr_cn = tls_common_name(curr_mi->context.c2.tls_multi, false); + if (strcmp(curr_cn, target)) + { + continue; + } + } + /* Either we found a matching client or 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; +} + +#ifdef ENABLE_MANAGEMENT +#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 ret = send_push_update(arg, &cid, options, UPT_BY_CID, PUSH_BUNDLE_SIZE); + + if (ret == -ENOENT) + { + msg(M_CLIENT, "ERROR: no client found with CID: %lu", cid); + } + + return (ret > 0); +} + +bool +management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options) +{ + int n_sent = send_push_update(arg, cn, options, UPT_BY_CN, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} + +bool +management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, const char *options) +{ + int n_sent = send_push_update(arg, maddr, options, UPT_BY_ADDR, 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 b24e03c..9a40512 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -343,4 +343,5 @@ $(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 \ + $(top_srcdir)/src/openvpn/list.c \ No newline at end of file diff --git a/tests/unit_tests/openvpn/test_push_update_msg.c b/tests/unit_tests/openvpn/test_push_update_msg.c index d0876bc..38ea9a6 100644 --- a/tests/unit_tests/openvpn/test_push_update_msg.c +++ b/tests/unit_tests/openvpn/test_push_update_msg.c @@ -8,6 +8,7 @@ #include <cmocka.h> #include "push.h" #include "options_util.h" +#include "multi.h" /* mocks */ @@ -36,6 +37,18 @@ 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, @@ -94,6 +107,49 @@ } } +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 @@ -124,7 +180,6 @@ free_buf(&buf); } - static void test_incoming_push_message_error2(void **state) { @@ -209,6 +264,207 @@ 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) { @@ -238,7 +494,20 @@ 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); -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 11 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: stipa <lst...@gm...> Gerrit-Attention: mrbff <ma...@ma...> Gerrit-MessageType: newpatchset |
From: mrbff (C. Review) <ge...@op...> - 2025-07-11 06:10:49
|
Attention is currently required from: cron2, flichtenheld, plaisthos, stipa. mrbff has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/869?usp=email ) Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... Patch Set 11: (2 comments) Patchset: PS7: > I did some testing and indeed I was able to send PUSH_UPDATE messages to the client. […] Now it should work, although it might need a patch to improve it. Patchset: PS10: > Fails build with --disable-management Done -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 11 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: stipa <lst...@gm...> Gerrit-Comment-Date: Fri, 11 Jul 2025 06:10:34 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Comment-In-Reply-To: flichtenheld <fr...@li...> Comment-In-Reply-To: stipa <lst...@gm...> Gerrit-MessageType: comment |
From: cron2 (C. Review) <ge...@op...> - 2025-07-21 19:53:51
|
Attention is currently required from: flichtenheld, mrbff, plaisthos, stipa. cron2 has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/869?usp=email ) Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... Patch Set 16: Code-Review-1 (13 comments) Patchset: PS16: lots of string and buffer related nagging, plus some general questioning if we need this in the full breadth... File doc/management-notes.txt: http://gerrit.openvpn.net/c/openvpn/+/869/comment/a8274ef9_1e452d98 : PS16, Line 1075: I do question whether this is a reasonable approach, to have "by cn" and "by addr" functions, that add code (... that needs to be tested and maintained) when a management client can just look up the CID itself, and then do `push-update-cid`? Is this something AS wants? Or "let's just do all variants, so nobody complains about a missing feature"? File src/openvpn/manage.c: http://gerrit.openvpn.net/c/openvpn/+/869/comment/54080c2e_539d2c6e : PS16, Line 1362: } I bet there are more elegant and less repetitive ways to do 4 different `if (status) print(SUCCESS!) else print(ERROR)` blocks... File src/openvpn/push.h: http://gerrit.openvpn.net/c/openvpn/+/869/comment/f1e00c73_49d8e6c7 : PS16, Line 54: This does confuse me a bit. If we have no ENABLE_MANAGEMENT, why would we have UPD_BY_ADDR or UPD_BY_CN? Is this usable from a plugin interface or a script? File src/openvpn/push_util.c: http://gerrit.openvpn.net/c/openvpn/+/869/comment/fb47d1a4_53787e8b : PS16, Line 66: /* Allocate memory and asseble the final message */ typo http://gerrit.openvpn.net/c/openvpn/+/869/comment/384c531b_cb1d0b7b : PS16, Line 82: } it might increase readability to juse use `snprintf( ... "%s,%s%s", push_update_cmd, src, continuation? continuation: 0)` here... and get rid of `i`. Or use `buf_printf()` and get rid of the length and malloc stuff... (returning `BSTR(&buf)` then). http://gerrit.openvpn.net/c/openvpn/+/869/comment/e1cd130c_c8e39305 : PS16, Line 122: msgs[im] = forge_msg(str, ",push-continuation 2", gc); I do wonder if this can be done without modifying the string, so getting rid of the `strdup()` requirement... http://gerrit.openvpn.net/c/openvpn/+/869/comment/5e9e0424_e91519f2 : PS16, Line 143: /* It actually send the already divided messagge to one single client */ This comment needs a visit from the grammar police ;-) ``` /* send the message(s) prepared to one single client */ ``` http://gerrit.openvpn.net/c/openvpn/+/869/comment/648be3fc_6ea876c8 : PS16, Line 158: buf_write(&buf, msgs[i], strlen(msgs[i])); If you want to have the message in a `buf` here, you could have `forge_msg()` just use `buf_printf()` and return the resulting `buf`...? http://gerrit.openvpn.net/c/openvpn/+/869/comment/caf4cb50_9100e63f : PS16, Line 165: /* After sending the control message, we update the options server-side in the client's context */ Can you extend the comment a bit on why this is needed? I assume (which might or might not be a bad idea) that this is so `ifconfig-push` and `ifconfig-ipv6-push` can work? `cipher` etc. is not PUSH_UPDATE-able, and most other options the server should not care about? But without a bit of more specific explanation, this is very, uh, non-intuitive stuff. http://gerrit.openvpn.net/c/openvpn/+/869/comment/693c5314_a1a08fa4 : PS16, Line 204: if (!message_splitter(gc_strdup(msg, &gc), msgs, &gc, safe_cap)) Please do not call `strdup()` in a function call... yes, this works, but it is hard to read and no gain compared to "just call strdup() at the beginning of `message_splutter()` (if it turns out to be necessary). http://gerrit.openvpn.net/c/openvpn/+/869/comment/8aa9f5e8_3e0aac7c : PS16, Line 239: how can we ever end here if ENABLE_MANAGEMENT is not defined? Except for the unit test, there is no caller remaining... http://gerrit.openvpn.net/c/openvpn/+/869/comment/ecc7ac6a_56bfc11e : PS16, Line 305: RETURN_UPDATE_STATUS(n_sent); so here we have this nice if/else macro that manage.c could use :-)... but only 3 out of 4 functions use it? Can we not do the "does this CID exist?" check in the caller (for `management_callback_send_push_update_by_cid()`)? -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 16 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: mrbff <ma...@ma...> Gerrit-Attention: stipa <lst...@gm...> Gerrit-Comment-Date: Mon, 21 Jul 2025 19:53:36 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: Yes Gerrit-MessageType: comment |
From: mrbff (C. Review) <ge...@op...> - 2025-07-24 08:20:12
|
Attention is currently required from: cron2, flichtenheld, mrbff, plaisthos, stipa. Hello cron2, flichtenheld, plaisthos, stipa, I'd like you to reexamine a change. Please visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email to look at the new patch set (#18). The following approvals got outdated and were removed: Code-Review-1 by cron2 Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... 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, via cid, via common name, via address) and send a PUSH_UPDATE control message to update some options. Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Signed-off-by: Marco Baffo <ma...@ma...> --- M CMakeLists.txt M doc/management-notes.txt M src/openvpn/manage.c M src/openvpn/manage.h M src/openvpn/multi.c M src/openvpn/multi.h M src/openvpn/push.h M src/openvpn/push_util.c M tests/unit_tests/openvpn/Makefile.am M tests/unit_tests/openvpn/test_push_update_msg.c 10 files changed, 861 insertions(+), 6 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/69/869/18 diff --git a/CMakeLists.txt b/CMakeLists.txt index 3866e21..97f0310 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -862,6 +862,7 @@ 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 f1d2930..58393da 100644 --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -1028,6 +1028,51 @@ 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" + +COMMAND -- push-update-cn (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target the clients based on the provided common name +(usually just one client per common name is permitted except if "duplicate-cn" option is used). + +Example + push-update-cid Client0 "route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400" + +COMMAND -- push-update-addr (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target only the client(s) connecting from the +provided address (real address). Support both IPv4 and IPv6. + +Example + push-update-addr 9.9.9.9 1234 "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 8836e79..42b0331 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -24,7 +24,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif - #include "syshead.h" #ifdef ENABLE_MANAGEMENT @@ -42,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "push.h" #include "memdbg.h" @@ -124,6 +124,11 @@ 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, "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, "push-update-cn CN options : Send an update message to the client(s) with the specified Common Name."); + msg(M_CLIENT, "push-update-addr ip port options : Send an update message to the client(s) connecting from the provided address."); msg(M_CLIENT, "END"); } @@ -1335,6 +1340,129 @@ } 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]); + } + else if (type == UPT_BY_CN) + { + if (!man->persist.callback.push_update_by_cn) + { + man_command_unsupported("push-update-cn"); + return; + } + + status = (*man->persist.callback.push_update_by_cn)(man->persist.callback.arg, p[1], p[2]); + } + else if (type == UPT_BY_ADDR) + { + if (!man->persist.callback.push_update_by_addr) + { + man_command_unsupported("push-update-addr"); + return; + } + + const char *ip_str = p[1]; + const char *port_str = p[2]; + const char *options = p[3]; + + if (!strlen(ip_str) || !strlen(port_str)) + { + msg(M_CLIENT, "ERROR: push-update-addr parse"); + return; + } + + struct addrinfo *res = NULL; + int port = atoi(port_str); + + if (port < 1 || port > 65535) + { + msg(M_CLIENT, "ERROR: port number is out of range: %s", port_str); + return; + } + + int addr_status = openvpn_getaddrinfo(GETADDR_MSG_VIRT_OUT, ip_str, port_str, 0, NULL, AF_UNSPEC, &res); + + if (addr_status != 0 || !res) + { + msg(M_CLIENT, "ERROR: error resolving address: %s (%s)", ip_str, gai_strerror(addr_status)); + return; + } + + struct addrinfo *rp; + + /* Iterate through resolved addresses */ + for (rp = res; rp != NULL; rp = rp->ai_next) + { + struct openvpn_sockaddr saddr; + struct mroute_addr maddr; + + CLEAR(saddr); + switch (rp->ai_family) + { + case AF_INET: + saddr.addr.in4 = *((struct sockaddr_in *)rp->ai_addr); + break; + + case AF_INET6: + saddr.addr.in6 = *((struct sockaddr_in6 *)rp->ai_addr); + break; + + default: + continue; + } + + if (!mroute_extract_openvpn_sockaddr(&maddr, &saddr, true)) + { + continue; + } + + status = (*man->persist.callback.push_update_by_addr)(man->persist.callback.arg, &maddr, options); + if (status) + { + break; + } + } + freeaddrinfo(res); + } + + 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) { struct gc_arena gc = gc_new(); @@ -1656,6 +1784,34 @@ 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); + } + } + else if (streq(p[0], "push-update-cn")) + { + if (man_need(man, p, 2, 0)) + { + man_push_update(man, p, UPT_BY_CN); + } + } + else if (streq(p[0], "push-update-addr")) + { + if (man_need(man, p, 3, 0)) + { + man_push_update(man, p, UPT_BY_ADDR); + } + } #if 1 else if (streq(p[0], "test")) { diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index eb19a4e..fd7cb11 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -44,7 +44,6 @@ #define MF_EXTERNAL_KEY_PSSPAD (1<<16) #define MF_EXTERNAL_KEY_DIGEST (1<<17) - #ifdef ENABLE_MANAGEMENT #include "misc.h" @@ -205,6 +204,10 @@ #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); + bool (*push_update_by_cn)(void *arg, const char *cn, const char *options); + bool (*push_update_by_addr)(void *arg, const struct mroute_addr *maddr, const char *options); }; /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 26f95ec..5972311 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -4100,7 +4100,7 @@ } } -static struct multi_instance * +struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { if (m) @@ -4248,6 +4248,10 @@ 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; + cb.push_update_by_cn = management_callback_send_push_update_by_cn; + cb.push_update_by_addr = management_callback_send_push_update_by_addr; management_set_callback(management, &cb); } #endif /* ifdef ENABLE_MANAGEMENT */ @@ -4373,3 +4377,47 @@ 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, remove old if exist */ + multi_learn_in_addr_t(m, mi, new_ip, 0, true); + } + + /* TO DO: + * else if (old_ip && !mi->context.options.ifconfig_local) + * { + * // 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, remove old if exist */ + multi_learn_in6_addr(m, mi, new_ipv6, 0, true); + } + + /* TO DO: + * else if (old_ipv6 && !mi->context.options.ifconfig_ipv6_local) + * { + * // remove old IPv6 + * } + */ +} diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 8b2704c..01c05ba 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -702,5 +702,13 @@ */ 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/push.h b/src/openvpn/push.h index 18dfcd8..e67c3ac 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -42,6 +42,16 @@ #define PUSH_OPT_TO_REMOVE (1<<0) #define PUSH_OPT_OPTIONAL (1<<1) +/* Push-update message sender modes */ +typedef enum { + UPT_BROADCAST = 0, + UPT_BY_ADDR = 1, + UPT_BY_CN = 2, +#ifdef ENABLE_MANAGEMENT + UPT_BY_CID = 3 +#endif +} push_update_type; + int process_incoming_push_request(struct context *c); /** @@ -134,4 +144,33 @@ void receive_auth_pending(struct context *c, const struct buffer *buffer); +/** + * @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); + +#ifdef ENABLE_MANAGEMENT + +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); + +bool management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options); + +bool management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, 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 b4d1e8b..c2a9d31 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -3,6 +3,8 @@ #endif #include "push.h" +#include "multi.h" +#include "ssl_verify.h" int process_incoming_push_update(struct context *c, @@ -42,3 +44,286 @@ return ret; } + +/** + * 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 char * +forge_msg(const char *src, const char *continuation, struct gc_arena *gc) +{ + int src_len = strlen(src); + int con_len = continuation ? strlen(continuation) : 0; + char *ret = gc_malloc((src_len + sizeof(push_update_cmd) + con_len + 2) * sizeof(char), true, gc); + + sprintf(ret, "%s,%s%s", push_update_cmd, src, continuation ? continuation : ""); + + return ret; +} + +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, char **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, char **msgs, unsigned int *option_types_found) +{ + if (!msgs[0] || !*msgs[0]) + { + return false; + } + int i = 0; + struct gc_arena gc = gc_new(); + + while (msgs[i] && *msgs[i]) + { + struct buffer buf = alloc_buf_gc(strlen(msgs[i]), &gc); + + buf_write(&buf, msgs[i], strlen(msgs[i])); + if (!send_control_channel_string(c, msgs[i], D_PUSH)) + { + return false; + } + i++; + + /* 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, the packets arriving from the client with the new address will be + * rejected because the value in the option is an old one. + * For the same reason we later update the vhash too in `send_push_update()` function. */ + buf_string_compare_advance(&buf, push_update_cmd); + if (process_incoming_push_update(c, pull_permission_mask(c), option_types_found, &buf) == 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); + } + } + gc_free(&gc); + 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); + char **msgs = gc_malloc(sizeof(char *) * (msgs_num + 1), true, &gc); + unsigned int option_types_found = 0; + + msgs[msgs_num] = NULL; + if (!message_splitter(msg, msgs, &gc, safe_cap)) + { + gc_free(&gc); + return -EINVAL; + } + +#ifdef ENABLE_MANAGEMENT + 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; + } + } +#endif /* ifdef ENABLE_MANAGEMENT */ + + 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; + } + if (type == UPT_BY_ADDR && !mroute_addr_equal(target, &curr_mi->real)) + { + continue; + } + else if (type == UPT_BY_CN) + { + const char *curr_cn = tls_common_name(curr_mi->context.c2.tls_multi, false); + if (strcmp(curr_cn, target)) + { + continue; + } + } + /* Either we found a matching client or 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; +} + +#ifdef ENABLE_MANAGEMENT +#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); +} + +bool +management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options) +{ + int n_sent = send_push_update(arg, cn, options, UPT_BY_CN, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} + +bool +management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, const char *options) +{ + int n_sent = send_push_update(arg, maddr, options, UPT_BY_ADDR, 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 b24e03c..9a40512 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -343,4 +343,5 @@ $(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 \ + $(top_srcdir)/src/openvpn/list.c \ No newline at end of file diff --git a/tests/unit_tests/openvpn/test_push_update_msg.c b/tests/unit_tests/openvpn/test_push_update_msg.c index d47286a..9341c34 100644 --- a/tests/unit_tests/openvpn/test_push_update_msg.c +++ b/tests/unit_tests/openvpn/test_push_update_msg.c @@ -8,6 +8,7 @@ #include <cmocka.h> #include "push.h" #include "options_util.h" +#include "multi.h" /* mocks */ @@ -36,6 +37,18 @@ 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, @@ -109,6 +122,49 @@ } } +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 @@ -139,7 +195,6 @@ free_buf(&buf); } - static void test_incoming_push_message_error2(void **state) { @@ -224,6 +279,207 @@ 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) { @@ -253,7 +509,20 @@ 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); -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 18 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: mrbff <ma...@ma...> Gerrit-Attention: stipa <lst...@gm...> Gerrit-MessageType: newpatchset |
From: mrbff (C. Review) <ge...@op...> - 2025-07-24 08:21:17
|
Attention is currently required from: cron2, flichtenheld, plaisthos, stipa. mrbff has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/869?usp=email ) Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... Patch Set 17: (12 comments) File doc/management-notes.txt: http://gerrit.openvpn.net/c/openvpn/+/869/comment/33d4ba61_04cc0d55 : PS16, Line 1075: > I do question whether this is a reasonable approach, to have "by cn" and "by addr" functions, that a […] i mostly followed the `kill` command, but yes it is the second one. File src/openvpn/manage.c: http://gerrit.openvpn.net/c/openvpn/+/869/comment/183ce8a9_448ff5ac : PS16, Line 1362: } > I bet there are more elegant and less repetitive ways to do 4 different `if (status) print(SUCCESS!) […] Done File src/openvpn/push.h: http://gerrit.openvpn.net/c/openvpn/+/869/comment/940bc697_89ca7f9f : PS16, Line 54: > This does confuse me a bit. […] you can get the cid only from management, but theoretically the rest could be used by other functions in the future File src/openvpn/push_util.c: http://gerrit.openvpn.net/c/openvpn/+/869/comment/ccbe9b3d_34774f2d : PS16, Line 66: /* Allocate memory and asseble the final message */ > typo Done http://gerrit.openvpn.net/c/openvpn/+/869/comment/a9514c2d_3d9f4c1f : PS16, Line 82: } > it might increase readability to juse use `snprintf( ... […] Done http://gerrit.openvpn.net/c/openvpn/+/869/comment/bc395641_798e9979 : PS16, Line 122: msgs[im] = forge_msg(str, ",push-continuation 2", gc); > I do wonder if this can be done without modifying the string, so getting rid of the `strdup()` requi […] Acknowledged http://gerrit.openvpn.net/c/openvpn/+/869/comment/9e0a4d3f_be2fdaaa : PS16, Line 143: /* It actually send the already divided messagge to one single client */ > This comment needs a visit from the grammar police ;-) […] Done http://gerrit.openvpn.net/c/openvpn/+/869/comment/c617f505_475db955 : PS16, Line 158: buf_write(&buf, msgs[i], strlen(msgs[i])); > If you want to have the message in a `buf` here, you could have `forge_msg()` just use `buf_printf() […] ok, working on it http://gerrit.openvpn.net/c/openvpn/+/869/comment/ac640134_01fb9aae : PS16, Line 165: /* After sending the control message, we update the options server-side in the client's context */ > Can you extend the comment a bit on why this is needed? I assume (which might or might not be a bad […] Done http://gerrit.openvpn.net/c/openvpn/+/869/comment/7a73548b_cfd887a6 : PS16, Line 204: if (!message_splitter(gc_strdup(msg, &gc), msgs, &gc, safe_cap)) > Please do not call `strdup()` in a function call... […] Done http://gerrit.openvpn.net/c/openvpn/+/869/comment/4e68ba97_68899c76 : PS16, Line 239: > how can we ever end here if ENABLE_MANAGEMENT is not defined? Except for the unit test, there is no […] We can't now, yes, but the function could be used somewhere else in the future http://gerrit.openvpn.net/c/openvpn/+/869/comment/d6fc2d7c_558c5256 : PS16, Line 305: RETURN_UPDATE_STATUS(n_sent); > so here we have this nice if/else macro that manage.c could use :-)... […] i need to do the lookup anyway in send_push_update(), so it would duplicate code with no good reason i think, but i can use the macro anyway, np -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 17 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: stipa <lst...@gm...> Gerrit-Comment-Date: Thu, 24 Jul 2025 08:21:03 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Comment-In-Reply-To: cron2 <ge...@gr...> Gerrit-MessageType: comment |
From: mrbff (C. Review) <ge...@op...> - 2025-07-24 12:19:18
|
Attention is currently required from: cron2, flichtenheld, plaisthos, stipa. Hello cron2, flichtenheld, plaisthos, stipa, I'd like you to reexamine a change. Please visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email to look at the new patch set (#19). Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... 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, via cid, via common name, via address) and send a PUSH_UPDATE control message to update some options. Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Signed-off-by: Marco Baffo <ma...@ma...> --- M CMakeLists.txt M doc/management-notes.txt M src/openvpn/manage.c M src/openvpn/manage.h M src/openvpn/multi.c M src/openvpn/multi.h M src/openvpn/push.h M src/openvpn/push_util.c M tests/unit_tests/openvpn/Makefile.am M tests/unit_tests/openvpn/test_push_update_msg.c 10 files changed, 859 insertions(+), 6 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/69/869/19 diff --git a/CMakeLists.txt b/CMakeLists.txt index 3866e21..97f0310 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -862,6 +862,7 @@ 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 f1d2930..58393da 100644 --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -1028,6 +1028,51 @@ 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" + +COMMAND -- push-update-cn (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target the clients based on the provided common name +(usually just one client per common name is permitted except if "duplicate-cn" option is used). + +Example + push-update-cid Client0 "route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400" + +COMMAND -- push-update-addr (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target only the client(s) connecting from the +provided address (real address). Support both IPv4 and IPv6. + +Example + push-update-addr 9.9.9.9 1234 "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 8836e79..42b0331 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -24,7 +24,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif - #include "syshead.h" #ifdef ENABLE_MANAGEMENT @@ -42,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "push.h" #include "memdbg.h" @@ -124,6 +124,11 @@ 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, "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, "push-update-cn CN options : Send an update message to the client(s) with the specified Common Name."); + msg(M_CLIENT, "push-update-addr ip port options : Send an update message to the client(s) connecting from the provided address."); msg(M_CLIENT, "END"); } @@ -1335,6 +1340,129 @@ } 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]); + } + else if (type == UPT_BY_CN) + { + if (!man->persist.callback.push_update_by_cn) + { + man_command_unsupported("push-update-cn"); + return; + } + + status = (*man->persist.callback.push_update_by_cn)(man->persist.callback.arg, p[1], p[2]); + } + else if (type == UPT_BY_ADDR) + { + if (!man->persist.callback.push_update_by_addr) + { + man_command_unsupported("push-update-addr"); + return; + } + + const char *ip_str = p[1]; + const char *port_str = p[2]; + const char *options = p[3]; + + if (!strlen(ip_str) || !strlen(port_str)) + { + msg(M_CLIENT, "ERROR: push-update-addr parse"); + return; + } + + struct addrinfo *res = NULL; + int port = atoi(port_str); + + if (port < 1 || port > 65535) + { + msg(M_CLIENT, "ERROR: port number is out of range: %s", port_str); + return; + } + + int addr_status = openvpn_getaddrinfo(GETADDR_MSG_VIRT_OUT, ip_str, port_str, 0, NULL, AF_UNSPEC, &res); + + if (addr_status != 0 || !res) + { + msg(M_CLIENT, "ERROR: error resolving address: %s (%s)", ip_str, gai_strerror(addr_status)); + return; + } + + struct addrinfo *rp; + + /* Iterate through resolved addresses */ + for (rp = res; rp != NULL; rp = rp->ai_next) + { + struct openvpn_sockaddr saddr; + struct mroute_addr maddr; + + CLEAR(saddr); + switch (rp->ai_family) + { + case AF_INET: + saddr.addr.in4 = *((struct sockaddr_in *)rp->ai_addr); + break; + + case AF_INET6: + saddr.addr.in6 = *((struct sockaddr_in6 *)rp->ai_addr); + break; + + default: + continue; + } + + if (!mroute_extract_openvpn_sockaddr(&maddr, &saddr, true)) + { + continue; + } + + status = (*man->persist.callback.push_update_by_addr)(man->persist.callback.arg, &maddr, options); + if (status) + { + break; + } + } + freeaddrinfo(res); + } + + 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) { struct gc_arena gc = gc_new(); @@ -1656,6 +1784,34 @@ 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); + } + } + else if (streq(p[0], "push-update-cn")) + { + if (man_need(man, p, 2, 0)) + { + man_push_update(man, p, UPT_BY_CN); + } + } + else if (streq(p[0], "push-update-addr")) + { + if (man_need(man, p, 3, 0)) + { + man_push_update(man, p, UPT_BY_ADDR); + } + } #if 1 else if (streq(p[0], "test")) { diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index eb19a4e..fd7cb11 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -44,7 +44,6 @@ #define MF_EXTERNAL_KEY_PSSPAD (1<<16) #define MF_EXTERNAL_KEY_DIGEST (1<<17) - #ifdef ENABLE_MANAGEMENT #include "misc.h" @@ -205,6 +204,10 @@ #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); + bool (*push_update_by_cn)(void *arg, const char *cn, const char *options); + bool (*push_update_by_addr)(void *arg, const struct mroute_addr *maddr, const char *options); }; /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 26f95ec..5972311 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -4100,7 +4100,7 @@ } } -static struct multi_instance * +struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { if (m) @@ -4248,6 +4248,10 @@ 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; + cb.push_update_by_cn = management_callback_send_push_update_by_cn; + cb.push_update_by_addr = management_callback_send_push_update_by_addr; management_set_callback(management, &cb); } #endif /* ifdef ENABLE_MANAGEMENT */ @@ -4373,3 +4377,47 @@ 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, remove old if exist */ + multi_learn_in_addr_t(m, mi, new_ip, 0, true); + } + + /* TO DO: + * else if (old_ip && !mi->context.options.ifconfig_local) + * { + * // 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, remove old if exist */ + multi_learn_in6_addr(m, mi, new_ipv6, 0, true); + } + + /* TO DO: + * else if (old_ipv6 && !mi->context.options.ifconfig_ipv6_local) + * { + * // remove old IPv6 + * } + */ +} diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 8b2704c..01c05ba 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -702,5 +702,13 @@ */ 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/push.h b/src/openvpn/push.h index 18dfcd8..e67c3ac 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -42,6 +42,16 @@ #define PUSH_OPT_TO_REMOVE (1<<0) #define PUSH_OPT_OPTIONAL (1<<1) +/* Push-update message sender modes */ +typedef enum { + UPT_BROADCAST = 0, + UPT_BY_ADDR = 1, + UPT_BY_CN = 2, +#ifdef ENABLE_MANAGEMENT + UPT_BY_CID = 3 +#endif +} push_update_type; + int process_incoming_push_request(struct context *c); /** @@ -134,4 +144,33 @@ void receive_auth_pending(struct context *c, const struct buffer *buffer); +/** + * @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); + +#ifdef ENABLE_MANAGEMENT + +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); + +bool management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options); + +bool management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, 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 b4d1e8b..c496fef 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -3,6 +3,9 @@ #endif #include "push.h" +#include "multi.h" +#include "ssl_verify.h" +#include "buffer.h" int process_incoming_push_update(struct context *c, @@ -42,3 +45,283 @@ return ret; } + +/** + * 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; + struct gc_arena gc = gc_new(); + + 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, the packets arriving from the client with the new address will be + * rejected because the value in the option is an old one. + * 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]) == 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); + } + } + gc_free(&gc); + 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[msgs_num + 1]; + + unsigned int option_types_found = 0; + + msgs[msgs_num].data = NULL; + if (!message_splitter(msg, msgs, &gc, safe_cap)) + { + gc_free(&gc); + return -EINVAL; + } + +#ifdef ENABLE_MANAGEMENT + 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; + } + } +#endif /* ifdef ENABLE_MANAGEMENT */ + + 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; + } + if (type == UPT_BY_ADDR && !mroute_addr_equal(target, &curr_mi->real)) + { + continue; + } + else if (type == UPT_BY_CN) + { + const char *curr_cn = tls_common_name(curr_mi->context.c2.tls_multi, false); + if (strcmp(curr_cn, target)) + { + continue; + } + } + /* Either we found a matching client or 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; +} + +#ifdef ENABLE_MANAGEMENT +#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); +} + +bool +management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options) +{ + int n_sent = send_push_update(arg, cn, options, UPT_BY_CN, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} + +bool +management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, const char *options) +{ + int n_sent = send_push_update(arg, maddr, options, UPT_BY_ADDR, 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 b24e03c..9a40512 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -343,4 +343,5 @@ $(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 \ + $(top_srcdir)/src/openvpn/list.c \ No newline at end of file diff --git a/tests/unit_tests/openvpn/test_push_update_msg.c b/tests/unit_tests/openvpn/test_push_update_msg.c index d47286a..9341c34 100644 --- a/tests/unit_tests/openvpn/test_push_update_msg.c +++ b/tests/unit_tests/openvpn/test_push_update_msg.c @@ -8,6 +8,7 @@ #include <cmocka.h> #include "push.h" #include "options_util.h" +#include "multi.h" /* mocks */ @@ -36,6 +37,18 @@ 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, @@ -109,6 +122,49 @@ } } +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 @@ -139,7 +195,6 @@ free_buf(&buf); } - static void test_incoming_push_message_error2(void **state) { @@ -224,6 +279,207 @@ 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) { @@ -253,7 +509,20 @@ 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); -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 19 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: stipa <lst...@gm...> Gerrit-MessageType: newpatchset |
From: mrbff (C. Review) <ge...@op...> - 2025-07-24 12:19:33
|
Attention is currently required from: cron2, flichtenheld, plaisthos, stipa. mrbff has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/869?usp=email ) Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... Patch Set 18: (1 comment) File src/openvpn/push_util.c: http://gerrit.openvpn.net/c/openvpn/+/869/comment/7f156fa3_fdb8685b : PS16, Line 158: buf_write(&buf, msgs[i], strlen(msgs[i])); > ok, working on it Done -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 18 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: stipa <lst...@gm...> Gerrit-Comment-Date: Thu, 24 Jul 2025 12:19:19 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Comment-In-Reply-To: cron2 <ge...@gr...> Comment-In-Reply-To: mrbff <ma...@ma...> Gerrit-MessageType: comment |
From: mrbff (C. Review) <ge...@op...> - 2025-07-25 05:20:12
|
Attention is currently required from: cron2, flichtenheld, plaisthos, stipa. Hello cron2, flichtenheld, plaisthos, stipa, I'd like you to reexamine a change. Please visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email to look at the new patch set (#20). Change subject: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages ...................................................................... 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, via cid, via common name, via address) and send a PUSH_UPDATE control message to update some options. Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Signed-off-by: Marco Baffo <ma...@ma...> --- M CMakeLists.txt M doc/management-notes.txt M src/openvpn/manage.c M src/openvpn/manage.h M src/openvpn/multi.c M src/openvpn/multi.h M src/openvpn/push.h M src/openvpn/push_util.c M tests/unit_tests/openvpn/Makefile.am M tests/unit_tests/openvpn/test_push_update_msg.c 10 files changed, 857 insertions(+), 6 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/69/869/20 diff --git a/CMakeLists.txt b/CMakeLists.txt index 3866e21..97f0310 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -862,6 +862,7 @@ 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 f1d2930..58393da 100644 --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -1028,6 +1028,51 @@ 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" + +COMMAND -- push-update-cn (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target the clients based on the provided common name +(usually just one client per common name is permitted except if "duplicate-cn" option is used). + +Example + push-update-cid Client0 "route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400" + +COMMAND -- push-update-addr (OpenVPN 2.7 or higher) +---------------------------------------------------- +Same as push-update-broad but target only the client(s) connecting from the +provided address (real address). Support both IPv4 and IPv6. + +Example + push-update-addr 9.9.9.9 1234 "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 8836e79..42b0331 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -24,7 +24,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif - #include "syshead.h" #ifdef ENABLE_MANAGEMENT @@ -42,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "dco.h" +#include "push.h" #include "memdbg.h" @@ -124,6 +124,11 @@ 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, "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, "push-update-cn CN options : Send an update message to the client(s) with the specified Common Name."); + msg(M_CLIENT, "push-update-addr ip port options : Send an update message to the client(s) connecting from the provided address."); msg(M_CLIENT, "END"); } @@ -1335,6 +1340,129 @@ } 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]); + } + else if (type == UPT_BY_CN) + { + if (!man->persist.callback.push_update_by_cn) + { + man_command_unsupported("push-update-cn"); + return; + } + + status = (*man->persist.callback.push_update_by_cn)(man->persist.callback.arg, p[1], p[2]); + } + else if (type == UPT_BY_ADDR) + { + if (!man->persist.callback.push_update_by_addr) + { + man_command_unsupported("push-update-addr"); + return; + } + + const char *ip_str = p[1]; + const char *port_str = p[2]; + const char *options = p[3]; + + if (!strlen(ip_str) || !strlen(port_str)) + { + msg(M_CLIENT, "ERROR: push-update-addr parse"); + return; + } + + struct addrinfo *res = NULL; + int port = atoi(port_str); + + if (port < 1 || port > 65535) + { + msg(M_CLIENT, "ERROR: port number is out of range: %s", port_str); + return; + } + + int addr_status = openvpn_getaddrinfo(GETADDR_MSG_VIRT_OUT, ip_str, port_str, 0, NULL, AF_UNSPEC, &res); + + if (addr_status != 0 || !res) + { + msg(M_CLIENT, "ERROR: error resolving address: %s (%s)", ip_str, gai_strerror(addr_status)); + return; + } + + struct addrinfo *rp; + + /* Iterate through resolved addresses */ + for (rp = res; rp != NULL; rp = rp->ai_next) + { + struct openvpn_sockaddr saddr; + struct mroute_addr maddr; + + CLEAR(saddr); + switch (rp->ai_family) + { + case AF_INET: + saddr.addr.in4 = *((struct sockaddr_in *)rp->ai_addr); + break; + + case AF_INET6: + saddr.addr.in6 = *((struct sockaddr_in6 *)rp->ai_addr); + break; + + default: + continue; + } + + if (!mroute_extract_openvpn_sockaddr(&maddr, &saddr, true)) + { + continue; + } + + status = (*man->persist.callback.push_update_by_addr)(man->persist.callback.arg, &maddr, options); + if (status) + { + break; + } + } + freeaddrinfo(res); + } + + 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) { struct gc_arena gc = gc_new(); @@ -1656,6 +1784,34 @@ 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); + } + } + else if (streq(p[0], "push-update-cn")) + { + if (man_need(man, p, 2, 0)) + { + man_push_update(man, p, UPT_BY_CN); + } + } + else if (streq(p[0], "push-update-addr")) + { + if (man_need(man, p, 3, 0)) + { + man_push_update(man, p, UPT_BY_ADDR); + } + } #if 1 else if (streq(p[0], "test")) { diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index eb19a4e..fd7cb11 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -44,7 +44,6 @@ #define MF_EXTERNAL_KEY_PSSPAD (1<<16) #define MF_EXTERNAL_KEY_DIGEST (1<<17) - #ifdef ENABLE_MANAGEMENT #include "misc.h" @@ -205,6 +204,10 @@ #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); + bool (*push_update_by_cn)(void *arg, const char *cn, const char *options); + bool (*push_update_by_addr)(void *arg, const struct mroute_addr *maddr, const char *options); }; /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 26f95ec..5972311 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -4100,7 +4100,7 @@ } } -static struct multi_instance * +struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { if (m) @@ -4248,6 +4248,10 @@ 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; + cb.push_update_by_cn = management_callback_send_push_update_by_cn; + cb.push_update_by_addr = management_callback_send_push_update_by_addr; management_set_callback(management, &cb); } #endif /* ifdef ENABLE_MANAGEMENT */ @@ -4373,3 +4377,47 @@ 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, remove old if exist */ + multi_learn_in_addr_t(m, mi, new_ip, 0, true); + } + + /* TO DO: + * else if (old_ip && !mi->context.options.ifconfig_local) + * { + * // 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, remove old if exist */ + multi_learn_in6_addr(m, mi, new_ipv6, 0, true); + } + + /* TO DO: + * else if (old_ipv6 && !mi->context.options.ifconfig_ipv6_local) + * { + * // remove old IPv6 + * } + */ +} diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 8b2704c..01c05ba 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -702,5 +702,13 @@ */ 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/push.h b/src/openvpn/push.h index 18dfcd8..e67c3ac 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -42,6 +42,16 @@ #define PUSH_OPT_TO_REMOVE (1<<0) #define PUSH_OPT_OPTIONAL (1<<1) +/* Push-update message sender modes */ +typedef enum { + UPT_BROADCAST = 0, + UPT_BY_ADDR = 1, + UPT_BY_CN = 2, +#ifdef ENABLE_MANAGEMENT + UPT_BY_CID = 3 +#endif +} push_update_type; + int process_incoming_push_request(struct context *c); /** @@ -134,4 +144,33 @@ void receive_auth_pending(struct context *c, const struct buffer *buffer); +/** + * @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); + +#ifdef ENABLE_MANAGEMENT + +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); + +bool management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options); + +bool management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, 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 b4d1e8b..245a29b 100644 --- a/src/openvpn/push_util.c +++ b/src/openvpn/push_util.c @@ -3,6 +3,9 @@ #endif #include "push.h" +#include "multi.h" +#include "ssl_verify.h" +#include "buffer.h" int process_incoming_push_update(struct context *c, @@ -42,3 +45,281 @@ return ret; } + +/** + * 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, the packets arriving from the client with the new address will be + * rejected because the value in the option is an old one. + * 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]) == 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; + } + +#ifdef ENABLE_MANAGEMENT + 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; + } + } +#endif /* ifdef ENABLE_MANAGEMENT */ + + 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; + } + if (type == UPT_BY_ADDR && !mroute_addr_equal(target, &curr_mi->real)) + { + continue; + } + else if (type == UPT_BY_CN) + { + const char *curr_cn = tls_common_name(curr_mi->context.c2.tls_multi, false); + if (strcmp(curr_cn, target)) + { + continue; + } + } + /* Either we found a matching client or 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; +} + +#ifdef ENABLE_MANAGEMENT +#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); +} + +bool +management_callback_send_push_update_by_cn(void *arg, const char *cn, const char *options) +{ + int n_sent = send_push_update(arg, cn, options, UPT_BY_CN, PUSH_BUNDLE_SIZE); + + RETURN_UPDATE_STATUS(n_sent); +} + +bool +management_callback_send_push_update_by_addr(void *arg, const struct mroute_addr *maddr, const char *options) +{ + int n_sent = send_push_update(arg, maddr, options, UPT_BY_ADDR, 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 b24e03c..9a40512 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -343,4 +343,5 @@ $(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 \ + $(top_srcdir)/src/openvpn/list.c \ No newline at end of file diff --git a/tests/unit_tests/openvpn/test_push_update_msg.c b/tests/unit_tests/openvpn/test_push_update_msg.c index d47286a..9341c34 100644 --- a/tests/unit_tests/openvpn/test_push_update_msg.c +++ b/tests/unit_tests/openvpn/test_push_update_msg.c @@ -8,6 +8,7 @@ #include <cmocka.h> #include "push.h" #include "options_util.h" +#include "multi.h" /* mocks */ @@ -36,6 +37,18 @@ 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, @@ -109,6 +122,49 @@ } } +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 @@ -139,7 +195,6 @@ free_buf(&buf); } - static void test_incoming_push_message_error2(void **state) { @@ -224,6 +279,207 @@ 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) { @@ -253,7 +509,20 @@ 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); -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/869?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc Gerrit-Change-Number: 869 Gerrit-PatchSet: 20 Gerrit-Owner: mrbff <ma...@ma...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: plaisthos <arn...@rf...> Gerrit-Reviewer: stipa <lst...@gm...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: stipa <lst...@gm...> Gerrit-MessageType: newpatchset |