public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: Jon Maloy <jmaloy@redhat.com>
To: sbrivio@redhat.com, dgibson@redhat.com,
	david@gibson.dropbear.id.au, jmaloy@redhat.com,
	passt-dev@passt.top
Subject: [PATCH v14 05/10] arp/ndp: send ARP announcement / unsolicited NA when neigbour entry added
Date: Tue, 14 Oct 2025 22:55:16 -0400	[thread overview]
Message-ID: <20251015025521.1449156-6-jmaloy@redhat.com> (raw)
In-Reply-To: <20251015025521.1449156-1-jmaloy@redhat.com>

ARP announcements and unsolicited NAs should be handled with caution
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 shortly later asks for a confirmation in the form of
a regular ARP/NS request. 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 <jmaloy@redhat.com>

---
v10: -Made small changes based of feedback from David G.
v11: -Moved from 'Gratuitous ARP reply' model to 'ARP Announcement'
      model.
v12: -Excluding loopback and default GW addresses from the ARP/NA
      announcement to be sent to the guest
v13: -Filtering out all announcements of our_tap_mac instead of
      explicitly comparing a set of IP addresses.
     -Changed annc.am.tha in arp_announce() to MAC_ZERO, which is
      'unknown' according to RFC826.
     -Renamed ndp_send_unsolicited_na() to ndp_unsolicited_na()
v14:  -Added quotes from RFC5227 to explain the format of ARP
       announce messages, as suggested by David.
---
 arp.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 arp.h |  2 ++
 fwd.c | 10 ++++++++++
 ndp.c | 10 ++++++++++
 ndp.h |  1 +
 5 files changed, 73 insertions(+)

diff --git a/arp.c b/arp.c
index b4a686f..0a89ae3 100644
--- a/arp.c
+++ b/arp.c
@@ -150,3 +150,53 @@ 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_announce() - Send an ARP announcement for an IPv4 host
+ * @c:		Execution context
+ * @ip:	IPv4 address we announce as owned by @mac
+ * @mac:	MAC address to advertise for @ip
+ */
+void arp_announce(const struct ctx *c, struct in_addr *ip,
+		  const unsigned char *mac)
+{
+	char ip_str[INET_ADDRSTRLEN];
+	char mac_str[ETH_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;
+
+	/* RFC5227, section 2.1.1, about Probe messages: "The client MUST fill
+	 * in the 'sender hardware address' field of the ARP Request with the
+	 * hardware address of the interface through which it is sending the
+	 * packet. [...] The 'target hardware address' field is ignored and
+	 * SHOULD be set to all zeroes."
+	 * RFC5227, section 2.3: "An ARP Announcement is identical to the ARP
+	 * Probe described above, except that now the sender and target IP
+	 * addresses are both set to the host's newly selected IPv4 address."
+	 */
+	memcpy(annc.am.sha, mac, sizeof(annc.am.sha));
+	memcpy(annc.am.sip, ip, sizeof(annc.am.sip));
+	memcpy(annc.am.tha, MAC_ZERO, sizeof(annc.am.tha));
+	memcpy(annc.am.tip, ip, sizeof(annc.am.tip));
+
+	inet_ntop(AF_INET, ip, ip_str, sizeof(ip_str));
+	eth_ntop(mac, mac_str, sizeof(mac_str));
+	debug("Announcing ARP for %s / %s", ip_str, mac_str);
+
+	tap_send_single(c, &annc, sizeof(annc));
+}
diff --git a/arp.h b/arp.h
index d5ad0e1..4862e90 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_announce(const struct ctx *c, struct in_addr *ip,
+		  const unsigned char *mac);
 
 #endif /* ARP_H */
diff --git a/fwd.c b/fwd.c
index f70e4fc..df92db8 100644
--- a/fwd.c
+++ b/fwd.c
@@ -27,6 +27,8 @@
 #include "lineread.h"
 #include "flow_table.h"
 #include "netlink.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);
@@ -140,6 +142,14 @@ void fwd_neigh_table_update(const struct ctx *c, const union inany_addr *addr,
 	memcpy(&e->addr, addr, sizeof(*addr));
 	memcpy(e->mac, mac, ETH_ALEN);
 	e->permanent = permanent;
+
+	if (!memcmp(mac, c->our_tap_mac, ETH_ALEN))
+		return;
+
+	if (inany_v4(addr))
+		arp_announce(c, inany_v4(addr), e->mac);
+	else
+		ndp_unsolicited_na(c, &addr->a6);
 }
 
 /**
diff --git a/ndp.c b/ndp.c
index 7e2ae2a..430d420 100644
--- a/ndp.c
+++ b/ndp.c
@@ -220,6 +220,16 @@ static void ndp_na(const struct ctx *c, const struct in6_addr *dst,
 	ndp_send(c, dst, &na, sizeof(na));
 }
 
+/**
+ * ndp_unsolicited_na() - Send unsolicited NA
+ * @c:         Execution context
+ * @addr:      IPv6 address to advertise
+ */
+void ndp_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..56b756d 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_unsolicited_na(const struct ctx *c, const struct in6_addr *addr);
 
 #endif /* NDP_H */
-- 
2.50.1


  parent reply	other threads:[~2025-10-15  2:55 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-10-15  2:55 [PATCH v14 00/10] Use true MAC address of LAN local remote hosts Jon Maloy
2025-10-15  2:55 ` [PATCH v14 01/10] netlink: add subscription on changes in NDP/ARP table Jon Maloy
2025-10-17  2:36   ` David Gibson
2025-10-19 10:07   ` Stefano Brivio
2025-10-20  0:17     ` David Gibson
2025-10-15  2:55 ` [PATCH v14 02/10] passt: add no_map_gw flag to struct ctx Jon Maloy
2025-10-19 10:07   ` Stefano Brivio
2025-10-15  2:55 ` [PATCH v14 03/10] fwd: Add cache table for ARP/NDP contents Jon Maloy
2025-10-17  3:05   ` David Gibson
2025-10-17 18:49     ` Jon Maloy
2025-10-20  0:06       ` David Gibson
2025-10-20 10:00         ` Jon Maloy
2025-10-22  1:20           ` David Gibson
2025-10-19 10:07   ` Stefano Brivio
2025-10-15  2:55 ` [PATCH v14 04/10] arp/ndp: respond with true MAC address of LAN local remote hosts Jon Maloy
2025-10-17  3:06   ` David Gibson
2025-10-15  2:55 ` Jon Maloy [this message]
2025-10-17  3:08   ` [PATCH v14 05/10] arp/ndp: send ARP announcement / unsolicited NA when neigbour entry added David Gibson
2025-10-19 10:08   ` Stefano Brivio
2025-10-15  2:55 ` [PATCH v14 06/10] flow: add MAC address of LAN local remote hosts to flow Jon Maloy
2025-10-15  2:55 ` [PATCH v14 07/10] udp: forward external source MAC address through tap interface Jon Maloy
2025-10-15  2:55 ` [PATCH v14 08/10] tcp: " Jon Maloy
2025-10-15  2:55 ` [PATCH v14 09/10] tap: change signature of function tap_push_l2h() Jon Maloy
2025-10-15  2:55 ` [PATCH v14 10/10] icmp: let icmp use mac address from flowside structure Jon Maloy
2025-10-19 10:08   ` Stefano Brivio

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20251015025521.1449156-6-jmaloy@redhat.com \
    --to=jmaloy@redhat.com \
    --cc=david@gibson.dropbear.id.au \
    --cc=dgibson@redhat.com \
    --cc=passt-dev@passt.top \
    --cc=sbrivio@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).