[Openvpn-devel,L] Change in openvpn[master]: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE c...

Message ID bfdd12a2df62908e7beca2893d3803f8c94a29d1-HTML@gerrit.openvpn.net
State New
Headers show
Series [Openvpn-devel,L] Change in openvpn[master]: PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE c... | expand

Commit Message

mrbff (Code Review) Jan. 16, 2025, 12:11 p.m. UTC
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 <marco@mandelbit.com>
---
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

Patch

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);