DNS-policymodul (v2)
Detta Linux-exempel för C ABI v2 visar hur DNS-frågor styrs och svar ersätts. Det använder libresolv för att parsa DNS-meddelanden och lagrar ändrade svar i en buffert som modulen äger.
Exempelbeteende
| Frågenamn | Resultat |
|---|---|
allow.test | Avslutar frågehookkedjan och fortsätter vanlig DNS-uppslagning. |
block.test | Returnerar REFUSED utan fråga till uppströmsservern. |
drop.test | Kastar frågan utan uppströmsuppslagning eller DNS-svar. |
redirect.test | Returnerar 192.0.2.42 för A och 2001:db8::42 för AAAA, med TTL 60 sekunder. |
rewrite.test | Ändrar A-postens adress i ett osignerat svar till 203.0.113.7 och TTL till 60 sekunder. |
| Övriga namn | Fortsätter med nästa modul och normal behandling. |
Konfigurera en test-DNS-server så att den svarar för allow.test och rewrite.test. Exemplet skapar inte uppströmsposter. Om svaret för rewrite.test saknar en relevant A-post eller innehåller signaturposter returneras det oförändrat.
ALLOW avslutar bara den aktuella hookkedjan. Det kringgår inte domänregler eller transportkontroller, och svarshookar kan fortfarande ändra resultatet. Se DNS-åtgärdsregler.
Hämta och bygga
Hämta policy.c och mudfish_dns_module.h till samma katalog. Hela headerfilen kan läsas på headersidan.
Installera en C-kompilator samt utvecklingsheaderfiler för standardbiblioteket och libresolv på Linux. Kör i nedladdningskatalogen:
cc -std=c11 -shared -fPIC -Wall -Wextra -Werror -I. \
policy.c -lresolv -o policy.so
Installera policy.so enligt Ladda en modul och starta om tjänsten. Starta DNS i appen innan du testar frågor.
Med standardadressen för lokal DNS kan följande frågor kontrollera blockering och genererade adressvar. dig måste vara installerat.
dig @127.0.0.1 block.test A
dig @127.0.0.1 redirect.test A
dig @127.0.0.1 redirect.test AAAA
Den första frågan ska ge REFUSED; de två andra ska ge adresserna i tabellen. Anpassa kommandona om lyssningsadress eller port ändrats. drop.test ger avsiktligt inget svar och därför en timeout.
Så fungerar det
Fråge- och svarscallbackar returnerar 0 vid framgång och anger åtgärden i result->action. Åtgärdskonstanten returneras inte direkt.
Vid initiering allokeras en svarsbuffert i modulkontexten. Svarshooken kopierar det lånade svaret till bufferten och ändrar det. destroy() frigör kontexten. Ersättningsbufferten förblir giltig efter callbackens retur enligt reglerna för buffertägarskap.
Svarshooken ändrar inte signerade meddelanden och varken validerar eller signerar om DNSSEC. Genererade eller ersatta svar lagras inte i värdens cache.
Fullständig källkod
#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);
}