From: Arran Cudbard-Bell Date: Fri, 5 Nov 2021 23:37:23 +0000 (-0400) Subject: Include dhcpv6 state machine options to control status-code contents X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6516c9c92a386bc9b403fa38685c9d5cfd74a9ea;p=thirdparty%2Ffreeradius-server.git Include dhcpv6 state machine options to control status-code contents --- diff --git a/raddb/sites-available/dhcpv6 b/raddb/sites-available/dhcpv6 index 9ef267f966..a6a267bce3 100644 --- a/raddb/sites-available/dhcpv6 +++ b/raddb/sites-available/dhcpv6 @@ -204,6 +204,73 @@ server dhcpv6 { } } + # + # #### State machine configuration + # + dhcpv6 { + # + # status_code_on_success:: Include a status-code + # option in the packet even when the operation is + # successful (status code 0). + # + # RFC8415 states that the absence of a status-code + # option is identical to a status-code option with + # value (0). This option is included in case + # there are broken DHCPv6 clients that require an + # explicit success notification. + # + # This config item is disabled by default as + # including status-code adds approximately 6 bytes + # per nested message, and some clients are buggy + # and count any status-code option as a failure + # indication. + # +# status_code_on_success = no + + # + # send_failure_message:: Concatenate the contents + # of any Module-Failure-Message attribute in the + # request, and include it in the message field + # of the status-code option when status-code is + # not 0 or when `status_code_on_success = yes`. + # + # This is disabled by default as these messages + # may reveal sensitive information about the + # internal state of the server. + # + # It's recommended to only enable this config item + # for debugging, or in conjunction with + # move_failure_message_to_parent where the upsteam + # relay is trusted and secure. + # +# send_failure_message = no + + # + # move_failure_message_to_parent:: Move all + # Module-Failure-Message attributes to the parent + # request. + # + # Attributes are only moved when: + # + # - A parent request is available. + # - The parent request of type DHCPv6. + # - status-code != 0, or `status_code_on_success = yes` + # + # When combined with send_failure_message and + # a secure upstream DHCPv6 relay this provides a + # useful debugging tool where the reason for a + # given allocation failure can be determined from + # packet traces, or trace functionality on the + # upstream relay. + # + # As relays will strip off the outer relay-message + # as the packet moves through them, the contents + # of the Module-Failure-Message will not reach + # the end DHCPv6 client. + # +# move_failure_message_to_parent = yes + } + # # Receive a Solicit message # diff --git a/src/process/dhcpv6/base.c b/src/process/dhcpv6/base.c index 8e0b305847..f1f6d8c5c7 100644 --- a/src/process/dhcpv6/base.c +++ b/src/process/dhcpv6/base.c @@ -37,52 +37,6 @@ #include #include -static fr_dict_t const *dict_dhcpv6; - -extern fr_dict_autoload_t process_dhcpv6_dict[]; -fr_dict_autoload_t process_dhcpv6_dict[] = { - { .out = &dict_dhcpv6, .proto = "dhcpv6" }, - { NULL } -}; - -static fr_dict_attr_t const *attr_client_id; -static fr_dict_attr_t const *attr_server_id; -static fr_dict_attr_t const *attr_hop_count; -static fr_dict_attr_t const *attr_interface_id; -static fr_dict_attr_t const *attr_packet_type; -static fr_dict_attr_t const *attr_relay_link_address; -static fr_dict_attr_t const *attr_relay_peer_address; -static fr_dict_attr_t const *attr_transaction_id; -static fr_dict_attr_t const *attr_status_code_value; - -extern fr_dict_attr_autoload_t process_dhcpv6_dict_attr[]; -fr_dict_attr_autoload_t process_dhcpv6_dict_attr[] = { - { .out = &attr_client_id, .name = "Client-ID", .type = FR_TYPE_STRUCT, .dict = &dict_dhcpv6 }, - { .out = &attr_hop_count, .name = "Hop-Count", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv6 }, - { .out = &attr_interface_id, .name = "Interface-ID", .type = FR_TYPE_OCTETS, .dict = &dict_dhcpv6 }, - { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_dhcpv6 }, - { .out = &attr_relay_link_address, .name = "Relay-Link-Address", .type = FR_TYPE_IPV6_ADDR, .dict = &dict_dhcpv6 }, - { .out = &attr_relay_peer_address, .name = "Relay-Peer-Address", .type = FR_TYPE_IPV6_ADDR, .dict = &dict_dhcpv6 }, - { .out = &attr_server_id, .name = "Server-ID", .type = FR_TYPE_STRUCT, .dict = &dict_dhcpv6 }, - { .out = &attr_status_code_value, .name = "Status-Code.Value", .type = FR_TYPE_UINT16, .dict = &dict_dhcpv6 }, - { .out = &attr_transaction_id, .name = "Transaction-Id", .type = FR_TYPE_OCTETS, .dict = &dict_dhcpv6 }, - { NULL } -}; - -static fr_value_box_t const *enum_status_code_success; -static fr_value_box_t const *enum_status_code_unspec_fail; -static fr_value_box_t const *enum_status_code_not_on_link; -static fr_value_box_t const *enum_status_code_no_binding; - -extern fr_dict_enum_autoload_t process_dhcpv6_dict_enum[]; -fr_dict_enum_autoload_t process_dhcpv6_dict_enum[] = { - { .out = &enum_status_code_success, .name = "success", .attr = &attr_status_code_value }, - { .out = &enum_status_code_unspec_fail, .name = "UnspecFail", .attr = &attr_status_code_value }, - { .out = &enum_status_code_not_on_link, .name = "NotOnLink", .attr = &attr_status_code_value }, - { .out = &enum_status_code_no_binding, .name = "NoBinding", .attr = &attr_status_code_value }, - { NULL } -}; - /* * DHCPV6 state machine configuration */ @@ -112,6 +66,31 @@ typedef struct { CONF_SECTION *server_cs; //!< Our virtual server. process_dhcpv6_sections_t sections; //!< Pointers to various config sections ///< we need to execute. + bool status_code_on_success; //!< Controls whether we add a status-code + ///< option to outbound packets if the status + ///< code would be 0. + ///< This is allowed by RFC 3315, but seems + ///< to cause issues with some clients. + + bool send_failure_message; //!< If true, all instances of + ///< Module-Failure-Message in the request + ///< are concatenated and returned in the + ///< status-message field of the status-code + ///< option if the status-code is anything + ///< other than success. + ///< This may leak information about the + ///< internal state of the server, so is + ///< disabled by default. + + bool move_failure_message_to_parent; //!< If true, and a parent exists, and the + ///< parent is a DHCPv6 request, all module + ///< failure messages will get copied to the + ///< parent and then freed. + ///< When combined with send_failure_message + ///< this ensures only the outer relay message + ///< contains failure data. The outer relay + ///< typically being controlled by the admin + ///< and not the end user. } process_dhcpv6_t; /** Records fields from the original request so we have a known good copy @@ -131,6 +110,60 @@ typedef struct { fr_pair_t *interface_id; } process_dhcpv6_relay_fields_t; +static fr_dict_t const *dict_dhcpv6; +static fr_dict_t const *dict_freeradius; + +extern fr_dict_autoload_t process_dhcpv6_dict[]; +fr_dict_autoload_t process_dhcpv6_dict[] = { + { .out = &dict_dhcpv6, .proto = "dhcpv6" }, + { .out = &dict_freeradius, .proto = "freeradius" }, + { NULL } +}; + +static fr_dict_attr_t const *attr_client_id; +static fr_dict_attr_t const *attr_server_id; +static fr_dict_attr_t const *attr_hop_count; +static fr_dict_attr_t const *attr_interface_id; +static fr_dict_attr_t const *attr_packet_type; +static fr_dict_attr_t const *attr_relay_link_address; +static fr_dict_attr_t const *attr_relay_peer_address; +static fr_dict_attr_t const *attr_transaction_id; +static fr_dict_attr_t const *attr_status_code_value; +static fr_dict_attr_t const *attr_status_code_message; + +static fr_dict_attr_t const *attr_module_failure_message; + +extern fr_dict_attr_autoload_t process_dhcpv6_dict_attr[]; +fr_dict_attr_autoload_t process_dhcpv6_dict_attr[] = { + { .out = &attr_client_id, .name = "Client-ID", .type = FR_TYPE_STRUCT, .dict = &dict_dhcpv6 }, + { .out = &attr_hop_count, .name = "Hop-Count", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv6 }, + { .out = &attr_interface_id, .name = "Interface-ID", .type = FR_TYPE_OCTETS, .dict = &dict_dhcpv6 }, + { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_dhcpv6 }, + { .out = &attr_relay_link_address, .name = "Relay-Link-Address", .type = FR_TYPE_IPV6_ADDR, .dict = &dict_dhcpv6 }, + { .out = &attr_relay_peer_address, .name = "Relay-Peer-Address", .type = FR_TYPE_IPV6_ADDR, .dict = &dict_dhcpv6 }, + { .out = &attr_server_id, .name = "Server-ID", .type = FR_TYPE_STRUCT, .dict = &dict_dhcpv6 }, + { .out = &attr_status_code_value, .name = "Status-Code.Value", .type = FR_TYPE_UINT16, .dict = &dict_dhcpv6 }, + { .out = &attr_status_code_message, .name = "Status-Code.Message", .type = FR_TYPE_STRING, .dict = &dict_dhcpv6 }, + { .out = &attr_transaction_id, .name = "Transaction-Id", .type = FR_TYPE_OCTETS, .dict = &dict_dhcpv6 }, + + { .out = &attr_module_failure_message, .name = "Module-Failure-Message", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, + { NULL } +}; + +static fr_value_box_t const *enum_status_code_success; +static fr_value_box_t const *enum_status_code_unspec_fail; +static fr_value_box_t const *enum_status_code_not_on_link; +static fr_value_box_t const *enum_status_code_no_binding; + +extern fr_dict_enum_autoload_t process_dhcpv6_dict_enum[]; +fr_dict_enum_autoload_t process_dhcpv6_dict_enum[] = { + { .out = &enum_status_code_success, .name = "success", .attr = &attr_status_code_value }, + { .out = &enum_status_code_unspec_fail, .name = "UnspecFail", .attr = &attr_status_code_value }, + { .out = &enum_status_code_not_on_link, .name = "NotOnLink", .attr = &attr_status_code_value }, + { .out = &enum_status_code_no_binding, .name = "NoBinding", .attr = &attr_status_code_value }, + { NULL } +}; + #define PROCESS_PACKET_TYPE fr_dhcpv6_packet_code_t #define PROCESS_CODE_MAX FR_DHCPV6_CODE_MAX #define PROCESS_CODE_DO_NOT_RESPOND FR_DHCPV6_DO_NOT_RESPOND @@ -145,6 +178,102 @@ typedef struct { #define PROCESS_STATE_EXTRA_FIELDS fr_value_box_t const **status_codes[RLM_MODULE_NUMCODES]; #include +static CONF_PARSER dhcpv6_process_config[] = { + { FR_CONF_OFFSET("status_code_on_success", FR_TYPE_BOOL, process_dhcpv6_t, status_code_on_success), .dflt = "no" }, + { FR_CONF_OFFSET("send_failure_message", FR_TYPE_BOOL, process_dhcpv6_t, send_failure_message), .dflt = "no" }, + { FR_CONF_OFFSET("move_failure_message_to_parent", FR_TYPE_BOOL, process_dhcpv6_t, move_failure_message_to_parent), .dflt = "yes" }, + CONF_PARSER_TERMINATOR +}; + +static const virtual_server_compile_t compile_list[] = { + { + .name = "recv", + .name2 = "Solicit", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(recv_solicit) + }, + { + .name = "recv", + .name2 = "Request", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(recv_request) + }, + { + .name = "recv", + .name2 = "Confirm", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(recv_confirm) + }, + { + .name = "recv", + .name2 = "Renew", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(recv_renew) + }, + { + .name = "recv", + .name2 = "Rebind", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(recv_rebind) + }, + { + .name = "recv", + .name2 = "Release", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(recv_release) + }, + { + .name = "recv", + .name2 = "Decline", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(recv_decline) + }, + { + .name = "recv", + .name2 = "Reconfigure", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(recv_reconfigure) + }, + { + .name = "recv", + .name2 = "Information-Request", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(recv_information_request) + }, + { + .name = "recv", + .name2 = "Relay-Forward", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(recv_relay_forward) + }, + + { + .name = "send", + .name2 = "Advertise", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(send_advertise) + }, + { + .name = "send", + .name2 = "Reply", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(send_reply) + }, + { + .name = "send", + .name2 = "Relay-Reply", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(send_relay_reply) + }, + { + .name = "send", + .name2 = "Do-Not-Respond", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(do_not_respond) + }, + COMPILE_TERMINATOR +}; + /* * Debug the packet if requested. */ @@ -369,10 +498,11 @@ int restore_field_list(request_t *request, fr_pair_list_t *to_restore) * */ static inline CC_HINT(always_inline) -void status_code_add(request_t *request, fr_value_box_t const **code) +void status_code_add(process_dhcpv6_t const *inst, request_t *request, fr_value_box_t const **code) { - fr_pair_t *vp; + fr_pair_t *vp, *failure_message = NULL; fr_value_box_t const *vb; + bool moved_failure_message = false; if (!code || !*code) return; @@ -381,15 +511,67 @@ void status_code_add(request_t *request, fr_value_box_t const **code) /* * If it's a success save some bytes * in the packet and don't bother - * adding the success code. + * adding the success code unless + * explicitly requested to. + */ + if ((vb->vb_uint16 == 0) && !inst->status_code_on_success) return; + + /* + * Don't override the user status + * code. + */ + if (pair_update_reply(&vp, attr_status_code_value) == 0) fr_value_box_copy(vp, &vp->data, vb); + + /* + * Move the module failure messages upwards + * if requested to by the user. */ - if (vb->vb_uint16 == 0) return; + if (inst->move_failure_message_to_parent && request->parent && (request->parent->dict == request->dict)) { + fr_pair_t const *prev = NULL; + + while ((failure_message = fr_pair_find_by_da(&request->request_pairs, + prev, attr_module_failure_message))) { + MEM(vp = fr_pair_copy(request->parent->request_ctx, failure_message)); + fr_pair_append(&request->parent->request_pairs, vp); + + prev = fr_pair_remove(&request->request_pairs, failure_message); + talloc_free(failure_message); + } + + moved_failure_message = true; + } /* - * Don't override user + * Concat all the module failure messages + * and place them in the status code + * message. */ - if (pair_update_reply(&vp, attr_status_code_value) == 1) return; - fr_value_box_copy(vp, &vp->data, vb); + if (inst->send_failure_message && !moved_failure_message && + (failure_message = fr_pair_find_by_da(&request->request_pairs, NULL, attr_module_failure_message)) && + (pair_update_reply(&vp, attr_status_code_message) == 0)) { + fr_sbuff_uctx_talloc_t tctx; + fr_sbuff_t sbuff; + + do { + /* + * Create an aggregation buffer up to + * the maximum length of a status + * message. + */ + fr_sbuff_init_talloc(vp, &sbuff, &tctx, 1024, UINT16_MAX - 2); + + /* + * Best effort... it's probably OK + * if we truncate really long messages. + */ + if (unlikely(fr_sbuff_in_bstrncpy(&sbuff, failure_message->vp_strvalue, + failure_message->vp_length) < 0)) break; + } while ((failure_message = fr_pair_find_by_da(&request->request_pairs, failure_message, + attr_module_failure_message)) && + (fr_sbuff_in_strcpy_literal(&sbuff, ". ") == 2)); + fr_sbuff_trim_talloc(&sbuff, SIZE_MAX); /* Fix size */ + fr_pair_value_bstrndup_shallow(vp, fr_sbuff_start(&sbuff), fr_sbuff_used(&sbuff), false); + } } /** Restore our copy of the header fields into the reply list @@ -397,6 +579,7 @@ void status_code_add(request_t *request, fr_value_box_t const **code) */ RESUME(send_to_client) { + process_dhcpv6_t *inst = talloc_get_type_abort(mctx->instance, process_dhcpv6_t); process_dhcpv6_client_fields_t *fields = talloc_get_type_abort(mctx->rctx, process_dhcpv6_client_fields_t); fr_process_state_t const *state; @@ -412,7 +595,7 @@ RESUME(send_to_client) /* * Add a status code if we have one */ - status_code_add(request, state->status_codes[*p_result]); + status_code_add(inst, request, state->status_codes[*p_result]); /* * If we have a status code entry then we'll @@ -498,6 +681,7 @@ RECV(from_relay) */ RESUME(send_to_relay) { + process_dhcpv6_t *inst = talloc_get_type_abort(mctx->instance, process_dhcpv6_t); process_dhcpv6_relay_fields_t *fields = talloc_get_type_abort(mctx->rctx, process_dhcpv6_relay_fields_t); fr_process_state_t const *state; @@ -506,7 +690,7 @@ RESUME(send_to_relay) /* * Add a status code if we have one */ - status_code_add(request, state->status_codes[*p_result]); + status_code_add(inst, request, state->status_codes[*p_result]); /* * Restore relay fields @@ -1045,100 +1229,11 @@ static fr_process_state_t const process_state[] = { } }; -static const virtual_server_compile_t compile_list[] = { - { - .name = "recv", - .name2 = "Solicit", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(recv_solicit) - }, - { - .name = "recv", - .name2 = "Request", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(recv_request) - }, - { - .name = "recv", - .name2 = "Confirm", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(recv_confirm) - }, - { - .name = "recv", - .name2 = "Renew", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(recv_renew) - }, - { - .name = "recv", - .name2 = "Rebind", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(recv_rebind) - }, - { - .name = "recv", - .name2 = "Release", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(recv_release) - }, - { - .name = "recv", - .name2 = "Decline", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(recv_decline) - }, - { - .name = "recv", - .name2 = "Reconfigure", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(recv_reconfigure) - }, - { - .name = "recv", - .name2 = "Information-Request", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(recv_information_request) - }, - { - .name = "recv", - .name2 = "Relay-Forward", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(recv_relay_forward) - }, - - { - .name = "send", - .name2 = "Advertise", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(send_advertise) - }, - { - .name = "send", - .name2 = "Reply", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(send_reply) - }, - { - .name = "send", - .name2 = "Relay-Reply", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(send_relay_reply) - }, - { - .name = "send", - .name2 = "Do-Not-Respond", - .component = MOD_POST_AUTH, - .offset = PROCESS_CONF_OFFSET(do_not_respond) - }, - COMPILE_TERMINATOR -}; - extern fr_process_module_t process_dhcpv6; fr_process_module_t process_dhcpv6 = { .magic = RLM_MODULE_INIT, .name = "process_dhcpv6", -// .config = config, + .config = dhcpv6_process_config, .inst_size = sizeof(process_dhcpv6_t), .bootstrap = mod_bootstrap,