الانتقال إلى المحتوى الرئيسي

وحدة سياسة DNS ‏(v2)

يوضح مثال Linux C ABI v2 التحكم باستعلامات DNS واستبدال استجاباتها. يستخدم libresolv لتحليل رسائل DNS ويحفظ الاستجابة المعدلة في مخزن تملكه الوحدة.

سلوك المثال​

اسم الاستعلامالنتيجة
allow.testينهي سلسلة خطافات الاستعلام ويتابع استعلام DNS العادي.
block.testيعيد REFUSED دون الاستعلام من الخادم العلوي.
drop.testيسقط الاستعلام دون طلب علوي أو استجابة DNS.
redirect.testيعيد 192.0.2.42 لطلب A و2001:db8::42 لطلب AAAA مع TTL قدره 60 ثانية.
rewrite.testيغيّر عنوان سجل A في استجابة غير موقعة إلى 203.0.113.7 ويضبط TTL على 60 ثانية.
الأسماء الأخرىيتابع الوحدة التالية والمعالجة العادية.

اضبط خادم DNS الاختباري لتقديم استجابات لـallow.test وrewrite.test؛ لا ينشئ المثال سجلات على الخادم العلوي. إذا لم يوجد سجل A مناسب في استجابة rewrite.test، أو وُجدت سجلات توقيع، تُعاد الاستجابة دون تغيير.

ينهي ALLOW سلسلة الخطافات الحالية فقط، ولا يتجاوز قواعد النطاقات أو فحوص النقل، وقد تغيّر خطافات الاستجابة النتيجة. راجع قواعد إجراءات DNS.

التنزيل والبناء​

نزّل policy.c وmudfish_dns_module.h في المجلد نفسه. يمكنك قراءة الترويسة كاملةً في دليل الترويسة.

ثبّت على Linux مترجم C وترويسات التطوير لمكتبة 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 عمدًا لأنه لا يرسل استجابة.

طريقة العمل​

تُعيد استدعاءات الاستعلام والاستجابة صفرًا عند النجاح، وتضع الإجراء في result->action بدلًا من إعادة ثابت الإجراء مباشرةً.

تخصص التهيئة مخزن استجابة في سياق الوحدة. ينسخ خطاف الاستجابة البيانات المستعارة إليه ثم يعدّلها، وتحرر destroy() السياق. وفق قواعد ملكية المخازن، يبقى المخزن البديل صالحًا بعد عودة الاستدعاء.

لا يغيّر خطاف الاستجابة الرسائل الموقعة، ولا يتحقق من بيانات DNSSEC أو يعيد توقيعها. لا تُخزّن الاستجابات المنشأة أو المستبدلة في ذاكرة المضيف المؤقتة.

المصدر الكامل​

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