[Dnsmasq-discuss] [PATCH v4] Connection track mark based DNS query filtering.
Geert Stappers
stappers at stappers.nl
Wed Feb 17 22:10:44 UTC 2021
On Sat, Jan 30, 2021 at 07:36:50PM +0100, Etan Kissling wrote:
> 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 at apple.com>
> ---
> v2: Rebase to v2.83, and fix compilation when HAVE_UBUS not present.
> v3: Rebase to v2.84test2.
> v4: Rebase to v2.84rc2 (update copyright notice).
>
> Makefile | 2 +-
> man/dnsmasq.8 | 31 +++-
> src/dnsmasq.h | 25 +++-
> src/forward.c | 121 +++++++++++++++-
> src/option.c | 134 ++++++++++++++++++
> src/pattern.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++
> src/rfc1035.c | 82 +++++++++++
> src/ubus.c | 182 ++++++++++++++++++++++++
> 8 files changed, 955 insertions(+), 8 deletions(-)
> create mode 100644 src/pattern.c
>
> diff --git a/Makefile b/Makefile
> index e4c3f5c..506e56b 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -79,7 +79,7 @@ copts_conf = .copts_$(sum)
> objs = cache.o rfc1035.o util.o option.o forward.o network.o \
> dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
> helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
> - dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
> + dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o pattern.o \
> domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \
> poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o \
> metrics.o hash_questions.o
> diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
> index ac7c9fa..04d666d 100644
> --- a/man/dnsmasq.8
> +++ b/man/dnsmasq.8
> @@ -368,7 +368,10 @@ provides service at that name, rather than the default which is
> .TP
> .B --enable-ubus[=<service-name>]
> Enable dnsmasq UBus interface. It sends notifications via UBus on
> -DHCPACK and DHCPRELEASE events. Furthermore it offers metrics.
> +DHCPACK and DHCPRELEASE events. Furthermore it offers metrics
> +and allows configuration of Linux connection track mark based filtering.
> +When DNS query filtering based on Linux connection track marks is enabled
> +UBus notifications are generated for each resolved or filtered DNS query.
> Requires that dnsmasq has been built with UBus support. If the service
> name is given, dnsmasq provides service at that namespace, rather than
> the default which is
> @@ -533,6 +536,32 @@ These IP sets must already exist. See
> .BR ipset (8)
> for more details.
> .TP
> +.B --connmark-allowlist-enable[=<mask>]
> +Enables filtering of incoming DNS queries with associated Linux connection track marks
> +according to individual allowlists configured via a series of \fB--connmark-allowlist\fP
> +options. Disallowed queries are not forwarded; they are rejected with a REFUSED error code.
> +DNS queries are only allowed if they do not have an associated Linux connection
> +track mark, or if the queried domains match the configured DNS patterns for the
> +associated Linux connection track mark. If no allowlist is configured for a
> +Linux connection track mark, all DNS queries associated with that mark are rejected.
> +If a mask is specified, Linux connection track marks are first bitwise ANDed
> +with the given mask before being processed.
> +.TP
> +.B --connmark-allowlist=<connmark>[/<mask>][,<pattern>[/<pattern>...]]
> +Configures the DNS patterns that are allowed in DNS queries associated with
> +the given Linux connection track mark.
> +If a mask is specified, Linux connection track marks are first bitwise ANDed
> +with the given mask before they are compared to the given connection track mark.
> +Patterns follow the syntax of DNS names, but additionally allow the wildcard
> +character "*" to be used up to twice per label to match 0 or more characters
> +within that label. Note that the wildcard never matches a dot (e.g., "*.example.com"
> +matches "api.example.com" but not "api.us.example.com"). Patterns must be
> +fully qualified, i.e., consist of at least two labels. The final label must not be
> +fully numeric, and must not be the "local" pseudo-TLD. A pattern must end with at least
> +two literal (non-wildcard) labels.
> +Instead of a pattern, "*" can be specified to disable allowlist filtering
> +for a given Linux connection track mark entirely.
> +.TP
> .B \-m, --mx-host=<mx name>[[,<hostname>],<preference>]
> Return an MX record named <mx name> pointing to the given hostname (if
> given), or
> diff --git a/src/dnsmasq.h b/src/dnsmasq.h
> index e770454..b48e433 100644
> --- a/src/dnsmasq.h
> +++ b/src/dnsmasq.h
> @@ -273,7 +273,8 @@ struct event_desc {
> #define OPT_IGNORE_CLID 59
> #define OPT_SINGLE_PORT 60
> #define OPT_LEASE_RENEW 61
> -#define OPT_LAST 62
> +#define OPT_CMARK_ALST_EN 62
> +#define OPT_LAST 63
>
> #define OPTION_BITS (sizeof(unsigned int)*8)
> #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
> @@ -567,6 +568,12 @@ struct ipsets {
> struct ipsets *next;
> };
>
> +struct allowlist {
> + uint32_t mark, mask;
> + char **patterns;
> + struct allowlist *next;
> +};
> +
I think the missing '# ifdef HAVE_CONNTRACK' will trigger "unused struct" warnings ...
> struct irec {
> union mysockaddr addr;
> struct in_addr netmask; /* only valid for IPv4 */
> @@ -1042,6 +1049,8 @@ extern struct daemon {
> struct bogus_addr *bogus_addr, *ignore_addr;
> struct server *servers;
> struct ipsets *ipsets;
> + uint32_t allowlist_mask;
I think the missing '# ifdef HAVE_CONNTRACK' will trigger "unused uint32_t" warnings ...
> + struct allowlist *allowlists;
> int log_fac; /* log facility */
> char *log_file; /* optional log file */
> int max_logs; /* queue limit */
> @@ -1231,6 +1240,9 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
> int extract_addresses(struct dns_header *header, size_t qlen, char *name,
> time_t now, char **ipsets, int is_sign, int check_rebind,
> int no_cache_dnssec, int secure, int *doctored);
> +#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
One of many
> +void report_addresses(struct dns_header *header, size_t len, uint32_t mark);
> +#endif
> size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
> struct in_addr local_addr, struct in_addr local_netmask,
> time_t now, int ad_reqd, int do_bit, int have_pseudoheader);
> @@ -1504,6 +1516,10 @@ void ubus_init(void);
> void set_ubus_listeners(void);
> void check_ubus_listeners(void);
> void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface);
> +# ifdef HAVE_CONNTRACK
One of many
> +void ubus_event_bcast_connmark_allowlist_refused(uint32_t mark, const char *name);
> +void ubus_event_bcast_connmark_allowlist_resolved(uint32_t mark, const char *pattern, const char *ip, uint32_t ttl);
> +# endif
> #endif
>
> /* ipset.c */
> @@ -1512,6 +1528,13 @@ void ipset_init(void);
> int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove);
> #endif
>
> +/* pattern.c */
> +#ifdef HAVE_CONNTRACK
One of many
> +int is_valid_dns_name(const char *value);
> +int is_valid_dns_name_pattern(const char *value);
> +int is_dns_name_matching_pattern(const char *name, const char *pattern);
> +#endif
> +
> /* helper.c */
> #if defined(HAVE_SCRIPT)
> int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd);
> diff --git a/src/forward.c b/src/forward.c
> index 8fb0327..3ca94ca 100644
> --- a/src/forward.c
> +++ b/src/forward.c
> @@ -614,6 +614,15 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
> plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl);
> if (oph)
> plen = add_pseudoheader(header, plen, ((unsigned char *) header) + PACKETSZ, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
> +#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
One of many
> + 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 && ((uint32_t) 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);
> }
>
.... snip .....
> +#ifdef HAVE_CONNTRACK
> + else if (!allowed)
> + {
> + m = setup_reply(header, n, /* addrp: */ NULL, /* flags: */ 0, /* ttl: */ 0);
> + 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
> - if (auth_dns)
> + else if (auth_dns)
That extra else feels odd.
> {
> 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 (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) 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
That swap of lines feels odd.
..... snip ....
Do know that it is _not_ up to me to decide on this patch.
Thing I'm saying is that it got some human attention.
Regards
Geert Stappers
Usual suspect on this mailinglist.
--
Silence is hard to parse
More information about the Dnsmasq-discuss
mailing list