--- 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();
 }
 
