From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: passt.top; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=dqlwB4IE; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by passt.top (Postfix) with ESMTPS id 8253B5A061B for ; Sun, 12 Oct 2025 21:33:52 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1760297631; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=gK/TSF2FZRybgSkUIv25rs0ezXa2eHe0N2I0bziGgAI=; b=dqlwB4IE+dVwR2RltoQmy0kwnah6BH9L01usrGTKddDK3As07hLaM/2f/FZYcxtsxKg4CP tyEhc2xRS1zrzzGZUICHX45Yd094TM9UPqrsoOzw+fCU8ukMOet1BjZHqBG0l88v378V/H m/RmDFU/VdIDWWhDHjdQoJ+DSH7rTDQ= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-470-gSTHL_UwN_qqyE4au425ZA-1; Sun, 12 Oct 2025 15:33:50 -0400 X-MC-Unique: gSTHL_UwN_qqyE4au425ZA-1 X-Mimecast-MFC-AGG-ID: gSTHL_UwN_qqyE4au425ZA_1760297629 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 6F69818004D4; Sun, 12 Oct 2025 19:33:49 +0000 (UTC) Received: from jmaloy-thinkpadp16vgen1.rmtcaqc.csb (unknown [10.22.80.81]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 8C8FB18004D8; Sun, 12 Oct 2025 19:33:47 +0000 (UTC) From: Jon Maloy To: sbrivio@redhat.com, dgibson@redhat.com, david@gibson.dropbear.id.au, jmaloy@redhat.com, passt-dev@passt.top Subject: [PATCH v13 05/10] arp/ndp: send ARP announcement / unsolicited NA when neigbour entry added Date: Sun, 12 Oct 2025 15:33:32 -0400 Message-ID: <20251012193337.616835-6-jmaloy@redhat.com> In-Reply-To: <20251012193337.616835-1-jmaloy@redhat.com> References: <20251012193337.616835-1-jmaloy@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: VUxdlIx0lnNNeUfs6Nx9ze3h7vEWLxNCQ5STUju8OjU_1760297629 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true Message-ID-Hash: X5GST5WDK4L3MWXK66735NKEU25437YC X-Message-ID-Hash: X5GST5WDK4L3MWXK66735NKEU25437YC X-MailFrom: jmaloy@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: 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 --- 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() --- arp.c | 42 ++++++++++++++++++++++++++++++++++++++++++ arp.h | 2 ++ fwd.c | 10 ++++++++++ ndp.c | 10 ++++++++++ ndp.h | 1 + 5 files changed, 65 insertions(+) diff --git a/arp.c b/arp.c index b4a686f..18fe1c7 100644 --- a/arp.c +++ b/arp.c @@ -150,3 +150,45 @@ 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; + + /* 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_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 d062ebc..3a61901 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); @@ -145,6 +147,14 @@ void fwd_neigh_table_update(const struct ctx *c, const union inany_addr *addr, memcpy(&e->addr, &daddr, sizeof(daddr)); memcpy(e->mac, mac, ETH_ALEN); e->permanent = permanent; + + if (!memcmp(mac, c->our_tap_mac, ETH_ALEN)) + return; + + if (inany_v4(&daddr)) + arp_announce(c, inany_v4(&daddr), e->mac); + else + ndp_unsolicited_na(c, &daddr.a6); } /** diff --git a/ndp.c b/ndp.c index 7cc6e48..1e41933 100644 --- a/ndp.c +++ b/ndp.c @@ -222,6 +222,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