[PATCH v15 0/9] Use true MAC address of LAN local remote hosts
Bug #120 asks us to use the true MAC addresses of LAN local remote hosts, since some programs need this information. These commits introduces this for ARP, NDP, UDP, TCP and ICMP. --- v3: Updated according to feedback from Stefano and David: - Made the ARP/NDP lookup call filter out the requested address by itself, qualified by the index if the template interface - Moved the flow specific MAC address from struct flowside to struct flow_common. v4: - Updated according to feedback from David and Stefan - Added a cache table for ARP/NDP table contents v5: - Updated according to feedback from David and Stefan - Added cache table entries to FIFO/LRU queue - New criteria for when to consult ARP/NDP v6: - Simplified and merged mac cache table commits - Other changes after feedback from David. v7: - Fixes in patch #2 based on feedback from David and Stefano. v8: - Redesigned netlink and cache table part to be based on a subscription model. v8: - Small fix to patch #2 so that we cover the case when a MAC addess for a host has changed. - Added a commit where we send a gratuitous ARP/ unsolicitated NA to the guest when a new host is added to the neighbour cache table. v10: - Some fixes after feedback from David Gibson - Reordered: Moved patch #9 to position #3. - Added synchronization step between ARP/NDP table contents and the neigbour table at initialization. This reduces the number of "false" ARP/NDP replies drastically, but not completly. - (Next step could be to scan over the flow table and update affeced entries when we receive a MAC address update.) v11: - Corrected the gratuitous ARP implementation to use the "ARP Announcement" model instead of the "Gratuitous ARP reply" model. v12: - Updated based on feedback from David and Stefano - Added special handling of default GW and loopback addresses. v13: - Updated based on discussion with David and Stefano - Conceptually moved to only considering guest-side visible addresss. A lot of things became simpler and clearer through this change. Thank you, David. - Introduced a 'permanent' flag in the special entries representing addessed mapping to own host and conditionally the guest gw. This flag indicates those entries cannot be altered by possible remote hosts shadowed by these addresses. Suggested by Stefano. - Reordered patch ##4 and 5, since #5 cannot work correctly for NDP unsolicited NA until #4 is in place. - Added a new commit #2 to get later access to the flag no_map_gw. It was wrong to call fwd_neigh_table_init() from inside conf(), it has to be done in main() after random_init() and tap_backend_init(). v14: - Some fixes after feedback from David Gibson, notably - Moved the call to nat_inbound() from fwd.c to netlink.c - Added RFC quotes to explain the format of ARP announce messages. v15: - More fixes, after feedback and discussions. Notably: - Added a secondary lookup in fwd_neigh_mac_get(), so that we respond with the guest GW's MAC address in case the primary lookup fails. - Removed guest_gw IP address as 'blocker' entry in the neighbor table. It is ok to let the host GW MAC address go through if the GW addresses match, and even when they don't we found it isn't needed. - Removed previous patch #2 about no_map_gw, since the change it introduced only was needed by the the functionality we just removed as described above. Jon Maloy (9): netlink: add subscription on changes in NDP/ARP table fwd: Add cache table for ARP/NDP contents arp/ndp: respond with true MAC address of LAN local remote hosts arp/ndp: send ARP announcement / unsolicited NA when neigbour entry added flow: add MAC address of LAN local remote hosts to flow udp: forward external source MAC address through tap interface tcp: forward external source MAC address through tap interface tap: change signature of function tap_push_l2h() icmp: let icmp use mac address from flowside structure arp.c | 59 +++++++++++++- arp.h | 2 + epoll_type.h | 2 + flow.c | 2 + flow.h | 2 + fwd.c | 212 +++++++++++++++++++++++++++++++++++++++++++++++++ fwd.h | 7 ++ icmp.c | 8 +- inany.c | 1 + ndp.c | 16 +++- ndp.h | 1 + netlink.c | 208 +++++++++++++++++++++++++++++++++++++++++++++++- netlink.h | 4 + passt.c | 17 ++-- passt.h | 3 +- pasta.c | 2 +- tap.c | 24 +++--- tap.h | 7 +- tcp.c | 20 ++++- tcp.h | 2 +- tcp_buf.c | 37 ++++----- tcp_internal.h | 4 +- tcp_vu.c | 5 +- udp.c | 57 ++++++++----- udp.h | 2 +- util.h | 2 + 26 files changed, 623 insertions(+), 83 deletions(-) -- 2.50.1
When we receive an ARP request or NDP neigbour solicitation over
the tap interface for a host on the local network segment attached
to the template interface, we respond with that host's real MAC
address, if available.
Signed-off-by: Jon Maloy
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
When communicating with remote hosts on the local network, some guest
applications want to see the real MAC address of that host instead
of PASST/PASTA's own tap address. The flow_common structure is a
convenient location for storing that address, so we do that in this
commit.
Note that we don´t add actual usage of this address here, that will
be done in later commits.
Signed-off-by: Jon Maloy
In the next commit it must be possible for the callers of function
tap_push_l2h() to specify which source MAC address should be
added to the ethernet header sent over the tap interface. As a
preparation, we now add a new argument to that function, still
without any logical changes.
Signed-off-by: Jon Maloy
We forward the incoming MAC address through the tap interface when
receiving incoming packets from network local hosts.
This is a part of the solution to bug
https://bugs.passt.top/show_bug.cgi?id=120
Signed-off-by: Jon Maloy
We forward the incoming mac address through the tap interface when
receiving incoming packets from network local hosts.
This is a part of the solution to bug
https://bugs.passt.top/show_bug.cgi?id=120
Signed-off-by: Jon Maloy
Even ICMP needs to be updated to use the external MAC address instead
of just the own tap address when applicable. We do that here.
Signed-off-by: Jon Maloy
On Thu, Oct 23, 2025 at 09:29:28PM -0400, Jon Maloy wrote:
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
Reviewed-by: David Gibson
On Thu, Oct 23, 2025 at 09:29:27PM -0400, Jon Maloy wrote:
When we receive an ARP request or NDP neigbour solicitation over the tap interface for a host on the local network segment attached to the template interface, we respond with that host's real MAC address, if available.
Signed-off-by: Jon Maloy
Reviewed-by: David Gibson
On Thu, 23 Oct 2025 21:29:28 -0400
Jon Maloy
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() v14: -Added quotes from RFC5227 to explain the format of ARP announce messages, as suggested by David. v15: -A couple of minor changes based on feedback from Stefano --- arp.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ arp.h | 2 ++ fwd.c | 10 ++++++++++ ndp.c | 10 ++++++++++ ndp.h | 1 + 5 files changed, 74 insertions(+)
diff --git a/arp.c b/arp.c index b4a686f..b0d2fc3 100644 --- a/arp.c +++ b/arp.c @@ -150,3 +150,54 @@ 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__)) msg; + + /* Ethernet header */ + msg.eh.h_proto = htons(ETH_P_ARP); + memcpy(msg.eh.h_dest, MAC_BROADCAST, sizeof(msg.eh.h_dest)); + memcpy(msg.eh.h_source, mac, sizeof(msg.eh.h_source)); + + /* ARP header */ + msg.ah.ar_op = htons(ARPOP_REQUEST); + msg.ah.ar_hrd = htons(ARPHRD_ETHER); + msg.ah.ar_pro = htons(ETH_P_IP); + msg.ah.ar_hln = ETH_ALEN; + msg.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(msg.am.sha, mac, sizeof(msg.am.sha)); + memcpy(msg.am.sip, ip, sizeof(msg.am.sip)); + memcpy(msg.am.tha, MAC_ZERO, sizeof(msg.am.tha)); + memcpy(msg.am.tip, ip, sizeof(msg.am.tip)); + + inet_ntop(AF_INET, ip, ip_str, sizeof(ip_str)); + eth_ntop(mac, mac_str, sizeof(mac_str)); + debug("ARP announcment for %s / %s", ip_str, mac_str);
Nit: announcement. I'll fix this on merge if there's nothing else that needs changes.
+ + tap_send_single(c, &msg, sizeof(msg)); +} 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 93d83e3..c1de4b8 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 */
-- Stefano
On Thu, 23 Oct 2025 21:29:24 -0400
Jon Maloy
Bug #120 asks us to use the true MAC addresses of LAN local remote hosts, since some programs need this information. These commits introduces this for ARP, NDP, UDP, TCP and ICMP.
Applied! With format modifier fixed in 1/9 and typo fix in 4/9 as discussed. -- Stefano
participants (3)
-
David Gibson
-
Jon Maloy
-
Stefano Brivio