lib: print ipv4 mapped ipv6 address in mixed notation

Per RFC 5952 section 5, https://datatracker.ietf.org/doc/html/rfc5952#section-5
it is RECOMMENDED to represent IPv4-mapped IPv6 addresses using "mixed notation"
with the IPv4 part in dot-decimal format: ::ffff:192.0.2.1 instead of ::ffff:c000:0201

It just improves the readability.

Testing:

root@leaf:~# vtysh -c "show bgp ipv6 unicast" | grep 200.100.222.111
*> ::ffff:200.100.222.111/128

leaf# show bgp ipv6 unicast ::ffff:200.100.222.111/128
BGP routing table entry for ::ffff:200.100.222.111/128, version 488

root@leaf:~# vtysh -c "show ipv6 route" | grep 200.100.222.111
B>* ::ffff:200.100.222.111/128 [20/0] via 2220:10::4:0:1, swp1s1, weight 255, 00:01:15

root@leaf:~# ip -6 route show ::ffff:200.100.222.111/128
::ffff:200.100.222.111 nhid 150 proto bgp metric 20 pref medium

Signed-off-by: Karthikeya Venkat Muppalla <kmuppalla@nvidia.com>
This commit is contained in:
Karthikeya Venkat Muppalla 2025-04-21 12:33:55 -07:00
parent 5d4c7d2ece
commit e97232e85e
2 changed files with 122 additions and 28 deletions

View file

@ -85,14 +85,6 @@ static inline int str2ipaddr(const char *str, struct ipaddr *ip)
return -1;
}
static inline char *ipaddr2str(const struct ipaddr *ip, char *buf, int size)
{
buf[0] = '\0';
if (ip)
inet_ntop(ip->ipa_type, &ip->ip.addr, buf, size);
return buf;
}
#define IS_MAPPED_IPV6(A) \
((A)->s6_addr32[0] == 0x00000000 \
? ((A)->s6_addr32[1] == 0x00000000 \
@ -126,6 +118,41 @@ static inline void ipv4_mapped_ipv6_to_ipv4(const struct in6_addr *in6,
memcpy(in, (char *)in6 + 12, sizeof(struct in_addr));
}
static inline char *ipaddr2str(const struct ipaddr *ip, char *buf, int size)
{
buf[0] = '\0';
if (ip) {
if (IS_IPADDR_V6(ip) && IN6_IS_ADDR_V4MAPPED(&ip->ipaddr_v6)) {
/* Handle IPv4-mapped IPv6 addresses specially */
struct in_addr ipv4;
char ipv4str[INET_ADDRSTRLEN];
/*
* Extract the IPv4 address from the mapped IPv6 address.
* Per RFC 5952 section 5, it is RECOMMENDED to represent
* IPv4-mapped IPv6 addresses using "mixed notation" with the
* IPv4 part in dot-decimal format: ::ffff:192.0.2.1
* instead of ::ffff:c000:0201
*/
ipv4_mapped_ipv6_to_ipv4(&ip->ipaddr_v6, &ipv4);
/* Format as IPv4-mapped IPv6 address (::ffff:a.b.c.d) */
inet_ntop(AF_INET, &ipv4, ipv4str, sizeof(ipv4str));
/*
* 1. Copy prefix (7 chars for "::ffff:")
* 2. Append IPv4 address safely with strlcat
*/
snprintf(buf, size, "::ffff:");
strlcat(buf, ipv4str, size);
} else {
/* Regular IP address formatting */
inet_ntop(ipaddr_family(ip), &ip->ip.addr, buf, size);
}
}
return buf;
}
/*
* generic ordering comparison between IP addresses
*/

View file

@ -1072,36 +1072,81 @@ static const char *prefixevpn2str(const struct prefix_evpn *p, char *str,
return str;
}
/* Helper function to format the prefix length in the format /xx */
static size_t format_prefixlen(char *buf, size_t l, int prefixlen, size_t buf_size)
{
int byte, tmp, a, b;
bool z = false;
/*
* Ensure buffer has enough space for the maximum prefix length case.
* For IPv6 with prefix length 128, we need:
* - 1 byte for '/'
* - 1 byte for '1' (hundreds place)
* - 1 byte for '2' (tens place)
* - 1 byte for '8' (ones place)
* - 1 byte for the null terminator
* Total: 5 bytes from the current position
*/
if (l + 4 >= buf_size)
return l;
buf[l++] = '/';
byte = prefixlen;
tmp = prefixlen - 100;
if (tmp >= 0) {
buf[l++] = '1';
z = true;
byte = tmp;
}
b = byte % 10;
a = byte / 10;
if (a || z)
buf[l++] = '0' + a;
buf[l++] = '0' + b;
buf[l] = '\0';
return l;
}
const char *prefix2str(union prefixconstptr pu, char *str, int size)
{
const struct prefix *p = pu.p;
char buf[PREFIX2STR_BUFFER];
int byte, tmp, a, b;
bool z = false;
size_t l;
switch (p->family) {
case AF_INET:
case AF_INET6:
inet_ntop(p->family, &p->u.prefix, buf, sizeof(buf));
l = strlen(buf);
buf[l++] = '/';
byte = p->prefixlen;
tmp = p->prefixlen - 100;
if (tmp >= 0) {
buf[l++] = '1';
z = true;
byte = tmp;
}
b = byte % 10;
a = byte / 10;
if (a || z)
buf[l++] = '0' + a;
buf[l++] = '0' + b;
buf[l] = '\0';
format_prefixlen(buf, strlen(buf), p->prefixlen, sizeof(buf));
strlcpy(str, buf, size);
break;
case AF_INET6:
/* Check if it's an IPv4-mapped IPv6 address */
if (IN6_IS_ADDR_V4MAPPED(&p->u.prefix6)) {
struct in_addr ipv4;
char ipv4str[INET_ADDRSTRLEN];
ipv4_mapped_ipv6_to_ipv4(&p->u.prefix6, &ipv4);
/* Format as ::ffff:a.b.c.d/plen format */
inet_ntop(AF_INET, &ipv4, ipv4str, sizeof(ipv4str));
/*
* 1. Copy prefix (7 chars for "::ffff:")
* 2. Append IPv4 address safely with strlcat
*/
snprintf(buf, sizeof(buf), "::ffff:");
strlcat(buf, ipv4str, sizeof(buf));
format_prefixlen(buf, strlen(buf), p->prefixlen, sizeof(buf));
strlcpy(str, buf, size);
} else {
/* Regular IPv6 address */
inet_ntop(p->family, &p->u.prefix, buf, sizeof(buf));
format_prefixlen(buf, strlen(buf), p->prefixlen, sizeof(buf));
strlcpy(str, buf, size);
}
break;
case AF_ETHERNET:
snprintf(str, size, "%s/%d",
prefix_mac2str(&p->u.prefix_eth, buf, sizeof(buf)),
@ -1611,7 +1656,29 @@ static ssize_t printfrr_i6(struct fbuf *buf, struct printfrr_eargs *ea,
if (use_star && !memcmp(ptr, &zero, sizeof(zero)))
return bputch(buf, '*');
inet_ntop(AF_INET6, ptr, cbuf, sizeof(cbuf));
/* Handle IPv4-mapped IPv6 addresses specially */
const struct in6_addr *addr = ptr;
if (IN6_IS_ADDR_V4MAPPED(addr)) {
struct in_addr ipv4;
char ipv4str[INET_ADDRSTRLEN];
/* Extract the IPv4 address from the mapped IPv6 address */
ipv4_mapped_ipv6_to_ipv4(addr, &ipv4);
/* Format as ::ffff:a.b.c.d */
inet_ntop(AF_INET, &ipv4, ipv4str, sizeof(ipv4str));
/*
* 1. Copy prefix (7 chars for "::ffff:")
* 2. Append IPv4 address safely with strlcat
*/
snprintf(cbuf, sizeof(cbuf), "::ffff:");
strlcat(cbuf, ipv4str, sizeof(cbuf));
} else {
/* Regular IPv6 address formatting */
inet_ntop(AF_INET6, ptr, cbuf, sizeof(cbuf));
}
return bputs(buf, cbuf);
}