跳至主要内容

DNS 策略模組 (v2)

此 Linux C ABI v2 範例顯示如何控制 DNS 查詢並取代 DNS 回應。使用 libresolv 解析 DNS 訊息,並將修改後的回應儲存在模組擁有的緩衝區中。

行為範例​

查詢名稱結果
allow.test結束 查詢掛鉤鏈並繼續正常的 DNS 查詢。
block.test傳回 REFUSED 而不查詢上游伺服器。
drop.test丟棄沒有上游查詢或 DNS 回應的查詢。
redirect.testA 返回 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 編譯器和標準 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 查詢故意不發送回應,導致逾時。

它是如何運作的​

查詢和回應回呼 成功時傳回 0 並將操作設定為 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);
}