[RFCv2] Babel: add v4viav6 support
Andreas Rammhold
andreas at rammhold.de
Fri Apr 1 02:22:55 CEST 2022
This implements [draft-ietf-babel-v4viav6] 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.
[draft-ietf-babel-v4viav6]: https://datatracker.ietf.org/doc/draft-ietf-babel-v4viav6/
[babeld PR]: https://github.com/jech/babeld/pull/56
---
The draft has reached the "Proposed Standard" stage:
https://mailarchive.ietf.org/arch/msg/ietf-announce/oybMxQbud2RvpyBBn8hkMTKakm4/
The mentioned babeld PR has been merged and a new babel release has been
cut a few hours ago.
I've addressed the feedback gathered in 2020 when I posted this for the
first time. On the topic of "feature detection" I didn't invest any more
time. It seems like the conclusion was that the adminitrator has to
ensure that the host system provides the required capabilities. If it
doesn't then we don't try to disable features.
I've rebased this onto Bird 2.0.9 and rerun my test suite. Everything
looks fine.
doc/bird.sgml | 6 +++
proto/babel/babel.c | 26 ++++++++--
proto/babel/babel.h | 3 ++
proto/babel/config.Y | 5 +-
proto/babel/packets.c | 114 ++++++++++++++++++++++++++++++------------
5 files changed, 116 insertions(+), 38 deletions(-)
diff --git a/doc/bird.sgml b/doc/bird.sgml
index 1d5ae056..14758627 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -1892,6 +1892,7 @@ protocol babel [<name>] {
algorithm ( hmac sha1 | hmac sha256 | hmac sha384 |
hmac sha512 | blake2s128 | blake2s256 | blake2b256 | blake2b512 );
};
+ ipv4 via ipv6 <switch>;
};
}
</code>
@@ -2000,6 +2001,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 draft-ietf-babel-v4viav6.
+ Default: yes.
</descrip>
<sect1>Attributes
diff --git a/proto/babel/babel.c b/proto/babel/babel.c
index 174fc9e2..cba407e3 100644
--- a/proto/babel/babel.c
+++ b/proto/babel/babel.c
@@ -965,8 +965,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))
@@ -1261,6 +1270,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 */
@@ -1666,7 +1681,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)
@@ -2049,8 +2064,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)
@@ -2067,6 +2082,7 @@ babel_show_interfaces(struct proto *P, const char *iff)
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 84feb085..191f734e 100644
--- a/proto/babel/babel.h
+++ b/proto/babel/babel.h
@@ -111,6 +111,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
};
@@ -150,6 +151,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 f13410e2..4b2acc64 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;
-
- /* 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);
+ 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;
- 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,15 @@ babel_read_route_request(struct babel_tlv *hdr, union babel_msg *m,
msg->full = 1;
return PARSE_SUCCESS;
+ /*
+ * When receiving requests, AEs 1 (IPv4) and 4 (v4-via-v6) MUST be
+ * treated in the same manner: the receiver processes the request as
+ * described in Section 3.8 of [RFC6126bis]. If an Update is sent, then
+ * it MAY be sent with AE 1 or 4, as described in Section 2.1 above,
+ * irrespective of which AE was used in the request.
+ */
case BABEL_AE_IP4:
+ case BABEL_AE_IPV4_VIA_IPV6:
if (tlv->plen > IP4_MAX_PREFIX_LENGTH)
return PARSE_ERROR;
@@ -1032,7 +1074,15 @@ babel_read_seqno_request(struct babel_tlv *hdr, union babel_msg *m,
case BABEL_AE_WILDCARD:
return PARSE_ERROR;
+ /*
+ * When receiving requests, AEs 1 (IPv4) and 4 (v4-via-v6) MUST be
+ * treated in the same manner: the receiver processes the request as
+ * described in Section 3.8 of [RFC6126bis]. If an Update is sent, then
+ * it MAY be sent with AE 1 or 4, as described in Section 2.1 above,
+ * irrespective of which AE was used in the request.
+ */
case BABEL_AE_IP4:
+ case BABEL_AE_IPV4_VIA_IPV6:
if (tlv->plen > IP4_MAX_PREFIX_LENGTH)
return PARSE_ERROR;
--
2.35.1
More information about the Bird-users
mailing list