Modul kebijakan DNS (v2)
Contoh Linux C ABI v2 ini menunjukkan cara mengendalikan kueri dan mengganti respons DNS. Pesan DNS diurai dengan libresolv, lalu respons yang diubah disimpan dalam buffer milik modul.
Perilaku contoh
| Nama kueri | Hasil |
|---|---|
allow.test | Mengakhiri rantai hook kueri lalu melanjutkan kueri DNS biasa. |
block.test | Mengembalikan REFUSED tanpa bertanya ke upstream. |
drop.test | Membuang kueri tanpa kueri upstream atau respons DNS. |
redirect.test | Mengembalikan 192.0.2.42 untuk A dan 2001:db8::42 untuk AAAA dengan TTL 60 detik. |
rewrite.test | Mengubah alamat record A pada respons tanpa tanda tangan menjadi 203.0.113.7, dengan TTL 60 detik. |
| Nama lain | Melanjutkan modul berikutnya dan pemrosesan biasa. |
Atur server DNS pengujian untuk menyediakan respons allow.test dan rewrite.test. Contoh tidak membuat record upstream. Jika respons rewrite.test tidak memiliki record A yang sesuai atau mengandung record tanda tangan, respons dikembalikan tanpa perubahan.
ALLOW hanya mengakhiri rantai hook saat ini. Tidak melewati aturan domain atau pemeriksaan transport, dan hook respons masih dapat mengubah hasil. Lihat Aturan tindakan DNS.
Unduhan dan build
Unduh policy.c dan mudfish_dns_module.h ke direktori yang sama. Header lengkap dapat dibaca di browser melalui Panduan header.
Pada Linux, pasang compiler C serta header pengembangan pustaka C standar dan libresolv. Jalankan dari direktori unduhan:
cc -std=c11 -shared -fPIC -Wall -Wextra -Werror -I. \
policy.c -lresolv -o policy.so
Pasang policy.so mengikuti Memuat modul, lalu mulai ulang layanan. Mulai DNS melalui aplikasi sebelum menguji kueri.
Pada pengujian lokal dengan alamat listener DNS bawaan, gunakan kueri berikut untuk memeriksa pemblokiran dan pembuatan respons alamat. dig harus terpasang.
dig @127.0.0.1 block.test A
dig @127.0.0.1 redirect.test A
dig @127.0.0.1 redirect.test AAAA
Kueri pertama harus mengembalikan REFUSED, dua lainnya alamat pada tabel. Jika alamat atau port listener diubah, sesuaikan perintah. Kueri drop.test sengaja tidak mendapat respons sehingga timeout.
Cara kerja
Callback kueri dan respons mengembalikan 0 saat berhasil dan mengatur tindakan pada result->action, bukan mengembalikan konstanta tindakan langsung.
Inisialisasi mengalokasikan buffer respons dalam konteks modul. Hook respons menyalin respons pinjaman ke buffer, lalu mengubahnya. destroy() membebaskan konteks. Sesuai Aturan kepemilikan buffer, buffer pengganti tetap valid setelah callback selesai.
Hook respons tidak mengubah pesan bertanda tangan dan tidak memvalidasi atau menandatangani ulang DNSSEC. Respons yang dibuat atau diganti tidak disimpan dalam cache host.
Sumber lengkap
#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);
}