]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
updates for relaying, including sample config.
authorAlan T. DeKok <aland@freeradius.org>
Wed, 11 Aug 2021 13:26:38 +0000 (09:26 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Wed, 11 Aug 2021 13:26:38 +0000 (09:26 -0400)
Untested, but (of course) it will work, right?

doc/antora/modules/raddb/pages/mods-available/dhcpv4.adoc
raddb/mods-available/dhcpv4
src/modules/rlm_dhcpv4/rlm_dhcpv4.c

index c0c055cf195fc2246a3882582a0141fdc86e6e4f..56ac5cb4086602b0f75555ee0c1126aedd7065bb 100644 (file)
 
 = DHCPv4 Module
 
-The `dhcpv4` module is useful only for `xlat`. To use it,
-put `dhcp` into the `instantiate { ... }` section.
 
-`%{dhcp_options:<Attribute-ref>}` may be used to decode
-DHCP options data included in RADIUS packets by vendors
-of DHCP to RADIUS gateways.
 
-This is known to work with the following VSAs:
+## Configuration Settings
 
-[options="header,autowidth"]
-|===
-| Vendor            | VSAs
-| Juniper           | `ERX-Dhcp-Options`
-| Alcatel lucent SR | `link:https://infoproducts.alcatel-lucent.com/html/0_add-h-f/93-0088-HTML/7750_SR_OS_Radius_Attributes_Reference_Guide/SROS_RADIUS_Attrib.html[Alc-ToServer-Dhcp-Options]`, `link:https://infoproducts.alcatel-lucent.com/html/0_add-h-f/93-0088-HTML/7750_SR_OS_Radius_Attributes_Reference_Guide/SROS_RADIUS_Attrib.html[Alc-ToClient-Dhcp-Options]`
-|===
+The DHCPv4 module is used as a relay.
 
-e.g: `%{dhcp_options:RX-Dhcp-Options}`
+For reach request, you should set &request.Packet-Dst-IP-Address to
+the address of the DHCPv4 server (or next relay).
 
+Packets MUST also have a Gateway-IP-Address option, otherwise they
+will be dropped.  This module does not (yet) support receiving
+replies from the DHCPv4 server.  Any packets sent to the socket
+IP/port will be dropped.
 
 
-## Configuration Settings
+ipaddr: The IP address we use for sending packets.
 
-This module takes no configuration.
+This MUST be an IPv4 address.  IPv6 addresses are not
+supported for DHCPv4.
 
 
 
-## Expansions
+port:: The port we use for sending packets.
 
-The rlm_dhcpv4 provides the below xlat's functions to handle the DHCPV4 packets.
+The default DHCPv4 client port is 68.  It should be changed
+from this only for testing.
 
-### %(dhcpv4_decode:...)
 
-Decode DHCP option declared in dictionary.rfc2131.
 
-.Return: _string_
+interface:: Interface to bind to.
 
-.Example
 
-[source,unlang]
-----
-update request {
-  &Tmp-Octets-0 := 0x520d0103abcdef0206010203040506
-}
-if ("%(dhcpv4_decode:%{Tmp-Octets-0})" != 2) {
-  update reply {
-    &Reply-Message := "Problems decoding the DHPCv4 fields."
-  }
-  reject
-}
-update reply {
-  &Reply-Message := "The value of Relay-Agent-Information.Circuit-ID=%{Relay-Agent-Information.Circuit-ID}, Relay-Agent-Information.Remote-ID=%{Relay-Agent-Information.Remote-ID}"
-}
-----
 
-.Output
+max_packet_size:: The maximum packet size.
 
-```
-"The value of Relay-Agent-Information.Circuit-ID=0xabcdef, Relay-Agent-Information.Remote-ID=0x010203040506"
-```
+Packets which are larger than this will not be sent.
 
-### %(dhcpv4_encode:...)
+Useful range: 300..1500 or so.
 
-Encode DHCP option declared in dictionary.rfc2131.
 
-.Return: _string_
 
-.Example
+send_buff:: How big the kernel's send buffer should be.
 
-[source,unlang]
-----
-update request {
-  &Relay-Agent-Information.Circuit-ID := 0xabcdef
-  &Relay-Agent-Information.Remote-ID := 0x010203040506
-}
-update request {
-  &Tmp-Octets-0 := "%(dhcpv4_encode:&request[*])"
-}
-if (&Tmp-Octets-0 != 0x520d0103abcdef0206010203040506) {
-  update reply {
-    &Reply-Message := "Invalid DHCP packets"
-  }
-  reject
-}
-----
-
-.Output
-
-```
-
-```
 
 == Default Configuration
 
 ```
 dhcpv4 {
+       ipaddr = 127.0.0.1
+#      port = 68
+#      interface = eth0
+#      max_packet_size = 576
+#      send_buff = 1048576
 }
 ```
index 7375a7fb6e30badad9000770520a8bb08e74b229..659b7acd3026650520bda3fb73f4408e206d4a47 100644 (file)
 #
 #  ## Configuration Settings
 #
-#  This module takes no configuration.
+#  The DHCPv4 module is used as a relay.
+#
+#  For reach request, you should set &request.Packet-Dst-IP-Address to
+#  the address of the DHCPv4 server (or next relay).
+#
+#  Packets MUST also have a Gateway-IP-Address option, otherwise they
+#  will be dropped.  This module does not (yet) support receiving
+#  replies from the DHCPv4 server.  Any packets sent to the socket
+#  IP/port will be dropped.
 #
 dhcpv4 {
+       #
+       #  ipaddr: The IP address we use for sending packets.
+       #
+       #  This MUST be an IPv4 address.  IPv6 addresses are not
+       #  supported for DHCPv4.
+       #
+       ipaddr = 127.0.0.1
+
+       #
+       #  port:: The port we use for sending packets.
+       #
+       #  The default DHCPv4 client port is 68.  It should be changed
+       #  from this only for testing.
+       #
+#      port = 68
+
+       #
+       #  interface:: Interface to bind to.
+       #
+#      interface = eth0
+
+       #
+       #  max_packet_size:: The maximum packet size.
+       #
+       #  Packets which are larger than this will not be sent.
+       #
+       #  Useful range: 300..1500 or so.
+       #
+#      max_packet_size = 576
+
+       #
+       #  send_buff:: How big the kernel's send buffer should be.
+       #
+#      send_buff = 1048576
 }
index 0b5bb632340b581e761f99762c72cc7321f953da..86aebe6503ba2c7def916711a6b21df6e61d1f77 100644 (file)
@@ -40,6 +40,25 @@ fr_dict_autoload_t rlm_dhcpv4_dict[] = {
        { NULL }
 };
 
+static fr_dict_attr_t const *attr_transaction_id;
+static fr_dict_attr_t const *attr_message_type;
+static fr_dict_attr_t const *attr_packet_type;
+static fr_dict_attr_t const *attr_packet_dst_ip_address;
+static fr_dict_attr_t const *attr_packet_dst_port;
+static fr_dict_attr_t const *attr_gateway_ip_address;
+
+extern fr_dict_attr_autoload_t rlm_dhcpv4_dict_attr[];
+fr_dict_attr_autoload_t rlm_dhcpv4_dict_attr[] = {
+       { .out = &attr_transaction_id, .name = "Transaction-Id", .type = FR_TYPE_UINT32, .dict = &dict_dhcpv4 },
+       { .out = &attr_gateway_ip_address, .name = "Gateway-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 },
+       { .out = &attr_message_type, .name = "Message-Type", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 },
+       { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_dhcpv4 },
+       { .out = &attr_packet_dst_ip_address, .name = "Packet-Dst-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 },
+       { .out = &attr_packet_dst_port, .name = "Packet-Dst-Port", .type = FR_TYPE_UINT16, .dict = &dict_dhcpv4 },
+       { NULL }
+};
+
+
 /*
  *     Define a structure for our module configuration.
  *
@@ -48,22 +67,318 @@ fr_dict_autoload_t rlm_dhcpv4_dict[] = {
  *     be used as the instance handle.
  */
 typedef struct {
-       int nothing;
+       char const              *name;
+       char const              *xlat_name;
+
+       int                     fd;                     //!< only relay, no proxying for now
+
+       fr_ipaddr_t             ipaddr;                 //!< socket IP address
+       uint16_t                port;                   //!< socket port
+
+       char const              *interface;             //!< Interface to bind to.
+
+       uint32_t                recv_buff;              //!< How big the kernel's receive buffer should be.
+       uint32_t                send_buff;              //!< How big the kernel's send buffer should be.
+
+       uint32_t                max_packet_size;        //!< Maximum packet size.
+
+       bool                    recv_buff_is_set;       //!< Whether we were provided with a recv_buf
+       bool                    send_buff_is_set;       //!< Whether we were provided with a send_buf
 } rlm_dhcpv4_t;
 
+typedef struct {
+       uint8_t                 *buffer;                //!< for encoding packets
+       uint32_t                buffer_size;            //!< Maximum packet size.
+} rlm_dhcpv4_thread_t;
 
-/*
- *     The module name should be the only globally exported symbol.
- *     That is, everything else should be 'static'.
+static const CONF_PARSER module_config[] = {
+       { FR_CONF_OFFSET("ipaddr", FR_TYPE_IPV4_ADDR, rlm_dhcpv4_t, ipaddr), },
+       { FR_CONF_OFFSET("ipv4addr", FR_TYPE_IPV4_ADDR, rlm_dhcpv4_t, ipaddr) },
+
+       { FR_CONF_OFFSET("port", FR_TYPE_UINT16, rlm_dhcpv4_t, port), .dflt = "68" },
+
+       { FR_CONF_OFFSET("interface", FR_TYPE_STRING, rlm_dhcpv4_t, interface) },
+
+       { FR_CONF_OFFSET_IS_SET("recv_buff", FR_TYPE_UINT32, rlm_dhcpv4_t, recv_buff) },
+       { FR_CONF_OFFSET_IS_SET("send_buff", FR_TYPE_UINT32, rlm_dhcpv4_t, send_buff) },
+
+       { FR_CONF_OFFSET("max_packet_size", FR_TYPE_UINT32, rlm_dhcpv4_t, max_packet_size), .dflt = "576" },
+
+       CONF_PARSER_TERMINATOR
+};
+
+/** Bootstrap the module
+ *
+ * Bootstrap I/O and type submodules.
+ *
+ * @param[in] instance Ctx data for this module
+ * @param[in] conf    our configuration section parsed to give us instance.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int mod_bootstrap(void *instance, CONF_SECTION *conf)
+{
+       rlm_dhcpv4_t    *inst = talloc_get_type_abort(instance, rlm_dhcpv4_t);
+
+       inst->xlat_name = cf_section_name2(conf);
+       if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
+       inst->name = inst->xlat_name;
+
+       /*
+        *      Ensure that we have a destination address.
+        */
+       if (inst->ipaddr.af == AF_UNSPEC) {
+               cf_log_err(conf, "A value must be given for 'ipaddr'");
+               return -1;
+       }
+
+       if (inst->ipaddr.af != AF_INET) {
+               cf_log_err(conf, "DHCPv4 can only use IPv4 addresses in 'ipaddr'");
+               return -1;
+       }
+
+       if (!inst->port) {
+               cf_log_err(conf, "A value must be given for 'port'");
+               return -1;
+       }
+
+       /*
+        *      Clamp max_packet_size first before checking recv_buff and send_buff
+        */
+       FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, DEFAULT_PACKET_SIZE);
+       FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, MAX_PACKET_SIZE);
+
+       if (inst->send_buff_is_set) {
+               FR_INTEGER_BOUND_CHECK("send_buff", inst->send_buff, >=, inst->max_packet_size);
+               FR_INTEGER_BOUND_CHECK("send_buff", inst->send_buff, <=, (1 << 30));
+       }
+
+       return 0;
+}
+
+/** Instantiate the module
+ *
+ * Instantiate I/O and type submodules.
+ *
+ * @param[in] instance data for this module
+ * @param[in] conf     our configuration section parsed to give us instance.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int mod_instantiate(void *instance, CONF_SECTION *conf)
+{
+       rlm_dhcpv4_t    *inst = talloc_get_type_abort(instance, rlm_dhcpv4_t);
+
+       /*
+        *      Open the outgoing socket.
+        */
+       inst->fd = fr_socket_server_udp(&inst->ipaddr, &inst->port, NULL, true);
+       if (inst->fd < 0) {
+               cf_log_err(conf, "Failed opening socket: %s", fr_strerror());
+               return -1;
+       }
+
+       /*
+        *      Bind to the interface, if required.
+        */
+       if (inst->interface) {
+               if (fr_socket_bind(inst->fd, &inst->ipaddr, &inst->port, inst->interface) < 0) {
+                       cf_log_err(conf, "Failed binding to interface %s: %s", inst->interface, fr_strerror());
+                       return -1;
+               }
+       }
+
+       fr_nonblock(inst->fd);
+
+#ifdef SO_RCVBUF
+       if (inst->recv_buff_is_set) {
+               int opt;
+
+               opt = inst->recv_buff;
+               if (setsockopt(inst->fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(int)) < 0) {
+                       cf_log_warn(conf, "Failed setting 'SO_RCVBUF': %s", fr_syserror(errno));
+               }
+       }
+#endif
+
+#ifdef SO_SNDBUF
+       if (inst->send_buff_is_set) {
+               int opt;
+
+               opt = inst->send_buff;
+               if (setsockopt(inst->fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(int)) < 0) {
+                       cf_log_warn(conf, "Failed setting 'SO_SNDBUF', write performance may be sub-optimal: %s",
+                                   fr_syserror(errno));
+               }
+       }
+#endif
+
+       return 0;
+}
+
+/** Instantiate thread data for the submodule.
+ *
+ */
+static int mod_thread_instantiate(CONF_SECTION const *cs, void *instance, UNUSED fr_event_list_t *el, void *thread)
+{
+       rlm_dhcpv4_t *inst = talloc_get_type_abort(instance, rlm_dhcpv4_t);
+       rlm_dhcpv4_thread_t *t = talloc_get_type_abort(thread, rlm_dhcpv4_thread_t);
+
+       t->buffer = talloc_array(t, uint8_t, inst->max_packet_size);
+       if (!t->buffer) {
+               cf_log_err(cs, "Failed allocating buffer");
+               return -1;
+       }
+
+       t->buffer_size = inst->max_packet_size;
+
+       return 0;
+}
+
+/** Send packets outbound.
  *
- *     If the module needs to temporarily modify it's instantiation
- *     data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
- *     The server will then take care of ensuring that the module
- *     is single-threaded.
  */
+static unlang_action_t CC_HINT(nonnull) mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+{
+       rlm_dhcpv4_t            *inst = talloc_get_type_abort(mctx->instance, rlm_dhcpv4_t);
+       rlm_dhcpv4_thread_t     *t = talloc_get_type_abort(mctx->thread, rlm_dhcpv4_thread_t);
+       ssize_t                 data_len;
+       dhcp_packet_t           *original = (dhcp_packet_t *) request->packet->data;
+       dhcp_packet_t           *packet;
+       struct sockaddr_storage sockaddr;
+       socklen_t               socklen;
+       uint32_t                xid;
+       fr_pair_t               *vp;
+       int                     code, port;
+
+       /*
+        *      Discard any incoming packets, as we don't care about
+        *      them.
+        *
+        *      @todo - maybe set up a thread listener to do this, if
+        *      we care.
+        */
+       while (read(inst->fd, t->buffer, t->buffer_size) > 0) {
+               /* do nothing with the data, we don't care */
+       }
+
+       /*
+        *      We can only send relayed packets, which have a gateway IP
+        */
+       vp = fr_pair_find_by_da(&request->request_pairs, attr_gateway_ip_address, 0);
+       if (!vp) {
+               REDEBUG("Relayed packets MUST have a Gateway-IP-Address attribute");
+               RETURN_MODULE_FAIL;
+       }
+
+       /*
+        *      Get the transaction ID.
+        */
+       vp = fr_pair_find_by_da(&request->request_pairs, attr_transaction_id, 0);
+       if (vp) {
+               xid = vp->vp_uint32;
+
+       } else if (original) {
+               xid = ntohl(original->xid);
+
+       } else {
+               xid = fr_rand(); /* shouldn't happen, as we're relaying packets, not creating them (yet) */
+       }
+
+       /*
+        *      Set the packet type.
+        *
+        *      @todo - make sure it's a client type.
+        */
+       vp = fr_pair_find_by_da(&request->request_pairs, attr_packet_type, 0);
+       if (vp) {
+               code = vp->vp_uint32;
+
+       } else if ((vp = fr_pair_find_by_da(&request->request_pairs, attr_message_type, 0)) != NULL) {
+               code = vp->vp_uint8;
+
+       } else {
+               code = request->packet->code;
+       }
+
+       /*
+        *      Set the destination port, defaulting to 67
+        */
+       vp = fr_pair_find_by_da(&request->request_pairs, attr_packet_dst_port, 0);
+       if (vp) {
+               port = vp->vp_uint16;
+       } else {
+               port = 67;      /* DHCPv4 server port */
+       }
+
+       /*
+        *      Get the destination address / port, and unicast it there.
+        */
+       vp = fr_pair_find_by_da(&request->request_pairs, attr_packet_dst_ip_address, 0);
+       if (!vp) {
+               RDEBUG("No Packet-Dst-IP-Address, cannot relay packet");
+               RETURN_MODULE_NOOP;
+       }
+
+       fr_ipaddr_to_sockaddr(&sockaddr, &socklen, &vp->vp_ip, port);
+
+       /*
+        *      Encode the packet using the original information.
+        */
+       data_len = fr_dhcpv4_encode(t->buffer, t->buffer_size, original, code, xid, &request->request_pairs);
+       if (data_len <= 0) {
+               RPEDEBUG("Failed encoding DHCPV4 request");
+               RETURN_MODULE_FAIL;
+       }
+
+       /*
+        *      Enforce some other RFC requirements.
+        */
+       packet = (dhcp_packet_t *) t->buffer;
+       if (packet->opcode == 1) {
+               if (original) {
+                       if (original->hops < 255) packet->hops = original->hops + 1;
+               } else {
+                       if (packet->hops < 255) packet->hops++;
+               }
+
+       } /* else sending a server message?  OK boomer. */
+
+       FR_PROTO_HEX_DUMP(t->buffer, data_len, "DHCPv4");
+
+       /*
+        *      Send the packet, via "fire and forget".
+        */
+       if (sendto(inst->fd, t->buffer, data_len, 0, (struct sockaddr *) &sockaddr, socklen) < 0) {
+               REDEBUG("Failed sending packet to %pV: %s", &vp->data, fr_syserror(errno));
+               RETURN_MODULE_FAIL;
+       }
+
+       RETURN_MODULE_OK;
+}
+
 extern module_t rlm_dhcpv4;
 module_t rlm_dhcpv4 = {
        .magic          = RLM_MODULE_INIT,
        .name           = "dhcpv4",
        .inst_size      = sizeof(rlm_dhcpv4_t),
+       .bootstrap      = mod_bootstrap,
+       .instantiate    = mod_instantiate,
+
+       .config                 = module_config,
+
+       .thread_inst_size = sizeof(rlm_dhcpv4_thread_t),
+       .thread_inst_type = "rlm_dhcpv4_thread_t",
+       .thread_instantiate = mod_thread_instantiate,
+
+       .methods = {
+               [MOD_AUTHORIZE]         = mod_process,
+               [MOD_POST_AUTH]         = mod_process,
+       },
+        .method_names = (module_method_names_t[]){
+                { .name1 = CF_IDENT_ANY,       .name2 = CF_IDENT_ANY,  .method = mod_process },
+                MODULE_NAME_TERMINATOR
+        },
 };