DNS бодлогын модуль (v2)
Энэхүү Linux C ABI v2 жишээ нь DNS асуулгыг удирдах болон DNS хариуг солих аргыг
харуулна. libresolv-оор DNS мессежийг парс хийж, модулийн эзэмшлийн буферт
өөрчилсөн хариуг хадгална.
Жишээний ажиллагаа
| Асуулгын нэр | Үр дүн |
|---|---|
allow.test | Асуулгын хук гинжийг төгсгөж ердийн DNS хайлтыг үргэлжлүүлнэ. |
block.test | Дээд (upstream) серверээс хайлгүйгээр REFUSED буцаана. |
drop.test | Дээд хайлт эсвэл DNS хариу өгөлгүйгээр асуулгыг хаяна. |
redirect.test | A асуулгад 192.0.2.42, AAAA асуулгад 2001:db8::42-ийг TTL 60 секундээр буцаана. |
rewrite.test | Гарын үсэггүй хариуны A бичилтийн хаягийг 203.0.113.7 болгон өөрчилж, TTL-ийг 60 секунд болгож тохируулна. |
| Бусад нэрс | Дараагийн модуль болон ердийн боловсруулалтыг үргэлжлүүлнэ. |
allow.test болон rewrite.test-ийг туршилтын DNS сервер дээр тухайн нэрийн хариуг
өгөхөөр тохируулна уу. Жишээ код нь өөрөө дээд бичлэгүүдийг үүсгэхгүй.
rewrite.test-ийн хариуд тохирох A бичилт байхгүй эсвэл гарын үсгийн бичилт байвал
хариуг өөрчлөлгүй шууд буцаана.
ALLOW нь зөвхөн одоогийн хук гинжийг төгсгөнө. Домэйны дүрэм болон холболтын аргын шалгалтыг
тойрохгүй бөгөөд хариуны хук дээр үр дүн өөрчлөгдөж болно.
DNS үйлдлийн дүрмүүд-ийг харна уу.
Татаж авах болон бүтээх
policy.c болон mudfish_dns_module.h файлуудыг нэг директорт татаж авна уу. Толгой файлын заавар-аас бүтэн толгой файлыг хөтчөөрөө уншиж болно.
Linux дээр C хөрвүүлэгч (compiler), стандарт C сан болон libresolv-ийн хөгжүүлэлтийн толгой файлуудыг
суулгана уу. Татаж авсан директороос дараах тушаалыг ажиллуулна.
cc -std=c11 -shared -fPIC -Wall -Wextra -Werror -I. \
policy.c -lresolv -o policy.so
Модуль ачаалах зааврын дагуу policy.so-ийг суулгаад үйлчилгээг
дахин эхлүүлнэ үү. Асуулгыг тестлэхийн өмнө апп дээрээс DNS-ийг эхлүүлнэ үү.
Анхдагч DNS хүлээн авах хаягийг ашиглаж буй локал тест дээр дараах асуулгуудаар хаах болон хаягийн
хариу үүсгэлтийг шалгаж болно. dig суусан байх шаардлагатай.
dig @127.0.0.1 block.test A
dig @127.0.0.1 redirect.test A
dig @127.0.0.1 redirect.test AAAA
Эхний асуулга нь REFUSED, үлдсэн хоёр асуулга нь хүснэгтэд заасан хаягуудыг буцаах
ёстой. Хүлээн авах хаяг эсвэл портоо өөрчилсөн бол тушаалаа түүнд тохируулна уу.
drop.test асуулга нь зориудаар хариу өгөхгүй тул хугацаа хэтрэх алдаа гарна.
Ажиллах зарчим
Асуулга болон хариуны callback нь амжилттай үед 0-ийг буцаах бөгөөд үйлдлийг result->action-д
тохируулна. Үйлдлийн тогтмол утгыг шууд буцаахгүй.
Эхлүүлэх явцад модулийн контекстод хариуны буферийг хуваарилна. Хариуны хук нь зээлж
авсан хариуг энэ буферт хуулсны дараа засварлаж, destroy() дээр контекстийг чөлөөлнө.
Буфер эзэмшлийн дүрмүүд-ийн дагуу callback
буцаасны дараа ч орлуулах буфер хүчинтэй хэвээр хадгалагдана.
Хариуны хук нь гарын үсэгтэй мессежийг өөрчлөхгүй. DNSSEC өгөгдлийг баталгаажуулах эсвэл дахин гарын үсэг зурахгүй. Үүсгэсэн эсвэл орлуулсан хариуг хостын кэшид хадгалахгүй.
Бүрэн эх код
#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);
}