On Sat, Sep 27, 2025 at 03:25:16PM -0400, Jon Maloy wrote:
Gratuitious ARP and unsolicitated NA should be handled with caution
s/unsolicitated/unsolicited/ Given the confusing history that RFC 5227 mentions, I feel like we should avoid the term "gratuitous ARP" as confusing.
because of the risk of malignant users emitting them to disturb network communication.
There is however one case we where we know it is legitimate and safe for us to send out such messages: The one time we switch from using ctx->own_tap_mac to a MAC address received via the recently added neigbour subscription function. Later changes to the MAC address of a host in an existing entry cannot be fully trusted, so we abstain from doing it in such cases.
When sending this type of messages, we notice that the guest accepts the update, but also asks for a confirmation in the form of a regular ARP/NS request.
Is this still true with the announcement-by-request method?
This is responded to with the new value, and we have exactly the effect we wanted.
This commit adds this functionality.
Signed-off-by: Jon Maloy
--- v10: -Made small changes based of feedback from David G. v11: -Moved from 'Gratuitous ARP reply' model to 'ARP Announcement' model. --- arp.c | 41 +++++++++++++++++++++++++++++++++++++++++ arp.h | 2 ++ fwd.c | 8 ++++++++ ndp.c | 10 ++++++++++ ndp.h | 1 + 5 files changed, 62 insertions(+)
diff --git a/arp.c b/arp.c index ad088b1..57e7b59 100644 --- a/arp.c +++ b/arp.c @@ -146,3 +146,44 @@ void arp_send_init_req(const struct ctx *c) debug("Sending initial ARP request for guest MAC address"); tap_send_single(c, &req, sizeof(req)); } + +/** + * arp_send_gratuitous() - Send a gratuitous ARP announcement for an IPv4 host
arp_announce() maybe?
+ * @c: Execution context + * @ip: IPv4 address we announce as owned by @mac + * @mac: MAC address to advertise for @ip + */ +void arp_send_gratuitous(const struct ctx *c, struct in_addr *ip, + const unsigned char *mac) +{ + char ip_str[INET_ADDRSTRLEN]; + struct { + struct ethhdr eh; + struct arphdr ah; + struct arpmsg am; + } __attribute__((__packed__)) annc; + + /* Ethernet header */ + annc.eh.h_proto = htons(ETH_P_ARP); + memcpy(annc.eh.h_dest, MAC_BROADCAST, sizeof(annc.eh.h_dest)); + memcpy(annc.eh.h_source, mac, sizeof(annc.eh.h_source)); + + /* ARP header */ + annc.ah.ar_op = htons(ARPOP_REQUEST); + annc.ah.ar_hrd = htons(ARPHRD_ETHER); + annc.ah.ar_pro = htons(ETH_P_IP); + annc.ah.ar_hln = ETH_ALEN; + annc.ah.ar_pln = 4; + + /* ARP message */ + memcpy(annc.am.sha, mac, sizeof(annc.am.sha)); + memcpy(annc.am.sip, ip, sizeof(annc.am.sip)); + memcpy(annc.am.tha, MAC_BROADCAST, sizeof(annc.am.tha)); + memcpy(annc.am.tip, ip, sizeof(annc.am.tip));
Does using tip == sip make sense? Or should tip be 255.255.255.255?
+ inet_ntop(AF_INET, ip, ip_str, sizeof(ip_str)); + debug("Sending ARP announcement for %s", ip_str);
Maybe include the MAC address too?
+ + tap_send_single(c, &annc, sizeof(annc)); +} + diff --git a/arp.h b/arp.h index d5ad0e1..2cf1326 100644 --- a/arp.h +++ b/arp.h @@ -22,5 +22,7 @@ struct arpmsg {
int arp(const struct ctx *c, struct iov_tail *data); void arp_send_init_req(const struct ctx *c); +void arp_send_gratuitous(const struct ctx *c, struct in_addr *ip, + const unsigned char *mac);
#endif /* ARP_H */ diff --git a/fwd.c b/fwd.c index 2fd6cee..7f38b40 100644 --- a/fwd.c +++ b/fwd.c @@ -26,6 +26,8 @@ #include "passt.h" #include "lineread.h" #include "flow_table.h" +#include "arp.h" +#include "ndp.h"
/* Empheral port range: values from RFC 6335 */ static in_port_t fwd_ephemeral_min = (1 << 15) + (1 << 14); @@ -131,6 +133,12 @@ void fwd_neigh_table_update(const struct ctx *c,
memcpy(&e->addr, addr, sizeof(*addr)); memcpy(e->mac, mac, ETH_ALEN); + + /* Send gratuitous ARP / unsolicited NA for the new mapping */ + if (inany_v4(addr)) + arp_send_gratuitous(c, inany_v4(addr), e->mac); + else + ndp_send_unsolicited_na(c, &addr->a6); }
/** diff --git a/ndp.c b/ndp.c index 588b48f..d7f64a3 100644 --- a/ndp.c +++ b/ndp.c @@ -218,6 +218,16 @@ static void ndp_na(const struct ctx *c, const struct in6_addr *dst, ndp_send(c, dst, &na, sizeof(na)); }
+/** + * ndp_send_unsolicited_na() - Send unsolicited NA + * @c: Execution context + * @addr: IPv6 address to advertise + */ +void ndp_send_unsolicited_na(const struct ctx *c, const struct in6_addr *addr) +{ + ndp_na(c, &in6addr_ll_all_nodes, addr); +} + /** * ndp_ra() - Send an NDP Router Advertisement (RA) message * @c: Execution context diff --git a/ndp.h b/ndp.h index 781ea86..320009c 100644 --- a/ndp.h +++ b/ndp.h @@ -12,5 +12,6 @@ int ndp(const struct ctx *c, const struct in6_addr *saddr, struct iov_tail *data); void ndp_timer(const struct ctx *c, const struct timespec *now); void ndp_send_init_req(const struct ctx *c); +void ndp_send_unsolicited_na(const struct ctx *c, const struct in6_addr *addr);
#endif /* NDP_H */ -- 2.50.1
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson