mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 18:28:25 +00:00
Connection track mark based DNS query filtering.
This extends query filtering support beyond what is currently possible with the `--ipset` configuration option, by adding support for: 1) Specifying allowlists on a per-client basis, based on their associated Linux connection track mark. 2) Dynamic configuration of allowlists via Ubus. 3) Reporting when a DNS query resolves or is rejected via Ubus. 4) DNS name patterns containing wildcards. Disallowed queries are not forwarded; they are rejected with a REFUSED error code. Signed-off-by: Etan Kissling <etan_kissling@apple.com> (addressed reviewer feedback) Signed-off-by: Etan Kissling <etan.kissling@gmail.com>
This commit is contained in:
committed by
Simon Kelley
parent
cbd76447fd
commit
627056febb
145
src/forward.c
145
src/forward.c
@@ -531,6 +531,16 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
if (oph)
|
||||
plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
|
||||
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
if (option_bool(OPT_CMARK_ALST_EN))
|
||||
{
|
||||
unsigned int mark;
|
||||
int have_mark = get_incoming_mark(udpaddr, dst_addr, /* istcp: */ 0, &mark);
|
||||
if (have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
report_addresses(header, plen, mark);
|
||||
}
|
||||
#endif
|
||||
|
||||
send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface);
|
||||
}
|
||||
|
||||
@@ -1152,6 +1162,16 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
|
||||
dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
if (option_bool(OPT_CMARK_ALST_EN))
|
||||
{
|
||||
unsigned int mark;
|
||||
int have_mark = get_incoming_mark(&src->source, &src->dest, /* istcp: */ 0, &mark);
|
||||
if (have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
report_addresses(header, nn, mark);
|
||||
}
|
||||
#endif
|
||||
|
||||
send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
|
||||
&src->source, &src->dest, src->iface);
|
||||
|
||||
@@ -1168,6 +1188,47 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
|
||||
}
|
||||
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
static int is_query_allowed_for_mark(u32 mark, const char *name)
|
||||
{
|
||||
int is_allowable_name, did_validate_name = 0;
|
||||
struct allowlist *allowlists;
|
||||
char **patterns_pos;
|
||||
|
||||
for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
|
||||
if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask))
|
||||
for (patterns_pos = allowlists->patterns; *patterns_pos; patterns_pos++)
|
||||
{
|
||||
if (!strcmp(*patterns_pos, "*"))
|
||||
return 1;
|
||||
if (!did_validate_name)
|
||||
{
|
||||
is_allowable_name = name ? is_valid_dns_name(name) : 0;
|
||||
did_validate_name = 1;
|
||||
}
|
||||
if (is_allowable_name && is_dns_name_matching_pattern(name, *patterns_pos))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t answer_disallowed(struct dns_header *header, size_t qlen, u32 mark, const char *name)
|
||||
{
|
||||
unsigned char *p;
|
||||
|
||||
#ifdef HAVE_UBUS
|
||||
if (name)
|
||||
ubus_event_bcast_connmark_allowlist_refused(mark, name);
|
||||
#endif
|
||||
|
||||
setup_reply(header, /* flags: */ 0);
|
||||
|
||||
if (!(p = skip_questions(header, qlen)))
|
||||
return 0;
|
||||
return p - (unsigned char *)header;
|
||||
}
|
||||
#endif
|
||||
|
||||
void receive_query(struct listener *listen, time_t now)
|
||||
{
|
||||
struct dns_header *header = (struct dns_header *)daemon->packet;
|
||||
@@ -1179,6 +1240,11 @@ void receive_query(struct listener *listen, time_t now)
|
||||
size_t m;
|
||||
ssize_t n;
|
||||
int if_index = 0, auth_dns = 0, do_bit = 0, have_pseudoheader = 0;
|
||||
#ifdef HAVE_CONNTRACK
|
||||
unsigned int mark = 0;
|
||||
int have_mark = 0;
|
||||
int is_single_query = 0, allowed = 1;
|
||||
#endif
|
||||
#ifdef HAVE_AUTH
|
||||
int local_auth = 0;
|
||||
#endif
|
||||
@@ -1407,6 +1473,11 @@ void receive_query(struct listener *listen, time_t now)
|
||||
#ifdef HAVE_DUMPFILE
|
||||
dump_packet(DUMP_QUERY, daemon->packet, (size_t)n, &source_addr, NULL);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
if (option_bool(OPT_CMARK_ALST_EN))
|
||||
have_mark = get_incoming_mark(&source_addr, &dst_addr, /* istcp: */ 0, &mark);
|
||||
#endif
|
||||
|
||||
if (extract_request(header, (size_t)n, daemon->namebuff, &type))
|
||||
{
|
||||
@@ -1417,6 +1488,10 @@ void receive_query(struct listener *listen, time_t now)
|
||||
|
||||
log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff,
|
||||
&source_addr, types);
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
is_single_query = 1;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_AUTH
|
||||
/* find queries for zones we're authoritative for, and answer them directly */
|
||||
@@ -1458,20 +1533,47 @@ void receive_query(struct listener *listen, time_t now)
|
||||
udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */
|
||||
}
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
#ifdef HAVE_AUTH
|
||||
if (auth_dns)
|
||||
if (!auth_dns || local_auth)
|
||||
#endif
|
||||
if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
allowed = is_query_allowed_for_mark((u32)mark, is_single_query ? daemon->namebuff : NULL);
|
||||
#endif
|
||||
|
||||
if (0);
|
||||
#ifdef HAVE_CONNTRACK
|
||||
else if (!allowed)
|
||||
{
|
||||
m = answer_disallowed(header, (size_t)n, (u32)mark, is_single_query ? daemon->namebuff : NULL);
|
||||
|
||||
if (m >= 1)
|
||||
{
|
||||
send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
|
||||
(char *)header, m, &source_addr, &dst_addr, if_index);
|
||||
daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_AUTH
|
||||
else if (auth_dns)
|
||||
{
|
||||
m = answer_auth(header, ((char *) header) + udp_size, (size_t)n, now, &source_addr,
|
||||
local_auth, do_bit, have_pseudoheader);
|
||||
if (m >= 1)
|
||||
{
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
if (local_auth)
|
||||
if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
report_addresses(header, m, mark);
|
||||
#endif
|
||||
send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
|
||||
(char *)header, m, &source_addr, &dst_addr, if_index);
|
||||
daemon->metrics[METRIC_DNS_AUTH_ANSWERED]++;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
else
|
||||
{
|
||||
int ad_reqd = do_bit;
|
||||
/* RFC 6840 5.7 */
|
||||
@@ -1483,6 +1585,10 @@ void receive_query(struct listener *listen, time_t now)
|
||||
|
||||
if (m >= 1)
|
||||
{
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
report_addresses(header, m, mark);
|
||||
#endif
|
||||
send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
|
||||
(char *)header, m, &source_addr, &dst_addr, if_index);
|
||||
daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
|
||||
@@ -1689,6 +1795,9 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
{
|
||||
size_t size = 0;
|
||||
int norebind;
|
||||
#ifdef HAVE_CONNTRACK
|
||||
int is_single_query = 0, allowed = 1;
|
||||
#endif
|
||||
#ifdef HAVE_AUTH
|
||||
int local_auth = 0;
|
||||
#endif
|
||||
@@ -1720,7 +1829,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
/* Get connection mark of incoming query to set on outgoing connections. */
|
||||
if (option_bool(OPT_CONNTRACK))
|
||||
if (option_bool(OPT_CONNTRACK) || option_bool(OPT_CMARK_ALST_EN))
|
||||
{
|
||||
union all_addr local;
|
||||
|
||||
@@ -1800,6 +1909,10 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff,
|
||||
&peer_addr, types);
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
is_single_query = 1;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_AUTH
|
||||
/* find queries for zones we're authoritative for, and answer them directly */
|
||||
if (!auth_dns && !option_bool(OPT_LOCALISE))
|
||||
@@ -1833,13 +1946,26 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
if (flags & 0x8000)
|
||||
do_bit = 1; /* do bit */
|
||||
}
|
||||
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
#ifdef HAVE_AUTH
|
||||
if (auth_dns)
|
||||
if (!auth_dns || local_auth)
|
||||
#endif
|
||||
if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
allowed = is_query_allowed_for_mark((u32)mark, is_single_query ? daemon->namebuff : NULL);
|
||||
#endif
|
||||
|
||||
if (0);
|
||||
#ifdef HAVE_CONNTRACK
|
||||
else if (!allowed)
|
||||
m = answer_disallowed(header, size, (u32)mark, is_single_query ? daemon->namebuff : NULL);
|
||||
#endif
|
||||
#ifdef HAVE_AUTH
|
||||
else if (auth_dns)
|
||||
m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr,
|
||||
local_auth, do_bit, have_pseudoheader);
|
||||
else
|
||||
#endif
|
||||
else
|
||||
{
|
||||
int ad_reqd = do_bit;
|
||||
/* RFC 6840 5.7 */
|
||||
@@ -1965,6 +2091,13 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
|
||||
*length = htons(m);
|
||||
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
#ifdef HAVE_AUTH
|
||||
if (!auth_dns || local_auth)
|
||||
#endif
|
||||
if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
report_addresses(header, m, mark);
|
||||
#endif
|
||||
if (!read_write(confd, packet, m + sizeof(u16), 0))
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user