From bdd43f3833c4dd749eaed3c3642de3988e5b8ea0 Mon Sep 17 00:00:00 2001 From: Nick Porter Date: Tue, 15 Jul 2025 10:54:19 +0100 Subject: [PATCH] Use netlink API to insert ARP entries on FreeBSD Comparable to how FreeBSD's arp command adds entries using netlink. --- src/listen/dhcpv4/proto_dhcpv4_udp.c | 25 +++++++- src/protocols/arp/arp.h | 4 ++ src/protocols/arp/base.c | 88 ++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/listen/dhcpv4/proto_dhcpv4_udp.c b/src/listen/dhcpv4/proto_dhcpv4_udp.c index 1d67c36f05..467e237e61 100644 --- a/src/listen/dhcpv4/proto_dhcpv4_udp.c +++ b/src/listen/dhcpv4/proto_dhcpv4_udp.c @@ -539,6 +539,29 @@ static ssize_t mod_write(fr_listen_t *li, void *packet_ctx, UNUSED fr_time_t req socket.inet.dst_ipaddr.addr.v4.s_addr = INADDR_BROADCAST; } #endif +#ifdef __FreeBSD__ + } else if (inst->broadcast && +#ifdef HAVE_LIBPCAP + !inst->use_pcap && +#endif + (inst->interface || if_name[0])) { + uint8_t macaddr[6]; + uint8_t ipaddr[4]; + + memcpy(&ipaddr, &packet->yiaddr, 4); + memcpy(&macaddr, &packet->chaddr, 6); + + /* + * FreeBSD version of above using netlink API + */ + if (fr_bsd_arp_entry_add(socket.inet.ifindex, ipaddr, macaddr) == 0) { + DEBUG("Reply will be unicast to YADDR, done ARP table updates."); + memcpy(&socket.inet.dst_ipaddr.addr.v4.s_addr, &packet->yiaddr, 4); + } else { + DEBUG("Failed adding ARP table entry. Reply will be broadcast."); + socket.inet.dst_ipaddr.addr.v4.s_addr = INADDR_BROADCAST; + } +#endif #ifdef HAVE_LIBPCAP } else if (inst->use_pcap && inst->broadcast && (inst->interface || if_name[0])) { proto_dhcpv4_pcap_t *pcap; @@ -889,7 +912,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) inst->port = ntohl(s->s_port); } -#ifdef SIOCSARP +#if defined(SIOCSARP) || defined(__FreeBSD__) /* * If we're listening for broadcast requests, we MUST */ diff --git a/src/protocols/arp/arp.h b/src/protocols/arp/arp.h index a7bcc2eea2..232796d502 100644 --- a/src/protocols/arp/arp.h +++ b/src/protocols/arp/arp.h @@ -45,6 +45,10 @@ ssize_t fr_arp_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *packe int fr_arp_entry_add(int fd, char const *interface, uint8_t ipaddr[static 4], uint8_t macaddr[static 6]); +#ifdef __FreeBSD__ +int fr_bsd_arp_entry_add(uint32_t ifindex, uint8_t ipaddr[static 4], uint8_t macaddr[static 6]); +#endif + /* * ARP for ethernet && IPv4. */ diff --git a/src/protocols/arp/base.c b/src/protocols/arp/base.c index f3b58faa30..e117e92627 100644 --- a/src/protocols/arp/base.c +++ b/src/protocols/arp/base.c @@ -38,6 +38,18 @@ RCSID("$Id$") #include +#ifdef __FreeBSD__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + static uint32_t instance_count = 0; fr_dict_t const *dict_arp; @@ -139,6 +151,82 @@ int fr_arp_entry_add(UNUSED int fd, UNUSED char const *interface, } #endif +#ifdef __FreeBSD__ +/** Use FreeBSD netlink API to add ARP entries + */ +int fr_bsd_arp_entry_add(uint32_t ifindex, uint8_t ipaddr[static 4], uint8_t macaddr[static 6]) +{ + struct sockaddr_in sin; + struct sockaddr_dl sdl; + struct snl_state state; + struct snl_writer nw; + struct nlmsghdr *msghdr; + struct ndmsg *msg; + struct snl_errmsg_data errmsg = {}; + + if (ifindex == 0) { + fr_strerror_const("Missing interface index"); + return -1; + } + + sin.sin_family = AF_INET; + memcpy(&sin.sin_addr.s_addr, ipaddr, 4); + + sdl = (struct sockaddr_dl){ + .sdl_len = sizeof(sdl), + .sdl_family = AF_LINK, + .sdl_alen = ETHER_ADDR_LEN + }; + memcpy(LLADDR(&sdl), macaddr, ETHER_ADDR_LEN); + + if (!snl_init(&state, NETLINK_ROUTE)) { + if (modfind("netlink") == -1 && errno == ENOENT) { + if (kldload("netlink") == -1) { + fr_strerror_const("netlink is not loaded and load attempt failed"); + return -1; + } + } else { + open_error: + fr_strerror_const("Unable to open netlink socket"); + } + if (!snl_init(&state, NETLINK_ROUTE)) goto open_error; + } + + snl_init_writer(&state, &nw); + msghdr = snl_create_msg_request(&nw, RTM_NEWNEIGH); + msghdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; + + msg = snl_reserve_msg_object(&nw, struct ndmsg); + if (!msg) { + fr_strerror_const("Failed reserving message"); + error: + snl_free(&state); + return -1; + } + msg->ndm_family = AF_INET; + msg->ndm_ifindex = ifindex; + msg->ndm_state = NUD_PERMANENT; + msg->ndm_flags = NTF_STICKY; + + snl_add_msg_attr_ip(&nw, NDA_DST, (struct sockaddr *)&sin); + snl_add_msg_attr(&nw, NDA_LLADDR, sdl.sdl_alen, LLADDR(&sdl)); + + if (!(msghdr = snl_finalize_msg(&nw)) || !snl_send_message(&state, msghdr)) { + fr_strerror_const("Failed sending netlink message."); + goto error; + } + + snl_read_reply_code(&state, msghdr->nlmsg_seq, &errmsg); + if (errmsg.error != 0) { + fr_strerror_printf("Failed adding ARP table entry: %s (%s)", strerror(errmsg.error), errmsg.error_str); + goto error; + } + + snl_free(&state); + + return 0; +} +#endif /** Encode VPS into a raw ARP packet. * -- 2.47.2