From: Nick Porter Date: Tue, 8 Jul 2025 17:18:29 +0000 (+0100) Subject: Add libpcap option for sending DHCP replies X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=aabf850a6ca3c6a325bdf5fbcd67f1019821a27f;p=thirdparty%2Ffreeradius-server.git Add libpcap option for sending DHCP replies Which will be used on non-Linux systems. Also, can be used on Linux for testing, using the hidden listener conf option of use_pcap --- diff --git a/src/listen/dhcpv4/proto_dhcpv4_udp.c b/src/listen/dhcpv4/proto_dhcpv4_udp.c index 3f9a79d65d..0429927543 100644 --- a/src/listen/dhcpv4/proto_dhcpv4_udp.c +++ b/src/listen/dhcpv4/proto_dhcpv4_udp.c @@ -33,10 +33,19 @@ #include #include #include +#include #include "proto_dhcpv4.h" extern fr_app_io_t proto_dhcpv4_udp; +#ifdef HAVE_LIBPCAP +typedef struct { + fr_rb_node_t node; //!< in tree of handles. + char const *interface; //!< name for the handle. + fr_pcap_t *pcap; //!< pcap handle. +} proto_dhcpv4_pcap_t; +#endif + typedef struct { char const *name; //!< socket name int sockfd; @@ -44,6 +53,10 @@ typedef struct { fr_io_address_t *connection; //!< for connected sockets. fr_stats_t stats; //!< statistics for this socket + +#ifdef HAVE_LIBPCAP + fr_rb_tree_t pcaps; //!< Tree of available pcap handles +#endif } proto_dhcpv4_udp_thread_t; typedef struct { @@ -70,12 +83,16 @@ typedef struct { //!< buffer value. bool dynamic_clients; //!< whether we have dynamic clients - fr_client_list_t *clients; //!< local clients + fr_client_list_t *clients; //!< local clients fr_client_t *default_client; //!< default 0/0 client fr_trie_t *trie; //!< for parsed networks fr_ipaddr_t *allow; //!< allowed networks for dynamic clients fr_ipaddr_t *deny; //!< denied networks for dynamic clients + +#ifdef HAVE_LIBPCAP + bool use_pcap; //!< use libpcap for unicast replies to broadcast requests. +#endif } proto_dhcpv4_udp_t; @@ -108,6 +125,9 @@ static const conf_parser_t udp_listen_config[] = { { FR_CONF_OFFSET("max_packet_size", proto_dhcpv4_udp_t, max_packet_size), .dflt = "4096" } , { FR_CONF_OFFSET("max_attributes", proto_dhcpv4_udp_t, max_attributes), .dflt = STRINGIFY(DHCPV4_MAX_ATTRIBUTES) } , +#ifdef HAVE_LIBPCAP + { FR_CONF_OFFSET_FLAGS("use_pcap", CONF_FLAG_HIDDEN, proto_dhcpv4_udp_t, use_pcap) }, +#endif CONF_PARSER_TERMINATOR }; @@ -129,6 +149,61 @@ fr_dict_attr_autoload_t proto_dhcpv4_udp_dict_attr[] = { { NULL } }; +#ifdef HAVE_LIBPCAP +static int8_t dhcpv4_pcap_cmp(void const *a, void const *b) +{ + proto_dhcpv4_pcap_t const *one = a, *two = b; + return strcmp(one->interface, two->interface); +} + +static proto_dhcpv4_pcap_t *dhcpv4_pcap_find(proto_dhcpv4_udp_thread_t *thread, char const *interface) +{ + proto_dhcpv4_pcap_t find, *pcap; + + find = (proto_dhcpv4_pcap_t) { + .interface = interface + }; + + pcap = fr_rb_find(&thread->pcaps, &find); + if (pcap) return pcap; + + MEM(pcap = talloc_zero(thread, proto_dhcpv4_pcap_t)); + + pcap->pcap = fr_pcap_init(pcap, interface, PCAP_INTERFACE_OUT); + + if (!pcap->pcap) { + ERROR("Failed creating pcap for interface %s", interface); + return NULL; + } + + if (fr_pcap_open(pcap->pcap) < 0) { + ERROR("Failed opening pcap on interface %s", interface); + error: + talloc_free(pcap); + return NULL; + } + + /* + * Use a filter which will match nothing - so we don't capture any packets + */ + if (fr_pcap_apply_filter(pcap->pcap, "tcp and udp") < 0) { + ERROR("Failed adding filter to pcap on interface %s", interface); + goto error; + } + + pcap->interface = pcap->pcap->name; + fr_rb_insert(&thread->pcaps, pcap); + + return pcap; +} + +static void dhcpv4_pcap_free(proto_dhcpv4_udp_thread_t *thread, proto_dhcpv4_pcap_t *pcap) +{ + fr_rb_remove(&thread->pcaps, pcap); + talloc_free(pcap); +} +#endif + static ssize_t mod_read(fr_listen_t *li, void **packet_ctx, fr_time_t *recv_time_p, uint8_t *buffer, size_t buffer_len, size_t *leftover) { @@ -413,6 +488,9 @@ static ssize_t mod_write(fr_listen_t *li, void *packet_ctx, UNUSED fr_time_t req case FR_DHCP_OFFER: { char if_name[IFNAMSIZ] = ""; +#ifdef HAVE_LIBPCAP + offer: +#endif #ifdef WITH_IFINDEX_NAME_RESOLUTION if (!inst->interface && socket.inet.ifindex) fr_ifname_from_ifindex(if_name, socket.inet.ifindex); #endif @@ -431,7 +509,11 @@ static ssize_t mod_write(fr_listen_t *li, void *packet_ctx, UNUSED fr_time_t req DEBUG("Reply will be unicast to YIADDR."); #ifdef SIOCSARP - } else if (inst->broadcast && (inst->interface || if_name[0])) { + } else if (inst->broadcast && +#ifdef HAVE_LIBPCAP + !inst->use_pcap && +#endif + (inst->interface || if_name[0])) { uint8_t macaddr[6]; uint8_t ipaddr[4]; @@ -455,7 +537,35 @@ static ssize_t mod_write(fr_listen_t *li, void *packet_ctx, UNUSED fr_time_t req DEBUG("Failed adding ARP 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; + uint8_t macaddr[6]; + fr_packet_t tosend; + + pcap = dhcpv4_pcap_find(thread, inst->interface ? inst->interface : if_name); + if (!pcap) return -1; + + DEBUG("Reply will be unicast to YIADDR."); + memcpy(&macaddr, &packet->chaddr, 6); + memcpy(&socket.inet.dst_ipaddr.addr.v4.s_addr, &packet->yiaddr, 4); + tosend = (fr_packet_t) { + .socket = socket, + .data = buffer, + .data_len = buffer_len + }; + + DEBUG("Sending %s XID %08x from %pV:%d to %pV:%d", dhcp_message_types[code[2]], packet->xid, + fr_box_ipaddr(socket.inet.src_ipaddr), socket.inet.src_port, + fr_box_ipaddr(socket.inet.dst_ipaddr), socket.inet.dst_port); + if (fr_dhcpv4_pcap_send(pcap->pcap, macaddr, &tosend) < 0) { + ERROR("Failed sending packet"); + dhcpv4_pcap_free(thread, pcap); + return -1; + } + return buffer_len; #endif } else { DEBUG("Reply will be broadcast as we do not create raw UDP sockets."); @@ -468,6 +578,12 @@ static ssize_t mod_write(fr_listen_t *li, void *packet_ctx, UNUSED fr_time_t req * ACKs are unicast to YIADDR */ case FR_DHCP_ACK: +#ifdef HAVE_LIBPCAP + /* + * Are we replying on a system using pcap - if so use the same logic as OFFER + */ + if (inst->use_pcap) goto offer; +#endif DEBUG("Reply will be unicast to YIADDR."); memcpy(&socket.inet.dst_ipaddr.addr.v4.s_addr, &packet->yiaddr, 4); break; @@ -588,9 +704,27 @@ static int mod_open(fr_listen_t *li) &inst->ipaddr, inst->port, inst->interface); +#ifdef HAVE_LIBPCAP + fr_rb_inline_init(&thread->pcaps, proto_dhcpv4_pcap_t, node, dhcpv4_pcap_cmp, NULL); +#endif return 0; } +#ifdef HAVE_LIBPCAP +static int mod_close(fr_listen_t *li) +{ + proto_dhcpv4_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dhcpv4_udp_thread_t); + void **pcap_to_free; + int i; + + if (fr_rb_flatten_inorder(NULL, &pcap_to_free, &thread->pcaps) < 0) return -1; + + for (i = talloc_array_length(pcap_to_free) - 1; i >= 0; i--) talloc_free(pcap_to_free[i]); + talloc_free(pcap_to_free); + + return 0; +} +#endif /** Set the file descriptor for this socket. * @@ -762,6 +896,9 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) cf_log_warn(conf, "You SHOULD set 'interface' if you have set 'broadcast = yes'."); cf_log_warn(conf, "All replies will be broadcast, as ARP updates require 'interface' to be set."); } +#elif defined HAVE_LIBPCAP + INFO("libpcap will be used for unicast replies to broadcast requests."); + inst->use_pcap = true; #endif /* @@ -851,6 +988,9 @@ fr_app_io_t proto_dhcpv4_udp = { .open = mod_open, .read = mod_read, .write = mod_write, +#ifdef HAVE_LIBPCAP + .close = mod_close, +#endif .fd_set = mod_fd_set, .track_create = mod_track_create, .track_compare = mod_track_compare,