[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