[PATCH] Babel: add RFC9229 (v4 via v6) support

andreas at rammhold.de andreas at rammhold.de
Sun Dec 18 18:57:48 CET 2022


From: Andreas Rammhold <andreas at rammhold.de>

This implements [RFC9229] an IPv4 via IPv6 extension
to the Babel routing protocol that allows annoncing routes to an IPv4
prefix with an IPv6 next-hop, which makes it possible for IPv4 traffic
to flow through interfaces that have not been assigned an IPv4 address.

The implementation is compatible with the current Babeld version (the
relevant changes can be seen in the [babeld PR]). I've verified this
with a few VMs in the following setup:

Bird <- v4 only -> Bird <- v6 only -> Babeld <- v4 only -> Babeld

Each routing daemon was running on their own VM and had L2 connectivity
to only its direct neighbors. At the nodes at the edges v4 networks have
been announced and full end-to-end communication was possible over the
mixed AF network. The v6 only link between Babel and Bird (at the
"center" of the above setup) did transport the v4 packets via the v6
link-local next hop addresses just as expected.

Thanks to Toke Høiland-Jørgensen for early review on this work.

[RFC9229]: https://www.ietf.org/rfc/rfc9229.txt
[babeld PR]: https://github.com/jech/babeld/pull/56

Acked-by: Toke Høiland-Jørgensen <toke at toke.dk>
---

This is a rebase of the patches from the previous two RFC rounds on this ML:
  * https://trubka.network.cz/pipermail/bird-users/2022-April/016046.html
    <20201215020627.30942-1-andreas at rammhold.de>
  * http://trubka.network.cz/pipermail/bird-users/2020-December/015082.html
    <20201215020627.30942-1-andreas at rammhold.de>

I'd like to drive this to the finish goal soon. I've been using it
ever since and know of other users as well.

Is there anything that should still be changed?

 doc/bird.sgml         |   6 +++
 proto/babel/babel.c   |  28 ++++++++---
 proto/babel/babel.h   |   3 ++
 proto/babel/config.Y  |   5 +-
 proto/babel/packets.c | 106 +++++++++++++++++++++++++++++-------------
 5 files changed, 109 insertions(+), 39 deletions(-)

diff --git a/doc/bird.sgml b/doc/bird.sgml
index 50657ebf..a7cedaee 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -1934,6 +1934,7 @@ protocol babel [<name>] {
 			algorithm ( hmac sha1 | hmac sha256 | hmac sha384 |
 	hmac sha512 | blake2s128 | blake2s256 | blake2b256 | blake2b512 );
 		};
+		ipv4 via ipv6 <switch>;
 	};
 }
 </code>
@@ -2042,6 +2043,11 @@ protocol babel [<name>] {
       protocol will only accept HMAC-based algorithms or one of the Blake
       algorithms, and the length of the supplied password string must match the
       key size used by the selected algorithm.
+
+      <tag><label id="babel-ipv4-via-ipv6">ipv4 via ipv6 <m/switch/</tag>
+      If enabled, Bird will accept and emit IPv4-via-IPv6 routes when IPv4
+      addresses are absent from the interface as described in RFC9229.
+      Default: yes.
 </descrip>
 
 <sect1>Attributes
diff --git a/proto/babel/babel.c b/proto/babel/babel.c
index ecfd2763..52a27256 100644
--- a/proto/babel/babel.c
+++ b/proto/babel/babel.c
@@ -981,8 +981,17 @@ babel_send_update_(struct babel_iface *ifa, btime changed, struct fib *rtable)
     msg.update.router_id = e->router_id;
     net_copy(&msg.update.net, e->n.addr);
 
-    msg.update.next_hop = ((e->n.addr->type == NET_IP4) ?
-			   ifa->next_hop_ip4 : ifa->next_hop_ip6);
+    if (e->n.addr->type == NET_IP4) {
+      /* Always prefer v4 nexthop if set */
+      if(!ipa_zero(ifa->next_hop_ip4))
+        msg.update.next_hop = ifa->next_hop_ip4;
+
+      /* Only send v4-via-v6 if enabled */
+      else if (ifa->cf->ip4_via_ip6)
+        msg.update.next_hop = ifa->next_hop_ip6;
+    } else {
+      msg.update.next_hop = ifa->next_hop_ip6;
+    }
 
     /* Do not send route if next hop is unknown, e.g. no configured IPv4 address */
     if (ipa_zero(msg.update.next_hop))
@@ -1277,6 +1286,12 @@ babel_handle_update(union babel_msg *m, struct babel_iface *ifa)
     return;
   }
 
+  /* Reject IPv4 via IPv6 routes if disabled */
+  if (!ifa->cf->ip4_via_ip6 && msg->net.type == NET_IP4 && ipa_is_ip6(msg->next_hop)) {
+    DBG("Babel: Ignoring disabled IPv4 via IPv6 route.\n");
+    return;
+  }
+
   /* Regular update */
   e = babel_get_entry(p, &msg->net);
   r = babel_get_route(p, e, nbr); /* the route entry indexed by neighbour */
@@ -1682,7 +1697,7 @@ babel_iface_update_addr4(struct babel_iface *ifa)
   ip_addr addr4 = ifa->iface->addr4 ? ifa->iface->addr4->ip : IPA_NONE;
   ifa->next_hop_ip4 = ipa_nonzero(ifa->cf->next_hop_ip4) ? ifa->cf->next_hop_ip4 : addr4;
 
-  if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel)
+  if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel && !ifa->cf->ip4_via_ip6)
     log(L_WARN "%s: Missing IPv4 next hop address for %s", p->p.name, ifa->ifname);
 
   if (ifa->up)
@@ -2074,8 +2089,8 @@ babel_show_interfaces(struct proto *P, const char *iff)
   }
 
   cli_msg(-1023, "%s:", p->p.name);
-  cli_msg(-1023, "%-10s %-6s %-5s %7s %6s %7s %-15s %s",
-	  "Interface", "State", "Auth", "RX cost", "Nbrs", "Timer",
+  cli_msg(-1023, "%-10s %-6s %-5s %-12s %7s %6s %7s %-15s %s",
+	  "Interface", "State", "Auth", "IPv4 via Ip6", "RX cost", "Nbrs", "Timer",
 	  "Next hop (v4)", "Next hop (v6)");
 
   WALK_LIST(ifa, p->interfaces)
@@ -2088,10 +2103,11 @@ babel_show_interfaces(struct proto *P, const char *iff)
 	nbrs++;
 
     btime timer = MIN(ifa->next_regular, ifa->next_hello) - current_time();
-    cli_msg(-1023, "%-10s %-6s %-5s %7u %6u %7t %-15I %I",
+    cli_msg(-1023, "%-10s %-6s %-5s %-12s %7u %6u %7t %-15I %I",
 	    ifa->iface->name, (ifa->up ? "Up" : "Down"),
             (ifa->cf->auth_type == BABEL_AUTH_MAC ?
              (ifa->cf->auth_permissive ? "Perm" : "Yes") : "No"),
+	    (ifa->cf->ip4_via_ip6 ? "Yes" : "No"),
 	    ifa->cf->rxcost, nbrs, MAX(timer, 0),
 	    ifa->next_hop_ip4, ifa->next_hop_ip6);
   }
diff --git a/proto/babel/babel.h b/proto/babel/babel.h
index 8b6da3c8..034a17db 100644
--- a/proto/babel/babel.h
+++ b/proto/babel/babel.h
@@ -112,6 +112,7 @@ enum babel_ae_type {
   BABEL_AE_IP4			= 1,
   BABEL_AE_IP6			= 2,
   BABEL_AE_IP6_LL		= 3,
+  BABEL_AE_IPV4_VIA_IPV6	= 4,
   BABEL_AE_MAX
 };
 
@@ -151,6 +152,8 @@ struct babel_iface_config {
   uint mac_num_keys;			/* Number of configured HMAC keys */
   uint mac_total_len;			/* Total digest length for all configured keys */
   list *passwords;			/* Passwords for authentication */
+
+  u8 ip4_via_ip6;			/* Enable IPv4 via IPv6 */
 };
 
 struct babel_proto {
diff --git a/proto/babel/config.Y b/proto/babel/config.Y
index 05210fa4..7190f099 100644
--- a/proto/babel/config.Y
+++ b/proto/babel/config.Y
@@ -25,7 +25,8 @@ CF_DECLS
 CF_KEYWORDS(BABEL, INTERFACE, METRIC, RXCOST, HELLO, UPDATE, INTERVAL, PORT,
 	TYPE, WIRED, WIRELESS, RX, TX, BUFFER, PRIORITY, LENGTH, CHECK, LINK,
 	NEXT, HOP, IPV4, IPV6, BABEL_METRIC, SHOW, INTERFACES, NEIGHBORS,
-	ENTRIES, RANDOMIZE, ROUTER, ID, AUTHENTICATION, NONE, MAC, PERMISSIVE)
+	ENTRIES, RANDOMIZE, ROUTER, ID, AUTHENTICATION, NONE, MAC, PERMISSIVE,
+	VIA)
 
 CF_GRAMMAR
 
@@ -67,6 +68,7 @@ babel_iface_start:
   BABEL_IFACE->tx_tos = IP_PREC_INTERNET_CONTROL;
   BABEL_IFACE->tx_priority = sk_priority_control;
   BABEL_IFACE->check_link = 1;
+  BABEL_IFACE->ip4_via_ip6 = 1;
 };
 
 
@@ -143,6 +145,7 @@ babel_iface_item:
  | CHECK LINK bool { BABEL_IFACE->check_link = $3; }
  | NEXT HOP IPV4 ipa { BABEL_IFACE->next_hop_ip4 = $4; if (!ipa_is_ip4($4)) cf_error("Must be an IPv4 address"); }
  | NEXT HOP IPV6 ipa { BABEL_IFACE->next_hop_ip6 = $4; if (!ipa_is_ip6($4)) cf_error("Must be an IPv6 address"); }
+ | IPV4 VIA IPV6 bool { BABEL_IFACE->ip4_via_ip6 = $4; }
  | AUTHENTICATION NONE { BABEL_IFACE->auth_type = BABEL_AUTH_NONE; }
  | AUTHENTICATION MAC { BABEL_IFACE->auth_type = BABEL_AUTH_MAC; BABEL_IFACE->auth_permissive = 0; }
  | AUTHENTICATION MAC PERMISSIVE { BABEL_IFACE->auth_type = BABEL_AUTH_MAC; BABEL_IFACE->auth_permissive = 1; }
diff --git a/proto/babel/packets.c b/proto/babel/packets.c
index d4acc170..9a71673a 100644
--- a/proto/babel/packets.c
+++ b/proto/babel/packets.c
@@ -167,9 +167,11 @@ struct babel_parse_state {
   u64 router_id;		/* Router ID used in subsequent updates */
   u8 def_ip6_prefix[16];	/* Implicit IPv6 prefix in network order */
   u8 def_ip4_prefix[4];		/* Implicit IPv4 prefix in network order */
+  u8 def_ip4viaip6_prefix[4];	/* Implicit IPv4 prefix in network order */
   u8 router_id_seen;		/* router_id field is valid */
   u8 def_ip6_prefix_seen;	/* def_ip6_prefix is valid */
   u8 def_ip4_prefix_seen;	/* def_ip4_prefix is valid */
+  u8 def_ip4viaip6_prefix_seen; /* def_ip4viaip6_prefix is valid*/
   u8 current_tlv_endpos;	/* End of self-terminating TLVs (offset from start) */
   u8 sadr_enabled;
   u8 is_unicast;
@@ -515,9 +517,6 @@ babel_read_ihu(struct babel_tlv *hdr, union babel_msg *m,
   msg->addr = IPA_NONE;
   msg->sender = state->saddr;
 
-  if (msg->ae >= BABEL_AE_MAX)
-    return PARSE_IGNORE;
-
   /*
    * We only actually read link-local IPs. In every other case, the addr field
    * will be 0 but validation will succeed. The handler takes care of these
@@ -530,13 +529,13 @@ babel_read_ihu(struct babel_tlv *hdr, union babel_msg *m,
     if (TLV_OPT_LENGTH(tlv) < 4)
       return PARSE_ERROR;
     state->current_tlv_endpos += 4;
-    break;
+    return PARSE_SUCCESS;
 
   case BABEL_AE_IP6:
     if (TLV_OPT_LENGTH(tlv) < 16)
       return PARSE_ERROR;
     state->current_tlv_endpos += 16;
-    break;
+    return PARSE_SUCCESS;
 
   case BABEL_AE_IP6_LL:
     if (TLV_OPT_LENGTH(tlv) < 8)
@@ -544,10 +543,10 @@ babel_read_ihu(struct babel_tlv *hdr, union babel_msg *m,
 
     msg->addr = ipa_from_ip6(get_ip6_ll(&tlv->addr));
     state->current_tlv_endpos += 8;
-    break;
+    return PARSE_SUCCESS;
   }
 
-  return PARSE_SUCCESS;
+  return PARSE_IGNORE;
 }
 
 static uint
@@ -647,6 +646,47 @@ babel_read_next_hop(struct babel_tlv *hdr, union babel_msg *m UNUSED,
   return PARSE_IGNORE;
 }
 
+
+/* This is called directly from babel_read_next_hop to handle the ip4 address
+   compression state */
+static int
+babel_get_ip4_prefix(struct babel_tlv_update *tlv,
+                             struct babel_msg_update *msg,
+                             u8 *def_prefix_seen,
+                             u8 (*def_prefix)[],
+                             ip_addr next_hop,
+                             int len)
+{
+    if (tlv->plen > IP4_MAX_PREFIX_LENGTH)
+      return PARSE_ERROR;
+
+    /* Cannot omit data if there is no saved prefix */
+    if (tlv->omitted && !*def_prefix_seen)
+      return PARSE_ERROR;
+
+    /* Update must have next hop, unless it is retraction */
+    if (ipa_zero(next_hop) && msg->metric != BABEL_INFINITY)
+      return PARSE_IGNORE;
+
+    /* Merge saved prefix and received prefix parts */
+    u8 buf[4] = {};
+    memcpy(buf, *def_prefix, tlv->omitted);
+    memcpy(buf + tlv->omitted, tlv->addr, len);
+
+    ip4_addr prefix4 = get_ip4(buf);
+    net_fill_ip4(&msg->net, prefix4, tlv->plen);
+
+    if (tlv->flags & BABEL_UF_DEF_PREFIX)
+    {
+      put_ip4(*def_prefix, prefix4);
+      *def_prefix_seen = 1;
+    }
+
+    msg->next_hop = next_hop;
+
+    return PARSE_SUCCESS;
+}
+
 /* This is called directly from babel_write_update() and returns -1 if a next
    hop should be written but there is not enough space. */
 static int
@@ -696,6 +736,7 @@ static int
 babel_read_update(struct babel_tlv *hdr, union babel_msg *m,
                   struct babel_parse_state *state)
 {
+  int rc;
   struct babel_tlv_update *tlv = (void *) hdr;
   struct babel_msg_update *msg = &m->update;
 
@@ -706,7 +747,6 @@ babel_read_update(struct babel_tlv *hdr, union babel_msg *m,
 
   /* Length of received prefix data without omitted part */
   int len = BYTES(tlv->plen) - (int) tlv->omitted;
-  u8 buf[16] = {};
 
   if ((len < 0) || ((uint) len > TLV_OPT_LENGTH(tlv)))
     return PARSE_ERROR;
@@ -724,31 +764,20 @@ babel_read_update(struct babel_tlv *hdr, union babel_msg *m,
     break;
 
   case BABEL_AE_IP4:
-    if (tlv->plen > IP4_MAX_PREFIX_LENGTH)
-      return PARSE_ERROR;
+    rc = babel_get_ip4_prefix(tlv, msg, &state->def_ip4_prefix_seen,
+		                      &state->def_ip4_prefix, state->next_hop_ip4,
+				      len);
+    if (rc != PARSE_SUCCESS)
+      return rc;
 
-    /* Cannot omit data if there is no saved prefix */
-    if (tlv->omitted && !state->def_ip4_prefix_seen)
-      return PARSE_ERROR;
-
-    /* Update must have next hop, unless it is retraction */
-    if (ipa_zero(state->next_hop_ip4) && (msg->metric != BABEL_INFINITY))
-      return PARSE_IGNORE;
-
-    /* Merge saved prefix and received prefix parts */
-    memcpy(buf, state->def_ip4_prefix, tlv->omitted);
-    memcpy(buf + tlv->omitted, tlv->addr, len);
-
-    ip4_addr prefix4 = get_ip4(buf);
-    net_fill_ip4(&msg->net, prefix4, tlv->plen);
-
-    if (tlv->flags & BABEL_UF_DEF_PREFIX)
-    {
-      put_ip4(state->def_ip4_prefix, prefix4);
-      state->def_ip4_prefix_seen = 1;
-    }
+    break;
 
-    msg->next_hop = state->next_hop_ip4;
+  case BABEL_AE_IPV4_VIA_IPV6:
+    rc = babel_get_ip4_prefix(tlv, msg, &state->def_ip4viaip6_prefix_seen,
+    				      &state->def_ip4viaip6_prefix, state->next_hop_ip6,
+				      len);
+    if (rc != PARSE_SUCCESS)
+      return rc;
 
     break;
 
@@ -761,6 +790,7 @@ babel_read_update(struct babel_tlv *hdr, union babel_msg *m,
       return PARSE_ERROR;
 
     /* Merge saved prefix and received prefix parts */
+    u8 buf[16] = {};
     memcpy(buf, state->def_ip6_prefix, tlv->omitted);
     memcpy(buf + tlv->omitted, tlv->addr, len);
 
@@ -863,7 +893,11 @@ babel_write_update(struct babel_tlv *hdr, union babel_msg *m,
   }
   else if (msg->net.type == NET_IP4)
   {
-    tlv->ae = BABEL_AE_IP4;
+    if (!ipa_zero(msg->next_hop) && ipa_is_ip6(msg->next_hop))
+      tlv->ae = BABEL_AE_IPV4_VIA_IPV6;
+    else
+      tlv->ae = BABEL_AE_IP4;
+
     tlv->plen = net4_pxlen(&msg->net);
     put_ip4_px(tlv->addr, &msg->net);
   }
@@ -931,7 +965,11 @@ babel_read_route_request(struct babel_tlv *hdr, union babel_msg *m,
     msg->full = 1;
     return PARSE_SUCCESS;
 
+  /* RFC9229 section 2.3: When receiving requests, AEs 1 (IPv4) and 4
+   * (v4-via-v6) MUST be treated in the same manner.
+   */
   case BABEL_AE_IP4:
+  case BABEL_AE_IPV4_VIA_IPV6:
     if (tlv->plen > IP4_MAX_PREFIX_LENGTH)
       return PARSE_ERROR;
 
@@ -1032,7 +1070,11 @@ babel_read_seqno_request(struct babel_tlv *hdr, union babel_msg *m,
   case BABEL_AE_WILDCARD:
     return PARSE_ERROR;
 
+  /* RFC9229 section 2.3: When receiving requests, AEs 1 (IPv4) and 4
+   * (v4-via-v6) MUST be treated in the same manner.
+   */
   case BABEL_AE_IP4:
+  case BABEL_AE_IPV4_VIA_IPV6:
     if (tlv->plen > IP4_MAX_PREFIX_LENGTH)
       return PARSE_ERROR;
 
-- 
2.38.1



More information about the Bird-users mailing list