From 2204b9a6b021c0b2c7e5d40427c214bc1318498f Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 8 May 2024 18:02:43 +0200 Subject: [PATCH 1/7] shared: add some nm_auto_unref* macros --- shared/nm-utils/nm-macros-internal.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h index 084219b..898e1c9 100644 --- a/shared/nm-utils/nm-macros-internal.h +++ b/shared/nm-utils/nm-macros-internal.h @@ -225,12 +225,17 @@ static inline int nm_close (int fd); NM_AUTO_DEFINE_FCN_VOID (void *, _nm_auto_free_impl, free) #define nm_auto_free nm_auto(_nm_auto_free_impl) +NM_AUTO_DEFINE_FCN0 (GVariant *, _nm_auto_unref_variant, g_variant_unref); +#define nm_auto_unref_variant nm_auto(_nm_auto_unref_variant) + NM_AUTO_DEFINE_FCN0 (GVariantIter *, _nm_auto_free_variant_iter, g_variant_iter_free) #define nm_auto_free_variant_iter nm_auto(_nm_auto_free_variant_iter) NM_AUTO_DEFINE_FCN0 (GVariantBuilder *, _nm_auto_unref_variant_builder, g_variant_builder_unref) #define nm_auto_unref_variant_builder nm_auto(_nm_auto_unref_variant_builder) +#define nm_auto_clear_variant_builder nm_auto(g_variant_builder_clear) + NM_AUTO_DEFINE_FCN (GList *, _nm_auto_free_list, g_list_free) #define nm_auto_free_list nm_auto(_nm_auto_free_list) @@ -245,6 +250,9 @@ NM_AUTO_DEFINE_FCN_VOID0 (void *, _nm_auto_unref_gtypeclass, g_type_class_unref) NM_AUTO_DEFINE_FCN0 (GByteArray *, _nm_auto_unref_bytearray, g_byte_array_unref) #define nm_auto_unref_bytearray nm_auto(_nm_auto_unref_bytearray) +NM_AUTO_DEFINE_FCN0(char **, _nm_auto_strfreev, g_strfreev); +#define nm_auto_strfreev nm_auto(_nm_auto_strfreev) + static inline void _nm_auto_free_gstring (GString **str) { -- GitLab From 45c697ad7764d41c50ffa75d1f978dab4c57fea7 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 13 May 2024 18:46:17 +0200 Subject: [PATCH 2/7] service: support detecting IPv6 SAD routes Similarly to what we currently do for IPv4, also detect IPv6 SAD routes based on XFRM policies. We match entries where the selector's destination network is not /0 and the gateway (tmpl) is our VPN gateway. Note that the selector and gateway can be of different address families; therefore we return a separate array for IPv4 and IPv6 routes. --- src/nm-libreswan-service.c | 90 ++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/src/nm-libreswan-service.c b/src/nm-libreswan-service.c index 516eaac..f72c702 100644 --- a/src/nm-libreswan-service.c +++ b/src/nm-libreswan-service.c @@ -936,8 +936,13 @@ badpipes: */ typedef struct { - struct in_addr gw4; + int gw_addr_family; + union { + struct in_addr v4; + struct in6_addr v6; + } gw; gboolean have_routes4; + gboolean have_routes6; } RoutesInfo; static int @@ -979,15 +984,14 @@ setup_socket (void) } static int -parse_reply (struct nl_msg *msg, void *arg) +parse_reply (struct nl_msg *msg, RoutesInfo *info) { - RoutesInfo *info = arg; struct nlmsghdr *n = nlmsg_hdr (msg); struct nlattr *tb[XFRMA_MAX + 1]; struct xfrm_userpolicy_info *xpinfo = NULL; - if (info->have_routes4) { - /* already found some routes */ + if (info->have_routes4 && info->have_routes6) { + /* Already determined that there are routes for both IPv4 and IPv6 */ return NL_SKIP; } @@ -1014,23 +1018,44 @@ parse_reply (struct nl_msg *msg, void *arg) return NL_SKIP; } + if (!NM_IN_SET (xpinfo->sel.family, AF_INET, AF_INET6)) + return NL_SKIP; + + /* We only look for subnet route associations, eg where + * (sel->prefixlen_d > 0), and for those associations, we match + * the xfrm_user_tmpl's destination address against the PLUTO_PEER. + */ + if (xpinfo->sel.prefixlen_d == 0) + return NL_SKIP; + if (tb[XFRMA_TMPL]) { int attrlen = nla_len (tb[XFRMA_TMPL]); struct xfrm_user_tmpl *list = nla_data (tb[XFRMA_TMPL]); + char saddr[INET6_ADDRSTRLEN]; + char daddr[INET6_ADDRSTRLEN]; + char gw[INET6_ADDRSTRLEN]; int i; - /* We only look for subnet route associations, eg where - * (sel->prefixlen_d > 0), and for those associations, we match - * the xfrm_user_tmpl's destination address against the PLUTO_PEER. - */ - if (xpinfo->sel.family == AF_INET && xpinfo->sel.prefixlen_d > 0) { - for (i = 0; i < attrlen / sizeof (struct xfrm_user_tmpl); i++) { - struct xfrm_user_tmpl *tmpl = &list[i]; + for (i = 0; i < attrlen / sizeof (struct xfrm_user_tmpl); i++) { + struct xfrm_user_tmpl *tmpl = &list[i]; + + if (!NM_IN_SET (tmpl->family, AF_INET, AF_INET6)) + continue; + + if ( tmpl->family == info->gw_addr_family + && memcmp (&tmpl->id.daddr, &info->gw, nm_utils_addr_family_to_size (tmpl->family)) == 0) { + + _LOGD("found SAD non-default route: src %s/%u dst %s/%u gw %s", + inet_ntop (xpinfo->sel.family, &xpinfo->sel.saddr, saddr, sizeof (saddr)), + xpinfo->sel.prefixlen_s, + inet_ntop( xpinfo->sel.family, &xpinfo->sel.daddr, daddr, sizeof (daddr)), + xpinfo->sel.prefixlen_d, + inet_ntop (tmpl->family, &tmpl->id.daddr, gw, sizeof (gw))); - if ( tmpl->family == AF_INET - && memcmp (&tmpl->id.daddr, &info->gw4, sizeof (struct in_addr)) == 0) { + if (xpinfo->sel.family == AF_INET) { info->have_routes4 = TRUE; - break; + } else if (xpinfo->sel.family == AF_INET6) { + info->have_routes6 = TRUE; } } } @@ -1039,37 +1064,45 @@ parse_reply (struct nl_msg *msg, void *arg) return NL_OK; } -static gboolean -have_sad_routes (const char *gw_addr4) +static void +have_sad_routes (const char *gw, int gw_addr_family, + gboolean *have_routes4, gboolean *have_routes6) { - RoutesInfo info = { { 0 }, FALSE }; + RoutesInfo info = { }; struct nl_sock *sk; int err; - if (inet_pton (AF_INET, gw_addr4, &info.gw4) != 1) - return FALSE; + *have_routes4 = FALSE; + *have_routes6 = FALSE; + + info.gw_addr_family = gw_addr_family; + + if (inet_pton (gw_addr_family, gw, &info.gw) != 1) + return; sk = setup_socket (); - if (!sk) - return FALSE; err = nl_send_simple (sk, XFRM_MSG_GETPOLICY, NLM_F_DUMP, NULL, 0); if (err < 0) { - _LOGW ("Error sending: %d %s", err, nl_geterror (err)); + _LOGW ("Error sending XFRM request: %d %s", err, nl_geterror (err)); goto done; } - nl_socket_modify_cb (sk, NL_CB_VALID, NL_CB_CUSTOM, parse_reply, &info); + nl_socket_modify_cb (sk, NL_CB_VALID, NL_CB_CUSTOM, + (nl_recvmsg_msg_cb_t) parse_reply, + &info); err = nl_recvmsgs_default (sk); if (err < 0) { - _LOGW ("Error parsing: %d %s", err, nl_geterror (err)); + _LOGW ("Error parsing XFRM policies: %d %s", err, nl_geterror (err)); goto done; } done: + *have_routes4 = info.have_routes4; + *have_routes6 = info.have_routes6; + nl_socket_free (sk); - return info.have_routes4; } /****************************************************************/ @@ -1269,6 +1302,8 @@ handle_callback (NMDBusLibreswanHelper *object, const char *str; gboolean is_xfrmi = FALSE; gboolean has_ip4; + gboolean have_routes4; + gboolean have_routes6; _LOGI ("Configuration from the helper received."); @@ -1408,7 +1443,8 @@ handle_callback (NMDBusLibreswanHelper *object, g_variant_builder_end (&builder)); /* :( */ - if (have_sad_routes (lookup_string (env, "PLUTO_PEER"))) + have_sad_routes (lookup_string (env, "PLUTO_PEER"), AF_INET, &have_routes4, &have_routes6); + if (have_routes4) g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT, g_variant_new_boolean (TRUE)); success = TRUE; -- GitLab From 2718d8a67f5a4ed58a0b710a2e16738781951f55 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 13 May 2024 19:09:46 +0200 Subject: [PATCH 3/7] service: track routes as NMIPRoute objects Store the routes as NMIPRoute objects instead of GVariant's. In this way we don't have to deconstruct the variants every time we need to walk the list. --- src/nm-libreswan-service.c | 219 ++++++++++++++++++++++++------------- 1 file changed, 143 insertions(+), 76 deletions(-) diff --git a/src/nm-libreswan-service.c b/src/nm-libreswan-service.c index f72c702..22b2e07 100644 --- a/src/nm-libreswan-service.c +++ b/src/nm-libreswan-service.c @@ -160,14 +160,6 @@ _debug_write_option (const char *setting) /****************************************************************/ -guint32 -nm_utils_ip4_prefix_to_netmask (guint32 prefix) -{ - return prefix < 32 ? ~htonl(0xFFFFFFFF >> prefix) : 0xFFFFFFFF; -} - -/****************************************************************/ - static gboolean pr_cb (GIOChannel *source, GIOCondition condition, gpointer user_data); static void @@ -1142,19 +1134,19 @@ addr4_to_gvariant (const char *str) return g_variant_new_uint32 (temp_addr.s_addr); } -static GVariant * -netmask4_to_gvariant (const char *str) +static gboolean +netmask4_to_prefixlen (const char *str, guint *plen) { - struct in_addr temp_addr; + struct in_addr addr; - /* Empty */ if (!str || strlen (str) < 1) - return NULL; + return FALSE; - if (inet_pton (AF_INET, str, &temp_addr) <= 0) - return NULL; + if (inet_pton (AF_INET, str, &addr) <= 0) + return FALSE; - return g_variant_new_uint32 (nm_utils_ip4_netmask_to_prefix (temp_addr.s_addr)); + *plen = nm_utils_ip4_netmask_to_prefix (addr.s_addr); + return TRUE; } static GVariant * @@ -1201,28 +1193,30 @@ lookup_string (GVariant *dict, const gchar *key) } static void -_take_route (GPtrArray *routes, GVariant *new, gboolean alive) +take_route (GPtrArray *routes, NMIPRoute *route, gboolean alive) { - guint32 network, network2, netmask; - guint32 plen, plen2; + int family; + int family2; + const char *dest; + const char *dest2; + guint plen; + guint plen2; guint i; - if (!new) + if (!route) return; - g_variant_get_child (new, 0, "u", &network); - g_variant_get_child (new, 1, "u", &plen); - - netmask = nm_utils_ip4_prefix_to_netmask (plen); + family = nm_ip_route_get_family (route); + plen = nm_ip_route_get_prefix (route); + dest = nm_ip_route_get_dest (route); + /* Check for duplicates */ for (i = 0; i < routes->len; i++) { - GVariant *r = routes->pdata[i]; - - g_variant_get_child (r, 0, "u", &network2); - g_variant_get_child (r, 1, "u", &plen2); + family2 = nm_ip_route_get_family (routes->pdata[i]); + plen2 = nm_ip_route_get_prefix (routes->pdata[i]); + dest2 = nm_ip_route_get_dest (routes->pdata[i]); - if ( plen2 == plen - && ((network ^ network2) & netmask) == 0) { + if (family == family2 && plen == plen2 && nm_streq (dest, dest2)) { g_ptr_array_remove_index (routes, i); break; } @@ -1231,56 +1225,119 @@ _take_route (GPtrArray *routes, GVariant *new, gboolean alive) if (alive) { /* On new or update, we always add the new route to the end. * For update, we basically move the route to the end. */ - g_ptr_array_add (routes, g_variant_ref_sink (new)); - } else - g_variant_unref (new); + g_ptr_array_add (routes, route); + } else { + nm_ip_route_unref (route); + } } -static void -handle_route (GPtrArray *routes, GVariant *env, gboolean alive, gboolean is_xfrmi) +static GVariant * +route_to_gvariant(NMIPRoute *route) { + const char *dest; + guint plen; + GVariant *variant; + const char *next_hop; + const char *src = NULL; GVariantBuilder builder; - const gchar *net, *mask, *next_hop, *my_sourceip; - if (!lookup_string (env, "PLUTO_PEER_CLIENT")) + dest = nm_ip_route_get_dest (route); + plen = nm_ip_route_get_prefix (route); + next_hop = nm_ip_route_get_next_hop (route); + + variant = nm_ip_route_get_attribute (route, NM_IP_ROUTE_ATTRIBUTE_SRC); + if (variant) { + nm_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)); + src = g_variant_get_string (variant, NULL); + } + + nm_assert (nm_ip_route_get_family (route) == AF_INET); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("au")); + g_variant_builder_add_value (&builder, addr4_to_gvariant (dest)); + g_variant_builder_add_value (&builder, g_variant_new_uint32 (plen)); + g_variant_builder_add_value (&builder, addr4_to_gvariant (next_hop ?: "0.0.0.0")); + g_variant_builder_add_value (&builder, g_variant_new_uint32 (0)); + if (src) + g_variant_builder_add_value (&builder, addr4_to_gvariant (src)); + + return g_variant_builder_end (&builder); +} + +static NMIPRoute * +new_route(int family, + const char *dest, + guint prefix, + const char *next_hop, + const char *src) +{ + NMIPRoute *route; + gs_free_error GError *error = NULL; + + route = nm_ip_route_new (family, dest, prefix, next_hop, 0, &error); + if (!route) { + _LOGW("Error creating route: dest %s, prefix %u, next_hop %s: %s", + dest, prefix, next_hop, error->message); + return NULL; + } + + if (src) { + nm_ip_route_set_attribute (route, NM_IP_ROUTE_ATTRIBUTE_SRC, g_variant_new_string (src)); + } + + return route; +} + +static void +handle_route (GPtrArray *routes, GVariant *env, const char *verb, gboolean is_xfrmi) +{ + gboolean alive; + const char *net; + const char *mask; + const char *next_hop = NULL; + const char *my_sourceip = NULL; + NMIPRoute *route; + guint plen; + gs_free_error GError *error = NULL; + + if (g_str_has_prefix (verb, "route-")) + alive = TRUE; + else if (g_str_has_prefix (verb, "unroute-")) + alive = FALSE; + else { + /* no route change */ return; + } net = lookup_string (env, "PLUTO_PEER_CLIENT_NET"); mask = lookup_string (env, "PLUTO_PEER_CLIENT_MASK"); next_hop = lookup_string (env, "PLUTO_NEXT_HOP"); my_sourceip = lookup_string (env, "PLUTO_MY_SOURCEIP"); - if (!net || !mask || !next_hop || !my_sourceip) return; if (is_xfrmi) next_hop = "0.0.0.0"; - if (g_strcmp0 (net, "0.0.0.0") == 0 && g_strcmp0 (mask, "0")) { - g_variant_builder_init (&builder, G_VARIANT_TYPE ("au")); - g_variant_builder_add_value (&builder, addr4_to_gvariant ("0.0.0.0")); - g_variant_builder_add_value (&builder, netmask4_to_gvariant ("128.0.0.0")); - g_variant_builder_add_value (&builder, addr4_to_gvariant (next_hop)); - g_variant_builder_add_value (&builder, g_variant_new_uint32 (0)); - g_variant_builder_add_value (&builder, addr4_to_gvariant (my_sourceip)); - _take_route (routes, g_variant_builder_end (&builder), alive); - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("au")); - g_variant_builder_add_value (&builder, addr4_to_gvariant ("128.0.0.0")); - g_variant_builder_add_value (&builder, netmask4_to_gvariant ("128.0.0.0")); - g_variant_builder_add_value (&builder, addr4_to_gvariant (next_hop)); - g_variant_builder_add_value (&builder, g_variant_new_uint32 (0)); - g_variant_builder_add_value (&builder, addr4_to_gvariant (my_sourceip)); - _take_route (routes, g_variant_builder_end (&builder), alive); - } else { - g_variant_builder_init (&builder, G_VARIANT_TYPE ("au")); - g_variant_builder_add_value (&builder, addr4_to_gvariant (net)); - g_variant_builder_add_value (&builder, netmask4_to_gvariant (mask)); - g_variant_builder_add_value (&builder, addr4_to_gvariant (next_hop)); - g_variant_builder_add_value (&builder, g_variant_new_uint32 (0)); - g_variant_builder_add_value (&builder, addr4_to_gvariant (my_sourceip)); - _take_route (routes, g_variant_builder_end (&builder), alive); + if (!netmask4_to_prefixlen (mask, &plen)) { + _LOGW("Invalid route netmask: %s", mask); + return; + } + + if (nm_streq (net, "0.0.0.0") && plen == 0) { + /* We want to override the default route that might already exist + * on the interface. Split our default route into two /1 routes + * that will be preferred due to the longest prefix. */ + route = new_route (AF_INET, "0.0.0.0", 1, next_hop, my_sourceip); + take_route (routes, route, alive); + + route = new_route (AF_INET, "128.0.0.0", 1, next_hop, my_sourceip); + take_route (routes, route, alive); + } else { + /* Generic route */ + route = new_route (AF_INET, net, plen, next_hop, my_sourceip); + take_route (routes, route, alive); } } @@ -1293,10 +1350,9 @@ handle_callback (NMDBusLibreswanHelper *object, NMLibreswanPluginPrivate *priv = NM_LIBRESWAN_PLUGIN_GET_PRIVATE (user_data); NMSettingVpn *s_vpn; GVariantBuilder config; - GVariantBuilder builder; GVariant *val; gboolean success = FALSE; - guint i; + GVariant *variant; const char *verb; const char *virt_if; const char *str; @@ -1429,18 +1485,29 @@ handle_callback (NMDBusLibreswanHelper *object, g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, val); } - /* This route */ - if (g_strcmp0 (verb, "route-client") == 0 || g_strcmp0 (verb, "route-host")) - handle_route (priv->routes, env, TRUE, is_xfrmi); - else if (g_strcmp0 (verb, "unroute-client") == 0 || g_strcmp0 (verb, "unroute-host")) - handle_route (priv->routes, env, FALSE, is_xfrmi); - /* Routes */ - g_variant_builder_init (&builder, G_VARIANT_TYPE ("aau")); - for (i = 0; i < priv->routes->len; i++) - g_variant_builder_add_value (&builder, (GVariant *) priv->routes->pdata[i]); - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_ROUTES, - g_variant_builder_end (&builder)); + { + nm_auto_unref_variant_builder GVariantBuilder *routes4 = NULL; + guint i; + + handle_route (priv->routes, env, verb, is_xfrmi); + + for (i = 0; i < priv->routes->len; i++) { + NMIPRoute *route = priv->routes->pdata[i]; + + nm_assert (nm_ip_route_get_family (route) == AF_INET); + variant = route_to_gvariant (route); + + if (!routes4) + routes4 = g_variant_builder_new (G_VARIANT_TYPE("aau")); + g_variant_builder_add_value (routes4, variant); + } + + if (routes4) { + g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_ROUTES, + g_variant_builder_end (routes4)); + } + } /* :( */ have_sad_routes (lookup_string (env, "PLUTO_PEER"), AF_INET, &have_routes4, &have_routes6); @@ -1986,7 +2053,7 @@ nm_libreswan_plugin_init (NMLibreswanPlugin *plugin) { NMLibreswanPluginPrivate *priv = NM_LIBRESWAN_PLUGIN_GET_PRIVATE (plugin); - priv->routes = g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_unref); + priv->routes = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_route_unref); } static void -- GitLab From 64d19991080b3057d360280c5cafcd3cf738554e Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 14 May 2024 10:26:10 +0200 Subject: [PATCH 4/7] service: fix handling of routes The next hop must not be set in routed mode. Also, it's not needed when it's the same as the the peer. Corresponding code in the libreswan script /usr/libexec/ipsec/_updown.xfrm: # use nexthop if nexthop is not %direct and POINTPOINT is not set if [ "${PLUTO_NEXT_HOP}" != "${PLUTO_PEER}" -a -z "${POINTPOINT}" ]; then # XFRM interface needs no nexthop if [ -z "${PLUTO_XFRMI_ROUTE}" ]; then parms2="via ${PLUTO_NEXT_HOP}" fi --- src/nm-libreswan-service.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/nm-libreswan-service.c b/src/nm-libreswan-service.c index 22b2e07..70a41a0 100644 --- a/src/nm-libreswan-service.c +++ b/src/nm-libreswan-service.c @@ -1293,6 +1293,7 @@ handle_route (GPtrArray *routes, GVariant *env, const char *verb, gboolean is_xf { gboolean alive; const char *net; + const char *peer; const char *mask; const char *next_hop = NULL; const char *my_sourceip = NULL; @@ -1309,16 +1310,23 @@ handle_route (GPtrArray *routes, GVariant *env, const char *verb, gboolean is_xf return; } + peer = lookup_string (env, "PLUTO_PEER"); net = lookup_string (env, "PLUTO_PEER_CLIENT_NET"); mask = lookup_string (env, "PLUTO_PEER_CLIENT_MASK"); - next_hop = lookup_string (env, "PLUTO_NEXT_HOP"); my_sourceip = lookup_string (env, "PLUTO_MY_SOURCEIP"); - if (!net || !mask || !next_hop || !my_sourceip) + if (!peer || !net || !mask) return; - if (is_xfrmi) - next_hop = "0.0.0.0"; + if (!is_xfrmi) { + next_hop = lookup_string (env, "PLUTO_NEXT_HOP"); + if (!next_hop) + return; + } + + /* Use the next hop only if it's not directly through the peer */ + if (nm_streq0 (peer, next_hop)) + next_hop = NULL; if (!netmask4_to_prefixlen (mask, &plen)) { _LOGW("Invalid route netmask: %s", mask); -- GitLab From ce27077a13a37072fd58824ea84d76693c9da65c Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 14 May 2024 09:28:33 +0200 Subject: [PATCH 5/7] service: add IPv6 support to helper functions --- src/nm-libreswan-service.c | 150 ++++++++++++++++++++++++------------- 1 file changed, 100 insertions(+), 50 deletions(-) diff --git a/src/nm-libreswan-service.c b/src/nm-libreswan-service.c index 70a41a0..717ef5f 100644 --- a/src/nm-libreswan-service.c +++ b/src/nm-libreswan-service.c @@ -1120,67 +1120,108 @@ str_to_gvariant (const char *str, gboolean try_convert) } static GVariant * -addr4_to_gvariant (const char *str) +addr_to_gvariant (const char *str, int addr_family) { - struct in_addr temp_addr; + union { + struct in_addr v4; + struct in6_addr v6; + } addr; - /* Empty */ if (!str || strlen (str) < 1) return NULL; - if (inet_pton (AF_INET, str, &temp_addr) <= 0) + if (inet_pton (addr_family, str, &addr) <= 0) return NULL; - return g_variant_new_uint32 (temp_addr.s_addr); + if (addr_family == AF_INET) { + return g_variant_new_uint32 (addr.v4.s_addr); + } else { + return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + &addr, + sizeof (addr.v6), + 1); + } } static gboolean -netmask4_to_prefixlen (const char *str, guint *plen) +netmask_to_prefixlen (const char *str, int addr_family, guint *plen) { - struct in_addr addr; + union { + struct in_addr v4; + struct in6_addr v6; + } addr; if (!str || strlen (str) < 1) return FALSE; - if (inet_pton (AF_INET, str, &addr) <= 0) + if (inet_pton (addr_family, str, &addr) <= 0) return FALSE; - *plen = nm_utils_ip4_netmask_to_prefix (addr.s_addr); - return TRUE; + if (addr_family == AF_INET) { + *plen = nm_utils_ip4_netmask_to_prefix (addr.v4.s_addr); + return TRUE; + } else { + guint tot_zeros = 0; + guint zeros; + int i; + + for (i = 3; i >= 0; i--) { + if (ntohl (addr.v6.s6_addr32[i]) == 0) + zeros = 32; + else + zeros = __builtin_ctz (ntohl (addr.v6.s6_addr32[i])); + + tot_zeros += zeros; + if (zeros != 32) + break; + } + *plen = 128 - tot_zeros; + return TRUE; + } } -static GVariant * -addr4_list_to_gvariant (const char *str) +static void +addr_list_to_gvariants (const char *str, const char *desc, GVariant **out4, GVariant **out6) { - GVariantBuilder builder; - char **split; - int i; + nm_auto_strfreev char **split = NULL; + nm_auto_unref_variant_builder GVariantBuilder *builder4 = NULL; + nm_auto_unref_variant_builder GVariantBuilder *builder6 = NULL; + GVariantBuilder **builder; + guint i; + + *out4 = NULL; + *out6 = NULL; - /* Empty */ if (!str || strlen (str) < 1) - return NULL; + return; split = g_strsplit (str, " ", -1); if (g_strv_length (split) == 0) - return NULL; - - g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + return; for (i = 0; split[i]; i++) { - struct in_addr addr; + GVariant *variant; + int addr_family; - if (inet_pton (AF_INET, split[i], &addr) > 0) { - g_variant_builder_add_value (&builder, g_variant_new_uint32 (addr.s_addr)); - } else { - g_strfreev (split); - g_variant_unref (g_variant_builder_end (&builder)); - return NULL; + addr_family = strchr (split[i], ':') ? AF_INET6 : AF_INET; + variant = addr_to_gvariant (split[i], addr_family); + if (!variant) { + _LOGW ("ignoring invalid address \"%s\" for %s", split[i], desc); + continue; } + + builder = (addr_family == AF_INET) ? &builder4 : &builder6; + if (!*builder) + *builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); + g_variant_builder_add_value (*builder, variant); } - g_strfreev (split); + if (builder4) + *out4 = g_variant_builder_end (builder4); + if (builder6) + *out6 = g_variant_builder_end (builder6); - return g_variant_builder_end (&builder); + return; } static const gchar * @@ -1239,7 +1280,6 @@ route_to_gvariant(NMIPRoute *route) GVariant *variant; const char *next_hop; const char *src = NULL; - GVariantBuilder builder; dest = nm_ip_route_get_dest (route); plen = nm_ip_route_get_prefix (route); @@ -1251,17 +1291,28 @@ route_to_gvariant(NMIPRoute *route) src = g_variant_get_string (variant, NULL); } - nm_assert (nm_ip_route_get_family (route) == AF_INET); + if (nm_ip_route_get_family (route) == AF_INET) { + GVariantBuilder builder; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("au")); + g_variant_builder_add_value (&builder, addr_to_gvariant (dest, AF_INET)); + g_variant_builder_add_value (&builder, g_variant_new_uint32 (plen)); + g_variant_builder_add_value (&builder, addr_to_gvariant (next_hop ?: "0.0.0.0", AF_INET)); + g_variant_builder_add_value (&builder, g_variant_new_uint32 (0)); + if (src) + g_variant_builder_add_value (&builder, addr_to_gvariant (src, AF_INET)); + return g_variant_builder_end (&builder); + } else { + gs_free GVariant **variants = g_new (GVariant *, 5); - g_variant_builder_init (&builder, G_VARIANT_TYPE ("au")); - g_variant_builder_add_value (&builder, addr4_to_gvariant (dest)); - g_variant_builder_add_value (&builder, g_variant_new_uint32 (plen)); - g_variant_builder_add_value (&builder, addr4_to_gvariant (next_hop ?: "0.0.0.0")); - g_variant_builder_add_value (&builder, g_variant_new_uint32 (0)); - if (src) - g_variant_builder_add_value (&builder, addr4_to_gvariant (src)); + variants[0] = addr_to_gvariant (dest, AF_INET6); + variants[1] = g_variant_new_uint32 (plen); + variants[2] = addr_to_gvariant (next_hop ?: "::", AF_INET6); + variants[3] = g_variant_new_uint32 (0); + variants[4] = addr_to_gvariant (src ?: "::", AF_INET6); - return g_variant_builder_end (&builder); + return g_variant_new_tuple (variants, 5); + } } static NMIPRoute * @@ -1328,7 +1379,7 @@ handle_route (GPtrArray *routes, GVariant *env, const char *verb, gboolean is_xf if (nm_streq0 (peer, next_hop)) next_hop = NULL; - if (!netmask4_to_prefixlen (mask, &plen)) { + if (!netmask_to_prefixlen (mask, AF_INET, &plen)) { _LOGW("Invalid route netmask: %s", mask); return; } @@ -1414,7 +1465,7 @@ handle_callback (NMDBusLibreswanHelper *object, g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_BANNER, val); /* Right peer (or Gateway) */ - val = addr4_to_gvariant (lookup_string (env, "PLUTO_PEER")); + val = addr_to_gvariant (lookup_string (env, "PLUTO_PEER"), AF_INET); if (val) g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, val); else { @@ -1433,7 +1484,7 @@ handle_callback (NMDBusLibreswanHelper *object, g_variant_builder_init (&config, G_VARIANT_TYPE_VARDICT); /* Right peer (or Gateway) */ - val = addr4_to_gvariant (lookup_string (env, "PLUTO_PEER")); + val = addr_to_gvariant (lookup_string (env, "PLUTO_PEER"), AF_INET); if (val) g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_GATEWAY, val); else { @@ -1442,7 +1493,7 @@ handle_callback (NMDBusLibreswanHelper *object, } /* IP address */ - val = addr4_to_gvariant (lookup_string (env, "PLUTO_MY_SOURCEIP")); + val = addr_to_gvariant (lookup_string (env, "PLUTO_MY_SOURCEIP"), AF_INET); if (val) g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, val); else { @@ -1451,7 +1502,7 @@ handle_callback (NMDBusLibreswanHelper *object, } /* PTP address; PTP address == internal IP4 address */ - val = addr4_to_gvariant (lookup_string (env, "PLUTO_MY_SOURCEIP")); + val = addr_to_gvariant (lookup_string (env, "PLUTO_MY_SOURCEIP"), AF_INET); if (val) g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_PTP, val); else { @@ -1464,11 +1515,10 @@ handle_callback (NMDBusLibreswanHelper *object, g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, val); /* DNS */ - val = addr4_list_to_gvariant (lookup_string (env, "PLUTO_CISCO_DNS_INFO")); - if (!val) { - /* libreswan value */ - val = addr4_list_to_gvariant (lookup_string (env, "PLUTO_PEER_DNS_INFO")); - } + str = lookup_string (env, "PLUTO_CISCO_DNS_INFO"); + if (!str) + str = lookup_string (env, "PLUTO_PEER_DNS_INFO"); + addr_list_to_gvariants (str, "DNS", &val, NULL); if (val) g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_DNS, val); @@ -1488,7 +1538,7 @@ handle_callback (NMDBusLibreswanHelper *object, /* The traffic needs to be sent directly over the interface without a gateway. * Ignore the next hop. */ } else { - val = addr4_to_gvariant (lookup_string (env, "PLUTO_NEXT_HOP")); + val = addr_to_gvariant (lookup_string (env, "PLUTO_NEXT_HOP"), AF_INET); if (val) g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, val); } -- GitLab From a402bd07faa7a9871ac30616f0463a0feffe874b Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 14 May 2024 11:16:30 +0200 Subject: [PATCH 6/7] service: support configurations without IP addresses In a subnet-to-subnet scenario in routed mode, we want to return no additional address for the XFRM interface, and still return routes. This is not possible with the current code, which returns an IP configuration only if the address is set. --- src/nm-libreswan-service.c | 240 +++++++++++++++++++------------------ 1 file changed, 122 insertions(+), 118 deletions(-) diff --git a/src/nm-libreswan-service.c b/src/nm-libreswan-service.c index 717ef5f..9875edd 100644 --- a/src/nm-libreswan-service.c +++ b/src/nm-libreswan-service.c @@ -1408,17 +1408,15 @@ handle_callback (NMDBusLibreswanHelper *object, { NMLibreswanPluginPrivate *priv = NM_LIBRESWAN_PLUGIN_GET_PRIVATE (user_data); NMSettingVpn *s_vpn; - GVariantBuilder config; - GVariant *val; - gboolean success = FALSE; + nm_auto_clear_variant_builder GVariantBuilder config = {}; + nm_auto_clear_variant_builder GVariantBuilder ip4_config = {}; GVariant *variant; + gboolean has_ip_config = FALSE; + const char *xfrm_interface = NULL; const char *verb; - const char *virt_if; - const char *str; - gboolean is_xfrmi = FALSE; - gboolean has_ip4; - gboolean have_routes4; - gboolean have_routes6; + gboolean success = FALSE; + const char *cstr; + char *str = NULL; _LOGI ("Configuration from the helper received."); @@ -1428,119 +1426,95 @@ handle_callback (NMDBusLibreswanHelper *object, goto out; } - /* First build and send the generic config */ g_variant_builder_init (&config, G_VARIANT_TYPE_VARDICT); + g_variant_builder_init (&ip4_config, G_VARIANT_TYPE_VARDICT); - if ( priv->connection - && (s_vpn = nm_connection_get_setting_vpn (priv->connection)) - && (str = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_KEY_LEFTMODECFGCLIENT)) - && nm_streq (str, "no")) { - has_ip4 = FALSE; - } else { - has_ip4 = TRUE; - } - - _LOGD ("Configuration has IPv4: %d", has_ip4); - - /* - * Enabled address families - */ - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_HAS_IP4, g_variant_new_boolean (has_ip4)); - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_HAS_IP6, g_variant_new_boolean (FALSE)); - - /* - * Tunnel device - */ - virt_if = lookup_string (env, "PLUTO_VIRT_INTERFACE"); - if (virt_if && !nm_streq (virt_if, "NULL")) { - val = g_variant_new_string (virt_if); - } else { - val = g_variant_new_string (NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV_NONE); - } - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_TUNDEV, val); + variant = str_to_gvariant (lookup_string (env, "PLUTO_PEER_BANNER"), TRUE); + if (variant) + g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_BANNER, variant); - /* Banner */ - val = str_to_gvariant (lookup_string (env, "PLUTO_PEER_BANNER"), TRUE); - if (val) - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_BANNER, val); - - /* Right peer (or Gateway) */ - val = addr_to_gvariant (lookup_string (env, "PLUTO_PEER"), AF_INET); - if (val) - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, val); + variant = addr_to_gvariant (lookup_string (env, "PLUTO_PEER"), AF_INET); + if (variant) + g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, variant); else { - _LOGW ("IPsec/Pluto Right Peer (VPN Gateway) is missing"); - goto out; - } - - nm_vpn_service_plugin_set_config (NM_VPN_SERVICE_PLUGIN (user_data), - g_variant_builder_end (&config)); - if (!has_ip4) { - success = TRUE; + _LOGW ("IPsec/Pluto Right Peer (VPN Gateway) is missing or invalid"); goto out; } - /* Then build and send the IPv4 config */ - g_variant_builder_init (&config, G_VARIANT_TYPE_VARDICT); - - /* Right peer (or Gateway) */ - val = addr_to_gvariant (lookup_string (env, "PLUTO_PEER"), AF_INET); - if (val) - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_GATEWAY, val); - else { - _LOGW ("IPsec/Pluto Right Peer (VPN Gateway) is missing"); - goto out; + if (nm_streq0 (lookup_string (env, "PLUTO_XFRMI_ROUTE"), "yes")) { + /* Route-based VPN, configured via option "ipsec-interface". No + * next-hop is needed, the traffic is sent over the interface without + * a gateway */ + xfrm_interface = lookup_string (env, "PLUTO_VIRT_INTERFACE"); + variant = str_to_gvariant (xfrm_interface, TRUE); + if (variant) + g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_TUNDEV, variant); + } else { + variant = addr_to_gvariant (lookup_string (env, "PLUTO_NEXT_HOP"), AF_INET); + if (variant) { + g_variant_builder_add (&config, "{sv}", + NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, + variant); + } } - /* IP address */ - val = addr_to_gvariant (lookup_string (env, "PLUTO_MY_SOURCEIP"), AF_INET); - if (val) - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, val); - else { - _LOGW ("IP4 Address is missing"); - goto out; - } + if ( priv->connection + && (s_vpn = nm_connection_get_setting_vpn (priv->connection)) + && (cstr = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_KEY_LEFTMODECFGCLIENT)) + && nm_streq (cstr, "no")) { + /* no dynamic address needed */ + } else { + /* IP address */ + variant = addr_to_gvariant (lookup_string (env, "PLUTO_MY_SOURCEIP"), AF_INET); + if (variant) { + g_variant_builder_add (&ip4_config, "{sv}", + NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, + variant); + /* no PTP is expressed as PTP == ADDRESS */ + g_variant_builder_add (&ip4_config, "{sv}", + NM_VPN_PLUGIN_IP4_CONFIG_PTP, + variant); + } else { + _LOGW ("IP Address is missing"); + goto out; + } - /* PTP address; PTP address == internal IP4 address */ - val = addr_to_gvariant (lookup_string (env, "PLUTO_MY_SOURCEIP"), AF_INET); - if (val) - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_PTP, val); - else { - _LOGW ("IP4 PTP Address is missing"); - goto out; + /* Netmask */ + variant = g_variant_new_uint32 (32); + g_variant_builder_add (&ip4_config, "{sv}", + NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, + variant); + has_ip_config = TRUE; } - /* Netmask */ - val = g_variant_new_uint32 (32); - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, val); - /* DNS */ - str = lookup_string (env, "PLUTO_CISCO_DNS_INFO"); - if (!str) - str = lookup_string (env, "PLUTO_PEER_DNS_INFO"); - addr_list_to_gvariants (str, "DNS", &val, NULL); - if (val) - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_DNS, val); - - /* Default domain */ - val = str_to_gvariant (lookup_string (env, "PLUTO_CISCO_DOMAIN_INFO"), TRUE); - if (!val) { - /* libreswan value */ - val = str_to_gvariant (lookup_string (env, "PLUTO_PEER_DOMAIN_INFO"), TRUE); - } - if (val) - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN, val); - - /* Indicates whether the VPN is using a XFRM interface (via option ipsec-interface=) */ - is_xfrmi = nm_streq0 (lookup_string (env, "PLUTO_XFRMI_ROUTE"), "yes"); - - if (is_xfrmi) { - /* The traffic needs to be sent directly over the interface without a gateway. - * Ignore the next hop. */ - } else { - val = addr_to_gvariant (lookup_string (env, "PLUTO_NEXT_HOP"), AF_INET); - if (val) - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, val); + cstr = lookup_string (env, "PLUTO_CISCO_DNS_INFO"); + if (!cstr) + cstr = lookup_string (env, "PLUTO_PEER_DNS_INFO"); + if (cstr) { + GVariant *dns4 = NULL; + GVariant *dns6 = NULL; + + addr_list_to_gvariants (cstr, "DNS", &dns4, &dns6); + if (dns4) { + g_variant_builder_add (&ip4_config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_DNS, dns4); + has_ip_config = TRUE; + } + if (dns6) + g_variant_unref (dns6); + } + + /* DNS domain */ + cstr = lookup_string (env, "PLUTO_CISCO_DOMAIN_INFO"); + if (!cstr) + cstr = lookup_string (env, "PLUTO_PEER_DOMAIN_INFO"); + if (cstr && has_ip_config) { + variant = str_to_gvariant (cstr, TRUE); + if (variant) { + g_variant_builder_add (&ip4_config, "{sv}", + NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN, + variant); + } } /* Routes */ @@ -1548,7 +1522,7 @@ handle_callback (NMDBusLibreswanHelper *object, nm_auto_unref_variant_builder GVariantBuilder *routes4 = NULL; guint i; - handle_route (priv->routes, env, verb, is_xfrmi); + handle_route (priv->routes, env, verb, !!xfrm_interface); for (i = 0; i < priv->routes->len; i++) { NMIPRoute *route = priv->routes->pdata[i]; @@ -1562,20 +1536,50 @@ handle_callback (NMDBusLibreswanHelper *object, } if (routes4) { - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_ROUTES, + g_variant_builder_add (&ip4_config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_ROUTES, g_variant_builder_end (routes4)); + has_ip_config = TRUE; } } - /* :( */ - have_sad_routes (lookup_string (env, "PLUTO_PEER"), AF_INET, &have_routes4, &have_routes6); - if (have_routes4) - g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT, g_variant_new_boolean (TRUE)); + if (has_ip_config) { + gboolean have_routes4; + gboolean have_routes6; - success = TRUE; - nm_vpn_service_plugin_set_ip4_config (NM_VPN_SERVICE_PLUGIN (user_data), - g_variant_builder_end (&config)); + /* Determine the never-default value based on the presence of SAD routes :( */ + have_sad_routes (lookup_string (env, "PLUTO_PEER"), AF_INET, &have_routes4, &have_routes6); + if (have_routes4) { + g_variant_builder_add (&ip4_config, "{sv}", + NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT, + g_variant_new_boolean (TRUE)); + } + } + g_variant_builder_add (&config, "{sv}", + NM_VPN_PLUGIN_CONFIG_HAS_IP4, + g_variant_new_boolean (has_ip_config)); + g_variant_builder_add (&config, "{sv}", + NM_VPN_PLUGIN_CONFIG_HAS_IP6, + g_variant_new_boolean (FALSE)); + + /* Finally, send configs to NM */ + variant = g_variant_builder_end (&config); + g_variant_ref_sink (variant); + _LOGD("sending config: %s", (str = g_variant_print (variant, FALSE))); + g_clear_pointer (&str, g_free); + nm_vpn_service_plugin_set_config (NM_VPN_SERVICE_PLUGIN (user_data), variant); + g_variant_unref (variant); + + if (has_ip_config) { + variant = g_variant_builder_end (&ip4_config); + _LOGD("sending IP4 config: %s", (str = g_variant_print (variant, FALSE))); + g_clear_pointer (&str, g_free); + nm_vpn_service_plugin_set_ip4_config (NM_VPN_SERVICE_PLUGIN (user_data), + variant); + g_variant_unref (variant); + } + + success = TRUE; out: if (!success) { connect_failed (NM_LIBRESWAN_PLUGIN (user_data), NULL, -- GitLab From b4c22fa613cb2f71173088cb1742f6548a732839 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 14 May 2024 13:49:08 +0200 Subject: [PATCH 7/7] service: support IPv6 Extend the callback handler and related functions to deal with IPv6. --- shared/utils.c | 64 +++++++++++++------ src/nm-libreswan-service.c | 126 ++++++++++++++++++++++++++----------- 2 files changed, 134 insertions(+), 56 deletions(-) diff --git a/shared/utils.c b/shared/utils.c index 5e19316..65bc603 100644 --- a/shared/utils.c +++ b/shared/utils.c @@ -112,6 +112,7 @@ nm_libreswan_config_write (gint fd, const char *leftrsasigkey; const char *rightrsasigkey; const char *authby; + const char *local_network; const char *remote_network; const char *ikev2 = NULL; const char *rightid; @@ -120,6 +121,7 @@ nm_libreswan_config_write (gint fd, const char *fragmentation; const char *mobike; const char *pfs; + const char *client_family; const char *item; gboolean is_ikev2 = FALSE; @@ -163,6 +165,14 @@ nm_libreswan_config_write (gint fd, WRITE_CHECK (fd, debug_write_fcn, error, " leftid=@%s", leftid); } + item = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_KEY_HOSTADDRFAMILY); + if (item && strlen (item)) + WRITE_CHECK (fd, debug_write_fcn, error, " hostaddrfamily=%s", item); + + client_family = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_KEY_CLIENTADDRFAMILY); + if (client_family && strlen (client_family)) + WRITE_CHECK (fd, debug_write_fcn, error, " clientaddrfamily=%s", client_family); + leftrsasigkey = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_KEY_LEFTRSASIGKEY); rightrsasigkey = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_KEY_RIGHTRSASIGKEY); leftcert = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_KEY_LEFTCERT); @@ -221,17 +231,43 @@ nm_libreswan_config_write (gint fd, WRITE_CHECK (fd, debug_write_fcn, error, " rightmodecfgserver=yes"); WRITE_CHECK (fd, debug_write_fcn, error, " modecfgpull=yes"); - remote_network = nm_setting_vpn_get_data_item (s_vpn, - NM_LIBRESWAN_KEY_REMOTENETWORK); - if (!remote_network || !strlen (remote_network)) - WRITE_CHECK (fd, debug_write_fcn, error, " rightsubnet=0.0.0.0/0"); - else - WRITE_CHECK (fd, debug_write_fcn, error, " rightsubnet=%s", - remote_network); - item = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_KEY_LOCALNETWORK); - if (item) { - WRITE_CHECK (fd, debug_write_fcn, error, " leftsubnet=%s", item); + local_network = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_KEY_LOCALNETWORK); + if (local_network) { + WRITE_CHECK (fd, debug_write_fcn, error, " leftsubnet=%s", local_network); + } + + remote_network = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_KEY_REMOTENETWORK); + if (!remote_network || !strlen (remote_network)) { + int addr_family = AF_UNSPEC; + + /* Detect the address family of the remote subnet. We use in order: + * 1) the "clientaddrfamily" property 2) the local network. + */ + if (nm_streq0 (client_family, "ipv4")) { + addr_family = AF_INET; + } else if (nm_streq0 (client_family, "ipv6")) { + addr_family = AF_INET6; + } else { + if ( local_network + && nm_utils_parse_inaddr_prefix_bin (AF_INET, local_network, NULL, NULL)) { + addr_family = AF_INET; + } else if (local_network + && nm_utils_parse_inaddr_prefix_bin (AF_INET6, local_network, NULL, NULL)) { + addr_family = AF_INET6; + } + } + + if (addr_family == AF_INET6) { + WRITE_CHECK (fd, debug_write_fcn, error, " rightsubnet=::/0"); + } else { + /* For backwards compatibility, if we can't determine the family + * assume it's IPv4. Anyway, in the future we need to stop adding + * the option automatically. */ + WRITE_CHECK (fd, debug_write_fcn, error, " rightsubnet=0.0.0.0/0"); + } + } else { + WRITE_CHECK (fd, debug_write_fcn, error, " rightsubnet=%s", remote_network); } if (!is_ikev2) { @@ -341,14 +377,6 @@ nm_libreswan_config_write (gint fd, if (item && strlen (item)) WRITE_CHECK (fd, debug_write_fcn, error, " type=%s", item); - item = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_KEY_HOSTADDRFAMILY); - if (item && strlen (item)) - WRITE_CHECK (fd, debug_write_fcn, error, " hostaddrfamily=%s", item); - - item = nm_setting_vpn_get_data_item (s_vpn, NM_LIBRESWAN_KEY_CLIENTADDRFAMILY); - if (item && strlen (item)) - WRITE_CHECK (fd, debug_write_fcn, error, " clientaddrfamily=%s", item); - WRITE_CHECK (fd, debug_write_fcn, error, " nm-configured=yes"); WRITE_CHECK_NEWLINE (fd, trailing_newline, debug_write_fcn, error, " auto=add"); diff --git a/src/nm-libreswan-service.c b/src/nm-libreswan-service.c index 9875edd..e5956af 100644 --- a/src/nm-libreswan-service.c +++ b/src/nm-libreswan-service.c @@ -1348,6 +1348,7 @@ handle_route (GPtrArray *routes, GVariant *env, const char *verb, gboolean is_xf const char *mask; const char *next_hop = NULL; const char *my_sourceip = NULL; + int addr_family; NMIPRoute *route; guint plen; gs_free_error GError *error = NULL; @@ -1379,12 +1380,14 @@ handle_route (GPtrArray *routes, GVariant *env, const char *verb, gboolean is_xf if (nm_streq0 (peer, next_hop)) next_hop = NULL; - if (!netmask_to_prefixlen (mask, AF_INET, &plen)) { + addr_family = strchr(net, ':') ? AF_INET6 : AF_INET; + + if (!netmask_to_prefixlen (mask, addr_family, &plen)) { _LOGW("Invalid route netmask: %s", mask); return; } - if (nm_streq (net, "0.0.0.0") && plen == 0) { + if (addr_family == AF_INET && nm_streq (net, "0.0.0.0") && plen == 0) { /* We want to override the default route that might already exist * on the interface. Split our default route into two /1 routes * that will be preferred due to the longest prefix. */ @@ -1393,9 +1396,15 @@ handle_route (GPtrArray *routes, GVariant *env, const char *verb, gboolean is_xf route = new_route (AF_INET, "128.0.0.0", 1, next_hop, my_sourceip); take_route (routes, route, alive); - } else { + } else if (addr_family == AF_INET6 && nm_streq (net, "::") && plen == 0) { + /* Do the same as Libreswan script /usr/libexec/ipsec/_updown.xfrm: + * add a route 2000::/3 that eclipses the default route without + * replacing it */ + route = new_route (AF_INET6, "2000::", 3, next_hop, my_sourceip); + take_route (routes, route, alive); + } else { /* Generic route */ - route = new_route (AF_INET, net, plen, next_hop, my_sourceip); + route = new_route (addr_family, net, plen, next_hop, my_sourceip); take_route (routes, route, alive); } } @@ -1410,30 +1419,39 @@ handle_callback (NMDBusLibreswanHelper *object, NMSettingVpn *s_vpn; nm_auto_clear_variant_builder GVariantBuilder config = {}; nm_auto_clear_variant_builder GVariantBuilder ip4_config = {}; + nm_auto_clear_variant_builder GVariantBuilder ip6_config = {}; + GVariantBuilder *ip_config[2] = { &ip4_config, &ip6_config }; + gboolean has_ip_config[2] = { FALSE, FALSE }; GVariant *variant; - gboolean has_ip_config = FALSE; const char *xfrm_interface = NULL; const char *verb; gboolean success = FALSE; + gboolean is_ipv6; const char *cstr; char *str = NULL; - _LOGI ("Configuration from the helper received."); - verb = lookup_string (env, "PLUTO_VERB"); if (!verb) { _LOGW ("PLUTO_VERB missing"); goto out; } + _LOGI ("Configuration from the helper received, verb '%s'", verb); + g_variant_builder_init (&config, G_VARIANT_TYPE_VARDICT); g_variant_builder_init (&ip4_config, G_VARIANT_TYPE_VARDICT); + g_variant_builder_init (&ip6_config, G_VARIANT_TYPE_VARDICT); + + /* address family for the tunnel. Note that VPN can support traffic of the other + * address family inside the tunnel, when the {left,right}subnets options specify + * the other family. */ + is_ipv6 = g_str_has_suffix (verb, "-v6"); variant = str_to_gvariant (lookup_string (env, "PLUTO_PEER_BANNER"), TRUE); if (variant) g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_BANNER, variant); - variant = addr_to_gvariant (lookup_string (env, "PLUTO_PEER"), AF_INET); + variant = addr_to_gvariant (lookup_string (env, "PLUTO_PEER"), is_ipv6 ? AF_INET6 : AF_INET); if (variant) g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, variant); else { @@ -1450,10 +1468,10 @@ handle_callback (NMDBusLibreswanHelper *object, if (variant) g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_TUNDEV, variant); } else { - variant = addr_to_gvariant (lookup_string (env, "PLUTO_NEXT_HOP"), AF_INET); + variant = addr_to_gvariant (lookup_string (env, "PLUTO_NEXT_HOP"), is_ipv6 ? AF_INET6 : AF_INET); if (variant) { g_variant_builder_add (&config, "{sv}", - NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, + is_ipv6 ? NM_VPN_PLUGIN_IP6_CONFIG_INT_GATEWAY : NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, variant); } } @@ -1465,26 +1483,28 @@ handle_callback (NMDBusLibreswanHelper *object, /* no dynamic address needed */ } else { /* IP address */ - variant = addr_to_gvariant (lookup_string (env, "PLUTO_MY_SOURCEIP"), AF_INET); + variant = addr_to_gvariant (lookup_string (env, "PLUTO_MY_SOURCEIP"), is_ipv6 ? AF_INET6 : AF_INET); if (variant) { - g_variant_builder_add (&ip4_config, "{sv}", - NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, - variant); - /* no PTP is expressed as PTP == ADDRESS */ - g_variant_builder_add (&ip4_config, "{sv}", - NM_VPN_PLUGIN_IP4_CONFIG_PTP, + g_variant_builder_add (ip_config[is_ipv6], "{sv}", + is_ipv6 ? NM_VPN_PLUGIN_IP6_CONFIG_ADDRESS : NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, variant); + if (!is_ipv6) { + /* no PTP is expressed as PTP == ADDRESS */ + g_variant_builder_add (ip_config[is_ipv6], "{sv}", + NM_VPN_PLUGIN_IP4_CONFIG_PTP, + variant); + } } else { _LOGW ("IP Address is missing"); goto out; } /* Netmask */ - variant = g_variant_new_uint32 (32); - g_variant_builder_add (&ip4_config, "{sv}", - NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, + variant = g_variant_new_uint32 (is_ipv6 ? 128 : 32); + g_variant_builder_add (ip_config[is_ipv6], "{sv}", + is_ipv6 ? NM_VPN_PLUGIN_IP6_CONFIG_PREFIX : NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, variant); - has_ip_config = TRUE; + has_ip_config[is_ipv6] = TRUE; } /* DNS */ @@ -1498,21 +1518,23 @@ handle_callback (NMDBusLibreswanHelper *object, addr_list_to_gvariants (cstr, "DNS", &dns4, &dns6); if (dns4) { g_variant_builder_add (&ip4_config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_DNS, dns4); - has_ip_config = TRUE; + has_ip_config[0] = TRUE; + } + if (dns6) { + g_variant_builder_add (&ip6_config, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_DNS, dns6); + has_ip_config[1] = TRUE; } - if (dns6) - g_variant_unref (dns6); } /* DNS domain */ cstr = lookup_string (env, "PLUTO_CISCO_DOMAIN_INFO"); if (!cstr) cstr = lookup_string (env, "PLUTO_PEER_DOMAIN_INFO"); - if (cstr && has_ip_config) { + if (cstr && (has_ip_config[0] || has_ip_config[1])) { variant = str_to_gvariant (cstr, TRUE); if (variant) { - g_variant_builder_add (&ip4_config, "{sv}", - NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN, + g_variant_builder_add (has_ip_config[0] ? &ip4_config : &ip6_config, "{sv}", + has_ip_config[0] ? NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN : NM_VPN_PLUGIN_IP6_CONFIG_DOMAIN, variant); } } @@ -1520,6 +1542,7 @@ handle_callback (NMDBusLibreswanHelper *object, /* Routes */ { nm_auto_unref_variant_builder GVariantBuilder *routes4 = NULL; + nm_auto_unref_variant_builder GVariantBuilder *routes6 = NULL; guint i; handle_route (priv->routes, env, verb, !!xfrm_interface); @@ -1527,40 +1550,60 @@ handle_callback (NMDBusLibreswanHelper *object, for (i = 0; i < priv->routes->len; i++) { NMIPRoute *route = priv->routes->pdata[i]; - nm_assert (nm_ip_route_get_family (route) == AF_INET); variant = route_to_gvariant (route); - if (!routes4) - routes4 = g_variant_builder_new (G_VARIANT_TYPE("aau")); - g_variant_builder_add_value (routes4, variant); + if (nm_ip_route_get_family (route) == AF_INET) { + if (!routes4) + routes4 = g_variant_builder_new (G_VARIANT_TYPE("aau")); + g_variant_builder_add_value (routes4, variant); + } else { + if (!routes6) + routes6 = g_variant_builder_new (G_VARIANT_TYPE ("a(ayuayuay)")); + g_variant_builder_add_value (routes6, variant); + } } if (routes4) { g_variant_builder_add (&ip4_config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_ROUTES, g_variant_builder_end (routes4)); - has_ip_config = TRUE; + has_ip_config[0] = TRUE; + } + if (routes6) { + g_variant_builder_add (&ip6_config, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_ROUTES, + g_variant_builder_end (routes6)); + has_ip_config[1] = TRUE; } } - if (has_ip_config) { + if (has_ip_config[0] || has_ip_config[1]) { gboolean have_routes4; gboolean have_routes6; /* Determine the never-default value based on the presence of SAD routes :( */ - have_sad_routes (lookup_string (env, "PLUTO_PEER"), AF_INET, &have_routes4, &have_routes6); - if (have_routes4) { + + have_sad_routes (lookup_string (env, "PLUTO_PEER"), + is_ipv6 ? AF_INET6 : AF_INET, + &have_routes4, + &have_routes6); + + if (has_ip_config[0] && have_routes4) { g_variant_builder_add (&ip4_config, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT, g_variant_new_boolean (TRUE)); } + if (has_ip_config[1] && have_routes6) { + g_variant_builder_add (&ip6_config, "{sv}", + NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT, + g_variant_new_boolean (TRUE)); + } } g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_HAS_IP4, - g_variant_new_boolean (has_ip_config)); + g_variant_new_boolean (has_ip_config[0])); g_variant_builder_add (&config, "{sv}", NM_VPN_PLUGIN_CONFIG_HAS_IP6, - g_variant_new_boolean (FALSE)); + g_variant_new_boolean (has_ip_config[1])); /* Finally, send configs to NM */ variant = g_variant_builder_end (&config); @@ -1570,7 +1613,7 @@ handle_callback (NMDBusLibreswanHelper *object, nm_vpn_service_plugin_set_config (NM_VPN_SERVICE_PLUGIN (user_data), variant); g_variant_unref (variant); - if (has_ip_config) { + if (has_ip_config[0]) { variant = g_variant_builder_end (&ip4_config); _LOGD("sending IP4 config: %s", (str = g_variant_print (variant, FALSE))); g_clear_pointer (&str, g_free); @@ -1578,6 +1621,13 @@ handle_callback (NMDBusLibreswanHelper *object, variant); g_variant_unref (variant); } + if (has_ip_config[1]) { + variant = g_variant_builder_end (&ip6_config); + _LOGD("sending IP6 config: %s", (str = g_variant_print (variant, FALSE))); + g_clear_pointer (&str, g_free); + nm_vpn_service_plugin_set_ip6_config (NM_VPN_SERVICE_PLUGIN (user_data), + variant); + } success = TRUE; out: -- GitLab