]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add libpcap option for sending DHCP replies
authorNick Porter <nick@portercomputing.co.uk>
Tue, 8 Jul 2025 17:18:29 +0000 (18:18 +0100)
committerNick Porter <nick@portercomputing.co.uk>
Tue, 8 Jul 2025 17:18:29 +0000 (18:18 +0100)
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

src/listen/dhcpv4/proto_dhcpv4_udp.c

index 3f9a79d65df52b56859f22f819a104a57a166ac7..0429927543e80660f5ab3be880578be10f3aefb3 100644 (file)
 #include <freeradius-devel/io/listen.h>
 #include <freeradius-devel/io/schedule.h>
 #include <freeradius-devel/protocol/dhcpv4/freeradius.internal.h>
+#include <freeradius-devel/util/pcap.h>
 #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,