public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: David Gibson <david@gibson.dropbear.id.au>
To: Jon Maloy <jmaloy@redhat.com>
Cc: sbrivio@redhat.com, dgibson@redhat.com, passt-dev@passt.top
Subject: Re: [PATCH v9 9/9] arp/ndp: send gratuitous ARP / unsolicitated NA when MAC cache entry added
Date: Wed, 24 Sep 2025 13:22:38 +1000	[thread overview]
Message-ID: <aNNj_gE_o_xCJGrQ@zatzit> (raw)
In-Reply-To: <20250924011330.1168921-10-jmaloy@redhat.com>

[-- Attachment #1: Type: text/plain, Size: 6373 bytes --]

On Tue, Sep 23, 2025 at 09:13:30PM -0400, Jon Maloy wrote:
> Gratuitious ARP and unsolicitated NA 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.

So, I think you're right that the gratuitous ARP is safe in this case.

But it concerns me that (other that some edge cases) we're sending
data to the guest under own_tap_mac before we get the real MAC.  At
the point we send data from a flow to the guest, I would have expected
to already have an entry in the host neighbour table (because by
definition the host is talking to the peer), therefore in our cache,
by the subscriber.

I'm wondering if it could be as simple as both the neighbour update
and the actual data coming in the same epoll batch, and we could avoid
the temporary use of own_tap_mac by prioritising processing of the
neighbour events.

> 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. 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>
> ---
>  arp.c | 39 +++++++++++++++++++++++++++++++++++++++
>  arp.h |  2 ++
>  fwd.c | 11 +++++++++++
>  ndp.c | 10 ++++++++++
>  ndp.h |  1 +
>  5 files changed, 63 insertions(+)
> 
> diff --git a/arp.c b/arp.c
> index 442faff..259f736 100644
> --- a/arp.c
> +++ b/arp.c
> @@ -151,3 +151,42 @@ 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
> + * @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__)) req;

'req' is not a great name, since this is an ARP response, not a
request (but see below).

> +	/* Ethernet header */
> +	req.eh.h_proto = htons(ETH_P_ARP);
> +	memcpy(req.eh.h_dest, MAC_BROADCAST, sizeof(req.eh.h_dest));
> +	memcpy(req.eh.h_source, c->our_tap_mac, sizeof(req.eh.h_source));
> +
> +	/* ARP header */
> +	req.ah.ar_op = htons(ARPOP_REPLY);
> +	req.ah.ar_hrd = htons(ARPHRD_ETHER);
> +	req.ah.ar_pro = htons(ETH_P_IP);
> +	req.ah.ar_hln = ETH_ALEN;
> +	req.ah.ar_pln = 4;
> +
> +	/* ARP message */
> +	memcpy(req.am.sha, mac, sizeof(req.am.sha));
> +	memcpy(req.am.sip, &ip, sizeof(req.am.sip));
> +	memcpy(req.am.tha, MAC_BROADCAST, sizeof(req.am.tha));
> +	memcpy(req.am.tip, &ip, sizeof(req.am.tip));

So, I was trying to check if it made sense to use the same IP for both
source and target here, and came across
    https://www.rfc-editor.org/rfc/rfc5227#section-3

Which suggests we should (counter intuitively) be using ARP requests,
not ARP replies for announcements.

> +	inet_ntop(AF_INET, &ip, ip_str, sizeof(ip_str));
> +	debug("Sending gratuitous ARP for %s", ip_str);
> +	tap_send_single(c, &req, sizeof(req));
> +}
> diff --git a/arp.h b/arp.h
> index d5ad0e1..b0dbb56 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 c6348ab..879a351 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);
> @@ -129,6 +131,15 @@ void fwd_neigh_mac_cache_alloc(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 */

AFAICT this doesn't actually implement what the commit message
describes - it seems to always send an ARP/NA when the neighbour table
is updated.

> +	if (inany_v4(addr)) {
> +		struct in_addr ip4 = *inany_v4(addr);
> +
> +		arp_send_gratuitous(c, ip4, e->mac);
> +	} else {
> +		ndp_send_unsolicited_na(c, &addr->a6);
> +	}
>  }
>  
>  /**
> diff --git a/ndp.c b/ndp.c
> index 70b68aa..8914f31 100644
> --- a/ndp.c
> +++ b/ndp.c
> @@ -226,6 +226,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

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

  reply	other threads:[~2025-09-24  3:34 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-09-24  1:13 [PATCH v9 0/9] Use true MAC address of LAN local remote hosts Jon Maloy
2025-09-24  1:13 ` [PATCH v9 1/9] netlink: add subsciption on changes in NDP/ARP table Jon Maloy
2025-09-24  2:47   ` David Gibson
2025-09-24  3:34     ` David Gibson
2025-09-24 18:40     ` Jon Maloy
2025-09-25  6:42       ` David Gibson
2025-09-24  1:13 ` [PATCH v9 2/9] fwd: Add cache table for ARP/NDP contents Jon Maloy
2025-09-24  3:03   ` David Gibson
2025-09-24 18:54     ` Jon Maloy
2025-09-24  1:13 ` [PATCH v9 3/9] arp/ndp: respond with true MAC address of LAN local remote hosts Jon Maloy
2025-09-24  1:13 ` [PATCH v9 4/9] flow: add MAC address of LAN local remote hosts to flow Jon Maloy
2025-09-24  1:13 ` [PATCH v9 5/9] udp: forward external source MAC address through tap interface Jon Maloy
2025-09-24  1:13 ` [PATCH v9 6/9] tcp: " Jon Maloy
2025-09-24  1:13 ` [PATCH v9 7/9] tap: change signature of function tap_push_l2h() Jon Maloy
2025-09-24  1:13 ` [PATCH v9 8/9] icmp: let icmp use mac address from flowside structure Jon Maloy
2025-09-24  1:13 ` [PATCH v9 9/9] arp/ndp: send gratuitous ARP / unsolicitated NA when MAC cache entry added Jon Maloy
2025-09-24  3:22   ` David Gibson [this message]
2025-09-24 22:18     ` Jon Maloy
2025-09-24 23:32       ` Jon Maloy
2025-09-25  6:38         ` David Gibson
2025-09-25 12:48           ` Jon Maloy
2025-09-26  0:47             ` David Gibson
2025-09-26 22:59               ` Jon Maloy
2025-09-29  4:03                 ` David Gibson
2025-09-25  6:36       ` David Gibson
2025-09-25 13:14         ` Jon Maloy
2025-09-26  0:55           ` David Gibson
2025-09-26 23:05             ` Jon Maloy
2025-09-29  4:04               ` David Gibson
2025-09-26 23:25     ` Jon Maloy
2025-09-27 19:32       ` Jon Maloy
2025-09-29  4:08         ` David Gibson
2025-09-29 22:23           ` Stefano Brivio
2025-09-30  0:15             ` David Gibson

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=aNNj_gE_o_xCJGrQ@zatzit \
    --to=david@gibson.dropbear.id.au \
    --cc=dgibson@redhat.com \
    --cc=jmaloy@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).