]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add and document src_port_start and src_port_end
authorAlan T. DeKok <aland@freeradius.org>
Tue, 11 Feb 2025 19:01:10 +0000 (14:01 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Tue, 11 Feb 2025 19:15:04 +0000 (14:15 -0500)
which currently only work for mode=dynamic-proxy

raddb/mods-available/radius
src/modules/rlm_radius/bio.c
src/modules/rlm_radius/rlm_radius.c
src/modules/rlm_radius/rlm_radius.h

index 313c4cfe5b169dd853cce20494e70f1ba078327a..d57dbc0db334f69cb037d4e10217e8f8b6b28ecb 100644 (file)
@@ -792,6 +792,34 @@ radius proxy {
                #
                src_ipaddr = *
 
+               #
+               #  src_port_start:: Start of source port range.
+               #
+               #  The outgoing proxy normally uses random source
+               #  ports.  When all of the RADIUS IDs are used for one
+               #  connection, it opens up another random source port.
+               #
+               #  However, it is sometimes useful to restrict the
+               #  range of source ports to known values.  The
+               #  `src_port_start` and `src_port_end` configuration
+               #  flags allow the port range to be controlled.  The
+               #  module will then restrict the source ports it is
+               #  using to be within this range.
+               #
+               #  When all ports in this range are used, the module
+               #  will not be able to open any more outgoing
+               #  connections.x
+               #
+               #  These two configuratiuon items can only be used for
+               #  UDP sockets.
+               #
+               src_port_start = 10000
+
+               #
+               #  src_port_end:: End of source port range.
+               #
+               src_port_end = 11000
+
                #
                #  `src_port` cannot be used.  If it is used here, the
                #  module will refuse to start.  Instead, the module
index 25988b26262ec6c5357afab4a0b675a690977230..240fd4acb6775861cc86cfcedc01a916718fc6e2 100644 (file)
@@ -49,14 +49,13 @@ typedef struct {
        rlm_radius_t const      *inst;          //!< our instance
        fr_event_list_t         *el;            //!< Event list.
        trunk_t                 *trunk;         //!< trunk handler
-       fr_bio_fd_config_t      *fd_config;     //!< for threads or sockets
+       fr_bio_fd_config_t      fd_config;      //!< for threads or sockets
        fr_bio_fd_info_t const  *fd_info;       //!< status of the FD.
        fr_radius_ctx_t         radius_ctx;
 } bio_handle_ctx_t;
 
 typedef struct {
-       bio_handle_ctx_t        ctx;            //!< for copying to bio_handle_t
-
+       bio_handle_ctx_t        ctx;            //!< common struct for home servers and BIO handles
 
        struct {
                fr_bio_t *fd;                   //!< writing
@@ -71,7 +70,7 @@ typedef struct bio_request_s bio_request_t;
  *
  */
 typedef struct {
-       bio_handle_ctx_t        ctx;            //! from thread or home server
+       bio_handle_ctx_t        ctx;            //!< common struct for home servers and BIO handles
 
        int                     fd;                     //!< File descriptor.
 
@@ -140,8 +139,6 @@ struct bio_request_s {
 typedef struct {
        bio_handle_ctx_t        ctx;            //!< for copying to bio_handle_t
 
-       fr_bio_fd_config_t      fd_config;      //!< fil descriptor configuration
-
        fr_rb_expire_node_t     expire;
 } home_server_t;
 
@@ -747,7 +744,14 @@ static connection_state_t conn_init(void **h_out, connection_t *conn, void *uctx
 
        MEM(h->tt = radius_track_alloc(h));
 
-       h->bio.fd = fr_bio_fd_alloc(h, h->ctx.fd_config, 0);
+       /*
+        *      Limit the source port to the given range.
+        */
+       if (h->ctx.inst->src_port_start) {
+               DEBUG("WARNING - src_port_start and src_port_end not currently supported.  A random source port will be chosen");
+       }
+
+       h->bio.fd = fr_bio_fd_alloc(h, &h->ctx.fd_config, 0);
        if (!h->bio.fd) {
                PERROR("%s - failed opening socket", h->ctx.module_name);
        fail:
@@ -774,7 +778,7 @@ static connection_state_t conn_init(void **h_out, connection_t *conn, void *uctx
         *      way we don't need a memory BIO for UDP sockets, but we can still add a verification layer for
         *      UDP sockets?
         */
-       if (h->ctx.fd_config->socket_type == SOCK_STREAM) {
+       if (h->ctx.fd_config.socket_type == SOCK_STREAM) {
                h->bio.mem = fr_bio_mem_alloc(h, 8192, 0, h->bio.fd);
                if (!h->bio.mem) {
                        PERROR("%s - Failed allocating memory buffer - ", h->ctx.module_name);
@@ -1411,7 +1415,7 @@ static void mod_dup(request_t *request, bio_request_t *u)
 
        h = talloc_get_type_abort(u->treq->tconn->conn->h, bio_handle_t);
 
-       if (h->ctx.fd_config->socket_type != SOCK_DGRAM) {
+       if (h->ctx.fd_config.socket_type != SOCK_DGRAM) {
                RDEBUG("Using stream sockets - suppressing retransmission");
                return;
        }
@@ -1735,7 +1739,7 @@ do_write:
        /*
         *      We don't retransmit over TCP.
         */
-       if (h->ctx.fd_config->socket_type != SOCK_DGRAM) return;
+       if (h->ctx.fd_config.socket_type != SOCK_DGRAM) return;
 
        /*
         *      If we only send one datagram packet, then don't bother saving it.
@@ -2427,7 +2431,6 @@ static const trunk_io_funcs_t     io_funcs = {
        .request_cancel = request_cancel,
 };
 
-
 /** Instantiate thread data for the submodule.
  *
  */
@@ -2438,7 +2441,7 @@ static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
 
        thread->ctx.el = mctx->el;
        thread->ctx.inst = inst;
-       thread->ctx.fd_config = &inst->fd_config;
+       thread->ctx.fd_config = inst->fd_config;
        thread->ctx.radius_ctx = inst->common_ctx;
 
        if ((inst->mode != RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) &&
@@ -2449,10 +2452,20 @@ static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
                return 0;
        }
 
+       /*
+        *      If we have a port range, allocate the source IP based
+        *      on the range start, plus the thread ID.  This means
+        *      that we can avoid "hunt and peck" attempts to open up
+        *      the source port.
+        */
+       if (thread->ctx.inst->src_port_start) {
+               thread->ctx.fd_config.src_port = thread->ctx.inst->src_port_start + fr_schedule_worker_id();
+       }
+
        /*
         *      Allocate the unconnected replication socket.
         */
-       thread->bio.fd = fr_bio_fd_alloc(thread, &thread->ctx.inst->fd_config, 0);
+       thread->bio.fd = fr_bio_fd_alloc(thread, &thread->ctx.fd_config, 0);
        if (!thread->bio.fd) {
                PERROR("%s - failed opening socket", inst->name);
                return CONNECTION_STATE_FAILED;
@@ -2597,10 +2610,10 @@ static int8_t home_server_cmp(void const *one, void const *two)
        home_server_t const *b = two;
        int8_t rcode;
 
-       rcode = fr_ipaddr_cmp(&a->fd_config.dst_ipaddr, &b->fd_config.dst_ipaddr);
+       rcode = fr_ipaddr_cmp(&a->ctx.fd_config.dst_ipaddr, &b->ctx.fd_config.dst_ipaddr);
        if (rcode != 0) return rcode;
 
-       return CMP(a->fd_config.dst_port, b->fd_config.dst_port);
+       return CMP(a->ctx.fd_config.dst_port, b->ctx.fd_config.dst_port);
 }
 
 static xlat_action_t xlat_sendto_resume(TALLOC_CTX *ctx, fr_dcursor_t *out,
@@ -2665,9 +2678,11 @@ static xlat_action_t xlat_radius_client(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcurso
        }
 
        home = fr_rb_find(&thread->bio.expires.tree, &(home_server_t) {
-                       .fd_config = (fr_bio_fd_config_t) {
-                               .dst_ipaddr = ipaddr->vb_ip,
-                               .dst_port = port->vb_uint16,
+                       .ctx = {
+                               .fd_config = (fr_bio_fd_config_t) {
+                                       .dst_ipaddr = ipaddr->vb_ip,
+                                       .dst_port = port->vb_uint16,
+                               },
                        },
                });
        if (!home) {
@@ -2681,12 +2696,14 @@ static xlat_action_t xlat_radius_client(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcurso
                        },
                };
 
-               home->fd_config = inst->fd_config;
-               home->ctx.fd_config = &home->fd_config;
-
-               home->fd_config.type = FR_BIO_FD_CONNECTED;
-               home->fd_config.dst_ipaddr = ipaddr->vb_ip;
-               home->fd_config.dst_port = port->vb_uint32;
+               /*
+                *      Copy the home server configuration from the root configuration.  Then update it with
+                *      the needs of the home server.
+                */
+               home->ctx.fd_config = inst->fd_config;
+               home->ctx.fd_config.type = FR_BIO_FD_CONNECTED;
+               home->ctx.fd_config.dst_ipaddr = ipaddr->vb_ip;
+               home->ctx.fd_config.dst_port = port->vb_uint32;
 
                home->ctx.radius_ctx = (fr_radius_ctx_t) {
                        .secret = talloc_strdup(home, secret->vb_strvalue),
index dc2d89dbd0b4937fd8c4e415226ad33bf7546992..fa5c689cd1a78b722c082dbabc8d46de8d80e2be 100644 (file)
@@ -103,6 +103,13 @@ static conf_parser_t const transport_config[] = {
        CONF_PARSER_TERMINATOR
 };
 
+static conf_parser_t const transport_unconnected_config[] = {
+       { FR_CONF_OFFSET("src_port_start", rlm_radius_t, src_port_start) },
+       { FR_CONF_OFFSET("src_port_end", rlm_radius_t, src_port_end) },
+
+       CONF_PARSER_TERMINATOR
+};
+
 /*
  *     We only parse the pool options if we're connected.
  */
@@ -118,6 +125,14 @@ static conf_parser_t const connected_config[] = {
        CONF_PARSER_TERMINATOR
 };
 
+static conf_parser_t const unconnected_config[] = {
+       { FR_CONF_POINTER("udp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_unconnected_config },
+
+       { FR_CONF_POINTER("tcp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_unconnected_config },
+
+       CONF_PARSER_TERMINATOR
+};
+
 /*
  *     We only parse the pool options if we're connected.
  */
@@ -126,6 +141,10 @@ static conf_parser_t const pool_config[] = {
 
        { FR_CONF_OFFSET_SUBSECTION("pool", 0, rlm_radius_t, trunk_conf, trunk_config ) },
 
+       { FR_CONF_POINTER("udp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_unconnected_config },
+
+       { FR_CONF_POINTER("tcp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_unconnected_config },
+
        CONF_PARSER_TERMINATOR
 };
 
@@ -279,6 +298,7 @@ static int mode_parse(UNUSED TALLOC_CTX *ctx, void *out, void *parent,
 
        case RLM_RADIUS_MODE_UNCONNECTED_REPLICATE:
                inst->fd_config.type = FR_BIO_FD_UNCONNECTED;
+               if (cf_section_rules_push(cs, unconnected_config) < 0) return -1;
                break;
        }
 
@@ -656,23 +676,104 @@ check_others:
        FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, 64);
        FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65535);
 
-       if (inst->mode != RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) {
+       /*
+        *      Check invalid configurations.
+        */
+       switch (inst->mode) {
+       default:
                /*
-                *      These limits are specific to RADIUS, and cannot be over-ridden
+                *      Filenames are write-only, and cannot get response packets.
+                */
+               if (inst->fd_config.filename) {
+                       cf_log_err(conf, "Cannot set 'filename' here - it is only supported for 'mode=replicate'.");
+                       return -1;
+               }
+
+               /*
+                *      For normal proxying or originating client packets, we need to be able to open multiple
+                *      source ports.  So the admin can't force a particular source port.
+                */
+               if (inst->fd_config.src_port && (inst->mode != RLM_RADIUS_MODE_XLAT_PROXY)) {
+                       cf_log_err(conf, "Cannot set 'src_port' when sending packets to a static destination");
+                       return -1;
+               }
+
+               /*
+                *      No src_port range, we don't need to check any other settings.
+                */
+               if (!inst->src_port_start && !inst->src_port_end) break;
+
+               if (inst->fd_config.path) {
+                       cf_log_err(conf, "Cannot set 'src_port_start' or 'src_port_end' for outgoing Unix sockets");
+                       return -1;
+               }
+
+               /*
+                *      We can only have one method of allocating source ports.
+                */
+               if (inst->fd_config.src_port) {
+                       cf_log_err(conf, "Cannot set 'src_port' and 'src_port_start' or 'src_port_end'");
+                       return -1;
+               }
+
+               /*
+                *      Cross-check src_port, src_port_start, and src_port_end.
+                */
+               if (inst->src_port_start) {
+                       if (!inst->src_port_end) {
+                               cf_log_err(conf, "Range has 'src_port_start', but is missing 'src_port_end'");
+                               return -1;
+                       }
+
+                       if (inst->src_port_start >= inst->src_port_end) {
+                               cf_log_err(conf, "Range has invalid values for 'src_port_start' ... 'src_port_end'");
+                               return -1;
+                       }
+
+               } else if (inst->src_port_end) {
+                       cf_log_err(conf, "Range has 'src_port_end', but is missing 'src_port_start'");
+                       return -1;
+               }
+
+               /*
+                *      Encorce limits per trunk, due to the 8-bit ID space.
                 */
                FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, >=, 2);
                FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, <=, 255);
                FR_INTEGER_BOUND_CHECK("trunk.per_connection_target", inst->trunk_conf.target_req_per_conn, <=, inst->trunk_conf.max_req_per_conn / 2);
-       }
+               break;
 
-       if ((inst->mode == RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) ||
-           (inst->mode == RLM_RADIUS_MODE_XLAT_PROXY)) {
-               if (inst->fd_config.src_port != 0) {
-                       cf_log_err(conf, "Cannot set 'src_port' when using this 'mode'");
+       case RLM_RADIUS_MODE_UNCONNECTED_REPLICATE:
+               /*
+                *      Replication to dynamic filenames or dynamic unix sockets isn't supported.
+                */
+               if (inst->fd_config.filename || inst->fd_config.path) {
+                       cf_log_err(conf, "Cannot set 'filename' or 'path' when using 'mode=unconnected-replicate'");
+                       return -1;
+               }
+               FALL_THROUGH;
+
+       case RLM_RADIUS_MODE_REPLICATE:
+               /*
+                *      We can force the source port, but then we have to set SO_REUSEPORT.
+                */
+               inst->fd_config.reuse_port = (inst->fd_config.src_port != 0);
+
+               /*
+                *      Files and unix sockets are OK.  The src_port can be set (or not), and that's fine.
+                */
+               if (inst->src_port_start || inst->src_port_end) {
+                       cf_log_err(conf, "Cannot set 'src_port_start' or 'src_port_end' when replicating packets");
                        return -1;
                }
+               break;
        }
 
+       /*
+        *      We allow what may otherwise be conflicting configurations, because the BIO code will pick one
+        *      path, and the conflicts won't affect anything else.  Only the src_port range is special.
+        */
+
        FR_TIME_DELTA_BOUND_CHECK("response_window", inst->zombie_period, >=, fr_time_delta_from_sec(1));
        FR_TIME_DELTA_BOUND_CHECK("response_window", inst->zombie_period, <=, fr_time_delta_from_sec(120));
 
index 8d6a2f9195f19ac2d64b48cd522e7b0e47c57e1e..15b43983ebe36b1449b7367e501af1e70db205ff 100644 (file)
@@ -64,6 +64,9 @@ struct rlm_radius_s {
        uint32_t                max_packet_size;        //!< Maximum packet size.
        uint16_t                max_send_coalesce;      //!< Maximum number of packets to coalesce into one mmsg call.
 
+       uint16_t                src_port_start;         //!< control the source port range for dynamic destinations
+       uint16_t                src_port_end;
+
        fr_radius_ctx_t         common_ctx;
 
        bool                    replicate;              //!< Ignore responses.