Aller au contenu principal

Module de politique DNS (v2)

Cet exemple Linux d’ABI C v2 montre comment contrôler les requêtes DNS et remplacer les réponses. Il utilise libresolv pour analyser les messages et conserve la réponse modifiée dans un tampon appartenant au module.

Comportement de l’exemple​

Nom demandéRésultat
allow.testTermine la chaîne de hooks de requête et poursuit la résolution DNS normale.
block.testRenvoie REFUSED sans interroger le serveur amont.
drop.testAbandonne la requête sans interrogation amont ni réponse DNS.
redirect.testRenvoie 192.0.2.42 pour A et 2001:db8::42 pour AAAA, avec un TTL de 60 secondes.
rewrite.testRemplace l’adresse des enregistrements A d’une réponse non signée par 203.0.113.7 et fixe le TTL à 60 secondes.
Autres nomsPoursuit avec le module suivant et le traitement normal.

Configurez votre serveur DNS de test pour qu’il fournisse des réponses à allow.test et rewrite.test. L’exemple ne crée pas d’enregistrements amont. Si la réponse à rewrite.test ne contient aucun enregistrement A concerné ou contient des signatures, elle est renvoyée sans modification.

ALLOW termine uniquement la chaîne courante. Il ne contourne pas les règles de domaine ni les contrôles de transport ; les hooks de réponse peuvent encore modifier le résultat. Consultez les règles des actions DNS.

Téléchargement et compilation​

Téléchargez policy.c et mudfish_dns_module.h dans le même dossier. La page de l’en-tête permet de lire son contenu complet dans le navigateur.

Sous Linux, installez un compilateur C et les en-têtes de développement de la bibliothèque C standard et de libresolv. Exécutez la commande suivante dans le dossier de téléchargement :

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

Installez policy.so selon les instructions de chargement des modules, puis redémarrez le service. Démarrez le DNS depuis l’application avant les tests.

Pour un test local utilisant l’adresse d’écoute DNS par défaut, vérifiez le blocage et la génération de réponses avec les requêtes suivantes. dig doit être installé.

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

La première requête doit renvoyer REFUSED et les deux suivantes les adresses du tableau. Adaptez les commandes si vous avez changé l’adresse ou le port d’écoute. Une requête drop.test expire volontairement, puisqu’aucune réponse n’est envoyée.

Fonctionnement​

Les callbacks de requête et de réponse retournent 0 en cas de succès et définissent l’action dans result->action. Ils ne retournent pas directement une constante d’action.

L’initialisation alloue un tampon de réponse dans le contexte du module. Le hook de réponse copie la réponse empruntée dans ce tampon, puis la modifie. destroy() libère le contexte. Conformément aux règles de propriété des tampons, le tampon de remplacement reste valide après le retour du callback.

Le hook de réponse ne modifie pas les messages signés. Il ne valide ni ne signe à nouveau les données DNSSEC. Les réponses générées ou remplacées ne sont pas mises dans le cache de l’hôte.

Code complet​

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);
}