--- a/sysdep/linux/netlink.c +++ b/sysdep/linux/netlink.c @@ -741,6 +741,110 @@ } #endif +#ifndef NDM_RTA +#define NDM_RTA(n) (struct rtattr *)(((char *) n) + NLMSG_ALIGN(sizeof(struct ndmsg))) +#endif + +#ifdef HAVE_MPLS_KERNEL +/* + * IPv6 on-link router cache + * + * 6VPE / VPNv6-over-IPv4-underlay routes resolve to a nexthop whose + * gateway is an IPv4(-family) address (the underlay is plain IPv4, + * there is no IPv6 IGP). The Linux IPv6 FIB (net/ipv6/route.c) does + * not accept RTA_VIA for IPv6 routes, so such a route can never be + * installed as-is, even though it carries a valid MPLS label stack. + * + * For an MPLS-encapsulated route, the gateway only selects the L2 + * adjacency (the packet leaves as an MPLS frame; the address family + * of the nexthop is otherwise irrelevant to forwarding). We can + * therefore substitute the real IPv6 link-local address of the + * on-link router for the given interface, as already known to the + * kernel from Router Advertisements / Neighbor Discovery -- without + * assuming any particular interface name or address value. + */ + +struct nl_v6_router { + struct nl_v6_router *hash_next; + u32 ifindex; + ip_addr ll; +}; + +static HASH(struct nl_v6_router) nl_v6_router_map; + +#define RTRH_KEY(p) (p->ifindex) +#define RTRH_NEXT(p) (p->hash_next) +#define RTRH_EQ(i1,i2) ((i1) == (i2)) +#define RTRH_FN(i) u32_hash(i) +#define RTRH_REHASH rtrh_rehash +#define RTRH_PARAMS /8, *1, 2, 2, 4, 20 + +HASH_DEFINE_REHASH_FN(RTRH, struct nl_v6_router) + +/* + * Parse one RTM_NEWNEIGH for AF_INET6. We only care about neighbors + * advertised as routers (NTF_ROUTER, i.e. learned via RA / are the + * default gateway of the segment) -- that is exactly the kind of + * adjacency a resolved MPLS nexthop on that interface needs. + */ +static void +nl_parse_v6_router(struct nlmsghdr *h) +{ + struct ndmsg *nd; + struct rtattr *a[BIRD_NDA_MAX]; + + if (!(nd = nl_checkin(h, sizeof(*nd)))) + return; + + if (nd->ndm_family != AF_INET6) + return; + + if (!(nd->ndm_flags & NTF_ROUTER)) + return; + + if (!nl_parse_attrs(NDM_RTA(nd), ndm_attr_want, a, sizeof(a))) + return; + + if (!a[NDA_DST]) + return; + + struct nl_v6_router *r = HASH_FIND(nl_v6_router_map, RTRH, (u32) nd->ndm_ifindex); + if (!r) + { + r = mb_allocz(krt_pool, sizeof(*r)); + r->ifindex = nd->ndm_ifindex; + HASH_INSERT2(nl_v6_router_map, RTRH, krt_pool, r); + } + + r->ll = rta_get_ipa(a[NDA_DST]); +} + +/* + * Refresh the on-link router cache. Called once per KRT scan cycle + * (see krt_do_scan()), so a change of the neighbor's link-local + * address is picked up within one scan interval; entries are simply + * overwritten in place, stale ifindexes are harmless (an interface + * that disappears just never gets looked up again). + */ +static void +nl_v6_router_scan(void) +{ + nl_request_dump_neigh(AF_INET6, 0); + + struct nlmsghdr *h; + while (h = nl_get_scan()) + if (h->nlmsg_type == RTM_NEWNEIGH) + nl_parse_v6_router(h); +} + +static ip_addr +nl_v6_router_find(struct iface *i) +{ + struct nl_v6_router *r = i ? HASH_FIND(nl_v6_router_map, RTRH, (u32) i->index) : NULL; + return r ? r->ll : IPA_NONE; +} +#endif /* HAVE_MPLS_KERNEL */ + struct rtattr * nl_add_attr(struct nlmsghdr *h, uint bufsize, uint code, const void *data, uint dlen) { @@ -887,6 +991,17 @@ { if (af == (ipa_is_ip4(nh->gw) ? AF_INET : AF_INET6)) nl_add_attr_ipa(h, bufsize, RTA_GATEWAY, nh->gw); + else if (af == AF_INET6 && ipa_is_ip4(nh->gw)) + { + /* IPv6 route with an IPv4(-family) gateway (6VPE over IPv4 underlay). + * Linux rejects RTA_VIA on IPv6 routes, so substitute the real + * link-local address of the on-link router, if already known. */ + ip_addr ll = nl_v6_router_find(nh->iface); + if (ipa_nonzero(ll)) + nl_add_attr_ipa(h, bufsize, RTA_GATEWAY, ll); + else + nl_add_attr_via(h, bufsize, nh->gw); + } else nl_add_attr_via(h, bufsize, nh->gw); } @@ -2124,6 +2239,12 @@ .scan = 1, }; +#ifdef HAVE_MPLS_KERNEL + /* Refresh the on-link IPv6 router cache before (re)exporting IPv6 routes */ + if (!p || p->af == AF_INET6) + nl_v6_router_scan(); +#endif + /* Table-specific scan or shared scan */ if (p) nl_request_dump_route(p->af, krt_table_id(p)); @@ -2270,10 +2391,6 @@ log(L_WARN "NL error %m"); } -#ifndef NDM_RTA -#define NDM_RTA(n) (struct rtattr *)(((char *) n) + NLMSG_ALIGN(sizeof(struct ndmsg))) -#endif - static void nl_parse_fdb(struct nl_parse_state *s, struct nlmsghdr *h) { @@ -2787,6 +2904,10 @@ HASH_INIT(nl_bridge_map, krt_pool, 4); #endif +#ifdef HAVE_MPLS_KERNEL + HASH_INIT(nl_v6_router_map, krt_pool, 4); +#endif + nl_ea_register(); }