diff options
Diffstat (limited to 'src/libnm-platform')
| -rw-r--r-- | src/libnm-platform/meson.build | 2 | ||||
| -rw-r--r-- | src/libnm-platform/nm-linux-platform.c | 127 | ||||
| -rw-r--r-- | src/libnm-platform/nm-platform.c | 84 | ||||
| -rw-r--r-- | src/libnm-platform/nm-platform.h | 6 | ||||
| -rw-r--r-- | src/libnm-platform/nmp-object.c | 23 | ||||
| -rw-r--r-- | src/libnm-platform/nmp-route-manager.c | 839 | ||||
| -rw-r--r-- | src/libnm-platform/nmp-route-manager.h | 79 | ||||
| -rw-r--r-- | src/libnm-platform/nmp-rules-manager.c | 809 | ||||
| -rw-r--r-- | src/libnm-platform/nmp-rules-manager.h | 53 |
9 files changed, 1077 insertions, 945 deletions
diff --git a/src/libnm-platform/meson.build b/src/libnm-platform/meson.build index 3e4f41a9..d0a7c6c2 100644 --- a/src/libnm-platform/meson.build +++ b/src/libnm-platform/meson.build @@ -9,7 +9,7 @@ libnm_platform = static_library( 'nm-platform.c', 'nmp-netns.c', 'nmp-object.c', - 'nmp-rules-manager.c', + 'nmp-route-manager.c', 'wifi/nm-wifi-utils-nl80211.c', 'wifi/nm-wifi-utils.c', 'wpan/nm-wpan-utils.c', diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index 83f2207d..93935b98 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -3268,7 +3268,7 @@ _new_from_nl_addr(struct nlmsghdr *nlh, gboolean id_only) }; struct nlattr *tb[G_N_ELEMENTS(policy)]; const struct ifaddrmsg *ifa; - gboolean is_v4; + gboolean IS_IPv4; nm_auto_nmpobj NMPObject *obj = NULL; int addr_len; guint32 lifetime, preferred, timestamp; @@ -3278,29 +3278,31 @@ _new_from_nl_addr(struct nlmsghdr *nlh, gboolean id_only) ifa = nlmsg_data(nlh); - if (!NM_IN_SET(ifa->ifa_family, AF_INET, AF_INET6)) + if (ifa->ifa_family == AF_INET) + IS_IPv4 = TRUE; + else if (ifa->ifa_family == AF_INET6) + IS_IPv4 = FALSE; + else return NULL; - is_v4 = ifa->ifa_family == AF_INET; - if (nlmsg_parse_arr(nlh, sizeof(*ifa), tb, policy) < 0) return NULL; - addr_len = is_v4 ? sizeof(in_addr_t) : sizeof(struct in6_addr); + addr_len = IS_IPv4 ? sizeof(in_addr_t) : sizeof(struct in6_addr); - if (ifa->ifa_prefixlen > (is_v4 ? 32 : 128)) + if (ifa->ifa_prefixlen > (IS_IPv4 ? 32 : 128)) return NULL; /*****************************************************************/ - obj = nmp_object_new(is_v4 ? NMP_OBJECT_TYPE_IP4_ADDRESS : NMP_OBJECT_TYPE_IP6_ADDRESS, NULL); + obj = nmp_object_new(IS_IPv4 ? NMP_OBJECT_TYPE_IP4_ADDRESS : NMP_OBJECT_TYPE_IP6_ADDRESS, NULL); obj->ip_address.ifindex = ifa->ifa_index; obj->ip_address.plen = ifa->ifa_prefixlen; _check_addr_or_return_null(tb, IFA_ADDRESS, addr_len); _check_addr_or_return_null(tb, IFA_LOCAL, addr_len); - if (is_v4) { + if (IS_IPv4) { /* For IPv4, kernel omits IFA_LOCAL/IFA_ADDRESS if (and only if) they * are effectively 0.0.0.0 (all-zero). */ if (tb[IFA_LOCAL]) @@ -3336,7 +3338,7 @@ _new_from_nl_addr(struct nlmsghdr *nlh, gboolean id_only) obj->ip_address.n_ifa_flags = tb[IFA_FLAGS] ? nla_get_u32(tb[IFA_FLAGS]) : ifa->ifa_flags; - if (is_v4) { + if (IS_IPv4) { if (tb[IFA_LABEL]) { char label[IFNAMSIZ]; @@ -3387,7 +3389,7 @@ _new_from_nl_route(struct nlmsghdr *nlh, gboolean id_only) }; const struct rtmsg *rtm; struct nlattr *tb[G_N_ELEMENTS(policy)]; - gboolean is_v4; + gboolean IS_IPv4; nm_auto_nmpobj NMPObject *obj = NULL; int addr_len; struct { @@ -3414,10 +3416,19 @@ _new_from_nl_route(struct nlmsghdr *nlh, gboolean id_only) * only handle ~supported~ routes. *****************************************************************/ - if (!NM_IN_SET(rtm->rtm_family, AF_INET, AF_INET6)) + if (rtm->rtm_family == AF_INET) + IS_IPv4 = TRUE; + else if (rtm->rtm_family == AF_INET6) + IS_IPv4 = FALSE; + else return NULL; - if (!NM_IN_SET(rtm->rtm_type, RTN_UNICAST, RTN_LOCAL)) + if (!NM_IN_SET(rtm->rtm_type, + RTN_UNICAST, + RTN_LOCAL, + RTN_BLACKHOLE, + RTN_UNREACHABLE, + RTN_PROHIBIT)) return NULL; if (nlmsg_parse_arr(nlh, sizeof(struct rtmsg), tb, policy) < 0) @@ -3425,10 +3436,9 @@ _new_from_nl_route(struct nlmsghdr *nlh, gboolean id_only) /*****************************************************************/ - is_v4 = rtm->rtm_family == AF_INET; - addr_len = is_v4 ? sizeof(in_addr_t) : sizeof(struct in6_addr); + addr_len = IS_IPv4 ? sizeof(in_addr_t) : sizeof(struct in6_addr); - if (rtm->rtm_dst_len > (is_v4 ? 32 : 128)) + if (rtm->rtm_dst_len > (IS_IPv4 ? 32 : 128)) return NULL; /***************************************************************** @@ -3492,16 +3502,45 @@ rta_multipath_done:; /* If no nexthops have been provided via RTA_MULTIPATH * we add it as regular nexthop to maintain backwards * compatibility */ - nh.ifindex = ifindex; - nh.gateway = gateway; + nh.ifindex = ifindex; + nh.gateway = gateway; + nh.is_present = TRUE; } else { /* Kernel supports new style nexthop configuration, * verify that it is a duplicate and ignore old-style nexthop. */ if (nh.ifindex != ifindex || memcmp(&nh.gateway, &gateway, addr_len) != 0) return NULL; } - } else if (!nh.is_present) - return NULL; + } + + if (nm_platform_route_type_is_nodev(rtm->rtm_type)) { + /* These routes are special. They don't have an device/ifindex. + * + * Well, actually, for IPv6 kernel will always say that the device is + * 1 (lo). Of course it does!! */ + if (nh.is_present) { + if (IS_IPv4) { + if (nh.ifindex != 0 || nh.gateway.addr4 != 0) { + /* we only accept kernel to notify about the ifindex/gateway, if it + * is zero. This is only to be a bit forgiving, but we really don't + * know how to handle such routes that have an ifindex. */ + return NULL; + } + } else { + if (!NM_IN_SET(nh.ifindex, 0, 1) || !IN6_IS_ADDR_UNSPECIFIED(&nh.gateway.addr6)) { + /* We allow an ifindex of 1 (will be normalized to zero). Otherwise, + * we don't expect a device/next hop. */ + return NULL; + } + nh.ifindex = 0; + } + } + } else { + if (!nh.is_present) { + /* a "normal" route needs a device. This is not the route we are looking for. */ + return NULL; + } + } /*****************************************************************/ @@ -3539,7 +3578,7 @@ rta_multipath_done:; /*****************************************************************/ - obj = nmp_object_new(is_v4 ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE, NULL); + obj = nmp_object_new(IS_IPv4 ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE, NULL); obj->ip_route.type_coerced = nm_platform_route_type_coerce(rtm->rtm_type); obj->ip_route.table_coerced = nm_platform_route_table_coerce( @@ -3555,22 +3594,22 @@ rta_multipath_done:; if (tb[RTA_PRIORITY]) obj->ip_route.metric = nla_get_u32(tb[RTA_PRIORITY]); - if (is_v4) + if (IS_IPv4) obj->ip4_route.gateway = nh.gateway.addr4; else obj->ip6_route.gateway = nh.gateway.addr6; - if (is_v4) + if (IS_IPv4) obj->ip4_route.scope_inv = nm_platform_route_scope_inv(rtm->rtm_scope); if (_check_addr_or_return_null(tb, RTA_PREFSRC, addr_len)) { - if (is_v4) + if (IS_IPv4) memcpy(&obj->ip4_route.pref_src, nla_data(tb[RTA_PREFSRC]), addr_len); else memcpy(&obj->ip6_route.pref_src, nla_data(tb[RTA_PREFSRC]), addr_len); } - if (is_v4) + if (IS_IPv4) obj->ip4_route.tos = rtm->rtm_tos; else { if (tb[RTA_SRC]) { @@ -3592,7 +3631,7 @@ rta_multipath_done:; obj->ip_route.lock_initrwnd = NM_FLAGS_HAS(lock, 1 << RTAX_INITRWND); obj->ip_route.lock_mtu = NM_FLAGS_HAS(lock, 1 << RTAX_MTU); - if (!is_v4) { + if (!IS_IPv4) { if (tb[RTA_PREF]) obj->ip6_route.rt_pref = nla_get_u8(tb[RTA_PREF]); } @@ -4715,23 +4754,23 @@ ip_route_ignored_protocol(const NMPlatformIPRoute *route) static struct nl_msg * _nl_msg_new_route(int nlmsg_type, guint16 nlmsgflags, const NMPObject *obj) { - nm_auto_nlmsg struct nl_msg *msg = NULL; - const NMPClass *klass = NMP_OBJECT_GET_CLASS(obj); - gboolean is_v4 = klass->addr_family == AF_INET; - const guint32 lock = ip_route_get_lock_flag(NMP_OBJECT_CAST_IP_ROUTE(obj)); + nm_auto_nlmsg struct nl_msg *msg = NULL; + const NMPClass *klass = NMP_OBJECT_GET_CLASS(obj); + const gboolean IS_IPv4 = NM_IS_IPv4(klass->addr_family); + const guint32 lock = ip_route_get_lock_flag(NMP_OBJECT_CAST_IP_ROUTE(obj)); const guint32 table = nm_platform_route_table_uncoerce(NMP_OBJECT_CAST_IP_ROUTE(obj)->table_coerced, TRUE); const struct rtmsg rtmsg = { .rtm_family = klass->addr_family, - .rtm_tos = is_v4 ? obj->ip4_route.tos : 0, + .rtm_tos = IS_IPv4 ? obj->ip4_route.tos : 0, .rtm_table = table <= 0xFF ? table : RT_TABLE_UNSPEC, .rtm_protocol = nmp_utils_ip_config_source_coerce_to_rtprot(obj->ip_route.rt_source), .rtm_scope = - is_v4 ? nm_platform_route_scope_inv(obj->ip4_route.scope_inv) : RT_SCOPE_NOWHERE, + IS_IPv4 ? nm_platform_route_scope_inv(obj->ip4_route.scope_inv) : RT_SCOPE_NOWHERE, .rtm_type = nm_platform_route_type_uncoerce(NMP_OBJECT_CAST_IP_ROUTE(obj)->type_coerced), .rtm_flags = obj->ip_route.r_rtm_flags & ((unsigned) (RTNH_F_ONLINK)), .rtm_dst_len = obj->ip_route.plen, - .rtm_src_len = is_v4 ? 0 : NMP_OBJECT_CAST_IP6_ROUTE(obj)->src_plen, + .rtm_src_len = IS_IPv4 ? 0 : NMP_OBJECT_CAST_IP6_ROUTE(obj)->src_plen, }; gsize addr_len; @@ -4745,28 +4784,28 @@ _nl_msg_new_route(int nlmsg_type, guint16 nlmsgflags, const NMPObject *obj) if (nlmsg_append_struct(msg, &rtmsg) < 0) goto nla_put_failure; - addr_len = is_v4 ? sizeof(in_addr_t) : sizeof(struct in6_addr); + addr_len = IS_IPv4 ? sizeof(in_addr_t) : sizeof(struct in6_addr); NLA_PUT(msg, RTA_DST, addr_len, - is_v4 ? (gconstpointer) &obj->ip4_route.network - : (gconstpointer) &obj->ip6_route.network); + IS_IPv4 ? (gconstpointer) &obj->ip4_route.network + : (gconstpointer) &obj->ip6_route.network); - if (!is_v4) { + if (!IS_IPv4) { if (!IN6_IS_ADDR_UNSPECIFIED(&NMP_OBJECT_CAST_IP6_ROUTE(obj)->src)) NLA_PUT(msg, RTA_SRC, addr_len, &obj->ip6_route.src); } NLA_PUT_U32(msg, RTA_PRIORITY, - is_v4 ? nm_platform_ip4_route_get_effective_metric(&obj->ip4_route) - : nm_platform_ip6_route_get_effective_metric(&obj->ip6_route)); + IS_IPv4 ? nm_platform_ip4_route_get_effective_metric(&obj->ip4_route) + : nm_platform_ip6_route_get_effective_metric(&obj->ip6_route)); if (table > 0xFF) NLA_PUT_U32(msg, RTA_TABLE, table); - if (is_v4) { + if (IS_IPv4) { if (NMP_OBJECT_CAST_IP4_ROUTE(obj)->pref_src) NLA_PUT(msg, RTA_PREFSRC, addr_len, &obj->ip4_route.pref_src); } else { @@ -4801,7 +4840,7 @@ _nl_msg_new_route(int nlmsg_type, guint16 nlmsgflags, const NMPObject *obj) } /* We currently don't have need for multi-hop routes... */ - if (is_v4) { + if (IS_IPv4) { NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip4_route.gateway); } else { if (!IN6_IS_ADDR_UNSPECIFIED(&obj->ip6_route.gateway)) @@ -4809,7 +4848,7 @@ _nl_msg_new_route(int nlmsg_type, guint16 nlmsgflags, const NMPObject *obj) } NLA_PUT_U32(msg, RTA_OIF, obj->ip_route.ifindex); - if (!is_v4 && obj->ip6_route.rt_pref != NM_ICMPV6_ROUTER_PREF_MEDIUM) + if (!IS_IPv4 && obj->ip6_route.rt_pref != NM_ICMPV6_ROUTER_PREF_MEDIUM) NLA_PUT_U8(msg, RTA_PREF, obj->ip6_route.rt_pref); return g_steal_pointer(&msg); @@ -8736,8 +8775,8 @@ ip_route_get(NMPlatform *platform, int oif_ifindex, NMPObject **out_route) { - const gboolean is_v4 = (addr_family == AF_INET); - const int addr_len = is_v4 ? 4 : 16; + const gboolean IS_IPv4 = NM_IS_IPv4(addr_family); + const int addr_len = IS_IPv4 ? 4 : 16; int try_count = 0; WaitForNlResponseResult seq_result; int nle; @@ -8758,7 +8797,7 @@ ip_route_get(NMPlatform *platform, .n.nlmsg_type = RTM_GETROUTE, .r.rtm_family = addr_family, .r.rtm_tos = 0, - .r.rtm_dst_len = is_v4 ? 32 : 128, + .r.rtm_dst_len = IS_IPv4 ? 32 : 128, .r.rtm_flags = 0x1000 /* RTM_F_LOOKUP_TABLE */, }; diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index a4863c0d..b95cd95e 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -3143,7 +3143,8 @@ nm_platform_wpan_set_channel(NMPlatform *self, int ifindex, guint8 page, guint8 static const char * _to_string_dev(NMPlatform *self, int ifindex, char *buf, size_t size) { - g_assert(buf && size >= TO_STRING_DEV_BUF_SIZE); + nm_assert(buf); + nm_assert(size >= TO_STRING_DEV_BUF_SIZE); if (ifindex) { const char *name = ifindex > 0 && self ? nm_platform_link_get_name(self, ifindex) : NULL; @@ -6458,12 +6459,19 @@ _rtm_flags_to_string_full(char *buf, gsize buf_size, unsigned rtm_flags) const char * nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsize len) { - char s_network[INET_ADDRSTRLEN], s_gateway[INET_ADDRSTRLEN]; + char s_network[INET_ADDRSTRLEN]; + char s_gateway[INET_ADDRSTRLEN]; char s_pref_src[INET_ADDRSTRLEN]; char str_dev[TO_STRING_DEV_BUF_SIZE]; char str_table[30]; - char str_scope[30], s_source[50]; - char str_tos[32], str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32]; + char str_scope[30]; + char s_source[50]; + char str_tos[32]; + char str_window[32]; + char str_cwnd[32]; + char str_initcwnd[32]; + char str_initrwnd[32]; + char str_mtu[32]; char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN]; char str_type[30]; char str_metric[30]; @@ -6472,7 +6480,11 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz return buf; inet_ntop(AF_INET, &route->network, s_network, sizeof(s_network)); - inet_ntop(AF_INET, &route->gateway, s_gateway, sizeof(s_gateway)); + + if (route->gateway == 0) + s_gateway[0] = '\0'; + else + inet_ntop(AF_INET, &route->gateway, s_gateway, sizeof(s_gateway)); _to_string_dev(NULL, route->ifindex, str_dev, sizeof(str_dev)); @@ -6482,21 +6494,22 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz "type %s " /* type */ "%s" /* table */ "%s/%d" - " via %s" + "%s%s" /* gateway */ "%s" " metric %s" - " mss %" G_GUINT32_FORMAT " rt-src %s" /* protocol */ - "%s" /* rtm_flags */ - "%s%s" /* scope */ - "%s%s" /* pref-src */ - "%s" /* tos */ - "%s" /* window */ - "%s" /* cwnd */ - "%s" /* initcwnd */ - "%s" /* initrwnd */ - "%s" /* mtu */ - "%s" /* r_assume_config_once */ - "%s" /* r_force_commit */ + " mss %" G_GUINT32_FORMAT /* mss */ + " rt-src %s" /* protocol */ + "%s" /* rtm_flags */ + "%s%s" /* scope */ + "%s%s" /* pref-src */ + "%s" /* tos */ + "%s" /* window */ + "%s" /* cwnd */ + "%s" /* initcwnd */ + "%s" /* initrwnd */ + "%s" /* mtu */ + "%s" /* r_assume_config_once */ + "%s" /* r_force_commit */ "", nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced), str_type), @@ -6509,6 +6522,7 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz : ""), s_network, route->plen, + s_gateway[0] ? " via " : "", s_gateway, str_dev, route->metric_any @@ -6596,7 +6610,11 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz return buf; inet_ntop(AF_INET6, &route->network, s_network, sizeof(s_network)); - inet_ntop(AF_INET6, &route->gateway, s_gateway, sizeof(s_gateway)); + + if (IN6_IS_ADDR_UNSPECIFIED(&route->gateway)) + s_gateway[0] = '\0'; + else + inet_ntop(AF_INET6, &route->gateway, s_gateway, sizeof(s_gateway)); if (IN6_IS_ADDR_UNSPECIFIED(&route->pref_src)) s_pref_src[0] = 0; @@ -6611,21 +6629,22 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz "type %s " /* type */ "%s" /* table */ "%s/%d" - " via %s" + "%s%s" /* gateway */ "%s" " metric %s" - " mss %" G_GUINT32_FORMAT " rt-src %s" /* protocol */ - "%s" /* source */ - "%s" /* rtm_flags */ - "%s%s" /* pref-src */ - "%s" /* window */ - "%s" /* cwnd */ - "%s" /* initcwnd */ - "%s" /* initrwnd */ - "%s" /* mtu */ - "%s" /* pref */ - "%s" /* r_assume_config_once */ - "%s" /* r_force_commit */ + " mss %" G_GUINT32_FORMAT /* mss */ + " rt-src %s" /* protocol */ + "%s" /* source */ + "%s" /* rtm_flags */ + "%s%s" /* pref-src */ + "%s" /* window */ + "%s" /* cwnd */ + "%s" /* initcwnd */ + "%s" /* initrwnd */ + "%s" /* mtu */ + "%s" /* pref */ + "%s" /* r_assume_config_once */ + "%s" /* r_force_commit */ "", nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced), str_type), @@ -6638,6 +6657,7 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz : ""), s_network, route->plen, + s_gateway[0] ? " via " : "", s_gateway, str_dev, route->metric_any diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index c305a1db..6f5cd524 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -1408,6 +1408,12 @@ _nm_platform_link_get_inet6_addr_gen_mode(const NMPlatformLink *pllink) return _nm_platform_uint8_inv(pllink->inet6_addr_gen_mode_inv); } +static inline gboolean +nm_platform_route_type_is_nodev(guint8 type) +{ + return NM_IN_SET(type, 6 /* RTN_BLACKHOLE */, 7 /* RTN_UNREACHABLE */, 8 /* RTN_PROHIBIT */); +} + /** * nm_platform_route_type_coerce: * @table: the route type, in its original value. diff --git a/src/libnm-platform/nmp-object.c b/src/libnm-platform/nmp-object.c index ef94942e..d518e6e5 100644 --- a/src/libnm-platform/nmp-object.c +++ b/src/libnm-platform/nmp-object.c @@ -386,7 +386,11 @@ _idx_obj_part(const DedupMultiIdxType *idx_type, nm_hash_update_val(h, obj_a); return 0; } - nm_assert(NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_a)->ifindex > 0); + nm_assert(NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_a)->ifindex > 0 + || (NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_a)->ifindex == 0 + && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_a), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE))); if (obj_b) { return NMP_OBJECT_GET_TYPE(obj_a) == NMP_OBJECT_GET_TYPE(obj_b) && NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_a)->ifindex @@ -401,14 +405,14 @@ _idx_obj_part(const DedupMultiIdxType *idx_type, case NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID: obj_type = NMP_OBJECT_GET_TYPE(obj_a); if (!NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE) - || NMP_OBJECT_CAST_IP_ROUTE(obj_a)->ifindex <= 0) { + || NMP_OBJECT_CAST_IP_ROUTE(obj_a)->ifindex < 0) { if (h) nm_hash_update_val(h, obj_a); return 0; } if (obj_b) { return obj_type == NMP_OBJECT_GET_TYPE(obj_b) - && NMP_OBJECT_CAST_IP_ROUTE(obj_b)->ifindex > 0 + && NMP_OBJECT_CAST_IP_ROUTE(obj_b)->ifindex >= 0 && (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE ? (nm_platform_ip4_route_cmp(&obj_a->ip4_route, &obj_b->ip4_route, @@ -1740,8 +1744,7 @@ _vt_cmd_obj_is_alive_ipx_route(const NMPObject *obj) * Instead we create a dead object, and nmp_cache_update_netlink() * will remove the old version of the update. **/ - return NMP_OBJECT_CAST_IP_ROUTE(obj)->ifindex > 0 - && !NM_FLAGS_HAS(obj->ip_route.r_rtm_flags, RTM_F_CLONED); + return !NM_FLAGS_HAS(NMP_OBJECT_CAST_IP_ROUTE(obj)->r_rtm_flags, RTM_F_CLONED); } static gboolean @@ -2143,7 +2146,15 @@ nmp_lookup_init_object(NMPLookup *lookup, NMPObjectType obj_type, int ifindex) NMP_OBJECT_TYPE_QDISC, NMP_OBJECT_TYPE_TFILTER)); - if (ifindex <= 0) { + if (G_UNLIKELY( + (ifindex < 0) + || (ifindex == 0 + && !NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)))) { + /* This function used to have a fallback that meant to lookup all objects, if + * ifindex is non-positive. As routes can have a zero ifindex, that fallback is + * confusing and no longer supported. Only have this code, to catch accidental bugs + * after the API change. */ + nm_assert_not_reached(); return nmp_lookup_init_obj_type(lookup, obj_type); } diff --git a/src/libnm-platform/nmp-route-manager.c b/src/libnm-platform/nmp-route-manager.c new file mode 100644 index 00000000..c31c9806 --- /dev/null +++ b/src/libnm-platform/nmp-route-manager.c @@ -0,0 +1,839 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nmp-route-manager.h" + +#include <linux/fib_rules.h> +#include <linux/rtnetlink.h> + +#include "libnm-log-core/nm-logging.h" +#include "libnm-std-aux/c-list-util.h" +#include "nmp-object.h" + +/*****************************************************************************/ + +struct _NMPRouteManager { + NMPlatform *platform; + GHashTable *by_obj; + GHashTable *by_user_tag; + GHashTable *by_data; + CList by_obj_lst_heads[3]; + guint ref_count; +}; + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG_PREFIX_NAME "route-manager" + +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, LOGD_PLATFORM, _NMLOG_PREFIX_NAME, __VA_ARGS__) + +/*****************************************************************************/ + +static gboolean +NMP_IS_ROUTE_MANAGER(gpointer self) +{ + return self && ((NMPRouteManager *) self)->ref_count > 0 + && NM_IS_PLATFORM(((NMPRouteManager *) self)->platform); +} + +/*****************************************************************************/ + +typedef struct { + const NMPObject *obj; + gconstpointer user_tag; + CList obj_lst; + CList user_tag_lst; + + /* track_priority_val zero is special: those are weakly tracked rules. + * That means: NetworkManager will restore them only if it removed them earlier. + * But it will not remove or add them otherwise. + * + * Otherwise, the track_priority_val goes together with track_priority_present. + * In case of one rule being tracked multiple times (with different priorities), + * the one with higher priority wins. See _track_obj_data_get_best_data(). + * Then, the winning present state either enforces that the rule is present + * or absent. + * + * If a rules is not tracked at all, it is ignored by NetworkManager. Assuming + * that it was added externally by the user. But unlike weakly tracked rules, + * NM will *not* restore such rules if NetworkManager themself removed them. */ + guint32 track_priority_val; + bool track_priority_present : 1; + + bool dirty : 1; +} TrackData; + +typedef enum { + CONFIG_STATE_NONE = 0, + CONFIG_STATE_ADDED_BY_US = 1, + CONFIG_STATE_REMOVED_BY_US = 2, + + /* ConfigState encodes whether the rule was touched by us at all (CONFIG_STATE_NONE). + * + * Maybe we would only need to track whether we touched the rule at all. But we + * track it more in detail what we did: did we add it (CONFIG_STATE_ADDED_BY_US) + * or did we remove it (CONFIG_STATE_REMOVED_BY_US)? + * Finally, we need CONFIG_STATE_OWNED_BY_US, which means that we didn't actively + * add/remove it, but whenever we are about to undo the add/remove, we need to do it. + * In that sense, CONFIG_STATE_OWNED_BY_US is really just a flag that we unconditionally + * force the state next time when necessary. */ + CONFIG_STATE_OWNED_BY_US = 3, +} ConfigState; + +typedef struct { + const NMPObject *obj; + CList obj_lst_head; + + CList by_obj_lst; + + /* indicates whether we configured/removed the rule (during sync()). We need that, so + * if the rule gets untracked, that we know to remove/restore it. + * + * This makes NMPRouteManager stateful (beyond the configuration that indicates + * which rules are tracked). + * After a restart, NetworkManager would no longer remember which rules were added + * by us. + * + * That is partially fixed by NetworkManager taking over the rules that it + * actively configures (see %NMP_ROUTE_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG). */ + ConfigState config_state; +} TrackObjData; + +typedef struct { + gconstpointer user_tag; + CList user_tag_lst_head; +} TrackUserTagData; + +/*****************************************************************************/ + +static void _track_data_untrack(NMPRouteManager *self, + TrackData *track_data, + gboolean remove_user_tag_data, + gboolean make_owned_by_us); + +/*****************************************************************************/ + +static CList * +_by_obj_lst_head(NMPRouteManager *self, NMPObjectType obj_type) +{ + G_STATIC_ASSERT(G_N_ELEMENTS(self->by_obj_lst_heads) == 3); + + switch (obj_type) { + case NMP_OBJECT_TYPE_IP4_ROUTE: + return &self->by_obj_lst_heads[0]; + case NMP_OBJECT_TYPE_IP6_ROUTE: + return &self->by_obj_lst_heads[1]; + case NMP_OBJECT_TYPE_ROUTING_RULE: + return &self->by_obj_lst_heads[2]; + default: + return nm_assert_unreachable_val(NULL); + } +} + +/*****************************************************************************/ + +static void +_track_data_assert(const TrackData *track_data, gboolean linked) +{ + nm_assert(track_data); + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(track_data->obj), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_ROUTING_RULE)); + nm_assert(nmp_object_is_visible(track_data->obj)); + nm_assert(track_data->user_tag); + nm_assert(!linked || !c_list_is_empty(&track_data->obj_lst)); + nm_assert(!linked || !c_list_is_empty(&track_data->user_tag_lst)); +} + +static guint +_track_data_hash(gconstpointer data) +{ + const TrackData *track_data = data; + NMHashState h; + + _track_data_assert(track_data, FALSE); + + nm_hash_init(&h, 269297543u); + nmp_object_id_hash_update(track_data->obj, &h); + nm_hash_update_val(&h, track_data->user_tag); + return nm_hash_complete(&h); +} + +static gboolean +_track_data_equal(gconstpointer data_a, gconstpointer data_b) +{ + const TrackData *track_data_a = data_a; + const TrackData *track_data_b = data_b; + + _track_data_assert(track_data_a, FALSE); + _track_data_assert(track_data_b, FALSE); + + return track_data_a->user_tag == track_data_b->user_tag + && nmp_object_id_equal(track_data_a->obj, track_data_b->obj); +} + +static void +_track_data_destroy(gpointer data) +{ + TrackData *track_data = data; + + _track_data_assert(track_data, FALSE); + + c_list_unlink_stale(&track_data->obj_lst); + c_list_unlink_stale(&track_data->user_tag_lst); + nmp_object_unref(track_data->obj); + nm_g_slice_free(track_data); +} + +static const TrackData * +_track_obj_data_get_best_data(TrackObjData *obj_data) +{ + TrackData *track_data; + const TrackData *td_best = NULL; + + c_list_for_each_entry (track_data, &obj_data->obj_lst_head, obj_lst) { + _track_data_assert(track_data, TRUE); + + if (td_best) { + if (td_best->track_priority_val > track_data->track_priority_val) + continue; + if (td_best->track_priority_val == track_data->track_priority_val) { + if (td_best->track_priority_present || !track_data->track_priority_present) { + /* if the priorities are identical, then "present" wins over + * "!present" (absent). */ + continue; + } + } + } + + td_best = track_data; + } + + return td_best; +} + +static guint +_track_obj_data_hash(gconstpointer data) +{ + const TrackObjData *obj_data = data; + + return nmp_object_id_hash(obj_data->obj); +} + +static gboolean +_track_obj_data_equal(gconstpointer data_a, gconstpointer data_b) +{ + const TrackObjData *obj_data_a = data_a; + const TrackObjData *obj_data_b = data_b; + + return nmp_object_id_equal(obj_data_a->obj, obj_data_b->obj); +} + +static void +_track_obj_data_destroy(gpointer data) +{ + TrackObjData *obj_data = data; + + c_list_unlink_stale(&obj_data->obj_lst_head); + c_list_unlink_stale(&obj_data->by_obj_lst); + nmp_object_unref(obj_data->obj); + nm_g_slice_free(obj_data); +} + +static void +_track_user_tag_data_destroy(gpointer data) +{ + TrackUserTagData *user_tag_data = data; + + c_list_unlink_stale(&user_tag_data->user_tag_lst_head); + nm_g_slice_free(user_tag_data); +} + +static TrackData * +_track_data_lookup(GHashTable *by_data, const NMPObject *obj, gconstpointer user_tag) +{ + TrackData track_data_needle = { + .obj = obj, + .user_tag = user_tag, + }; + + return g_hash_table_lookup(by_data, &track_data_needle); +} + +/*****************************************************************************/ + +/** + * nmp_route_manager_track: + * @self: the #NMPRouteManager instance + * @obj_type: the NMPObjectType of @obj that we are tracking. + * @obj: the NMPlatformObject (of type NMPObjectType) to track. Usually + * a #NMPlatformRoutingRule, #NMPlatformIP4Route or #NMPlatformIP6Route + * pointer. + * @track_priority: the priority for tracking the rule. Note that + * negative values indicate a forced absence of the rule. Priorities + * are compared with their absolute values (with higher absolute + * value being more important). For example, if you track the same + * rule twice, once with priority -5 and +10, then the rule is + * present (because the positive number is more important). + * The special value 0 indicates weakly-tracked rules. + * @user_tag: the tag associated with tracking this rule. The same tag + * must be used to untrack the rule later. + * @user_tag_untrack: if not %NULL, at the same time untrack this user-tag + * for the same rule. Note that this is different from a plain nmp_route_manager_untrack_rule(), + * because it enforces ownership of the now tracked rule. On the other hand, + * a plain nmp_route_manager_untrack_rule() merely forgets about the tracking. + * The purpose here is to set this to %NMP_ROUTE_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG. + * + * Returns: %TRUE, if something changed. + */ +gboolean +nmp_route_manager_track(NMPRouteManager *self, + NMPObjectType obj_type, + gconstpointer obj, + gint32 track_priority, + gconstpointer user_tag, + gconstpointer user_tag_untrack) +{ + NMPObject obj_stack; + const NMPObject *p_obj_stack; + TrackData *track_data; + TrackObjData *obj_data; + TrackUserTagData *user_tag_data; + gboolean changed = FALSE; + gboolean changed_untrack = FALSE; + guint32 track_priority_val; + gboolean track_priority_present; + + g_return_val_if_fail(NMP_IS_ROUTE_MANAGER(self), FALSE); + g_return_val_if_fail(obj, FALSE); + g_return_val_if_fail(user_tag, FALSE); + + /* The route must not be tied to an interface. We can only handle here + * blackhole/unreachable/prohibit route types. */ + g_return_val_if_fail( + obj_type == NMP_OBJECT_TYPE_ROUTING_RULE + || (NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE) + && ((const NMPlatformIPRoute *) obj)->ifindex == 0), + FALSE); + + nm_assert(track_priority != G_MININT32); + + p_obj_stack = nmp_object_stackinit(&obj_stack, obj_type, obj); + + nm_assert(nmp_object_is_visible(p_obj_stack)); + + if (track_priority >= 0) { + track_priority_val = track_priority; + track_priority_present = TRUE; + } else { + track_priority_val = -track_priority; + track_priority_present = FALSE; + } + + track_data = _track_data_lookup(self->by_data, p_obj_stack, user_tag); + + if (!track_data) { + track_data = g_slice_new(TrackData); + *track_data = (TrackData){ + .obj = nm_dedup_multi_index_obj_intern(nm_platform_get_multi_idx(self->platform), + p_obj_stack), + .user_tag = user_tag, + .track_priority_val = track_priority_val, + .track_priority_present = track_priority_present, + .dirty = FALSE, + }; + g_hash_table_add(self->by_data, track_data); + + obj_data = g_hash_table_lookup(self->by_obj, &track_data->obj); + if (!obj_data) { + obj_data = g_slice_new(TrackObjData); + *obj_data = (TrackObjData){ + .obj = nmp_object_ref(track_data->obj), + .obj_lst_head = C_LIST_INIT(obj_data->obj_lst_head), + .config_state = CONFIG_STATE_NONE, + }; + g_hash_table_add(self->by_obj, obj_data); + c_list_link_tail(_by_obj_lst_head(self, obj_type), &obj_data->by_obj_lst); + } + c_list_link_tail(&obj_data->obj_lst_head, &track_data->obj_lst); + + user_tag_data = g_hash_table_lookup(self->by_user_tag, &track_data->user_tag); + if (!user_tag_data) { + user_tag_data = g_slice_new(TrackUserTagData); + *user_tag_data = (TrackUserTagData){ + .user_tag = user_tag, + .user_tag_lst_head = C_LIST_INIT(user_tag_data->user_tag_lst_head), + }; + g_hash_table_add(self->by_user_tag, user_tag_data); + } + c_list_link_tail(&user_tag_data->user_tag_lst_head, &track_data->user_tag_lst); + changed = TRUE; + } else { + track_data->dirty = FALSE; + if (track_data->track_priority_val != track_priority_val + || track_data->track_priority_present != track_priority_present) { + track_data->track_priority_val = track_priority_val; + track_data->track_priority_present = track_priority_present; + changed = TRUE; + } + } + + if (user_tag_untrack) { + if (user_tag != user_tag_untrack) { + TrackData *track_data_untrack; + + track_data_untrack = _track_data_lookup(self->by_data, p_obj_stack, user_tag_untrack); + if (track_data_untrack) { + _track_data_untrack(self, track_data_untrack, FALSE, TRUE); + changed_untrack = TRUE; + } + } else + nm_assert_not_reached(); + } + + _track_data_assert(track_data, TRUE); + + if (changed) { + _LOGD("track [" NM_HASH_OBFUSCATE_PTR_FMT ",%s%u] %s \"%s\"", + NM_HASH_OBFUSCATE_PTR(track_data->user_tag), + (track_data->track_priority_val == 0 + ? "" + : (track_data->track_priority_present ? "+" : "-")), + (guint) track_data->track_priority_val, + NMP_OBJECT_GET_CLASS(track_data->obj)->obj_type_name, + nmp_object_to_string(track_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + } + + return changed || changed_untrack; +} + +static void +_track_data_untrack(NMPRouteManager *self, + TrackData *track_data, + gboolean remove_user_tag_data, + gboolean make_owned_by_us) +{ + TrackObjData *obj_data; + + nm_assert(NMP_IS_ROUTE_MANAGER(self)); + _track_data_assert(track_data, TRUE); + nm_assert(self->by_data); + nm_assert(g_hash_table_lookup(self->by_data, track_data) == track_data); + + _LOGD("untrack [" NM_HASH_OBFUSCATE_PTR_FMT "] %s \"%s\"", + NM_HASH_OBFUSCATE_PTR(track_data->user_tag), + NMP_OBJECT_GET_CLASS(track_data->obj)->obj_type_name, + nmp_object_to_string(track_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + +#if NM_MORE_ASSERTS + { + TrackUserTagData *user_tag_data; + + user_tag_data = g_hash_table_lookup(self->by_user_tag, &track_data->user_tag); + nm_assert(user_tag_data); + nm_assert(c_list_contains(&user_tag_data->user_tag_lst_head, &track_data->user_tag_lst)); + } +#endif + + nm_assert(!c_list_is_empty(&track_data->user_tag_lst)); + + obj_data = g_hash_table_lookup(self->by_obj, &track_data->obj); + nm_assert(obj_data); + nm_assert(c_list_contains(&obj_data->obj_lst_head, &track_data->obj_lst)); + nm_assert(obj_data == g_hash_table_lookup(self->by_obj, &track_data->obj)); + + if (make_owned_by_us) { + if (obj_data->config_state == CONFIG_STATE_NONE) { + /* we need to mark this entry that it requires a touch on the next + * sync. */ + obj_data->config_state = CONFIG_STATE_OWNED_BY_US; + } + } else if (remove_user_tag_data && c_list_length_is(&track_data->user_tag_lst, 1)) + g_hash_table_remove(self->by_user_tag, &track_data->user_tag); + + /* if obj_data is marked to be "added_by_us" or "removed_by_us", we need to keep this entry + * around for the next sync -- so that we can undo what we did earlier. */ + if (obj_data->config_state == CONFIG_STATE_NONE && c_list_length_is(&track_data->obj_lst, 1)) + g_hash_table_remove(self->by_obj, &track_data->obj); + + g_hash_table_remove(self->by_data, track_data); +} + +gboolean +nmp_route_manager_untrack(NMPRouteManager *self, + NMPObjectType obj_type, + gconstpointer obj, + gconstpointer user_tag) +{ + NMPObject obj_stack; + const NMPObject *p_obj_stack; + TrackData *track_data; + gboolean changed = FALSE; + + g_return_val_if_fail(NMP_IS_ROUTE_MANAGER(self), FALSE); + nm_assert(NM_IN_SET(obj_type, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_ROUTING_RULE)); + g_return_val_if_fail(obj, FALSE); + g_return_val_if_fail(user_tag, FALSE); + + p_obj_stack = nmp_object_stackinit(&obj_stack, obj_type, obj); + + nm_assert(nmp_object_is_visible(p_obj_stack)); + + track_data = _track_data_lookup(self->by_data, p_obj_stack, user_tag); + if (track_data) { + _track_data_untrack(self, track_data, TRUE, FALSE); + changed = TRUE; + } + + return changed; +} + +void +nmp_route_manager_set_dirty(NMPRouteManager *self, gconstpointer user_tag) +{ + TrackData *track_data; + TrackUserTagData *user_tag_data; + + g_return_if_fail(NMP_IS_ROUTE_MANAGER(self)); + g_return_if_fail(user_tag); + + user_tag_data = g_hash_table_lookup(self->by_user_tag, &user_tag); + if (!user_tag_data) + return; + + c_list_for_each_entry (track_data, &user_tag_data->user_tag_lst_head, user_tag_lst) + track_data->dirty = TRUE; +} + +gboolean +nmp_route_manager_untrack_all(NMPRouteManager *self, + gconstpointer user_tag, + gboolean all /* or only dirty */, + gboolean make_survivors_dirty) +{ + TrackData *track_data; + TrackData *track_data_safe; + TrackUserTagData *user_tag_data; + gboolean changed = FALSE; + + g_return_val_if_fail(NMP_IS_ROUTE_MANAGER(self), FALSE); + g_return_val_if_fail(user_tag, FALSE); + + user_tag_data = g_hash_table_lookup(self->by_user_tag, &user_tag); + if (!user_tag_data) + return FALSE; + + c_list_for_each_entry_safe (track_data, + track_data_safe, + &user_tag_data->user_tag_lst_head, + user_tag_lst) { + if (all || track_data->dirty) { + _track_data_untrack(self, track_data, FALSE, FALSE); + changed = TRUE; + continue; + } + if (make_survivors_dirty) + track_data->dirty = TRUE; + } + if (c_list_is_empty(&user_tag_data->user_tag_lst_head)) + g_hash_table_remove(self->by_user_tag, user_tag_data); + + return changed; +} + +/*****************************************************************************/ + +void +nmp_route_manager_sync(NMPRouteManager *self, NMPObjectType obj_type, gboolean keep_deleted) +{ + const NMDedupMultiHeadEntry *pl_head_entry; + NMDedupMultiIter pl_iter; + const NMPObject *plobj; + gs_unref_ptrarray GPtrArray *objs_to_delete = NULL; + TrackObjData *obj_data; + TrackObjData *obj_data_safe; + CList *by_obj_lst_head; + guint i; + const TrackData *td_best; + + g_return_if_fail(NMP_IS_ROUTE_MANAGER(self)); + g_return_if_fail(NM_IN_SET(obj_type, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_ROUTING_RULE)); + + _LOGD("sync %s%s", + nmp_class_from_type(obj_type)->obj_type_name, + keep_deleted ? " (don't remove any)" : ""); + + if (obj_type == NMP_OBJECT_TYPE_ROUTING_RULE) + pl_head_entry = nm_platform_lookup_obj_type(self->platform, obj_type); + else + pl_head_entry = nm_platform_lookup_object(self->platform, obj_type, 0); + + if (pl_head_entry) { + nmp_cache_iter_for_each (&pl_iter, pl_head_entry, &plobj) { + obj_data = g_hash_table_lookup(self->by_obj, &plobj); + + if (!obj_data) { + /* this obj is not tracked. It was externally added, hence we + * ignore it. */ + continue; + } + + td_best = _track_obj_data_get_best_data(obj_data); + if (td_best) { + if (td_best->track_priority_present) { + if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US) + obj_data->config_state = CONFIG_STATE_ADDED_BY_US; + continue; + } + if (td_best->track_priority_val == 0) { + if (!NM_IN_SET(obj_data->config_state, + CONFIG_STATE_ADDED_BY_US, + CONFIG_STATE_OWNED_BY_US)) { + obj_data->config_state = CONFIG_STATE_NONE; + continue; + } + obj_data->config_state = CONFIG_STATE_NONE; + } + } + + if (keep_deleted) { + _LOGD("forget/leak object added by us: %s \"%s\"", + NMP_OBJECT_GET_CLASS(plobj)->obj_type_name, + nmp_object_to_string(plobj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + continue; + } + + if (!objs_to_delete) + objs_to_delete = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + + g_ptr_array_add(objs_to_delete, (gpointer) nmp_object_ref(plobj)); + + obj_data->config_state = CONFIG_STATE_REMOVED_BY_US; + } + } + + if (objs_to_delete) { + for (i = 0; i < objs_to_delete->len; i++) + nm_platform_object_delete(self->platform, objs_to_delete->pdata[i]); + } + + by_obj_lst_head = _by_obj_lst_head(self, obj_type); + + c_list_for_each_entry_safe (obj_data, obj_data_safe, by_obj_lst_head, by_obj_lst) { + nm_assert(NMP_OBJECT_GET_TYPE(obj_data->obj) == obj_type); + + td_best = _track_obj_data_get_best_data(obj_data); + + if (!td_best) { + g_hash_table_remove(self->by_obj, obj_data); + continue; + } + + if (!td_best->track_priority_present) { + if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US) + obj_data->config_state = CONFIG_STATE_REMOVED_BY_US; + continue; + } + if (td_best->track_priority_val == 0) { + if (!NM_IN_SET(obj_data->config_state, + CONFIG_STATE_REMOVED_BY_US, + CONFIG_STATE_OWNED_BY_US)) { + obj_data->config_state = CONFIG_STATE_NONE; + continue; + } + obj_data->config_state = CONFIG_STATE_NONE; + } + + plobj = + nm_platform_lookup_obj(self->platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj_data->obj); + if (plobj) + continue; + + obj_data->config_state = CONFIG_STATE_ADDED_BY_US; + + if (obj_type == NMP_OBJECT_TYPE_ROUTING_RULE) { + nm_platform_routing_rule_add(self->platform, + NMP_NLM_FLAG_ADD, + NMP_OBJECT_CAST_ROUTING_RULE(obj_data->obj)); + } else + nm_platform_ip_route_add(self->platform, NMP_NLM_FLAG_APPEND, obj_data->obj); + } +} + +/*****************************************************************************/ + +void +nmp_route_manager_track_rule_from_platform(NMPRouteManager *self, + NMPlatform *platform, + int addr_family, + gint32 tracking_priority, + gconstpointer user_tag) +{ + NMPLookup lookup; + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject *o; + + g_return_if_fail(NMP_IS_ROUTE_MANAGER(self)); + + if (!platform) + platform = self->platform; + else + g_return_if_fail(NM_IS_PLATFORM(platform)); + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + head_entry = nm_platform_lookup(platform, &lookup); + nmp_cache_iter_for_each (&iter, head_entry, &o) { + const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE(o); + + if (addr_family != AF_UNSPEC && rr->addr_family != addr_family) + continue; + + nmp_route_manager_track_rule(self, rr, tracking_priority, user_tag, NULL); + } +} + +/*****************************************************************************/ + +void +nmp_route_manager_track_rule_default(NMPRouteManager *self, + int addr_family, + gint32 track_priority, + gconstpointer user_tag) +{ + g_return_if_fail(NMP_IS_ROUTE_MANAGER(self)); + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + /* track the default rules. See also `man ip-rule`. */ + + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) { + nmp_route_manager_track_rule(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET, + .priority = 0, + .table = RT_TABLE_LOCAL, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + nmp_route_manager_track_rule(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET, + .priority = 32766, + .table = RT_TABLE_MAIN, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + nmp_route_manager_track_rule(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET, + .priority = 32767, + .table = RT_TABLE_DEFAULT, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + } + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) { + nmp_route_manager_track_rule(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET6, + .priority = 0, + .table = RT_TABLE_LOCAL, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + nmp_route_manager_track_rule(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET6, + .priority = 32766, + .table = RT_TABLE_MAIN, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + } +} + +/*****************************************************************************/ + +NMPRouteManager * +nmp_route_manager_new(NMPlatform *platform) +{ + NMPRouteManager *self; + + g_return_val_if_fail(NM_IS_PLATFORM(platform), NULL); + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(TrackUserTagData, user_tag) == 0); + + self = g_slice_new(NMPRouteManager); + *self = (NMPRouteManager){ + .ref_count = 1, + .platform = g_object_ref(platform), + .by_data = + g_hash_table_new_full(_track_data_hash, _track_data_equal, NULL, _track_data_destroy), + .by_obj = g_hash_table_new_full(_track_obj_data_hash, + _track_obj_data_equal, + NULL, + _track_obj_data_destroy), + .by_user_tag = g_hash_table_new_full(nm_pdirect_hash, + nm_pdirect_equal, + NULL, + _track_user_tag_data_destroy), + .by_obj_lst_heads[0] = C_LIST_INIT(self->by_obj_lst_heads[0]), + .by_obj_lst_heads[1] = C_LIST_INIT(self->by_obj_lst_heads[1]), + .by_obj_lst_heads[2] = C_LIST_INIT(self->by_obj_lst_heads[2]), + }; + return self; +} + +NMPRouteManager * +nmp_route_manager_ref(NMPRouteManager *self) +{ + g_return_val_if_fail(NMP_IS_ROUTE_MANAGER(self), NULL); + + self->ref_count++; + return self; +} + +void +nmp_route_manager_unref(NMPRouteManager *self) +{ + g_return_if_fail(NMP_IS_ROUTE_MANAGER(self)); + + if (--self->ref_count > 0) + return; + + g_hash_table_destroy(self->by_user_tag); + g_hash_table_destroy(self->by_obj); + g_hash_table_destroy(self->by_data); + nm_assert(c_list_is_empty(&self->by_obj_lst_heads[0])); + nm_assert(c_list_is_empty(&self->by_obj_lst_heads[1])); + nm_assert(c_list_is_empty(&self->by_obj_lst_heads[2])); + g_object_unref(self->platform); + nm_g_slice_free(self); +} diff --git a/src/libnm-platform/nmp-route-manager.h b/src/libnm-platform/nmp-route-manager.h new file mode 100644 index 00000000..97ec3840 --- /dev/null +++ b/src/libnm-platform/nmp-route-manager.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NMP_ROUTE_MANAGER_H__ +#define __NMP_ROUTE_MANAGER_H__ + +#include "nm-platform.h" + +/*****************************************************************************/ + +#define NMP_ROUTE_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG ((const void *) nmp_route_manager_new) + +typedef struct _NMPRouteManager NMPRouteManager; + +NMPRouteManager *nmp_route_manager_new(NMPlatform *platform); + +NMPRouteManager *nmp_route_manager_ref(NMPRouteManager *self); +void nmp_route_manager_unref(NMPRouteManager *self); + +#define nm_auto_unref_route_manager nm_auto(_nmp_route_manager_unref) +NM_AUTO_DEFINE_FCN0(NMPRouteManager *, _nmp_route_manager_unref, nmp_route_manager_unref); + +gboolean nmp_route_manager_track(NMPRouteManager *self, + NMPObjectType obj_type, + gconstpointer obj, + gint32 track_priority, + gconstpointer user_tag, + gconstpointer user_tag_untrack); + +static inline gboolean +nmp_route_manager_track_rule(NMPRouteManager *self, + const NMPlatformRoutingRule *routing_rule, + gint32 track_priority, + gconstpointer user_tag, + gconstpointer user_tag_untrack) +{ + return nmp_route_manager_track(self, + NMP_OBJECT_TYPE_ROUTING_RULE, + routing_rule, + track_priority, + user_tag, + user_tag_untrack); +} + +void nmp_route_manager_track_rule_default(NMPRouteManager *self, + int addr_family, + gint32 track_priority, + gconstpointer user_tag); + +void nmp_route_manager_track_rule_from_platform(NMPRouteManager *self, + NMPlatform *platform, + int addr_family, + gint32 tracking_priority, + gconstpointer user_tag); + +gboolean nmp_route_manager_untrack(NMPRouteManager *self, + NMPObjectType obj_type, + gconstpointer obj, + gconstpointer user_tag); + +static inline gboolean +nmp_route_manager_untrack_rule(NMPRouteManager *self, + const NMPlatformRoutingRule *routing_rule, + gconstpointer user_tag) +{ + return nmp_route_manager_untrack(self, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule, user_tag); +} + +void nmp_route_manager_set_dirty(NMPRouteManager *self, gconstpointer user_tag); + +gboolean nmp_route_manager_untrack_all(NMPRouteManager *self, + gconstpointer user_tag, + gboolean all /* or only dirty */, + gboolean make_survivors_dirty); + +void nmp_route_manager_sync(NMPRouteManager *self, NMPObjectType obj_type, gboolean keep_deleted); + +/*****************************************************************************/ + +#endif /* __NMP_ROUTE_MANAGER_H__ */ diff --git a/src/libnm-platform/nmp-rules-manager.c b/src/libnm-platform/nmp-rules-manager.c deleted file mode 100644 index 746d1eaa..00000000 --- a/src/libnm-platform/nmp-rules-manager.c +++ /dev/null @@ -1,809 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" - -#include "nmp-rules-manager.h" - -#include <linux/fib_rules.h> -#include <linux/rtnetlink.h> - -#include "libnm-log-core/nm-logging.h" -#include "libnm-std-aux/c-list-util.h" -#include "nmp-object.h" - -/*****************************************************************************/ - -struct _NMPRulesManager { - NMPlatform *platform; - GHashTable *by_obj; - GHashTable *by_user_tag; - GHashTable *by_data; - guint ref_count; -}; - -/*****************************************************************************/ - -static void _rules_init(NMPRulesManager *self); - -/*****************************************************************************/ - -#define _NMLOG_DOMAIN LOGD_PLATFORM -#define _NMLOG_PREFIX_NAME "rules-manager" - -#define _NMLOG(level, ...) \ - G_STMT_START \ - { \ - const NMLogLevel __level = (level); \ - \ - if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ - _nm_log(__level, \ - _NMLOG_DOMAIN, \ - 0, \ - NULL, \ - NULL, \ - "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ - _NMLOG_PREFIX_NAME _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ - } \ - } \ - G_STMT_END - -/*****************************************************************************/ - -static gboolean -NMP_IS_RULES_MANAGER(gpointer self) -{ - return self && ((NMPRulesManager *) self)->ref_count > 0 - && NM_IS_PLATFORM(((NMPRulesManager *) self)->platform); -} - -#define _USER_TAG_LOG(user_tag) nm_hash_obfuscate_ptr(1240261787u, (user_tag)) - -/*****************************************************************************/ - -typedef struct { - const NMPObject *obj; - gconstpointer user_tag; - CList obj_lst; - CList user_tag_lst; - - /* track_priority_val zero is special: those are weakly tracked rules. - * That means: NetworkManager will restore them only if it removed them earlier. - * But it will not remove or add them otherwise. - * - * Otherwise, the track_priority_val goes together with track_priority_present. - * In case of one rule being tracked multiple times (with different priorities), - * the one with higher priority wins. See _rules_obj_get_best_data(). - * Then, the winning present state either enforces that the rule is present - * or absent. - * - * If a rules is not tracked at all, it is ignored by NetworkManager. Assuming - * that it was added externally by the user. But unlike weakly tracked rules, - * NM will *not* restore such rules if NetworkManager themself removed them. */ - guint32 track_priority_val; - bool track_priority_present : 1; - - bool dirty : 1; -} RulesData; - -typedef enum { - CONFIG_STATE_NONE = 0, - CONFIG_STATE_ADDED_BY_US = 1, - CONFIG_STATE_REMOVED_BY_US = 2, - - /* ConfigState encodes whether the rule was touched by us at all (CONFIG_STATE_NONE). - * - * Maybe we would only need to track whether we touched the rule at all. But we - * track it more in detail what we did: did we add it (CONFIG_STATE_ADDED_BY_US) - * or did we remove it (CONFIG_STATE_REMOVED_BY_US)? - * Finally, we need CONFIG_STATE_OWNED_BY_US, which means that we didn't actively - * add/remove it, but whenever we are about to undo the add/remove, we need to do it. - * In that sense, CONFIG_STATE_OWNED_BY_US is really just a flag that we unconditionally - * force the state next time when necessary. */ - CONFIG_STATE_OWNED_BY_US = 3, -} ConfigState; - -typedef struct { - const NMPObject *obj; - CList obj_lst_head; - - /* indicates whether we configured/removed the rule (during sync()). We need that, so - * if the rule gets untracked, that we know to remove/restore it. - * - * This makes NMPRulesManager stateful (beyond the configuration that indicates - * which rules are tracked). - * After a restart, NetworkManager would no longer remember which rules were added - * by us. - * - * That is partially fixed by NetworkManager taking over the rules that it - * actively configures (see %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG). */ - ConfigState config_state; -} RulesObjData; - -typedef struct { - gconstpointer user_tag; - CList user_tag_lst_head; -} RulesUserTagData; - -/*****************************************************************************/ - -static void _rules_data_untrack(NMPRulesManager *self, - RulesData *rules_data, - gboolean remove_user_tag_data, - gboolean make_owned_by_us); - -/*****************************************************************************/ - -static void -_rules_data_assert(const RulesData *rules_data, gboolean linked) -{ - nm_assert(rules_data); - nm_assert(NMP_OBJECT_GET_TYPE(rules_data->obj) == NMP_OBJECT_TYPE_ROUTING_RULE); - nm_assert(nmp_object_is_visible(rules_data->obj)); - nm_assert(rules_data->user_tag); - nm_assert(!linked || !c_list_is_empty(&rules_data->obj_lst)); - nm_assert(!linked || !c_list_is_empty(&rules_data->user_tag_lst)); -} - -static guint -_rules_data_hash(gconstpointer data) -{ - const RulesData *rules_data = data; - NMHashState h; - - _rules_data_assert(rules_data, FALSE); - - nm_hash_init(&h, 269297543u); - nm_platform_routing_rule_hash_update(NMP_OBJECT_CAST_ROUTING_RULE(rules_data->obj), - NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, - &h); - nm_hash_update_val(&h, rules_data->user_tag); - return nm_hash_complete(&h); -} - -static gboolean -_rules_data_equal(gconstpointer data_a, gconstpointer data_b) -{ - const RulesData *rules_data_a = data_a; - const RulesData *rules_data_b = data_b; - - _rules_data_assert(rules_data_a, FALSE); - _rules_data_assert(rules_data_b, FALSE); - - return rules_data_a->user_tag == rules_data_b->user_tag - && (nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(rules_data_a->obj), - NMP_OBJECT_CAST_ROUTING_RULE(rules_data_b->obj), - NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) - == 0); -} - -static void -_rules_data_destroy(gpointer data) -{ - RulesData *rules_data = data; - - _rules_data_assert(rules_data, FALSE); - - c_list_unlink_stale(&rules_data->obj_lst); - c_list_unlink_stale(&rules_data->user_tag_lst); - nmp_object_unref(rules_data->obj); - g_slice_free(RulesData, rules_data); -} - -static const RulesData * -_rules_obj_get_best_data(RulesObjData *obj_data) -{ - RulesData *rules_data; - const RulesData *rd_best = NULL; - - c_list_for_each_entry (rules_data, &obj_data->obj_lst_head, obj_lst) { - _rules_data_assert(rules_data, TRUE); - - if (rd_best) { - if (rd_best->track_priority_val > rules_data->track_priority_val) - continue; - if (rd_best->track_priority_val == rules_data->track_priority_val) { - if (rd_best->track_priority_present || !rules_data->track_priority_present) { - /* if the priorities are identical, then "present" wins over - * "!present" (absent). */ - continue; - } - } - } - - rd_best = rules_data; - } - - return rd_best; -} - -static guint -_rules_obj_hash(gconstpointer data) -{ - const RulesObjData *obj_data = data; - NMHashState h; - - nm_hash_init(&h, 432817559u); - nm_platform_routing_rule_hash_update(NMP_OBJECT_CAST_ROUTING_RULE(obj_data->obj), - NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, - &h); - return nm_hash_complete(&h); -} - -static gboolean -_rules_obj_equal(gconstpointer data_a, gconstpointer data_b) -{ - const RulesObjData *obj_data_a = data_a; - const RulesObjData *obj_data_b = data_b; - - return (nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(obj_data_a->obj), - NMP_OBJECT_CAST_ROUTING_RULE(obj_data_b->obj), - NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) - == 0); -} - -static void -_rules_obj_destroy(gpointer data) -{ - RulesObjData *obj_data = data; - - c_list_unlink_stale(&obj_data->obj_lst_head); - nmp_object_unref(obj_data->obj); - g_slice_free(RulesObjData, obj_data); -} - -static guint -_rules_user_tag_hash(gconstpointer data) -{ - const RulesUserTagData *user_tag_data = data; - - return nm_hash_val(644693447u, user_tag_data->user_tag); -} - -static gboolean -_rules_user_tag_equal(gconstpointer data_a, gconstpointer data_b) -{ - const RulesUserTagData *user_tag_data_a = data_a; - const RulesUserTagData *user_tag_data_b = data_b; - - return user_tag_data_a->user_tag == user_tag_data_b->user_tag; -} - -static void -_rules_user_tag_destroy(gpointer data) -{ - RulesUserTagData *user_tag_data = data; - - c_list_unlink_stale(&user_tag_data->user_tag_lst_head); - g_slice_free(RulesUserTagData, user_tag_data); -} - -static RulesData * -_rules_data_lookup(GHashTable *by_data, const NMPObject *obj, gconstpointer user_tag) -{ - RulesData rules_data_needle = { - .obj = obj, - .user_tag = user_tag, - }; - - return g_hash_table_lookup(by_data, &rules_data_needle); -} - -/** - * nmp_rules_manager_track: - * @self: the #NMPRulesManager instance - * @routing_rule: the #NMPlatformRoutingRule to track or untrack - * @track_priority: the priority for tracking the rule. Note that - * negative values indicate a forced absence of the rule. Priorities - * are compared with their absolute values (with higher absolute - * value being more important). For example, if you track the same - * rule twice, once with priority -5 and +10, then the rule is - * present (because the positive number is more important). - * The special value 0 indicates weakly-tracked rules. - * @user_tag: the tag associated with tracking this rule. The same tag - * must be used to untrack the rule later. - * @user_tag_untrack: if not %NULL, at the same time untrack this user-tag - * for the same rule. Note that this is different from a plain nmp_rules_manager_untrack(), - * because it enforces ownership of the now tracked rule. On the other hand, - * a plain nmp_rules_manager_untrack() merely forgets about the tracking. - * The purpose here is to set this to %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG. - */ -void -nmp_rules_manager_track(NMPRulesManager *self, - const NMPlatformRoutingRule *routing_rule, - gint32 track_priority, - gconstpointer user_tag, - gconstpointer user_tag_untrack) -{ - NMPObject obj_stack; - const NMPObject *p_obj_stack; - RulesData *rules_data; - RulesObjData *obj_data; - RulesUserTagData *user_tag_data; - gboolean changed = FALSE; - guint32 track_priority_val; - gboolean track_priority_present; - - g_return_if_fail(NMP_IS_RULES_MANAGER(self)); - g_return_if_fail(routing_rule); - g_return_if_fail(user_tag); - nm_assert(track_priority != G_MININT32); - - _rules_init(self); - - p_obj_stack = nmp_object_stackinit(&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule); - - nm_assert(nmp_object_is_visible(p_obj_stack)); - - if (track_priority >= 0) { - track_priority_val = track_priority; - track_priority_present = TRUE; - } else { - track_priority_val = -track_priority; - track_priority_present = FALSE; - } - - rules_data = _rules_data_lookup(self->by_data, p_obj_stack, user_tag); - - if (!rules_data) { - rules_data = g_slice_new(RulesData); - *rules_data = (RulesData){ - .obj = nm_dedup_multi_index_obj_intern(nm_platform_get_multi_idx(self->platform), - p_obj_stack), - .user_tag = user_tag, - .track_priority_val = track_priority_val, - .track_priority_present = track_priority_present, - .dirty = FALSE, - }; - g_hash_table_add(self->by_data, rules_data); - - obj_data = g_hash_table_lookup(self->by_obj, &rules_data->obj); - if (!obj_data) { - obj_data = g_slice_new(RulesObjData); - *obj_data = (RulesObjData){ - .obj = nmp_object_ref(rules_data->obj), - .obj_lst_head = C_LIST_INIT(obj_data->obj_lst_head), - .config_state = CONFIG_STATE_NONE, - }; - g_hash_table_add(self->by_obj, obj_data); - } - c_list_link_tail(&obj_data->obj_lst_head, &rules_data->obj_lst); - - user_tag_data = g_hash_table_lookup(self->by_user_tag, &rules_data->user_tag); - if (!user_tag_data) { - user_tag_data = g_slice_new(RulesUserTagData); - *user_tag_data = (RulesUserTagData){ - .user_tag = user_tag, - .user_tag_lst_head = C_LIST_INIT(user_tag_data->user_tag_lst_head), - }; - g_hash_table_add(self->by_user_tag, user_tag_data); - } - c_list_link_tail(&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst); - changed = TRUE; - } else { - rules_data->dirty = FALSE; - if (rules_data->track_priority_val != track_priority_val - || rules_data->track_priority_present != track_priority_present) { - rules_data->track_priority_val = track_priority_val; - rules_data->track_priority_present = track_priority_present; - changed = TRUE; - } - } - - if (user_tag_untrack) { - if (user_tag != user_tag_untrack) { - RulesData *rules_data_untrack; - - rules_data_untrack = _rules_data_lookup(self->by_data, p_obj_stack, user_tag_untrack); - if (rules_data_untrack) - _rules_data_untrack(self, rules_data_untrack, FALSE, TRUE); - } else - nm_assert_not_reached(); - } - - _rules_data_assert(rules_data, TRUE); - - if (changed) { - _LOGD("routing-rule: track [" NM_HASH_OBFUSCATE_PTR_FMT ",%s%u] \"%s\")", - _USER_TAG_LOG(rules_data->user_tag), - (rules_data->track_priority_val == 0 - ? "" - : (rules_data->track_priority_present ? "+" : "-")), - (guint) rules_data->track_priority_val, - nmp_object_to_string(rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); - } -} - -static void -_rules_data_untrack(NMPRulesManager *self, - RulesData *rules_data, - gboolean remove_user_tag_data, - gboolean make_owned_by_us) -{ - RulesObjData *obj_data; - - nm_assert(NMP_IS_RULES_MANAGER(self)); - _rules_data_assert(rules_data, TRUE); - nm_assert(self->by_data); - nm_assert(g_hash_table_lookup(self->by_data, rules_data) == rules_data); - - _LOGD("routing-rule: untrack [" NM_HASH_OBFUSCATE_PTR_FMT "] \"%s\"", - _USER_TAG_LOG(rules_data->user_tag), - nmp_object_to_string(rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); - -#if NM_MORE_ASSERTS - { - RulesUserTagData *user_tag_data; - - user_tag_data = g_hash_table_lookup(self->by_user_tag, &rules_data->user_tag); - nm_assert(user_tag_data); - nm_assert(c_list_contains(&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst)); - } -#endif - - nm_assert(!c_list_is_empty(&rules_data->user_tag_lst)); - - obj_data = g_hash_table_lookup(self->by_obj, &rules_data->obj); - nm_assert(obj_data); - nm_assert(c_list_contains(&obj_data->obj_lst_head, &rules_data->obj_lst)); - nm_assert(obj_data == g_hash_table_lookup(self->by_obj, &rules_data->obj)); - - if (make_owned_by_us) { - if (obj_data->config_state == CONFIG_STATE_NONE) { - /* we need to mark this entry that it requires a touch on the next - * sync. */ - obj_data->config_state = CONFIG_STATE_OWNED_BY_US; - } - } else if (remove_user_tag_data && c_list_length_is(&rules_data->user_tag_lst, 1)) - g_hash_table_remove(self->by_user_tag, &rules_data->user_tag); - - /* if obj_data is marked to be "added_by_us" or "removed_by_us", we need to keep this entry - * around for the next sync -- so that we can undo what we did earlier. */ - if (obj_data->config_state == CONFIG_STATE_NONE && c_list_length_is(&rules_data->obj_lst, 1)) - g_hash_table_remove(self->by_obj, &rules_data->obj); - - g_hash_table_remove(self->by_data, rules_data); -} - -void -nmp_rules_manager_untrack(NMPRulesManager *self, - const NMPlatformRoutingRule *routing_rule, - gconstpointer user_tag) -{ - NMPObject obj_stack; - const NMPObject *p_obj_stack; - RulesData *rules_data; - - g_return_if_fail(NMP_IS_RULES_MANAGER(self)); - g_return_if_fail(routing_rule); - g_return_if_fail(user_tag); - - _rules_init(self); - - p_obj_stack = nmp_object_stackinit(&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule); - - nm_assert(nmp_object_is_visible(p_obj_stack)); - - rules_data = _rules_data_lookup(self->by_data, p_obj_stack, user_tag); - if (rules_data) - _rules_data_untrack(self, rules_data, TRUE, FALSE); -} - -void -nmp_rules_manager_set_dirty(NMPRulesManager *self, gconstpointer user_tag) -{ - RulesData *rules_data; - RulesUserTagData *user_tag_data; - - g_return_if_fail(NMP_IS_RULES_MANAGER(self)); - g_return_if_fail(user_tag); - - if (!self->by_data) - return; - - user_tag_data = g_hash_table_lookup(self->by_user_tag, &user_tag); - if (!user_tag_data) - return; - - c_list_for_each_entry (rules_data, &user_tag_data->user_tag_lst_head, user_tag_lst) - rules_data->dirty = TRUE; -} - -void -nmp_rules_manager_untrack_all(NMPRulesManager *self, - gconstpointer user_tag, - gboolean all /* or only dirty */) -{ - RulesData *rules_data; - RulesData *rules_data_safe; - RulesUserTagData *user_tag_data; - - g_return_if_fail(NMP_IS_RULES_MANAGER(self)); - g_return_if_fail(user_tag); - - if (!self->by_data) - return; - - user_tag_data = g_hash_table_lookup(self->by_user_tag, &user_tag); - if (!user_tag_data) - return; - - c_list_for_each_entry_safe (rules_data, - rules_data_safe, - &user_tag_data->user_tag_lst_head, - user_tag_lst) { - if (all || rules_data->dirty) - _rules_data_untrack(self, rules_data, FALSE, FALSE); - } - if (c_list_is_empty(&user_tag_data->user_tag_lst_head)) - g_hash_table_remove(self->by_user_tag, user_tag_data); -} - -void -nmp_rules_manager_sync(NMPRulesManager *self, gboolean keep_deleted_rules) -{ - const NMDedupMultiHeadEntry *pl_head_entry; - NMDedupMultiIter pl_iter; - const NMPObject *plobj; - gs_unref_ptrarray GPtrArray *rules_to_delete = NULL; - RulesObjData *obj_data; - GHashTableIter h_iter; - guint i; - const RulesData *rd_best; - - g_return_if_fail(NMP_IS_RULES_MANAGER(self)); - - if (!self->by_data) - return; - - _LOGD("sync%s", keep_deleted_rules ? " (don't remove any rules)" : ""); - - pl_head_entry = nm_platform_lookup_obj_type(self->platform, NMP_OBJECT_TYPE_ROUTING_RULE); - if (pl_head_entry) { - nmp_cache_iter_for_each (&pl_iter, pl_head_entry, &plobj) { - obj_data = g_hash_table_lookup(self->by_obj, &plobj); - - if (!obj_data) { - /* this rule is not tracked. It was externally added, hence we - * ignore it. */ - continue; - } - - rd_best = _rules_obj_get_best_data(obj_data); - if (rd_best) { - if (rd_best->track_priority_present) { - if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US) - obj_data->config_state = CONFIG_STATE_ADDED_BY_US; - continue; - } - if (rd_best->track_priority_val == 0) { - if (!NM_IN_SET(obj_data->config_state, - CONFIG_STATE_ADDED_BY_US, - CONFIG_STATE_OWNED_BY_US)) { - obj_data->config_state = CONFIG_STATE_NONE; - continue; - } - obj_data->config_state = CONFIG_STATE_NONE; - } - } - - if (keep_deleted_rules) { - _LOGD("forget/leak rule added by us: %s", - nmp_object_to_string(plobj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); - continue; - } - - if (!rules_to_delete) - rules_to_delete = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); - - g_ptr_array_add(rules_to_delete, (gpointer) nmp_object_ref(plobj)); - - obj_data->config_state = CONFIG_STATE_REMOVED_BY_US; - } - } - - if (rules_to_delete) { - for (i = 0; i < rules_to_delete->len; i++) - nm_platform_object_delete(self->platform, rules_to_delete->pdata[i]); - } - - g_hash_table_iter_init(&h_iter, self->by_obj); - while (g_hash_table_iter_next(&h_iter, (gpointer *) &obj_data, NULL)) { - rd_best = _rules_obj_get_best_data(obj_data); - - if (!rd_best) { - g_hash_table_iter_remove(&h_iter); - continue; - } - - if (!rd_best->track_priority_present) { - if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US) - obj_data->config_state = CONFIG_STATE_REMOVED_BY_US; - continue; - } - if (rd_best->track_priority_val == 0) { - if (!NM_IN_SET(obj_data->config_state, - CONFIG_STATE_REMOVED_BY_US, - CONFIG_STATE_OWNED_BY_US)) { - obj_data->config_state = CONFIG_STATE_NONE; - continue; - } - obj_data->config_state = CONFIG_STATE_NONE; - } - - plobj = - nm_platform_lookup_obj(self->platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj_data->obj); - if (plobj) - continue; - - obj_data->config_state = CONFIG_STATE_ADDED_BY_US; - nm_platform_routing_rule_add(self->platform, - NMP_NLM_FLAG_ADD, - NMP_OBJECT_CAST_ROUTING_RULE(obj_data->obj)); - } -} - -void -nmp_rules_manager_track_from_platform(NMPRulesManager *self, - NMPlatform *platform, - int addr_family, - gint32 tracking_priority, - gconstpointer user_tag) -{ - NMPLookup lookup; - const NMDedupMultiHeadEntry *head_entry; - NMDedupMultiIter iter; - const NMPObject *o; - - g_return_if_fail(NMP_IS_RULES_MANAGER(self)); - - if (!platform) - platform = self->platform; - else - g_return_if_fail(NM_IS_PLATFORM(platform)); - - nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); - - nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); - head_entry = nm_platform_lookup(platform, &lookup); - nmp_cache_iter_for_each (&iter, head_entry, &o) { - const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE(o); - - if (addr_family != AF_UNSPEC && rr->addr_family != addr_family) - continue; - - nmp_rules_manager_track(self, rr, tracking_priority, user_tag, NULL); - } -} - -/*****************************************************************************/ - -void -nmp_rules_manager_track_default(NMPRulesManager *self, - int addr_family, - gint32 track_priority, - gconstpointer user_tag) -{ - g_return_if_fail(NMP_IS_RULES_MANAGER(self)); - - nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); - - /* track the default rules. See also `man ip-rule`. */ - - if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) { - nmp_rules_manager_track(self, - &((NMPlatformRoutingRule){ - .addr_family = AF_INET, - .priority = 0, - .table = RT_TABLE_LOCAL, - .action = FR_ACT_TO_TBL, - .protocol = RTPROT_KERNEL, - }), - track_priority, - user_tag, - NULL); - nmp_rules_manager_track(self, - &((NMPlatformRoutingRule){ - .addr_family = AF_INET, - .priority = 32766, - .table = RT_TABLE_MAIN, - .action = FR_ACT_TO_TBL, - .protocol = RTPROT_KERNEL, - }), - track_priority, - user_tag, - NULL); - nmp_rules_manager_track(self, - &((NMPlatformRoutingRule){ - .addr_family = AF_INET, - .priority = 32767, - .table = RT_TABLE_DEFAULT, - .action = FR_ACT_TO_TBL, - .protocol = RTPROT_KERNEL, - }), - track_priority, - user_tag, - NULL); - } - if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) { - nmp_rules_manager_track(self, - &((NMPlatformRoutingRule){ - .addr_family = AF_INET6, - .priority = 0, - .table = RT_TABLE_LOCAL, - .action = FR_ACT_TO_TBL, - .protocol = RTPROT_KERNEL, - }), - track_priority, - user_tag, - NULL); - nmp_rules_manager_track(self, - &((NMPlatformRoutingRule){ - .addr_family = AF_INET6, - .priority = 32766, - .table = RT_TABLE_MAIN, - .action = FR_ACT_TO_TBL, - .protocol = RTPROT_KERNEL, - }), - track_priority, - user_tag, - NULL); - } -} - -static void -_rules_init(NMPRulesManager *self) -{ - if (self->by_data) - return; - - self->by_data = - g_hash_table_new_full(_rules_data_hash, _rules_data_equal, NULL, _rules_data_destroy); - self->by_obj = - g_hash_table_new_full(_rules_obj_hash, _rules_obj_equal, NULL, _rules_obj_destroy); - self->by_user_tag = g_hash_table_new_full(_rules_user_tag_hash, - _rules_user_tag_equal, - NULL, - _rules_user_tag_destroy); -} - -/*****************************************************************************/ - -NMPRulesManager * -nmp_rules_manager_new(NMPlatform *platform) -{ - NMPRulesManager *self; - - g_return_val_if_fail(NM_IS_PLATFORM(platform), NULL); - - self = g_slice_new(NMPRulesManager); - *self = (NMPRulesManager){ - .ref_count = 1, - .platform = g_object_ref(platform), - }; - return self; -} - -void -nmp_rules_manager_ref(NMPRulesManager *self) -{ - g_return_if_fail(NMP_IS_RULES_MANAGER(self)); - - self->ref_count++; -} - -void -nmp_rules_manager_unref(NMPRulesManager *self) -{ - g_return_if_fail(NMP_IS_RULES_MANAGER(self)); - - if (--self->ref_count > 0) - return; - - if (self->by_data) { - g_hash_table_destroy(self->by_user_tag); - g_hash_table_destroy(self->by_obj); - g_hash_table_destroy(self->by_data); - } - g_object_unref(self->platform); - g_slice_free(NMPRulesManager, self); -} diff --git a/src/libnm-platform/nmp-rules-manager.h b/src/libnm-platform/nmp-rules-manager.h deleted file mode 100644 index 65e8ddb2..00000000 --- a/src/libnm-platform/nmp-rules-manager.h +++ /dev/null @@ -1,53 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#ifndef __NMP_RULES_MANAGER_H__ -#define __NMP_RULES_MANAGER_H__ - -#include "nm-platform.h" - -/*****************************************************************************/ - -#define NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG ((const void *) nmp_rules_manager_new) - -typedef struct _NMPRulesManager NMPRulesManager; - -NMPRulesManager *nmp_rules_manager_new(NMPlatform *platform); - -void nmp_rules_manager_ref(NMPRulesManager *self); -void nmp_rules_manager_unref(NMPRulesManager *self); - -#define nm_auto_unref_rules_manager nm_auto(_nmp_rules_manager_unref) -NM_AUTO_DEFINE_FCN0(NMPRulesManager *, _nmp_rules_manager_unref, nmp_rules_manager_unref); - -void nmp_rules_manager_track(NMPRulesManager *self, - const NMPlatformRoutingRule *routing_rule, - gint32 track_priority, - gconstpointer user_tag, - gconstpointer user_tag_untrack); - -void nmp_rules_manager_track_default(NMPRulesManager *self, - int addr_family, - gint32 track_priority, - gconstpointer user_tag); - -void nmp_rules_manager_track_from_platform(NMPRulesManager *self, - NMPlatform *platform, - int addr_family, - gint32 tracking_priority, - gconstpointer user_tag); - -void nmp_rules_manager_untrack(NMPRulesManager *self, - const NMPlatformRoutingRule *routing_rule, - gconstpointer user_tag); - -void nmp_rules_manager_set_dirty(NMPRulesManager *self, gconstpointer user_tag); - -void nmp_rules_manager_untrack_all(NMPRulesManager *self, - gconstpointer user_tag, - gboolean all /* or only dirty */); - -void nmp_rules_manager_sync(NMPRulesManager *self, gboolean keep_deleted_rules); - -/*****************************************************************************/ - -#endif /* __NMP_RULES_MANAGER_H__ */ |