On Thu, Sep 11, 2025 at 10:19:05PM +0200, Volker Diels-Grabsch wrote: 11;rgb:ffff/ffff/ffff> 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 > --- > v4: > - Shorten debug() messages > - Split out unrelated improvements into separate patches > --- > arp.c | 34 ++++++++++++++++++++++++++++++++++ > arp.h | 1 + > ndp.c | 20 ++++++++++++++++++++ > ndp.h | 1 + > passt.1 | 4 ++-- > tap.c | 5 +++++ > 6 files changed, 63 insertions(+), 2 deletions(-) > > diff --git a/arp.c b/arp.c > index 44677ad..ad088b1 100644 > --- a/arp.c > +++ b/arp.c > @@ -112,3 +112,37 @@ int arp(const struct ctx *c, struct iov_tail *data) > > return 1; > } > + > +/** > + * arp_send_init_req() - Send initial ARP request to retrieve guest MAC address > + * @c: Execution context > + */ > +void arp_send_init_req(const struct ctx *c) > +{ > + struct { > + struct ethhdr eh; > + struct arphdr ah; > + struct arpmsg am; > + } __attribute__((__packed__)) req; > + > + /* 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_REQUEST); > + 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, c->our_tap_mac, sizeof(req.am.sha)); > + memcpy(req.am.sip, &c->ip4.our_tap_addr, sizeof(req.am.sip)); > + memcpy(req.am.tha, MAC_BROADCAST, sizeof(req.am.tha)); > + memcpy(req.am.tip, &c->ip4.addr, sizeof(req.am.tip)); > + > + debug("Sending initial ARP request for guest MAC address"); > + tap_send_single(c, &req, sizeof(req)); > +} > diff --git a/arp.h b/arp.h > index 86bcbf8..d5ad0e1 100644 > --- a/arp.h > +++ b/arp.h > @@ -21,5 +21,6 @@ struct arpmsg { > } __attribute__((__packed__)); > > int arp(const struct ctx *c, struct iov_tail *data); > +void arp_send_init_req(const struct ctx *c); > > #endif /* ARP_H */ > diff --git a/ndp.c b/ndp.c > index eb090cd..588b48f 100644 > --- a/ndp.c > +++ b/ndp.c > @@ -438,3 +438,23 @@ void ndp_timer(const struct ctx *c, const struct timespec *now) > first: > next_ra = now->tv_sec + interval; > } > + > +/** > + * ndp_send_init_req() - Send initial NDP NS to retrieve guest MAC address > + * @c: Execution context > + */ > +void ndp_send_init_req(const struct ctx *c) > +{ > + struct ndp_ns ns = { > + .ih = { > + .icmp6_type = NS, > + .icmp6_code = 0, > + .icmp6_router = 0, /* Reserved */ > + .icmp6_solicited = 0, /* Reserved */ > + .icmp6_override = 0, /* Reserved */ > + }, > + .target_addr = c->ip6.addr > + }; > + debug("Sending initial NDP NS request for guest MAC address"); > + ndp_send(c, &c->ip6.addr, &ns, sizeof(ns)); > +} > diff --git a/ndp.h b/ndp.h > index b1dd5e8..781ea86 100644 > --- a/ndp.h > +++ b/ndp.h > @@ -11,5 +11,6 @@ struct icmp6hdr; > 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); > > #endif /* NDP_H */ > diff --git a/passt.1 b/passt.1 > index dd00b08..af5726a 100644 > --- a/passt.1 > +++ b/passt.1 > @@ -330,8 +330,8 @@ selected IPv4 default route. > > .TP > .BR \-\-no-ndp > -Disable NDP responses. NDP messages coming from guest or target namespace will > -be ignored. > +Disable Neighbor Discovery. NDP messages coming from guest or target > +namespace will be ignored. No initial NDP message will be sent. > > .TP > .BR \-\-no-dhcpv6 > diff --git a/tap.c b/tap.c > index ba6d7b4..2a8e11b 100644 > --- a/tap.c > +++ b/tap.c > @@ -1359,6 +1359,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); > + > + if (c->ifi4) > + arp_send_init_req(c); > + if (c->ifi6 && !c->no_ndp) > + ndp_send_init_req(c); > } > > /** > -- > 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