[PATCH v2 30/42] wireguard: Add routes for other than any addresses
Jussi Laakkonen <[email protected]> Wed, 13 Aug 2025 18:02:02 +0300
| Newsgroups | dev.linux.lists.connman |
|---|---|
| Message-ID | <[email protected]> |
This adds a use of the vpn_provider_append_route_complete() to setup the
routes to the AllowedIP networks after WireGuard has connected. Peer
endpoint is used as a gateway and routes to other address family types
are ignored, as well as the any routes, which are setup by
libwireguard.c. This follows then the approach used in wg-quick.
When the endpoint is resolved to a new address the old routes are
removed and new ones are added. This is done without a delay, at startup
there is ROUTE_SETUP_TIMEOUT delay (200ms) to make sure setup is complete.
---
vpn/plugins/wireguard.c | 147 +++++++++++++++++++++++++++++++++++++---
1 file changed, 138 insertions(+), 9 deletions(-)
diff --git a/vpn/plugins/wireguard.c b/vpn/plugins/wireguard.c
index 271b57b2..92a538ea 100644
--- a/vpn/plugins/wireguard.c
+++ b/vpn/plugins/wireguard.c
@@ -53,6 +53,7 @@
#define DNS_RERESOLVE_TIMEOUT 20
#define DNS_RERESOLVE_ERROR_LIMIT 5
+#define ROUTE_SETUP_TIMEOUT 200 // ms
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
struct wireguard_info {
@@ -66,6 +67,7 @@ struct wireguard_info {
guint resolv_id;
guint remove_resolv_id;
guint dying_id;
+ guint route_setup_id;
};
struct sockaddr_u {
@@ -262,6 +264,25 @@ struct wg_ipaddresses {
struct connman_ipaddress *ipaddress_ipv6;
};
+static char *cidr_to_netmask(int family, unsigned char cidr)
+{
+ switch (family) {
+ case AF_INET:
+ return g_strdup_printf("%d.%d.%d.%d",
+ ((0xffffffff << (32 - cidr)) >> 24) & 0xff,
+ ((0xffffffff << (32 - cidr)) >> 16) & 0xff,
+ ((0xffffffff << (32 - cidr)) >> 8) & 0xff,
+ ((0xffffffff << (32 - cidr)) >> 0) & 0xff);
+
+ case AF_INET6:
+ return g_strdup_printf("%u", cidr);
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
static int parse_addresses(const char *address, const char *gateway,
struct wg_ipaddresses *ipaddresses)
{
@@ -303,12 +324,7 @@ static int parse_addresses(const char *address, const char *gateway,
}
family = AF_INET;
- netmask = g_strdup_printf("%d.%d.%d.%d",
- ((0xffffffff << (32 - prefixlen)) >> 24) & 0xff,
- ((0xffffffff << (32 - prefixlen)) >> 16) & 0xff,
- ((0xffffffff << (32 - prefixlen)) >> 8) & 0xff,
- ((0xffffffff << (32 - prefixlen)) >> 0) & 0xff);
-
+ netmask = cidr_to_netmask(family, prefixlen);
ipaddress = connman_ipaddress_alloc(family);
err = connman_ipaddress_set_ipv4(ipaddress, tokens[0],
netmask, gateway);
@@ -401,6 +417,7 @@ static bool sockaddr_cmp_addr(struct sockaddr_u *a, struct sockaddr_u *b)
}
static void run_dns_reresolve(struct wireguard_info *info);
+static void run_route_setup(struct wireguard_info *info, guint timeout);
static void remove_resolv(struct wireguard_info *info)
{
@@ -524,6 +541,13 @@ static void resolve_endpoint_cb(GResolvResultStatus status,
info->device.name);
run_dns_reresolve(info);
+
+ /*
+ * Endpoint has changed and only one peer is used -> all old routes
+ * are invalid. Redo them without delay.
+ */
+ vpn_provider_delete_all_routes(info->provider);
+ run_route_setup(info, 0);
}
static int disconnect(struct vpn_provider *provider, int error);
@@ -565,16 +589,107 @@ static gboolean wg_dns_reresolve_cb(gpointer user_data)
return G_SOURCE_REMOVE;
}
+static const char *endpoint_to_str(struct wg_peer *peer, char *buf,
+ socklen_t len)
+{
+ struct sockaddr_u *addr;
+ int family;
+
+ addr = (struct sockaddr_u *)&peer->endpoint.addr;
+ family = peer->endpoint.addr.sa_family;
+
+ switch (family) {
+ case AF_INET:
+ return inet_ntop(family, &addr->sin.sin_addr, buf, len);
+ case AF_INET6:
+ return inet_ntop(family, &addr->sin6.sin6_addr, buf, len);
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static gboolean wg_route_setup_cb(gpointer user_data)
+{
+ struct wireguard_info *info = user_data;
+ struct wg_allowedip *allowedip;
+ char addr[INET6_ADDRSTRLEN] = { 0 };
+ char endpoint[INET6_ADDRSTRLEN] = { 0 };
+ char *netmask;
+ int family;
+ unsigned long idx = 0;
+
+ info->route_setup_id = 0;
+
+ family = info->peer.endpoint.addr.sa_family;
+
+ if (!endpoint_to_str(&info->peer, endpoint, INET6_ADDRSTRLEN)) {
+ DBG("Faulty endpoint set, use endpoint_fqdn");
+ memcpy(&endpoint, info->endpoint_fqdn, INET6_ADDRSTRLEN);
+ family = connman_inet_check_ipaddress(info->endpoint_fqdn);
+ }
+
+ wg_for_each_allowedip(&info->peer, allowedip) {
+ // TODO: search peers when multiple peers are supported
+ if (allowedip->family != family) {
+ DBG("Ignoring AllowedIP with different family as host");
+ continue;
+ }
+
+ memset(&addr, 0, INET6_ADDRSTRLEN);
+
+ switch (allowedip->family) {
+ case AF_INET:
+ if (!inet_ntop(allowedip->family, &allowedip->ip4, addr,
+ INET6_ADDRSTRLEN)) {
+ DBG("ignore invalid IPv4 address");
+ continue;
+ }
+
+ break;
+ case AF_INET6:
+ if (!inet_ntop(allowedip->family, &allowedip->ip6, addr,
+ INET6_ADDRSTRLEN)) {
+ DBG("ignore invalid IPv6 address");
+ continue;
+ }
+
+ break;
+ default:
+ DBG("ignore invalid IP family");
+ continue;
+ }
+
+ if (connman_inet_is_any_addr(addr, allowedip->family)) {
+ DBG("ignore any addr %s", addr);
+ continue;
+ }
+
+ netmask = cidr_to_netmask(allowedip->family, allowedip->cidr);
+
+ vpn_provider_append_route_complete(info->provider, idx,
+ allowedip->family, addr,
+ netmask, endpoint);
+
+ g_free(netmask);
+ ++idx;
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
static void run_dns_reresolve(struct wireguard_info *info)
{
if (info->reresolve_id)
g_source_remove(info->reresolve_id);
+ info->reresolve_id = 0;
+
if (vpn_provider_get_connection_errors(info->provider) >=
DNS_RERESOLVE_ERROR_LIMIT) {
connman_warn("reresolve error limit reached");
disconnect(info->provider, -ENONET);
- info->reresolve_id = 0;
return;
}
@@ -582,6 +697,14 @@ static void run_dns_reresolve(struct wireguard_info *info)
wg_dns_reresolve_cb, info);
}
+static void run_route_setup(struct wireguard_info *info, guint timeout)
+{
+ if (info->route_setup_id)
+ g_source_remove(info->route_setup_id);
+
+ info->route_setup_id = g_timeout_add(timeout, wg_route_setup_cb, info);
+}
+
static int wg_connect(struct vpn_provider *provider,
struct connman_task *task, const char *if_name,
vpn_provider_connect_cb_t cb,
@@ -663,6 +786,7 @@ static int wg_connect(struct vpn_provider *provider,
vpn_provider_set_boolean(provider, "SplitRouting", do_split_routing,
false);
+
option = vpn_provider_get_string(provider,
"WireGuard.PersistentKeepalive");
if (option) {
@@ -742,8 +866,10 @@ done:
connman_ipaddress_free(ipaddresses.ipaddress_ipv4);
connman_ipaddress_free(ipaddresses.ipaddress_ipv6);
- if (!err)
+ if (!err) {
run_dns_reresolve(info);
+ run_route_setup(info, ROUTE_SETUP_TIMEOUT);
+ }
return err;
@@ -798,9 +924,12 @@ static int disconnect(struct vpn_provider *provider, int err)
if (info->dying_id)
return -EALREADY;
- if (info->reresolve_id > 0)
+ if (info->reresolve_id)
g_source_remove(info->reresolve_id);
+ if (info->route_setup_id)
+ g_source_remove(info->route_setup_id);
+
if (info->resolv || info->resolv_id)
remove_resolv(info);
--
2.39.5