On Tue, Sep 09, 2025 at 04:49:20PM +0200, Volker Diels-Grabsch wrote:
When restarting passt while QEMU keeps running with a configured "reconnect-ms" setting, the port forwardings will stop working until the guest sends some outgoing network traffic.
Reason: Although QEMU reconnects successfully to the unix domain socket of the new passt process, that one no longer knows the guest's MAC address and uses instead the broadcast MAC address. However, this is ignored by the guest, at least if the guest runs Linux. Only after the guest sends some network package on its own initiative, passt will know the MAC address and will be able to establish forwarded connections.
This change fixes this issue by sending an ARP and an NDP request to resolve the guest's MAC address via its IPv4 and IPv6 address, which we do know, right after the unix domain socket (re)connection.
The only case where the IP is "wrong" would be if the configuration changed, or on the very first start right after qemu started. But in those cases, we just wouldn't get an ARP/NDP response, and can't do anything until we receive the guest's DHCP request - just as before. In other words, in the worst case the ARP/NDP requests would be harmless.
Signed-off-by: Volker Diels-Grabsch
Reviewed-by: David Gibson
diff --git a/tap.c b/tap.c index 7ba6399..ea61eae 100644 --- a/tap.c +++ b/tap.c @@ -1088,6 +1088,7 @@ void tap_add_packet(struct ctx *c, struct iov_tail *data, { struct ethhdr eh_storage; const struct ethhdr *eh; + char bufmac[ETH_ADDRSTRLEN];
We'd generally prefer to move this local to the if block where it's used.
pcap_iov(data->iov, data->cnt, data->off);
@@ -1097,6 +1098,7 @@ void tap_add_packet(struct ctx *c, struct iov_tail *data,
if (memcmp(c->guest_mac, eh->h_source, ETH_ALEN)) { memcpy(c->guest_mac, eh->h_source, ETH_ALEN); + info("Guest MAC address: %s", eth_ntop(c->guest_mac, bufmac, sizeof(bufmac))); proto_update_l2_buf(c->guest_mac, NULL); }
@@ -1355,6 +1357,11 @@ static void tap_start_connection(const struct ctx *c) ev.events = EPOLLIN | EPOLLRDHUP; ev.data.u64 = ref.u64; epoll_ctl(c->epollfd, EPOLL_CTL_ADD, c->fd_tap, &ev); + + info("Sending initial ARP and NDP request to retrieve" + " guest MAC address after reconnect");
I think it's going to be rare that we care about this, so I'd demote it to a debug().
+ arp_send_init_req(c); + ndp_send_init_req(c); }
/** @@ -1503,11 +1510,12 @@ void tap_backend_init(struct ctx *c) case MODE_PASST: tap_sock_unix_init(c);
- /* In passt mode, we don't know the guest's MAC address until it - * sends us packets. Use the broadcast address so that our - * first packets will reach it. + /* In passt mode, we don't know the guest's MAC address until + * it sends us packets (e.g. responds to our initial ARP or + * NDP request). Until then, use the broadcast address so + * that our first packets will have a chance to reach it. */ - memset(&c->guest_mac, 0xff, sizeof(c->guest_mac)); + memcpy(&c->guest_mac, MAC_BROADCAST, sizeof(c->guest_mac)); break; }
diff --git a/util.h b/util.h index 2a8c38f..3719f0c 100644 --- a/util.h +++ b/util.h @@ -97,6 +97,7 @@ void abort_with_msg(const char *fmt, ...) #define FD_PROTO(x, proto) \ (IN_INTERVAL(c->proto.fd_min, c->proto.fd_max, (x)))
+#define MAC_BROADCAST ((uint8_t [ETH_ALEN]){ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }) #define MAC_ZERO ((uint8_t [ETH_ALEN]){ 0 }) #define MAC_IS_ZERO(addr) (!memcmp((addr), MAC_ZERO, ETH_ALEN))
-- 2.47.3
-- 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