@@ -4496,6 +4496,7 @@
static void
unlearn_ifconfig(struct multi_context *m, struct multi_instance *mi)
{
+ multi_del_dco_iroutes_v4(m, mi);
in_addr_t old_addr = 0;
old_addr = htonl(mi->context.c2.push_ifconfig_local);
multi_unlearn_in_addr_t(m, mi, old_addr);
@@ -4508,6 +4509,7 @@
static void
unlearn_ifconfig_ipv6(struct multi_context *m, struct multi_instance *mi)
{
+ multi_del_dco_iroutes_v6(m, mi);
struct in6_addr old_addr6;
CLEAR(old_addr6);
old_addr6 = mi->context.c2.push_ifconfig_ipv6_local;
@@ -4517,9 +4519,54 @@
CLEAR(mi->reporting_addr_ipv6);
}
+/* Reinstall only routes that are still owned by this client. */
+static void
+multi_reinstall_owned_dco_iroute(struct multi_context *m, struct multi_instance *mi,
+ const struct mroute_addr *addr)
+{
+ const struct multi_route *route = hash_lookup(m->vhash, addr);
+ if (route && route->instance == mi && multi_route_defined(m, route))
+ {
+ dco_install_iroute(m, mi, addr);
+ }
+}
+
+/* Userspace iroutes do not depend on the client's VPN address. Only their
+ * DCO routes need to be reinstalled when the gateway changes. */
+static void
+multi_reinstall_dco_iroutes(struct multi_context *m, struct multi_instance *mi, int flags)
+{
+ if ((flags & (1 << AF_INET)) && mi->context.c2.push_ifconfig_defined)
+ {
+ for (const struct iroute *ir = mi->context.options.iroutes; ir; ir = ir->next)
+ {
+ struct mroute_addr addr = { 0 };
+ mroute_extract_in_addr_t(&addr, ir->network);
+ addr.type |= MR_WITH_NETBITS;
+ addr.netbits = (uint8_t)ir->netbits;
+ multi_reinstall_owned_dco_iroute(m, mi, &addr);
+ }
+ }
+
+ if ((flags & (1 << AF_INET6)) && mi->context.c2.push_ifconfig_ipv6_defined)
+ {
+ for (const struct iroute_ipv6 *ir6 = mi->context.options.iroutes_ipv6; ir6; ir6 = ir6->next)
+ {
+ struct mroute_addr addr = { 0 };
+ addr.type = MR_ADDR_IPV6 | MR_WITH_NETBITS;
+ addr.len = 16;
+ addr.netbits = (uint8_t)ir6->netbits;
+ addr.v6.addr = ir6->network;
+ mroute_addr_mask_host_bits(&addr);
+ multi_reinstall_owned_dco_iroute(m, mi, &addr);
+ }
+ }
+}
+
/**
- * Update the vhash when a push-update message containing
- * ifconfig/ifconfig-ipv6 options is sent from the server.
+ * Update the vhash and DCO peer addresses when a push-update message containing
+ * ifconfig/ifconfig-ipv6 options is sent from the server. Preserve userspace
+ * iroutes and reinstall their DCO routes with the new IP if needed.
*
* @param m The multi_context
* @param mi The multi_instance of the client we are updating
@@ -4531,15 +4578,20 @@
bool
update_vaddr(struct multi_context *m, struct multi_instance *mi, const char *new_ip, const char *new_ipv6)
{
+ /* Track changed address families to avoid resetting unchanged DCO addresses. */
+ int update_flags = 0;
+
/* -ifconfig */
if (!new_ip && mi->context.c2.push_ifconfig_defined)
{
unlearn_ifconfig(m, mi);
+ update_flags |= (1 << AF_INET);
}
/* -ifconfig-ipv6 */
if (!new_ipv6 && mi->context.c2.push_ifconfig_ipv6_defined)
{
unlearn_ifconfig_ipv6(m, mi);
+ update_flags |= (1 << AF_INET6);
}
if (new_ip)
@@ -4553,6 +4605,7 @@
if (mi->context.c2.push_ifconfig_defined)
{
unlearn_ifconfig(m, mi);
+ update_flags |= (1 << AF_INET);
}
/* DCO needs the new address while installing its on-link route. */
@@ -4562,6 +4615,7 @@
{
/* set our client's VPN endpoint for status reporting purposes */
mi->reporting_addr = mi->context.c2.push_ifconfig_local;
+ update_flags |= (1 << AF_INET);
}
else
{
@@ -4589,6 +4643,7 @@
if (mi->context.c2.push_ifconfig_ipv6_defined)
{
unlearn_ifconfig_ipv6(m, mi);
+ update_flags |= (1 << AF_INET6);
}
/* DCO needs the new address while installing its on-link route. */
@@ -4598,6 +4653,7 @@
{
/* set our client's VPN endpoint for status reporting purposes */
mi->reporting_addr_ipv6 = mi->context.c2.push_ifconfig_ipv6_local;
+ update_flags |= (1 << AF_INET6);
}
else
{
@@ -4613,6 +4669,24 @@
}
}
}
+
+ if (!update_flags || !dco_enabled(&m->top.options))
+ {
+ return true;
+ }
+
+ int ret = dco_multi_update_peer_addr(&mi->context, update_flags);
+ if (ret < 0)
+ {
+ msg(M_WARN, "MULTI: failed to update DCO peer addresses (error %d)", ret);
+ /* PUSH_UPDATE is already queued, so reconnect to restore consistent state. */
+ send_restart(&mi->context, NULL);
+ multi_schedule_context_wakeup(m, mi);
+ return false;
+ }
+
+ multi_reinstall_dco_iroutes(m, mi, update_flags);
+
return true;
}
@@ -255,12 +255,14 @@
static int
send_push_update(struct multi_context *m, const void *target, const char *msg, const push_update_type type, const size_t push_bundle_size)
{
+#if !defined(TARGET_LINUX)
if (dco_enabled(&m->top.options))
{
msg(M_WARN, "WARN: PUSH_UPDATE messages cannot currently be sent while DCO is enabled."
" To send a PUSH_UPDATE message, be sure to use the --disable-dco option.");
return 0;
}
+#endif
if (!msg || !*msg || !m || (!target && type != UPT_BROADCAST))
{
@@ -303,6 +305,7 @@
}
if (!mi->halt
+ && !event_timeout_defined(&mi->context.c2.scheduled_exit)
&& send_single_push_update(m, mi, msgs))
{
buffer_list_free(msgs);
@@ -323,7 +326,9 @@
{
struct multi_instance *curr_mi = m->instances[i];
- if (!curr_mi || curr_mi->halt || !support_push_update(curr_mi))
+ if (!curr_mi || curr_mi->halt
+ || event_timeout_defined(&curr_mi->context.c2.scheduled_exit)
+ || !support_push_update(curr_mi))
{
continue;
}