Ga naar hoofdinhoud

DNS-beleidsmodule (v2)

Dit Linux-voorbeeld met C ABI v2 toont hoe je DNS-verzoeken beheert en antwoorden vervangt. Het gebruikt libresolv om DNS-berichten te parsen en bewaart gewijzigde antwoorden in een buffer die eigendom van de module is.

Gedrag van het voorbeeld​

Opgevraagde naamResultaat
allow.testBeëindigt de verzoekhookketen en vervolgt de normale DNS-opzoeking.
block.testGeeft REFUSED terug zonder de upstreamserver te raadplegen.
drop.testVerwerpt het verzoek zonder upstreamopzoeking of DNS-antwoord.
redirect.testGeeft 192.0.2.42 terug voor A en 2001:db8::42 voor AAAA, met een TTL van 60 seconden.
rewrite.testWijzigt A-recordadressen in niet-ondertekende antwoorden naar 203.0.113.7 en stelt de TTL in op 60 seconden.
Andere namenVervolgt met de volgende module en normale verwerking.

Stel je test-DNS-server in om antwoorden voor allow.test en rewrite.test te leveren. Het voorbeeld maakt geen upstreamrecords aan. Als het antwoord voor rewrite.test geen passend A-record bevat of wel handtekeningrecords bevat, wordt het ongewijzigd teruggegeven.

ALLOW beëindigt alleen de huidige hookketen. Domeinregels en transportcontroles worden niet omzeild; antwoordhooks kunnen het resultaat nog wijzigen. Zie DNS-acties.

Downloaden en bouwen​

Download policy.c en mudfish_dns_module.h naar dezelfde map. Je kunt de volledige header in de browser lezen op de headerpagina.

Installeer op Linux een C-compiler en de ontwikkelheaders van de standaard-C-bibliotheek en libresolv. Voer in de downloadmap uit:

cc -std=c11 -shared -fPIC -Wall -Wextra -Werror -I. \
policy.c -lresolv -o policy.so

Installeer policy.so volgens Een module laden en herstart de service. Start DNS in de app voordat je verzoeken test.

Bij een lokale test met het standaardluisteradres kun je blokkering en adresantwoorden met de volgende verzoeken controleren. Hiervoor moet dig geïnstalleerd zijn.

dig @127.0.0.1 block.test A
dig @127.0.0.1 redirect.test A
dig @127.0.0.1 redirect.test AAAA

Het eerste verzoek moet REFUSED opleveren; de andere twee de adressen uit de tabel. Pas de opdrachten aan als je het luisteradres of de poort hebt veranderd. drop.test krijgt bewust geen antwoord en veroorzaakt dus een time-out.

Werking​

Verzoek- en antwoordcallbacks geven bij succes 0 terug en stellen de actie in via result->action. Ze geven geen actieconstante rechtstreeks terug.

Bij initialisatie wordt een antwoordbuffer in de modulecontext gereserveerd. De antwoordhook kopieert het geleende antwoord naar deze buffer en wijzigt het daar. destroy() geeft de context vrij. Volgens de regels voor buffereigendom blijft de vervangende buffer na terugkeer van de callback geldig.

De antwoordhook wijzigt geen ondertekende berichten en valideert of herondertekent geen DNSSEC-gegevens. Gegenereerde of vervangen antwoorden worden niet in de hostcache opgeslagen.

Volledige broncode​

policy.c
#include "mudfish_dns_module.h"

#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

struct policy {
uint8_t response[65535];
};

static int
question(const struct mudfish_dns_event *event, ns_rr *rr)
{
ns_msg message;

if (event->query.len > 65535 ||
ns_initparse(event->query.data, (int)event->query.len, &message) < 0)
return (-1);
return (ns_parserr(&message, ns_s_qd, 0, rr));
}

static int32_t
dns_query(void *context, const struct mudfish_dns_event *event,
struct mudfish_dns_result *result)
{
ns_rr rr;

(void)context;
if (question(event, &rr) < 0)
return (-1);
if (strcasecmp(ns_rr_name(rr), "allow.test") == 0)
result->action = MUDFISH_DNS_ALLOW;
else if (strcasecmp(ns_rr_name(rr), "block.test") == 0)
result->action = MUDFISH_DNS_BLOCK;
else if (strcasecmp(ns_rr_name(rr), "drop.test") == 0)
result->action = MUDFISH_DNS_DROP;
else if (strcasecmp(ns_rr_name(rr), "redirect.test") == 0) {
result->action = MUDFISH_DNS_ANSWER;
result->ttl = 60;
if (ns_rr_type(rr) == ns_t_aaaa) {
result->address_family = MUDFISH_DNS_IPV6;
inet_pton(AF_INET6, "2001:db8::42", result->address);
} else {
result->address_family = MUDFISH_DNS_IPV4;
inet_pton(AF_INET, "192.0.2.42", result->address);
}
}
return (0);
}

static int32_t
dns_response(void *context, const struct mudfish_dns_event *event,
struct mudfish_dns_result *result)
{
struct policy *policy = context;
ns_msg message;
ns_rr rr;
uint8_t *rdata;
int section, i, changed = 0;

if (question(event, &rr) < 0)
return (-1);
if (strcasecmp(ns_rr_name(rr), "rewrite.test") != 0)
return (0);
if (event->response.len > sizeof(policy->response))
return (-1);
memcpy(policy->response, event->response.data, event->response.len);
if (ns_initparse(policy->response, (int)event->response.len, &message) < 0)
return (-1);
/* This example only edits unsigned answers. A policy that edits signed
* data must also rebuild/remove the affected signatures.
*/
for (section = ns_s_an; section <= ns_s_ar; section++) {
for (i = 0; i < ns_msg_count(message, section); i++) {
if (ns_parserr(&message, (ns_sect)section, i, &rr) < 0)
return (-1);
if (ns_rr_type(rr) == ns_t_rrsig || ns_rr_type(rr) == ns_t_sig ||
ns_rr_type(rr) == ns_t_tsig)
return (0);
}
}
for (i = 0; i < ns_msg_count(message, ns_s_an); i++) {
if (ns_parserr(&message, ns_s_an, i, &rr) < 0)
return (-1);
if (ns_rr_class(rr) != ns_c_in || ns_rr_type(rr) != ns_t_a ||
ns_rr_rdlen(rr) != 4)
continue;
/* rdata belongs to our copy. TTL precedes RDLENGTH (2 bytes). */
rdata = policy->response + (ns_rr_rdata(rr) - policy->response);
inet_pton(AF_INET, "203.0.113.7", rdata);
ns_put32(60, rdata - 6);
changed = 1;
}
if (changed) {
result->action = MUDFISH_DNS_REPLACE;
result->response.data = policy->response;
result->response.len = event->response.len;
}
return (0);
}

static void
destroy(void *context)
{

free(context);
}

int32_t
mudfish_dns_module_init_v2(uint32_t abi_version, size_t module_size,
struct mudfish_dns_module_v2 *module)
{

if (abi_version != MUDFISH_DNS_MODULE_ABI_V2 || module_size != sizeof(*module))
return (-1);
module->context = calloc(1, sizeof(struct policy));
if (module->context == NULL)
return (-1);
module->on_dns_query = dns_query;
module->on_dns_response = dns_response;
module->destroy = destroy;
return (0);
}