]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
A functional DNS state machine
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Fri, 15 Mar 2024 23:35:13 +0000 (19:35 -0400)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Sat, 16 Mar 2024 00:15:40 +0000 (20:15 -0400)
raddb/sites-available/dns
src/process/dns/base.c

index ef80ec3257e5e8f7abf03f60ad0bd41964912061..cf9cc6ba0951a10910bc5187ef8c389aaeb4c2a2 100644 (file)
 #
 #  This is the `dns` virtual server.
 #
-#  It is (for now) only a toy.  It only decodes nested attributes, which `unlang`
-#  cannot (yet) handle well.  It only handles a few types of RRs.  You have to manually
-#  do pretty much everything necessary to make DNS "work".  There's no DB integration.
-#
 #  It's not meant to be fast.  Don't use it as a root server, or as a server for an ISP
 #  with millions of users.  But it should be able to do thousands to tens of thousands
 #  of queries per second, without really trying hard.
 #
 #  It's meant to be a _flexible_ DNS server.  Want to give different answers to VoIP phones
-#  and desktops?  It can do that.
+#  and desktops, or other types of split horizon?  It can do that.
+#
+#  Because DNS uses the &Header.Rcode to communicate the result of a query (instead of opcode)
+#  the DNS state machine works differently to other protocols.
+#
+#  Requests will pass through the following processing sections:
+#  - A `recv { ... }` section matching the opcode. e.g. `recv Query { ... }`.  Query processing
+#    and response formulation should be done here.
+#    If this section returns anything other than `ok` or `updated`, a &reply.Header.Rcode value is
+#    set.
+#  - An `error { ... }` section matching &reply.Header.Rcode. e.g. `error Server-Fail { ... }`.
+#    If the `recv { ... }` section produced a &reply.Header.Rcode value other than `No-Error`
+#    and a `error { ... } section matching the &reply.Header.Rcode is provided, it is executed.
+#    Error sections are intended only for simplifying logging, and as such, the rcode of the section is ignored.
+#    Error sections can manipulate the reply, e.g. setting a new value for &reply.Header.Rcode, but
+#    this will not cause additional `error { ... }` section to be executed.
+#  - A `send { ... }` section matching the opcode. e.g. `send Query-Response { ... }`.  This can be
+#    used for general massaging of the reply.  Return codes are ignored.
 #
-server DNS {
+server dns {
        #
        #  namespace:: The protocol / dictionary to use.
        #
@@ -40,12 +53,22 @@ server DNS {
                #
                udp {
                        ipaddr = *
-                       port = 5300
+                       port = 53
                }
        }
 
 
 recv Query {
+       if (&Question[0].Name == 'foo.example.com') {
+               &reply.Resource-Record := {
+                       &Name = 'foo.example.com'
+                       &Type = A
+                       &Class = ::Internet
+                       &TTL = 0
+                       &Type.A.IP = 127.0.0.1
+               }
+       }
+
        ok
 }
 
index 5c0ad90238978ac332631fd06dc9c08557d43c68..9b00018dd79873212653c9da9cce5449d379fb08 100644 (file)
  * @file src/process/dns/base.c
  * @brief DNS processing.
  *
+ * @copyright 2024 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
  * @copyright 2020 Network RADIUS SAS (legal@networkradius.com)
  */
+#include "lib/server/components.h"
+#include "lib/server/rcode.h"
 #include <freeradius-devel/server/protocol.h>
+#include <freeradius-devel/server/pair.h>
 #include <freeradius-devel/util/debug.h>
+#include <freeradius-devel/util/pair.h>
+#include <freeradius-devel/unlang/interpret.h>
 #include <freeradius-devel/dns/dns.h>
 #include <freeradius-devel/protocol/dns/rfc1034.h>
 
+/** Update this if new rcodes are added
+ */
+#define FR_DNS_RCODE_MAX       FR_RCODE_VALUE_BAD_COOKIE
+
 static fr_dict_t const *dict_dns;
 
 extern fr_dict_autoload_t process_dns_dict[];
@@ -35,93 +45,190 @@ fr_dict_autoload_t process_dns_dict[] = {
 };
 
 static fr_dict_attr_t const *attr_packet_type;
+static fr_dict_attr_t const *attr_header;
+static fr_dict_attr_t const *attr_id;
+static fr_dict_attr_t const *attr_response_bit;
+static fr_dict_attr_t const *attr_opcode;
+static fr_dict_attr_t const *attr_rcode;
+static fr_dict_attr_t const *attr_authoritative_bit;
 
 extern fr_dict_attr_autoload_t process_dns_dict_attr[];
 fr_dict_attr_autoload_t process_dns_dict_attr[] = {
        { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_dns},
+       { .out = &attr_header, .name = "Header", .type = FR_TYPE_STRUCT, .dict = &dict_dns},
+       { .out = &attr_opcode, .name = "Header.Opcode", .type = FR_TYPE_UINT8, .dict = &dict_dns},
+       { .out = &attr_id, .name = "Header.ID", .type = FR_TYPE_UINT16, .dict = &dict_dns},
+       { .out = &attr_response_bit, .name = "Header.Query", .type = FR_TYPE_BOOL, .dict = &dict_dns},
+       { .out = &attr_rcode, .name = "Header.Rcode", .type = FR_TYPE_UINT8, .dict = &dict_dns},
+       { .out = &attr_authoritative_bit, .name = "Header.Authoritative", .type = FR_TYPE_BOOL, .dict = &dict_dns},
+       { NULL }
+};
+
+static fr_value_box_t const *enum_rcode_no_error;
+static fr_value_box_t const *enum_rcode_format_error;
+static fr_value_box_t const *enum_rcode_server_failure;
+static fr_value_box_t const *enum_rcode_name_error;
+static fr_value_box_t const *enum_rcode_refused;
+
+extern fr_dict_enum_autoload_t process_dns_dict_enum[];
+fr_dict_enum_autoload_t process_dns_dict_enum[] = {
+       { .out = &enum_rcode_no_error, .name = "No-Error", .attr = &attr_rcode },                       /* ok/updated */
+       { .out = &enum_rcode_format_error, .name = "Format-Error", .attr = &attr_rcode },               /* invalid */
+       { .out = &enum_rcode_server_failure, .name = "Server-Failure", .attr = &attr_rcode },           /* fail */
+       { .out = &enum_rcode_name_error, .name = "Name-Error", .attr = &attr_rcode },                   /* notfound */
+       { .out = &enum_rcode_refused, .name = "Refused", .attr = &attr_rcode },                         /* reject */
        { NULL }
 };
 
 typedef struct {
        uint64_t        nothing;                // so that the next field isn't at offset 0
 
-       CONF_SECTION    *request;
-       CONF_SECTION    *reply;
-       CONF_SECTION    *recv_reply;
-       CONF_SECTION    *reverse_request;
-       CONF_SECTION    *reverse_reply;
+       /** Request/response sections
+        *
+        */
+       CONF_SECTION    *query;
+       CONF_SECTION    *query_response;
+       CONF_SECTION    *inverse_query;
+       CONF_SECTION    *inverse_query_response;
+       CONF_SECTION    *status;
+       CONF_SECTION    *status_response;
+       CONF_SECTION    *update;
+       CONF_SECTION    *update_response;
+       CONF_SECTION    *stateful_operation;
+       CONF_SECTION    *stateful_operation_response;
+
+       /** DNS rcode error sections (not the same as rlm_rcode_t values)
+        *
+        * These are called after the `recv { ... }` section runs if rcode is non-zero
+        */
+       CONF_SECTION    *rcode[FR_DNS_RCODE_MAX];
+
        CONF_SECTION    *do_not_respond;
 } process_dns_sections_t;
 
 typedef struct {
-       bool            test;
-
        process_dns_sections_t  sections;
 } process_dns_t;
 
+/** Records fields from the original request so we have a known good copy
+ */
+typedef struct {
+       uint16_t        id;             //!< Identity of the request.
+       uint8_t         opcode;         //!< Opcode, what type of query this is.
+} process_dns_fields_t;
+
 #define PROCESS_PACKET_TYPE            fr_dns_packet_code_t
 #define PROCESS_CODE_MAX               FR_DNS_CODE_MAX
 #define PROCESS_CODE_DO_NOT_RESPOND    FR_DNS_DO_NOT_RESPOND
 #define PROCESS_PACKET_CODE_VALID      FR_DNS_PACKET_CODE_VALID
 #define PROCESS_INST                   process_dns_t
-#include <freeradius-devel/server/process.h>
 
-static fr_process_state_t const process_state[] = {
-       [ FR_DNS_QUERY ] = {
-               .packet_type = {
-                       [RLM_MODULE_NOOP] =     FR_DNS_QUERY_RESPONSE,
-                       [RLM_MODULE_OK] =       FR_DNS_QUERY_RESPONSE,
-                       [RLM_MODULE_UPDATED] =  FR_DNS_QUERY_RESPONSE,
+/** Map an rlm_rcode_t to a header.rcode value
+ */
+#define PROCESS_STATE_EXTRA_FIELDS     fr_value_box_t const **dns_rcode[RLM_MODULE_NUMCODES];
 
-                       [RLM_MODULE_REJECT] =   FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_FAIL] =     FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_INVALID] =  FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_DISALLOW] = FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_NOTFOUND] = FR_DNS_DO_NOT_RESPOND,
-               },
-               .rcode = RLM_MODULE_NOOP,
-               .recv = recv_generic,
-               .resume = resume_recv_generic,
-               .section_offset = PROCESS_CONF_OFFSET(request),
-       },
-       [ FR_DNS_QUERY_RESPONSE ] = {
-               .packet_type = {
-                       [RLM_MODULE_NOOP] =     FR_DNS_QUERY_RESPONSE,
-                       [RLM_MODULE_OK] =       FR_DNS_QUERY_RESPONSE,
-                       [RLM_MODULE_UPDATED] =  FR_DNS_QUERY_RESPONSE,
+#include <freeradius-devel/server/process.h>
 
-                       [RLM_MODULE_REJECT] =   FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_FAIL] =     FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_INVALID] =  FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_DISALLOW] = FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_NOTFOUND] = FR_DNS_DO_NOT_RESPOND,
-               },
-               .rcode = RLM_MODULE_NOOP,
-               .send = send_generic,
-               .resume = resume_send_generic,
-               .section_offset = PROCESS_CONF_OFFSET(reply),
+static const virtual_server_compile_t compile_list[] = {
+       {
+               .name = "recv",
+               .name2 = "Query",
+               .component = MOD_AUTHORIZE,
+               .offset = PROCESS_CONF_OFFSET(query),
+       },
+       {
+               .name = "send",
+               .name2 = "Query-Response",
+               .component = MOD_POST_AUTH,
+               .offset = PROCESS_CONF_OFFSET(query_response),
+       },
+       {
+               .name = "recv",
+               .name2 = "Inverse-Query",
+               .component = MOD_AUTHORIZE,
+               .offset = PROCESS_CONF_OFFSET(inverse_query),
+       },
+       {
+               .name = "send",
+               .name2 = "Inverse-Query-Response",
+               .component = MOD_POST_AUTH,
+               .offset = PROCESS_CONF_OFFSET(inverse_query_response),
+       },
+       {
+               .name = "recv",
+               .name2 = "Status",
+               .component = MOD_AUTHORIZE,
+               .offset = PROCESS_CONF_OFFSET(status),
+       },
+       {
+               .name = "send",
+               .name2 = "Status-Response",
+               .component = MOD_POST_AUTH,
+               .offset = PROCESS_CONF_OFFSET(status_response),
+       },
+       {
+               .name = "recv",
+               .name2 = "Update",
+               .component = MOD_AUTHORIZE,
+               .offset = PROCESS_CONF_OFFSET(update),
+       },
+       {
+               .name = "send",
+               .name2 = "Update-Response",
+               .component = MOD_POST_AUTH,
+               .offset = PROCESS_CONF_OFFSET(update_response),
+       },
+       {
+               .name = "recv",
+               .name2 = "Stateful-Operation",
+               .component = MOD_AUTHORIZE,
+               .offset = PROCESS_CONF_OFFSET(stateful_operation),
+       },
+       {
+               .name = "send",
+               .name2 = "Stateful-Operation-Response",
+               .component = MOD_POST_AUTH,
+               .offset = PROCESS_CONF_OFFSET(stateful_operation_response),
+       },
+       {
+               .name = "send",
+               .name2 = "Do-Not-Respond",
+               .component = MOD_POST_AUTH,
+               .offset = PROCESS_CONF_OFFSET(do_not_respond),
        },
 
+#define ERROR_SECTION(_name, _number) \
+       { \
+               .name = "error", \
+               .name2 = _name, \
+               .component = MOD_POST_AUTH, \
+               .offset = PROCESS_CONF_OFFSET(rcode[_number]), \
+       }
 
-       // @todo - recv reply, to look at other replies.
-
-       [ FR_DNS_DO_NOT_RESPOND ] = {
-               .packet_type = {
-                       [RLM_MODULE_NOOP] =     FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_OK] =       FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_UPDATED] =  FR_DNS_DO_NOT_RESPOND,
-
-                       [RLM_MODULE_REJECT] =   FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_FAIL] =     FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_INVALID] =  FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_DISALLOW] = FR_DNS_DO_NOT_RESPOND,
-                       [RLM_MODULE_NOTFOUND] = FR_DNS_DO_NOT_RESPOND,
-               },
-               .rcode = RLM_MODULE_NOOP,
-               .send = send_generic,
-               .resume = resume_send_generic,
-               .section_offset = PROCESS_CONF_OFFSET(do_not_respond),
-       },
+       /*
+        *      Error sections that can execute after the recv { ... }
+        *      section has run.
+        */
+       ERROR_SECTION("Format-Error", FR_RCODE_VALUE_FORMAT_ERROR),
+       ERROR_SECTION("Server-Failure", FR_RCODE_VALUE_SERVER_FAILURE),
+       ERROR_SECTION("Name-Error", FR_RCODE_VALUE_NAME_ERROR),
+       ERROR_SECTION("Not-Implemented", FR_RCODE_VALUE_NOT_IMPLEMENTED),
+       ERROR_SECTION("Refused", FR_RCODE_VALUE_REFUSED),
+       ERROR_SECTION("YX-Domain", FR_RCODE_VALUE_YX_DOMAIN),
+       ERROR_SECTION("YX-Resource-Record-Set", FR_RCODE_VALUE_YX_RESOURCE_RECORD_SET),
+       ERROR_SECTION("NX-Resource-Record-Set", FR_RCODE_VALUE_NX_RESOURCE_RECORD_SET),
+       ERROR_SECTION("Not-Auth", FR_RCODE_VALUE_NOT_AUTH),
+       ERROR_SECTION("Not-Zone", FR_RCODE_VALUE_NOT_ZONE),
+       ERROR_SECTION("DSO-Type-Not-Implemented", FR_RCODE_VALUE_DSO_TYPE_NOT_IMPLEMENTED),
+       ERROR_SECTION("Bad-Signature", FR_RCODE_VALUE_BAD_SIGNATURE),
+       ERROR_SECTION("Bad-Key", FR_RCODE_VALUE_BAD_KEY),
+       ERROR_SECTION("Bad-Time", FR_RCODE_VALUE_BAD_TIME),
+       ERROR_SECTION("Bad-Mode", FR_RCODE_VALUE_BAD_MODE),
+       ERROR_SECTION("Bad-Name", FR_RCODE_VALUE_BAD_NAME),
+       ERROR_SECTION("Bad-Algorithm", FR_RCODE_VALUE_BAD_ALGORITHM),
+       ERROR_SECTION("Bad-Truncation", FR_RCODE_VALUE_BAD_TRUNCATION),
+       ERROR_SECTION("Bad-Cookie", FR_RCODE_VALUE_BAD_COOKIE),
+       COMPILE_TERMINATOR
 };
 
 /*
@@ -145,6 +252,230 @@ static void dns_packet_debug(request_t *request, fr_packet_t const *packet, fr_p
        }
 }
 
+/** Keep a copy of header fields to prevent them being tampered with
+ *
+ */
+static inline CC_HINT(always_inline)
+process_dns_fields_t *dns_fields_store(request_t *request)
+{
+       fr_pair_t               *header;
+       fr_pair_t               *id;
+       fr_pair_t               *opcode;
+       process_dns_fields_t    *rctx;
+
+       /*
+        *      We could use fr_find_by_da_nested, but it's more efficient
+        *      to look up the header attribute once.
+        */
+       header = fr_pair_find_by_da(&request->request_pairs, NULL, attr_header);
+       if (!header) {
+               REDEBUG("Missing Header attribute");
+               return NULL;
+       }
+
+       id = fr_pair_find_by_da(&header->vp_group, NULL, attr_id);
+       if (!id) {
+               REDEBUG("Missing ID attribute");
+               return NULL;
+       }
+
+       opcode = fr_pair_find_by_da(&header->vp_group, NULL, attr_opcode);
+       if (!opcode) {
+               REDEBUG("Missing Opcode attribute");
+               return NULL;
+       }
+
+       MEM(rctx = talloc(unlang_interpret_frame_talloc_ctx(request), process_dns_fields_t));
+       rctx->id = id->vp_uint16;
+       rctx->opcode = opcode->vp_uint8;
+
+       return rctx;
+}
+
+/** Copy values from the request header back into the response
+ *
+ * If a value already exists in the response, don't overwrite it so the user has absolute control
+ */
+static inline CC_HINT(always_inline)
+int dns_fields_restore(request_t *request, process_dns_fields_t *rctx)
+{
+       fr_pair_t *header;
+       fr_pair_t *id;
+       fr_pair_t *response;
+       fr_pair_t *authoritative;
+       fr_pair_t *opcode;
+       int ret;
+
+       MEM(pair_update_reply(&header, attr_header) >= 0);
+
+       /*
+        *      ID should always match the request
+        *      but we allow overrides for testing.
+        */
+       MEM((ret = fr_pair_update_by_da_parent(header, &id, attr_id)) != -1);
+       fr_assert_msg(ret >= 0, "Failed to update header attribute %s:", fr_strerror());
+       if (ret == 0) id->vp_uint16 = rctx->id;
+
+       /*
+        *      This marks the packet as a response.
+        *      Save the user from having to do this manually.
+        */
+       MEM((ret = fr_pair_update_by_da_parent(header, &response, attr_response_bit)) != -1);
+       fr_assert_msg(ret >= 0, "Failed to update response_bit attribute %s:", fr_strerror());
+       if (ret == 0) response->vp_bool = true;
+
+       /*
+        *      Opcode should always match the request
+        *      but we allow overrides for testing.
+        */
+       MEM((ret = fr_pair_update_by_da_parent(header, &opcode, attr_opcode)) != -1);
+       fr_assert_msg(ret >= 0, "Failed to update opcode attribute %s:", fr_strerror());
+       if (ret == 0) opcode->vp_uint8 = rctx->opcode;
+
+       /*
+        *      Default to setting the authoritative bit if
+        *      it's not been set by something already.
+        */
+       MEM((ret = fr_pair_update_by_da_parent(header, &authoritative, attr_authoritative_bit)) != -1);
+       fr_assert_msg(ret >= 0, "Failed to update authoritative_bit attribute %s:", fr_strerror());
+       if (ret == 0) authoritative->vp_bool = true;
+
+       return 0;
+}
+
+/** Add/update the rcode attribute based on the last rlm_rcode value
+ *
+ */
+static inline CC_HINT(always_inline)
+void dns_rcode_add(fr_pair_t **rcode, request_t *request, fr_value_box_t const **code)
+{
+       fr_value_box_t const    *vb;
+
+       if (!code || !*code) return;
+
+       vb = *code;
+
+       /*
+        *      Don't override the user status
+        *      code.
+        */
+       if (fr_pair_update_by_da_parent(request->reply_ctx, rcode, attr_rcode) == 0) {
+               fr_value_box_copy(*rcode, &(*rcode)->data, vb);
+               (*rcode)->data.enumv = (*rcode)->da;    /* Hack, boxes should have their enumv field populated */
+       }
+}
+
+/** Store basic information from the request, and jump into the correct processing section
+ *
+ */
+RECV(request)
+{
+       process_dns_fields_t            *rctx;
+
+       PROCESS_TRACE;
+
+       rctx = dns_fields_store(request);
+       if (!rctx) RETURN_MODULE_INVALID;
+
+       return CALL_RECV_RCTX(generic, rctx);
+}
+
+/** Sets the DNS rcode after we get a result from the recv section
+ *
+ * Calls error processing sections as appropriate
+ */
+RESUME(recv_request)
+{
+       process_dns_t const             *inst = talloc_get_type_abort_const(mctx->inst->data, process_dns_t);
+       fr_process_state_t const        *state;
+       fr_pair_t                       *rcode = NULL;
+
+       PROCESS_TRACE;
+
+       /*
+        *      Pick the next state based on the response
+        */
+       UPDATE_STATE(reply);
+
+       /*
+        *      Don't bother adding VPs if we're not going
+        *      be responding to the client.
+        */
+       if (state->packet_type[*p_result] == FR_DNS_DO_NOT_RESPOND) return CALL_RESUME(recv_generic);
+
+       /*
+        *      Add an rcode based on the result of the `recv { ... }` section
+        */
+       dns_rcode_add(&rcode, request, state->dns_rcode[*p_result]);
+
+       /*
+        *      Call an appropriate error section if it's been set
+        *      otherwise, just call the generic recv resume
+        *      which'll call an appropriate send section.
+        */
+       if ((rcode->vp_uint8 < NUM_ELEMENTS(inst->sections.rcode)) &&
+           (inst->sections.rcode[rcode->vp_uint8])) {
+               return unlang_module_yield_to_section(p_result, request,
+                                                     inst->sections.rcode[rcode->vp_uint8],
+                                                     RLM_MODULE_NOOP,
+                                                     /*
+                                                      *        We ignore everything from the error section
+                                                      *        it's only there for logging.
+                                                      *
+                                                      *        Jump straight to the send function.
+                                                      */
+                                                     state->send,
+                                                     NULL, 0, mctx->rctx);
+       }
+
+       /*
+        *      Use that rcode to determine the processing section
+        */
+       return CALL_RESUME(recv_generic);
+}
+
+/** Set defaults in the response and values copied from the request like opcode and id
+ *
+ */
+RESUME(send_response)
+{
+       fr_process_state_t const        *state;
+       fr_pair_t                       *vp;
+
+       UPDATE_STATE(reply);
+
+       /*
+        *      Don't bother adding VPs if we're not going
+        *      be responding to the client.
+        */
+       if (state->packet_type[*p_result] == FR_DNS_DO_NOT_RESPOND) return CALL_RESUME(send_generic);
+
+       /*
+        *      Add fields from the request back in,
+        *      deferring to user specified values.
+        */
+       dns_fields_restore(request, talloc_get_type_abort(mctx->rctx, process_dns_fields_t));
+
+       /*
+        *      Do this last, so we show everything
+        *      we'll be sending back.
+        */
+       dns_packet_debug(request, request->reply, &request->reply_pairs, false);
+
+       /*
+        *      Hack.  This is because this stupid framework uses
+        *      packet_type values to represent request and response
+        *      packet types, and DNS uses the same values for
+        *      both request and response packet types.
+        */
+       MEM(pair_update_reply(&vp, attr_packet_type) >= 1);
+       request->reply->code = vp->vp_uint8 = state->default_reply;
+
+       return CALL_RESUME(send_generic);
+}
+
+/** Entry point into the state machine
+ */
 static unlang_action_t mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
 {
        fr_process_state_t const *state;
@@ -170,30 +501,118 @@ static unlang_action_t mod_process(rlm_rcode_t *p_result, module_ctx_t const *mc
        return state->recv(p_result, mctx, request);
 }
 
+#define DNS_RCODE_COMMON \
+       .dns_rcode = { \
+               [RLM_MODULE_NOOP] = &enum_rcode_no_error, \
+               [RLM_MODULE_OK] = &enum_rcode_no_error, \
+               [RLM_MODULE_UPDATED] = &enum_rcode_no_error, \
+               [RLM_MODULE_HANDLED] = &enum_rcode_no_error, \
+               [RLM_MODULE_REJECT] = &enum_rcode_refused, \
+               [RLM_MODULE_FAIL] = &enum_rcode_server_failure, \
+               [RLM_MODULE_INVALID] = &enum_rcode_format_error, \
+               [RLM_MODULE_DISALLOW] = &enum_rcode_refused, \
+               [RLM_MODULE_NOTFOUND] = &enum_rcode_name_error \
+       }
 
-static const virtual_server_compile_t compile_list[] = {
-       {
-               .name = "recv",
-               .name2 = "Query",
-               .component = MOD_POST_AUTH,
-               .offset = PROCESS_CONF_OFFSET(request),
+static fr_process_state_t const process_state[] = {
+       [ FR_DNS_QUERY ] = {
+               DNS_RCODE_COMMON,
+               .default_reply = FR_DNS_QUERY_RESPONSE,
+               .rcode = RLM_MODULE_NOOP,
+               .recv = recv_request,
+               .resume = resume_recv_request,
+               .section_offset = PROCESS_CONF_OFFSET(query),
        },
-       {
-               .name = "send",
-               .name2 = "Query-Response",
-               .component = MOD_POST_AUTH,
-               .offset = PROCESS_CONF_OFFSET(reply),
+       [ FR_DNS_INVERSE_QUERY ] = {
+               DNS_RCODE_COMMON,
+               .default_reply = FR_DNS_INVERSE_QUERY_RESPONSE,
+               .rcode = RLM_MODULE_NOOP,
+               .recv = recv_request,
+               .resume = resume_recv_request,
+               .section_offset = PROCESS_CONF_OFFSET(inverse_query),
        },
-       {
-               .name = "send",
-               .name2 = "Do-Not-Respond",
-               .component = MOD_POST_AUTH,
-               .offset = PROCESS_CONF_OFFSET(do_not_respond),
+       [ FR_DNS_STATUS ] = {
+               DNS_RCODE_COMMON,
+               .default_reply = FR_DNS_STATUS_RESPONSE,
+               .rcode = RLM_MODULE_NOOP,
+               .recv = recv_request,
+               .resume = resume_recv_request,
+               .section_offset = PROCESS_CONF_OFFSET(status),
+       },
+       [ FR_DNS_UPDATE ] = {
+               DNS_RCODE_COMMON,
+               .default_reply = FR_DNS_UPDATE_RESPONSE,
+               .rcode = RLM_MODULE_NOOP,
+               .recv = recv_request,
+               .resume = resume_recv_request,
+               .section_offset = PROCESS_CONF_OFFSET(update),
+       },
+       [ FR_DNS_STATEFUL_OPERATION ] = {
+               DNS_RCODE_COMMON,
+               .default_reply = FR_DNS_STATEFUL_OPERATION_RESPONSE,
+               .rcode = RLM_MODULE_NOOP,
+               .recv = recv_request,
+               .resume = resume_recv_request,
+               .section_offset = PROCESS_CONF_OFFSET(stateful_operation),
+       },
+       [ FR_DNS_QUERY_RESPONSE ] = {
+               .default_reply = FR_DNS_QUERY,
+               .rcode = RLM_MODULE_NOOP,
+               .send = send_generic,
+               .resume = resume_send_response,
+               .section_offset = PROCESS_CONF_OFFSET(query_response),
        },
 
-       COMPILE_TERMINATOR
-};
+       [ FR_DNS_INVERSE_QUERY_RESPONSE ] = {
+               .default_reply = FR_DNS_QUERY,
+               .rcode = RLM_MODULE_NOOP,
+               .send = send_generic,
+               .resume = resume_send_response,
+               .section_offset = PROCESS_CONF_OFFSET(inverse_query_response),
+       },
+
+       [ FR_DNS_STATUS_RESPONSE ] = {
+               .default_reply = FR_DNS_STATUS,
+               .rcode = RLM_MODULE_NOOP,
+               .send = send_generic,
+               .resume = resume_send_response,
+               .section_offset = PROCESS_CONF_OFFSET(status_response),
+       },
+
+       [ FR_DNS_UPDATE_RESPONSE ] = {
+               .default_reply = FR_DNS_UPDATE,
+               .rcode = RLM_MODULE_NOOP,
+               .send = send_generic,
+               .resume = resume_send_response,
+               .section_offset = PROCESS_CONF_OFFSET(update_response),
+       },
+
+       [ FR_DNS_STATEFUL_OPERATION_RESPONSE ] = {
+               .default_reply = FR_DNS_STATEFUL_OPERATION,
+               .rcode = RLM_MODULE_NOOP,
+               .send = send_generic,
+               .resume = resume_send_response,
+               .section_offset = PROCESS_CONF_OFFSET(stateful_operation_response),
+       },
+
+       [ FR_DNS_DO_NOT_RESPOND ] = {
+               .packet_type = {
+                       [RLM_MODULE_NOOP] =     FR_DNS_DO_NOT_RESPOND,
+                       [RLM_MODULE_OK] =       FR_DNS_DO_NOT_RESPOND,
+                       [RLM_MODULE_UPDATED] =  FR_DNS_DO_NOT_RESPOND,
 
+                       [RLM_MODULE_REJECT] =   FR_DNS_DO_NOT_RESPOND,
+                       [RLM_MODULE_FAIL] =     FR_DNS_DO_NOT_RESPOND,
+                       [RLM_MODULE_INVALID] =  FR_DNS_DO_NOT_RESPOND,
+                       [RLM_MODULE_DISALLOW] = FR_DNS_DO_NOT_RESPOND,
+                       [RLM_MODULE_NOTFOUND] = FR_DNS_DO_NOT_RESPOND,
+               },
+               .rcode = RLM_MODULE_NOOP,
+               .send = send_generic,
+               .resume = resume_send_response,
+               .section_offset = PROCESS_CONF_OFFSET(do_not_respond),
+       },
+};
 
 extern fr_process_module_t process_dns;
 fr_process_module_t process_dns = {