Browse code

PUSH_UPDATE message sender: enabling the server to send PUSH_UPDATE control messages

Using the management interface you can now target one or more clients
(via broadcast or via cid) and send a PUSH_UPDATE control message
to update some options. See doc/management-notes.txt for details.

Change-Id: Ie82bcc7a8e583de9156b185d71d1a323ed8df3fc
Signed-off-by: Marco Baffo <marco@mandelbit.com>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <20250903164826.13284-1-gert@greenie.muc.de>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32807.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Marco Baffo authored on 2025/09/04 01:48:20
Showing 13 changed files
... ...
@@ -874,9 +874,10 @@ if (BUILD_TESTING)
874 874
     target_sources(test_push_update_msg PRIVATE
875 875
         tests/unit_tests/openvpn/mock_msg.c
876 876
         tests/unit_tests/openvpn/mock_get_random.c
877
-	    src/openvpn/push_util.c
878
-	    src/openvpn/options_util.c
879
-	    src/openvpn/otime.c
877
+        src/openvpn/push_util.c
878
+        src/openvpn/options_util.c
879
+        src/openvpn/otime.c
880
+        src/openvpn/list.c
880 881
         )
881 882
 
882 883
     if (TARGET test_argv)
... ...
@@ -1028,6 +1028,35 @@ This capability is intended to allow the use of certificates
1028 1028
 stored outside of the filesystem (e.g. in Mac OS X Keychain)
1029 1029
 with OpenVPN via the management interface.
1030 1030
 
1031
+COMMAND -- push-update-broad (OpenVPN 2.7 or higher)
1032
+----------------------------------------------------
1033
+Send a message to every connected client to update options at runtime.
1034
+The updatable options are: "block-ipv6", "block-outside-dns", "dhcp-option",
1035
+"dns", "ifconfig", "ifconfig-ipv6", "redirect-gateway", "redirect-private",
1036
+"route", "route-gateway", "route-ipv6", "route-metric", "topology",
1037
+"tun-mtu", "keepalive". When a valid option is pushed, the receiving client will
1038
+delete every previous value and set new value, so the update of the option will
1039
+not be incremental even when theoretically possible (ex. with "redirect-gateway").
1040
+The '-' symbol in front of an option means the option should be removed.
1041
+When an option is used with '-', it cannot take any parameter.
1042
+The '?' symbol in front of an option means the option's update is optional
1043
+so if the client do not support it, that option will just be ignored without
1044
+making fail the entire command. The '-' and '?' symbols can be used together.
1045
+
1046
+Option Format Ex.
1047
+  `-?option`, `-option`, `?option parameters` are valid formats,
1048
+  `?-option` is not a valid format.
1049
+
1050
+Example
1051
+  push-update-broad "route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400"
1052
+
1053
+COMMAND -- push-update-cid (OpenVPN 2.7 or higher)
1054
+----------------------------------------------------
1055
+Same as push-update-broad but you must target a single client using client id.
1056
+
1057
+Example
1058
+  push-update-cid 42 "route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400"
1059
+
1031 1060
 OUTPUT FORMAT
1032 1061
 -------------
1033 1062
 
... ...
@@ -41,6 +41,7 @@
41 41
 #include "manage.h"
42 42
 #include "openvpn.h"
43 43
 #include "dco.h"
44
+#include "push.h"
44 45
 #include "multi.h"
45 46
 
46 47
 #include "memdbg.h"
... ...
@@ -132,8 +133,10 @@ man_help(void)
132 132
     msg(M_CLIENT, "test n                 : Produce n lines of output for testing/debugging.");
133 133
     msg(M_CLIENT, "username type u        : Enter username u for a queried OpenVPN username.");
134 134
     msg(M_CLIENT, "verb [n]               : Set log verbosity level to n, or show if n is absent.");
135
-    msg(M_CLIENT,
136
-        "version [n]            : Set client's version to n or show current version of daemon.");
135
+    msg(M_CLIENT, "version [n]            : Set client's version to n or show current version of daemon.");
136
+    msg(M_CLIENT, "push-update-broad options : Broadcast a message to update the specified options.");
137
+    msg(M_CLIENT, "                            Ex. push-update-broad \"route something, -dns\"");
138
+    msg(M_CLIENT, "push-update-cid CID options : Send an update message to the client identified by CID.");
137 139
     msg(M_CLIENT, "END");
138 140
 }
139 141
 
... ...
@@ -1333,6 +1336,48 @@ set_client_version(struct management *man, const char *version)
1333 1333
 }
1334 1334
 
1335 1335
 static void
1336
+man_push_update(struct management *man, const char **p, const push_update_type type)
1337
+{
1338
+    bool status = false;
1339
+
1340
+    if (type == UPT_BROADCAST)
1341
+    {
1342
+        if (!man->persist.callback.push_update_broadcast)
1343
+        {
1344
+            man_command_unsupported("push-update-broad");
1345
+            return;
1346
+        }
1347
+
1348
+        status = (*man->persist.callback.push_update_broadcast)(man->persist.callback.arg, p[1]);
1349
+    }
1350
+    else if (type == UPT_BY_CID)
1351
+    {
1352
+        if (!man->persist.callback.push_update_by_cid)
1353
+        {
1354
+            man_command_unsupported("push-update-cid");
1355
+            return;
1356
+        }
1357
+
1358
+        unsigned long cid = 0;
1359
+
1360
+        if (!parse_cid(p[1], &cid))
1361
+        {
1362
+            msg(M_CLIENT, "ERROR: push-update-cid fail during cid parsing");
1363
+            return;
1364
+        }
1365
+
1366
+        status = (*man->persist.callback.push_update_by_cid)(man->persist.callback.arg, cid, p[2]);
1367
+    }
1368
+
1369
+    if (status)
1370
+    {
1371
+        msg(M_CLIENT, "SUCCESS: push-update command succeeded");
1372
+        return;
1373
+    }
1374
+    msg(M_CLIENT, "ERROR: push-update command failed");
1375
+}
1376
+
1377
+static void
1336 1378
 man_dispatch_command(struct management *man, struct status_output *so, const char **p,
1337 1379
                      const int nparms)
1338 1380
 {
... ...
@@ -1655,6 +1700,20 @@ man_dispatch_command(struct management *man, struct status_output *so, const cha
1655 1655
             man_remote(man, p);
1656 1656
         }
1657 1657
     }
1658
+    else if (streq(p[0], "push-update-broad"))
1659
+    {
1660
+        if (man_need(man, p, 1, 0))
1661
+        {
1662
+            man_push_update(man, p, UPT_BROADCAST);
1663
+        }
1664
+    }
1665
+    else if (streq(p[0], "push-update-cid"))
1666
+    {
1667
+        if (man_need(man, p, 2, 0))
1668
+        {
1669
+            man_push_update(man, p, UPT_BY_CID);
1670
+        }
1671
+    }
1658 1672
 #if 1
1659 1673
     else if (streq(p[0], "test"))
1660 1674
     {
... ...
@@ -43,7 +43,6 @@
43 43
 #define MF_EXTERNAL_KEY_PSSPAD    (1u << 16)
44 44
 #define MF_EXTERNAL_KEY_DIGEST    (1u << 17)
45 45
 
46
-
47 46
 #ifdef ENABLE_MANAGEMENT
48 47
 
49 48
 #include "misc.h"
... ...
@@ -197,6 +196,8 @@ struct management_callback
197 197
 #endif
198 198
     unsigned int (*remote_entry_count)(void *arg);
199 199
     bool (*remote_entry_get)(void *arg, unsigned int index, char **remote);
200
+    bool (*push_update_broadcast)(void *arg, const char *options);
201
+    bool (*push_update_by_cid)(void *arg, unsigned long cid, const char *options);
200 202
 };
201 203
 
202 204
 /*
... ...
@@ -3996,7 +3996,7 @@ management_delete_event(void *arg, event_t event)
3996 3996
     }
3997 3997
 }
3998 3998
 
3999
-static struct multi_instance *
3999
+struct multi_instance *
4000 4000
 lookup_by_cid(struct multi_context *m, const unsigned long cid)
4001 4001
 {
4002 4002
     if (m)
... ...
@@ -4137,6 +4137,8 @@ init_management_callback_multi(struct multi_context *m)
4137 4137
         cb.client_auth = management_client_auth;
4138 4138
         cb.client_pending_auth = management_client_pending_auth;
4139 4139
         cb.get_peer_info = management_get_peer_info;
4140
+        cb.push_update_broadcast = management_callback_send_push_update_broadcast;
4141
+        cb.push_update_by_cid = management_callback_send_push_update_by_cid;
4140 4142
         management_set_callback(management, &cb);
4141 4143
     }
4142 4144
 #endif /* ifdef ENABLE_MANAGEMENT */
... ...
@@ -4261,3 +4263,47 @@ tunnel_server(struct context *top)
4261 4261
     multi_top_free(&multi);
4262 4262
     close_instance(top);
4263 4263
 }
4264
+
4265
+/**
4266
+ * Update the vhash with new IP/IPv6 addresses in the multi_context when a
4267
+ * push-update message containing ifconfig/ifconfig-ipv6 options is sent
4268
+ * from the server. This function should be called after a push-update
4269
+ * and old_ip/old_ipv6 are the previous addresses of the client in
4270
+ * ctx->options.ifconfig_local and ctx->options.ifconfig_ipv6_local.
4271
+ */
4272
+void
4273
+update_vhash(struct multi_context *m, struct multi_instance *mi, const char *old_ip, const char *old_ipv6)
4274
+{
4275
+    struct in_addr addr;
4276
+    struct in6_addr new_ipv6;
4277
+
4278
+    if ((mi->context.options.ifconfig_local && (!old_ip || strcmp(old_ip, mi->context.options.ifconfig_local)))
4279
+        && inet_pton(AF_INET, mi->context.options.ifconfig_local, &addr) == 1)
4280
+    {
4281
+        in_addr_t new_ip = ntohl(addr.s_addr);
4282
+
4283
+        /* Add new IP */
4284
+        multi_learn_in_addr_t(m, mi, new_ip, -1, true);
4285
+    }
4286
+
4287
+    /* TO DO:
4288
+     *  else if (old_ip)
4289
+     *  {
4290
+     *      // remove old IP
4291
+     *  }
4292
+     */
4293
+
4294
+    if ((mi->context.options.ifconfig_ipv6_local && (!old_ipv6 || strcmp(old_ipv6, mi->context.options.ifconfig_ipv6_local)))
4295
+        && inet_pton(AF_INET6, mi->context.options.ifconfig_ipv6_local, &new_ipv6) == 1)
4296
+    {
4297
+        /* Add new IPv6 */
4298
+        multi_learn_in6_addr(m, mi, new_ipv6, -1, true);
4299
+    }
4300
+
4301
+    /* TO DO:
4302
+     *  else if (old_ipv6)
4303
+     *  {
4304
+     *      // remove old IPv6
4305
+     *  }
4306
+     */
4307
+}
... ...
@@ -686,5 +686,12 @@ multi_set_pending(struct multi_context *m, struct multi_instance *mi)
686 686
  */
687 687
 void multi_assign_peer_id(struct multi_context *m, struct multi_instance *mi);
688 688
 
689
+#ifdef ENABLE_MANAGEMENT
690
+struct multi_instance *
691
+lookup_by_cid(struct multi_context *m, const unsigned long cid);
692
+#endif
693
+
694
+void
695
+update_vhash(struct multi_context *m, struct multi_instance *mi, const char *old_ip, const char *old_ipv6);
689 696
 
690 697
 #endif /* MULTI_H */
... ...
@@ -5488,7 +5488,6 @@ apply_push_options(struct context *c, struct options *options, struct buffer *bu
5488 5488
             {
5489 5489
                 continue; /* Ignoring this option */
5490 5490
             }
5491
-            throw_signal_soft(SIGUSR1, "Offending option received from server");
5492 5491
             return false; /* Cause push/pull error and stop push processing */
5493 5492
         }
5494 5493
 
... ...
@@ -236,11 +236,11 @@ check_push_update_option_flags(char *line, int *i, unsigned int *flags)
236 236
     {
237 237
         if (*flags & PUSH_OPT_OPTIONAL)
238 238
         {
239
-            msg(D_PUSH, "Pushed option is not updatable: '%s'. Ignoring.", line);
239
+            msg(D_PUSH, "Pushed dispensable option is not updatable: '%s'. Ignoring.", line);
240 240
         }
241 241
         else
242 242
         {
243
-            msg(M_WARN, "Pushed option is not updatable: '%s'. Restarting.", line);
243
+            msg(M_WARN, "Pushed option is not updatable: '%s'.", line);
244 244
             return false;
245 245
         }
246 246
     }
... ...
@@ -1073,6 +1073,10 @@ process_incoming_push_reply(struct context *c, unsigned int permission_mask,
1073 1073
                     break;
1074 1074
             }
1075 1075
         }
1076
+        else
1077
+        {
1078
+            throw_signal_soft(SIGUSR1, "Offending option received from server");
1079
+        }
1076 1080
     }
1077 1081
     else if (ch == '\0')
1078 1082
     {
... ...
@@ -1100,7 +1104,7 @@ process_incoming_push_msg(struct context *c, const struct buffer *buffer,
1100 1100
     }
1101 1101
     else if (honor_received_options && buf_string_compare_advance(&buf, push_update_cmd))
1102 1102
     {
1103
-        return process_incoming_push_update(c, permission_mask, option_types_found, &buf);
1103
+        return process_incoming_push_update(c, permission_mask, option_types_found, &buf, false);
1104 1104
     }
1105 1105
     else
1106 1106
     {
... ...
@@ -41,6 +41,15 @@
41 41
 #define PUSH_OPT_TO_REMOVE (1 << 0)
42 42
 #define PUSH_OPT_OPTIONAL  (1 << 1)
43 43
 
44
+#ifdef ENABLE_MANAGEMENT
45
+/* Push-update message sender modes */
46
+typedef enum
47
+{
48
+    UPT_BROADCAST = 0,
49
+    UPT_BY_CID = 1
50
+} push_update_type;
51
+#endif
52
+
44 53
 int process_incoming_push_request(struct context *c);
45 54
 
46 55
 /**
... ...
@@ -56,6 +65,7 @@ int process_incoming_push_request(struct context *c);
56 56
  * @param option_types_found A pointer to a variable that will be filled with the types of options
57 57
  *                           found in the message.
58 58
  * @param buf A buffer containing the received message.
59
+ * @param msg_sender A boolean indicating if function is called by the message sender (server).
59 60
  *
60 61
  * @return
61 62
  * - `PUSH_MSG_UPDATE`: The message was processed successfully, and the updates were applied.
... ...
@@ -65,7 +75,8 @@ int process_incoming_push_request(struct context *c);
65 65
  */
66 66
 
67 67
 int process_incoming_push_update(struct context *c, unsigned int permission_mask,
68
-                                 unsigned int *option_types_found, struct buffer *buf);
68
+                                 unsigned int *option_types_found, struct buffer *buf,
69
+                                 bool msg_sender);
69 70
 
70 71
 int process_incoming_push_msg(struct context *c, const struct buffer *buffer,
71 72
                               bool honor_received_options, unsigned int permission_mask,
... ...
@@ -127,4 +138,28 @@ void send_push_reply_auth_token(struct tls_multi *multi);
127 127
  */
128 128
 void receive_auth_pending(struct context *c, const struct buffer *buffer);
129 129
 
130
+#ifdef ENABLE_MANAGEMENT
131
+/**
132
+ * @brief A function to send a PUSH_UPDATE control message from server to client(s).
133
+ *
134
+ * @param m the multi_context, contains all the clients connected to this server.
135
+ * @param target the target to which to send the message. It should be:
136
+ * `NULL` if `type == UPT_BROADCAST`,
137
+ * a `mroute_addr *` if `type == UPT_BY_ADDR`,
138
+ * a `char *` if `type == UPT_BY_CN`,
139
+ * an `unsigned long *` if `type == UPT_BY_CID`.
140
+ * @param msg a string containing the options to send.
141
+ * @param type the way to address the message (broadcast, by cid, by cn, by address).
142
+ * @param push_bundle_size the maximum size of a bundle of pushed option. Just use PUSH_BUNDLE_SIZE macro.
143
+ * @return the number of clients to which the message was sent.
144
+ */
145
+int
146
+send_push_update(struct multi_context *m, const void *target, const char *msg, const push_update_type type, const int push_bundle_size);
147
+
148
+bool management_callback_send_push_update_broadcast(void *arg, const char *options);
149
+
150
+bool management_callback_send_push_update_by_cid(void *arg, unsigned long cid, const char *options);
151
+
152
+#endif /* ifdef ENABLE_MANAGEMENT*/
153
+
130 154
 #endif /* ifndef PUSH_H */
... ...
@@ -3,10 +3,16 @@
3 3
 #endif
4 4
 
5 5
 #include "push.h"
6
+#include "buffer.h"
7
+
8
+#ifdef ENABLE_MANAGEMENT
9
+#include "multi.h"
10
+#endif
6 11
 
7 12
 int
8 13
 process_incoming_push_update(struct context *c, unsigned int permission_mask,
9
-                             unsigned int *option_types_found, struct buffer *buf)
14
+                             unsigned int *option_types_found, struct buffer *buf,
15
+                             bool msg_sender)
10 16
 {
11 17
     int ret = PUSH_MSG_ERROR;
12 18
     const uint8_t ch = buf_read_u8(buf);
... ...
@@ -27,6 +33,10 @@ process_incoming_push_update(struct context *c, unsigned int permission_mask,
27 27
                     break;
28 28
             }
29 29
         }
30
+        else if (!msg_sender)
31
+        {
32
+            throw_signal_soft(SIGUSR1, "Offending option received from server");
33
+        }
30 34
     }
31 35
     else if (ch == '\0')
32 36
     {
... ...
@@ -35,3 +45,260 @@ process_incoming_push_update(struct context *c, unsigned int permission_mask,
35 35
 
36 36
     return ret;
37 37
 }
38
+
39
+#ifdef ENABLE_MANAGEMENT
40
+/**
41
+ * Return index of last `,` or `0` if it didn't find any.
42
+ * If there is a comma at index `0` it's an error anyway
43
+ */
44
+static int
45
+find_first_comma_of_next_bundle(const char *str, int ix)
46
+{
47
+    while (ix > 0)
48
+    {
49
+        if (str[ix] == ',')
50
+        {
51
+            return ix;
52
+        }
53
+        ix--;
54
+    }
55
+    return 0;
56
+}
57
+
58
+/* Allocate memory and assemble the final message */
59
+static struct buffer
60
+forge_msg(const char *src, const char *continuation, struct gc_arena *gc)
61
+{
62
+    int src_len = strlen(src);
63
+    int con_len = continuation ? strlen(continuation) : 0;
64
+    struct buffer buf = alloc_buf_gc(src_len + sizeof(push_update_cmd) + con_len + 2, gc);
65
+
66
+    buf_printf(&buf, "%s,%s%s", push_update_cmd, src, continuation ? continuation : "");
67
+
68
+    return buf;
69
+}
70
+
71
+static char *
72
+gc_strdup(const char *src, struct gc_arena *gc)
73
+{
74
+    char *ret = gc_malloc((strlen(src) + 1) * sizeof(char), true, gc);
75
+
76
+    strcpy(ret, src);
77
+    return ret;
78
+}
79
+
80
+/* It split the messagge (if necessay) and fill msgs with the message chunks.
81
+ * Return `false` on failure an `true` on success.
82
+ */
83
+static bool
84
+message_splitter(const char *s, struct buffer *msgs, struct gc_arena *gc, const int safe_cap)
85
+{
86
+    if (!s || !*s)
87
+    {
88
+        return false;
89
+    }
90
+
91
+    char *str = gc_strdup(s, gc);
92
+    int i = 0;
93
+    int im = 0;
94
+
95
+    while (*str)
96
+    {
97
+        /* + ',' - '/0' */
98
+        if (strlen(str) > safe_cap)
99
+        {
100
+            int ci = find_first_comma_of_next_bundle(str, safe_cap);
101
+            if (!ci)
102
+            {
103
+                /* if no commas were found go to fail, do not send any message */
104
+                return false;
105
+            }
106
+            str[ci] = '\0';
107
+            /* copy from i to (ci -1) */
108
+            msgs[im] = forge_msg(str, ",push-continuation 2", gc);
109
+            i = ci + 1;
110
+        }
111
+        else
112
+        {
113
+            if (im)
114
+            {
115
+                msgs[im] = forge_msg(str, ",push-continuation 1", gc);
116
+            }
117
+            else
118
+            {
119
+                msgs[im] = forge_msg(str, NULL, gc);
120
+            }
121
+            i = strlen(str);
122
+        }
123
+        str = &str[i];
124
+        im++;
125
+    }
126
+    return true;
127
+}
128
+
129
+/* send the message(s) prepared to one single client */
130
+static bool
131
+send_single_push_update(struct context *c, struct buffer *msgs, unsigned int *option_types_found)
132
+{
133
+    if (!msgs[0].data || !*(msgs[0].data))
134
+    {
135
+        return false;
136
+    }
137
+    int i = -1;
138
+
139
+    while (msgs[++i].data && *(msgs[i].data))
140
+    {
141
+        if (!send_control_channel_string(c, BSTR(&msgs[i]), D_PUSH))
142
+        {
143
+            return false;
144
+        }
145
+
146
+        /* After sending the control message, we update the options
147
+         * server-side in the client's context so pushed options like
148
+         * ifconfig/ifconfig-ipv6 can actually work.
149
+         * If we don't do that, packets arriving from the client with the
150
+         * new address will be rejected and packets for the new address
151
+         * will not be routed towards the client.
152
+         * For the same reason we later update the vhash too in
153
+         * `send_push_update()` function.
154
+         */
155
+        buf_string_compare_advance(&msgs[i], push_update_cmd);
156
+        if (process_incoming_push_update(c, pull_permission_mask(c), option_types_found, &msgs[i], true) == PUSH_MSG_ERROR)
157
+        {
158
+            msg(M_WARN, "Failed to process push update message sent to client ID: %u",
159
+                c->c2.tls_multi ? c->c2.tls_multi->peer_id : UINT32_MAX);
160
+            continue;
161
+        }
162
+        c->options.push_option_types_found |= *option_types_found;
163
+        if (!options_postprocess_pull(&c->options, c->c2.es))
164
+        {
165
+            msg(M_WARN, "Failed to post-process push update message sent to client ID: %u",
166
+                c->c2.tls_multi ? c->c2.tls_multi->peer_id : UINT32_MAX);
167
+        }
168
+    }
169
+    return true;
170
+}
171
+
172
+int
173
+send_push_update(struct multi_context *m, const void *target, const char *msg, const push_update_type type, const int push_bundle_size)
174
+{
175
+    if (!msg || !*msg || !m
176
+        || (!target && type != UPT_BROADCAST))
177
+    {
178
+        return -EINVAL;
179
+    }
180
+
181
+    struct gc_arena gc = gc_new();
182
+    /* extra space for possible trailing ifconfig and push-continuation */
183
+    const int extra = 84 + sizeof(push_update_cmd);
184
+    /* push_bundle_size is the maximum size of a message, so if the message
185
+     * we want to send exceeds that size we have to split it into smaller messages */
186
+    const int safe_cap = push_bundle_size - extra;
187
+    int msgs_num = (strlen(msg) / safe_cap) + ((strlen(msg) % safe_cap) != 0);
188
+    struct buffer *msgs = gc_malloc((msgs_num + 1) * sizeof(struct buffer), true, &gc);
189
+
190
+    unsigned int option_types_found = 0;
191
+
192
+    msgs[msgs_num].data = NULL;
193
+    if (!message_splitter(msg, msgs, &gc, safe_cap))
194
+    {
195
+        gc_free(&gc);
196
+        return -EINVAL;
197
+    }
198
+
199
+    if (type == UPT_BY_CID)
200
+    {
201
+        struct multi_instance *mi = lookup_by_cid(m, *((unsigned long *)target));
202
+
203
+        if (!mi)
204
+        {
205
+            return -ENOENT;
206
+        }
207
+
208
+        const char *old_ip = mi->context.options.ifconfig_local;
209
+        const char *old_ipv6 = mi->context.options.ifconfig_ipv6_local;
210
+        if (!mi->halt
211
+            && send_single_push_update(&mi->context, msgs, &option_types_found))
212
+        {
213
+            if (option_types_found & OPT_P_UP)
214
+            {
215
+                update_vhash(m, mi, old_ip, old_ipv6);
216
+            }
217
+            gc_free(&gc);
218
+            return 1;
219
+        }
220
+        else
221
+        {
222
+            gc_free(&gc);
223
+            return 0;
224
+        }
225
+    }
226
+
227
+    int count = 0;
228
+    struct hash_iterator hi;
229
+    const struct hash_element *he;
230
+
231
+    hash_iterator_init(m->iter, &hi);
232
+    while ((he = hash_iterator_next(&hi)))
233
+    {
234
+        struct multi_instance *curr_mi = he->value;
235
+
236
+        if (curr_mi->halt)
237
+        {
238
+            continue;
239
+        }
240
+
241
+        /* Type is UPT_BROADCAST so we update every client */
242
+        option_types_found = 0;
243
+        const char *old_ip = curr_mi->context.options.ifconfig_local;
244
+        const char *old_ipv6 = curr_mi->context.options.ifconfig_ipv6_local;
245
+        if (!send_single_push_update(&curr_mi->context, msgs, &option_types_found))
246
+        {
247
+            msg(M_CLIENT, "ERROR: Peer ID: %u has not been updated",
248
+                curr_mi->context.c2.tls_multi ? curr_mi->context.c2.tls_multi->peer_id : UINT32_MAX);
249
+            continue;
250
+        }
251
+        if (option_types_found & OPT_P_UP)
252
+        {
253
+            update_vhash(m, curr_mi, old_ip, old_ipv6);
254
+        }
255
+        count++;
256
+    }
257
+
258
+    hash_iterator_free(&hi);
259
+    gc_free(&gc);
260
+    return count;
261
+}
262
+
263
+#define RETURN_UPDATE_STATUS(n_sent)                                  \
264
+    do                                                                \
265
+    {                                                                 \
266
+        if ((n_sent) > 0)                                             \
267
+        {                                                             \
268
+            msg(M_CLIENT, "SUCCESS: %d client(s) updated", (n_sent)); \
269
+            return true;                                              \
270
+        }                                                             \
271
+        else                                                          \
272
+        {                                                             \
273
+            msg(M_CLIENT, "ERROR: no client updated");                \
274
+            return false;                                             \
275
+        }                                                             \
276
+    } while (0)
277
+
278
+
279
+bool
280
+management_callback_send_push_update_broadcast(void *arg, const char *options)
281
+{
282
+    int n_sent = send_push_update(arg, NULL, options, UPT_BROADCAST, PUSH_BUNDLE_SIZE);
283
+
284
+    RETURN_UPDATE_STATUS(n_sent);
285
+}
286
+
287
+bool
288
+management_callback_send_push_update_by_cid(void *arg, unsigned long cid, const char *options)
289
+{
290
+    int n_sent = send_push_update(arg, &cid, options, UPT_BY_CID, PUSH_BUNDLE_SIZE);
291
+
292
+    RETURN_UPDATE_STATUS(n_sent);
293
+}
294
+#endif /* ifdef ENABLE_MANAGEMENT */
... ...
@@ -337,7 +337,8 @@ push_update_msg_testdriver_SOURCES = test_push_update_msg.c \
337 337
 	$(top_srcdir)/src/openvpn/platform.c \
338 338
 	$(top_srcdir)/src/openvpn/push_util.c \
339 339
 	$(top_srcdir)/src/openvpn/options_util.c \
340
-	$(top_srcdir)/src/openvpn/otime.c
340
+	$(top_srcdir)/src/openvpn/otime.c \
341
+	$(top_srcdir)/src/openvpn/list.c
341 342
 
342 343
 socket_testdriver_CFLAGS  = \
343 344
 	-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \
... ...
@@ -8,9 +8,16 @@
8 8
 #include <cmocka.h>
9 9
 #include "push.h"
10 10
 #include "options_util.h"
11
+#include "multi.h"
11 12
 
12 13
 /* mocks */
13 14
 
15
+void
16
+throw_signal_soft(const int signum, const char *signal_text)
17
+{
18
+    msg(M_WARN, "Offending option received from server");
19
+}
20
+
14 21
 unsigned int
15 22
 pull_permission_mask(const struct context *c)
16 23
 {
... ...
@@ -21,6 +28,18 @@ pull_permission_mask(const struct context *c)
21 21
     return flags;
22 22
 }
23 23
 
24
+void
25
+update_vhash(struct multi_context *m, struct multi_instance *mi, const char *old_ip, const char *old_ipv6)
26
+{
27
+    return;
28
+}
29
+
30
+bool
31
+options_postprocess_pull(struct options *options, struct env_set *es)
32
+{
33
+    return true;
34
+}
35
+
24 36
 bool
25 37
 apply_push_options(struct context *c, struct options *options, struct buffer *buf,
26 38
                    unsigned int permission_mask, unsigned int *option_types_found,
... ...
@@ -48,7 +67,6 @@ apply_push_options(struct context *c, struct options *options, struct buffer *bu
48 48
                 {
49 49
                     continue; /* Ignoring this option */
50 50
                 }
51
-                msg(M_WARN, "Offending option received from server");
52 51
                 return false; /* Cause push/pull error and stop push processing */
53 52
             }
54 53
         }
... ...
@@ -77,7 +95,7 @@ process_incoming_push_msg(struct context *c, const struct buffer *buffer,
77 77
     }
78 78
     else if (honor_received_options && buf_string_compare_advance(&buf, push_update_cmd))
79 79
     {
80
-        return process_incoming_push_update(c, permission_mask, option_types_found, &buf);
80
+        return process_incoming_push_update(c, permission_mask, option_types_found, &buf, false);
81 81
     }
82 82
     else
83 83
     {
... ...
@@ -85,6 +103,49 @@ process_incoming_push_msg(struct context *c, const struct buffer *buffer,
85 85
     }
86 86
 }
87 87
 
88
+const char *
89
+tls_common_name(const struct tls_multi *multi, const bool null)
90
+{
91
+    return NULL;
92
+}
93
+
94
+#ifndef ENABLE_MANAGEMENT
95
+bool
96
+send_control_channel_string(struct context *c, const char *str, int msglevel)
97
+{
98
+    return true;
99
+}
100
+#else  /* ifndef ENABLE_MANAGEMENT */
101
+char **res;
102
+int i;
103
+
104
+bool
105
+send_control_channel_string(struct context *c, const char *str, int msglevel)
106
+{
107
+    if (res && res[i] && strcmp(res[i], str))
108
+    {
109
+        printf("\n\nexpected: %s\n\n  actual: %s\n\n", res[i], str);
110
+        return false;
111
+    }
112
+    i++;
113
+    return true;
114
+}
115
+
116
+struct multi_instance *
117
+lookup_by_cid(struct multi_context *m, const unsigned long cid)
118
+{
119
+    return *(m->instances);
120
+}
121
+
122
+bool
123
+mroute_extract_openvpn_sockaddr(struct mroute_addr *addr,
124
+                                const struct openvpn_sockaddr *osaddr,
125
+                                bool use_port)
126
+{
127
+    return true;
128
+}
129
+#endif /* ifndef ENABLE_MANAGEMENT */
130
+
88 131
 /* tests */
89 132
 
90 133
 static void
... ...
@@ -120,7 +181,6 @@ test_incoming_push_message_error1(void **state)
120 120
     free_buf(&buf);
121 121
 }
122 122
 
123
-
124 123
 static void
125 124
 test_incoming_push_message_error2(void **state)
126 125
 {
... ...
@@ -219,6 +279,207 @@ test_incoming_push_message_mix2(void **state)
219 219
     free_buf(&buf);
220 220
 }
221 221
 
222
+#ifdef ENABLE_MANAGEMENT
223
+char *r0[] = {
224
+    "PUSH_UPDATE,redirect-gateway local,route 192.168.1.0 255.255.255.0"
225
+};
226
+char *r1[] = {
227
+    "PUSH_UPDATE,-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2",
228
+    "PUSH_UPDATE, akakakakakakakakakakakaf, dhcp-option DNS 8.8.8.8,redirect-gateway local,push-continuation 2",
229
+    "PUSH_UPDATE,route 192.168.1.0 255.255.255.0,push-continuation 1"
230
+};
231
+char *r3[] = {
232
+    "PUSH_UPDATE,,,"
233
+};
234
+char *r4[] = {
235
+    "PUSH_UPDATE,-dhcp-option, blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2",
236
+    "PUSH_UPDATE, akakakakakakakakakakakaf,dhcp-option DNS 8.8.8.8, redirect-gateway local,push-continuation 2",
237
+    "PUSH_UPDATE, route 192.168.1.0 255.255.255.0,,push-continuation 1"
238
+};
239
+char *r5[] = {
240
+    "PUSH_UPDATE,,-dhcp-option, blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2",
241
+    "PUSH_UPDATE, akakakakakakakakakakakaf,dhcp-option DNS 8.8.8.8, redirect-gateway local,push-continuation 2",
242
+    "PUSH_UPDATE, route 192.168.1.0 255.255.255.0,push-continuation 1"
243
+};
244
+char *r6[] = {
245
+    "PUSH_UPDATE,-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2",
246
+    "PUSH_UPDATE, akakakakakakakakakakakaf, dhcp-option DNS 8.8.8.8, redirect-gateway 10.10.10.10,,push-continuation 2",
247
+    "PUSH_UPDATE, route 192.168.1.0 255.255.255.0,,push-continuation 1"
248
+};
249
+char *r7[] = {
250
+    "PUSH_UPDATE,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,push-continuation 2",
251
+    "PUSH_UPDATE,,,,,,,,,,,,,,,,,,,push-continuation 1"
252
+};
253
+char *r8[] = {
254
+    "PUSH_UPDATE,-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2",
255
+    "PUSH_UPDATE, akakakakakakakakakakakaf, dhcp-option DNS 8.8.8.8,redirect-gateway\n local,push-continuation 2",
256
+    "PUSH_UPDATE,route 192.168.1.0 255.255.255.0\n\n\n,push-continuation 1"
257
+};
258
+char *r9[] = {
259
+    "PUSH_UPDATE,,"
260
+};
261
+
262
+
263
+const char *msg0 = "redirect-gateway local,route 192.168.1.0 255.255.255.0";
264
+const char *msg1 = "-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,"
265
+                   " akakakakakakakakakakakaf, dhcp-option DNS 8.8.8.8,redirect-gateway local,route 192.168.1.0 255.255.255.0";
266
+const char *msg2 = "";
267
+const char *msg3 = ",,";
268
+const char *msg4 = "-dhcp-option, blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,"
269
+                   " akakakakakakakakakakakaf,dhcp-option DNS 8.8.8.8, redirect-gateway local, route 192.168.1.0 255.255.255.0,";
270
+const char *msg5 = ",-dhcp-option, blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,"
271
+                   " akakakakakakakakakakakaf,dhcp-option DNS 8.8.8.8, redirect-gateway local, route 192.168.1.0 255.255.255.0";
272
+const char *msg6 = "-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf, akakakakakakakakakakakaf,"
273
+                   " dhcp-option DNS 8.8.8.8, redirect-gateway 10.10.10.10,, route 192.168.1.0 255.255.255.0,";
274
+const char *msg7 = ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,";
275
+const char *msg8 = "-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf, akakakakakakakakakakakaf,"
276
+                   " dhcp-option DNS 8.8.8.8,redirect-gateway\n local,route 192.168.1.0 255.255.255.0\n\n\n";
277
+const char *msg9 = ",";
278
+
279
+const char *msg10 = "abandon ability able about above absent absorb abstract absurd abuse access accident account accuse achieve"
280
+                    "acid acoustic acquire across act action actor actress actual adapt add addict address adjust"
281
+                    "baby bachelor bacon badge bag balance balcony ball bamboo banana banner bar barely bargain barrel base basic"
282
+                    "basket battle beach bean beauty because become beef before begin behave behind"
283
+                    "cabbage cabin cable cactus cage cake call calm camera camp can canal cancel candy cannon canoe canvas canyon"
284
+                    "capable capital captain car carbon card cargo carpet carry cart case"
285
+                    "daisy damage damp dance danger daring dash daughter dawn day deal debate debris decade december decide decline"
286
+                    "decorate decrease deer defense define defy degree delay deliver demand demise denial";
287
+
288
+#define PUSH_BUNDLE_SIZE_TEST 184
289
+
290
+static void
291
+test_send_push_msg0(void **state)
292
+{
293
+    i = 0;
294
+    res = r0;
295
+    struct multi_context *m = *state;
296
+    const unsigned long cid = 0;
297
+    assert_int_equal(send_push_update(m, &cid, msg0, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);
298
+}
299
+static void
300
+test_send_push_msg1(void **state)
301
+{
302
+    i = 0;
303
+    res = r1;
304
+    struct multi_context *m = *state;
305
+    const unsigned long cid = 0;
306
+    assert_int_equal(send_push_update(m, &cid, msg1, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);
307
+}
308
+
309
+static void
310
+test_send_push_msg2(void **state)
311
+{
312
+    i = 0;
313
+    res = NULL;
314
+    struct multi_context *m = *state;
315
+    const unsigned long cid = 0;
316
+    assert_int_equal(send_push_update(m, &cid, msg2, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), -EINVAL);
317
+}
318
+
319
+static void
320
+test_send_push_msg3(void **state)
321
+{
322
+    i = 0;
323
+    res = r3;
324
+    struct multi_context *m = *state;
325
+    const unsigned long cid = 0;
326
+    assert_int_equal(send_push_update(m, &cid, msg3, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);
327
+}
328
+
329
+static void
330
+test_send_push_msg4(void **state)
331
+{
332
+    i = 0;
333
+    res = r4;
334
+    struct multi_context *m = *state;
335
+    const unsigned long cid = 0;
336
+    assert_int_equal(send_push_update(m, &cid, msg4, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);
337
+}
338
+
339
+static void
340
+test_send_push_msg5(void **state)
341
+{
342
+    i = 0;
343
+    res = r5;
344
+    struct multi_context *m = *state;
345
+    const unsigned long cid = 0;
346
+    assert_int_equal(send_push_update(m, &cid, msg5, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);
347
+}
348
+
349
+static void
350
+test_send_push_msg6(void **state)
351
+{
352
+    i = 0;
353
+    res = r6;
354
+    struct multi_context *m = *state;
355
+    const unsigned long cid = 0;
356
+    assert_int_equal(send_push_update(m, &cid, msg6, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);
357
+}
358
+
359
+static void
360
+test_send_push_msg7(void **state)
361
+{
362
+    i = 0;
363
+    res = r7;
364
+    struct multi_context *m = *state;
365
+    const unsigned long cid = 0;
366
+    assert_int_equal(send_push_update(m, &cid, msg7, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);
367
+}
368
+
369
+static void
370
+test_send_push_msg8(void **state)
371
+{
372
+    i = 0;
373
+    res = r8;
374
+    struct multi_context *m = *state;
375
+    const unsigned long cid = 0;
376
+    assert_int_equal(send_push_update(m, &cid, msg8, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);
377
+}
378
+
379
+static void
380
+test_send_push_msg9(void **state)
381
+{
382
+    i = 0;
383
+    res = r9;
384
+    struct multi_context *m = *state;
385
+    const unsigned long cid = 0;
386
+    assert_int_equal(send_push_update(m, &cid, msg9, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);
387
+}
388
+
389
+static void
390
+test_send_push_msg10(void **state)
391
+{
392
+    i = 0;
393
+    res = NULL;
394
+    struct multi_context *m = *state;
395
+    const unsigned long cid = 0;
396
+    assert_int_equal(send_push_update(m, &cid, msg10, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), -EINVAL);
397
+}
398
+
399
+#undef PUSH_BUNDLE_SIZE_TEST
400
+
401
+static int
402
+setup2(void **state)
403
+{
404
+    struct multi_context *m = calloc(1, sizeof(struct multi_context));
405
+    m->instances = calloc(1, sizeof(struct multi_instance *));
406
+    struct multi_instance *mi = calloc(1, sizeof(struct multi_instance));
407
+    *(m->instances) = mi;
408
+    *state = m;
409
+    return 0;
410
+}
411
+
412
+static int
413
+teardown2(void **state)
414
+{
415
+    struct multi_context *m = *state;
416
+    free(*(m->instances));
417
+    free(m->instances);
418
+    free(m);
419
+    return 0;
420
+}
421
+#endif /* ifdef ENABLE_MANAGEMENT */
422
+
222 423
 static int
223 424
 setup(void **state)
224 425
 {
... ...
@@ -249,7 +510,20 @@ main(void)
249 249
         cmocka_unit_test_setup_teardown(test_incoming_push_message_1, setup, teardown),
250 250
         cmocka_unit_test_setup_teardown(test_incoming_push_message_bad_format, setup, teardown),
251 251
         cmocka_unit_test_setup_teardown(test_incoming_push_message_mix, setup, teardown),
252
-        cmocka_unit_test_setup_teardown(test_incoming_push_message_mix2, setup, teardown)
252
+        cmocka_unit_test_setup_teardown(test_incoming_push_message_mix2, setup, teardown),
253
+#ifdef ENABLE_MANAGEMENT
254
+        cmocka_unit_test_setup_teardown(test_send_push_msg0, setup2, teardown2),
255
+        cmocka_unit_test_setup_teardown(test_send_push_msg1, setup2, teardown2),
256
+        cmocka_unit_test_setup_teardown(test_send_push_msg2, setup2, teardown2),
257
+        cmocka_unit_test_setup_teardown(test_send_push_msg3, setup2, teardown2),
258
+        cmocka_unit_test_setup_teardown(test_send_push_msg4, setup2, teardown2),
259
+        cmocka_unit_test_setup_teardown(test_send_push_msg5, setup2, teardown2),
260
+        cmocka_unit_test_setup_teardown(test_send_push_msg6, setup2, teardown2),
261
+        cmocka_unit_test_setup_teardown(test_send_push_msg7, setup2, teardown2),
262
+        cmocka_unit_test_setup_teardown(test_send_push_msg8, setup2, teardown2),
263
+        cmocka_unit_test_setup_teardown(test_send_push_msg9, setup2, teardown2),
264
+        cmocka_unit_test_setup_teardown(test_send_push_msg10, setup2, teardown2)
265
+#endif
253 266
     };
254 267
 
255 268
     return cmocka_run_group_tests(tests, NULL, NULL);