/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 dated June, 1991, or (at your option) version 3 dated 29 June, 2007. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "dnsmasq.h" /* Time between forced re-loads from kernel. */ #define INTERVAL 90 #define ARP_MARK 0 #define ARP_FOUND 1 /* Confirmed */ #define ARP_NEW 2 /* Newly created */ #define ARP_EMPTY 3 /* No MAC addr */ struct arp_record { unsigned short hwlen, status; int family; unsigned char hwaddr[DHCP_CHADDR_MAX]; union all_addr addr; struct arp_record *next; }; static struct arp_record *arps = NULL, *old = NULL, *freelist = NULL; static time_t last = 0; static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) { struct arp_record *arp; (void)parmv; if (maclen > DHCP_CHADDR_MAX) return 1; /* Look for existing entry */ for (arp = arps; arp; arp = arp->next) { if (family != arp->family || arp->status == ARP_NEW) continue; if (family == AF_INET) { if (arp->addr.addr4.s_addr != ((struct in_addr *)addrp)->s_addr) continue; } else { if (!IN6_ARE_ADDR_EQUAL(&arp->addr.addr6, (struct in6_addr *)addrp)) continue; } if (arp->status == ARP_EMPTY) { /* existing address, was negative. */ arp->status = ARP_NEW; arp->hwlen = maclen; memcpy(arp->hwaddr, mac, maclen); } else if (arp->hwlen == maclen && memcmp(arp->hwaddr, mac, maclen) == 0) /* Existing entry matches - confirm. */ arp->status = ARP_FOUND; else continue; break; } if (!arp) { /* New entry */ if (freelist) { arp = freelist; freelist = freelist->next; } else if (!(arp = whine_malloc(sizeof(struct arp_record)))) return 1; arp->next = arps; arps = arp; arp->status = ARP_NEW; arp->hwlen = maclen; arp->family = family; memcpy(arp->hwaddr, mac, maclen); if (family == AF_INET) arp->addr.addr4.s_addr = ((struct in_addr *)addrp)->s_addr; else memcpy(&arp->addr.addr6, addrp, IN6ADDRSZ); } return 1; } /* If in lazy mode, we cache absence of ARP entries. */ int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now) { struct arp_record *arp, *tmp, **up; int updated = 0; again: /* If the database is less then INTERVAL old, look in there */ if (difftime(now, last) < INTERVAL) { /* addr == NULL -> just make cache up-to-date */ if (!addr) return 0; for (arp = arps; arp; arp = arp->next) { if (addr->sa.sa_family != arp->family) continue; if (arp->family == AF_INET && arp->addr.addr4.s_addr != addr->in.sin_addr.s_addr) continue; if (arp->family == AF_INET6 && !IN6_ARE_ADDR_EQUAL(&arp->addr.addr6, &addr->in6.sin6_addr)) continue; /* Only accept positive entries unless in lazy mode. */ if (arp->status != ARP_EMPTY || lazy || updated) { if (mac && arp->hwlen != 0) memcpy(mac, arp->hwaddr, arp->hwlen); return arp->hwlen; } } } /* Not found, try the kernel */ if (!updated) { updated = 1; last = now; /* Mark all non-negative entries */ for (arp = arps; arp; arp = arp->next) if (arp->status != ARP_EMPTY) arp->status = ARP_MARK; iface_enumerate(AF_UNSPEC, NULL, filter_mac); /* Remove all unconfirmed entries to old list. */ for (arp = arps, up = &arps; arp; arp = tmp) { tmp = arp->next; if (arp->status == ARP_MARK) { *up = arp->next; arp->next = old; old = arp; } else up = &arp->next; } goto again; } /* record failure, so we don't consult the kernel each time we're asked for this address */ if (freelist) { arp = freelist; freelist = freelist->next; } else arp = whine_malloc(sizeof(struct arp_record)); if (arp) { arp->next = arps; arps = arp; arp->status = ARP_EMPTY; arp->family = addr->sa.sa_family; arp->hwlen = 0; if (addr->sa.sa_family == AF_INET) arp->addr.addr4.s_addr = addr->in.sin_addr.s_addr; else memcpy(&arp->addr.addr6, &addr->in6.sin6_addr, IN6ADDRSZ); } return 0; } int do_arp_script_run(void) { struct arp_record *arp; /* Notify any which went, then move to free list */ if (old) { #ifdef HAVE_SCRIPT if (option_bool(OPT_SCRIPT_ARP)) queue_arp(ACTION_ARP_DEL, old->hwaddr, old->hwlen, old->family, &old->addr); #endif arp = old; old = arp->next; arp->next = freelist; freelist = arp; return 1; } for (arp = arps; arp; arp = arp->next) if (arp->status == ARP_NEW) { #ifdef HAVE_SCRIPT if (option_bool(OPT_SCRIPT_ARP)) queue_arp(ACTION_ARP, arp->hwaddr, arp->hwlen, arp->family, &arp->addr); #endif arp->status = ARP_FOUND; return 1; } return 0; }