[Dnsmasq-discuss] [PATCH v3] openwrt: Integration of connmark based DNS filtering

Etan Kissling etan.kissling at gmail.com
Wed May 5 08:29:51 UTC 2021


This integrates the proposed Dnsmasq patch from email:
- [PATCH v5] dnsmasq: connection track mark based DNS query filtering
into OpenWrt 21.02.

Signed-off-by: Etan Kissling <etan_kissling at apple.com>
(updated to latest patch)
Signed-off-by: Etan Kissling <etan.kissling at gmail.com>
---
Etan Kissling <etan.kissling at gmail.com>:
v2: Update to v6 of underlying dnsmasq patch.
v3: Update to v7 of underlying dnsmasq patch.

 .../services/dnsmasq/files/dnsmasq.init       |   12 +
 ...track-mark-based-DNS-query-filtering.patch | 1320 +++++++++++++++++
 2 files changed, 1332 insertions(+)
 create mode 100644 package/network/services/dnsmasq/patches/300-Connection-track-mark-based-DNS-query-filtering.patch

diff --git a/package/network/services/dnsmasq/files/dnsmasq.init b/package/network/services/dnsmasq/files/dnsmasq.init
index 680e72f..b46988f 100644
--- a/package/network/services/dnsmasq/files/dnsmasq.init
+++ b/package/network/services/dnsmasq/files/dnsmasq.init
@@ -172,6 +172,10 @@ append_ipset() {
 	xappend "--ipset=$1"
 }
 
+append_connmark_allowlist() {
+	xappend "--connmark-allowlist=$1"
+}
+
 append_interface() {
 	network_get_device ifname "$1" || ifname="$1"
 	xappend "--interface=$ifname"
@@ -913,6 +917,14 @@ dnsmasq_start()
 	config_list_foreach "$cfg" "rev_server" append_rev_server
 	config_list_foreach "$cfg" "address" append_address
 	config_list_foreach "$cfg" "ipset" append_ipset
+
+	local connmark_allowlist_enable
+	config_get connmark_allowlist_enable "$cfg" connmark_allowlist_enable 0
+	[ "$connmark_allowlist_enable" -gt 0 ] && {
+		append_parm "$cfg" "connmark_allowlist_enable" "--connmark-allowlist-enable"
+		config_list_foreach "$cfg" "connmark_allowlist" append_connmark_allowlist
+	}
+
 	[ -n "$BOOT" ] || {
 		config_list_foreach "$cfg" "interface" append_interface
 		config_list_foreach "$cfg" "notinterface" append_notinterface
diff --git a/package/network/services/dnsmasq/patches/300-Connection-track-mark-based-DNS-query-filtering.patch b/package/network/services/dnsmasq/patches/300-Connection-track-mark-based-DNS-query-filtering.patch
new file mode 100644
index 0000000..bb3533e
--- /dev/null
+++ b/package/network/services/dnsmasq/patches/300-Connection-track-mark-based-DNS-query-filtering.patch
@@ -0,0 +1,1320 @@
+From 462371dc003ac17f2fb36935c68314063e2c5dfd Mon Sep 17 00:00:00 2001
+From: Etan Kissling <etan_kissling at apple.com>
+Date: Tue, 12 Jan 2021 10:51:21 +0100
+Subject: [PATCH v7] 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 at apple.com>
+(addressed reviewer feedback)
+Signed-off-by: Etan Kissling <etan.kissling at gmail.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).
+v5: Correct logging of `ubus_notify` errors (also in existing code).
+
+Etan Kissling <etan.kissling at gmail.com>:
+v6: Integrate checks for weird queries into `extract_request`.
+    Skip Ubus reporting when daemon->namebuff is not initialized.
+    Fix options parsing for mark / mask with bit 31 set.
+    Disable filtering for external queries (`auth_dns && !local_auth`).
+    Report all CNAME RRs via Ubus instead of just a (potential) subset.
+    Avoid redundant `is_valid_dns_name` evaluations.
+    Unify DNS name pattern matching logic across transports (UDP / TCP).
+v7: Fix typos and adjust code style to project.
+
+ Makefile      |   2 +-
+ man/dnsmasq.8 |  31 +++-
+ src/dnsmasq.h |  25 +++-
+ src/forward.c | 134 +++++++++++++++++-
+ src/option.c  | 151 ++++++++++++++++++++
+ src/pattern.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ src/rfc1035.c |  77 +++++++++-
+ src/ubus.c    | 184 +++++++++++++++++++++++-
+ 8 files changed, 980 insertions(+), 10 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;
++};
++
+ 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;
++  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)
++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
++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
++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..61592a8 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)
++      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);
+     }
+ 
+@@ -1301,6 +1310,15 @@ void reply_query(int fd, int family, time_t now)
+ 	      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 && ((uint32_t) 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);
+ 
+@@ -1318,6 +1336,31 @@ void reply_query(int fd, int family, time_t now)
+ }
+ 
+ 
++#ifdef HAVE_CONNTRACK
++static int is_query_allowed_for_mark(uint32_t 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;
++}
++#endif
++
+ void receive_query(struct listener *listen, time_t now)
+ {
+   struct dns_header *header = (struct dns_header *)daemon->packet;
+@@ -1329,6 +1372,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
+@@ -1557,6 +1605,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))
+     {
+@@ -1571,6 +1624,10 @@ void receive_query(struct listener *listen, time_t now)
+       else
+ 	log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, 
+ 		  (union all_addr *)&source_addr.in6.sin6_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 */
+@@ -1612,20 +1669,50 @@ 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 && ((uint32_t) mark & daemon->allowlist_mask))
++      allowed = is_query_allowed_for_mark((uint32_t) mark, is_single_query ? daemon->namebuff : NULL);
++#endif
++  
++  if (0);
++#ifdef HAVE_CONNTRACK
++  else if (!allowed)
++    {
++#ifdef HAVE_UBUS
++      if (is_single_query)
++	ubus_event_bcast_connmark_allowlist_refused(mark, daemon->namebuff);
++#endif
++      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
++  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 && ((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
+     {
+       int ad_reqd = do_bit;
+        /* RFC 6840 5.7 */
+@@ -1637,6 +1724,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 && ((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_LOCAL_ANSWERED]++;
+@@ -1827,6 +1918,9 @@ unsigned char *tcp_request(int confd, time_t now,
+ {
+   size_t size = 0;
+   int norebind = 0;
++#ifdef HAVE_CONNTRACK
++  int is_single_query = 0, allowed = 1;
++#endif
+ #ifdef HAVE_AUTH
+   int local_auth = 0;
+ #endif
+@@ -1859,7 +1953,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;
+ 		      
+@@ -1943,6 +2037,10 @@ unsigned char *tcp_request(int confd, time_t now,
+ 	    log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, 
+ 		      (union all_addr *)&peer_addr.in6.sin6_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))
+@@ -1974,13 +2072,32 @@ 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 || local_auth)
++#endif
++	if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) mark & daemon->allowlist_mask))
++	  allowed = is_query_allowed_for_mark((uint32_t) mark, is_single_query ? daemon->namebuff : NULL);
++#endif
+ 
++      if (0);
++#ifdef HAVE_CONNTRACK
++      else if (!allowed)
++	{
++#ifdef HAVE_UBUS
++	  if (is_single_query)
++	    ubus_event_bcast_connmark_allowlist_refused(mark, daemon->namebuff);
++#endif
++	  m = setup_reply(header, size, /* addrp: */ NULL, /* flags: */ 0, /* ttl: */ 0);
++	}
++#endif
+ #ifdef HAVE_AUTH
+-      if (auth_dns)
++      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 */
+@@ -2208,6 +2325,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 (m != 0 && option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) mark & daemon->allowlist_mask))
++	  report_addresses(header, m, mark);
++#endif
+       if (m == 0 || !read_write(confd, packet, m + sizeof(u16), 0))
+ 	return packet;
+     }
+diff --git a/src/option.c b/src/option.c
+index 0a72406..312d15f 100644
+--- a/src/option.c
++++ b/src/option.c
+@@ -168,6 +168,8 @@ struct myoption {
+ #define LOPT_SINGLE_PORT   359
+ #define LOPT_SCRIPT_TIME   360
+ #define LOPT_PXE_VENDOR    361
++#define LOPT_CMARK_ALST_EN 362
++#define LOPT_CMARK_ALST    363
+  
+ #ifdef HAVE_GETOPT_LONG
+ static const struct option opts[] =  
+@@ -321,6 +323,8 @@ static const struct myoption opts[] =
+     { "auth-sec-servers", 1, 0, LOPT_AUTHSFS },
+     { "auth-peer", 1, 0, LOPT_AUTHPEER }, 
+     { "ipset", 1, 0, LOPT_IPSET },
++    { "connmark-allowlist-enable", 2, 0, LOPT_CMARK_ALST_EN },
++    { "connmark-allowlist", 1, 0, LOPT_CMARK_ALST },
+     { "synth-domain", 1, 0, LOPT_SYNTH },
+     { "dnssec", 0, 0, LOPT_SEC_VALID },
+     { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
+@@ -501,6 +505,8 @@ static struct {
+   { LOPT_AUTHSFS, ARG_DUP, "<NS>[,<NS>...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL },
+   { LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL },
+   { LOPT_IPSET, ARG_DUP, "/<domain>[/<domain>...]/<ipset>...", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
++  { LOPT_CMARK_ALST_EN, ARG_ONE, "[=<mask>]", gettext_noop("Enable filtering of DNS queries with connection-track marks."), NULL },
++  { LOPT_CMARK_ALST, ARG_DUP, "<connmark>[/<mask>][,<pattern>[/<pattern>...]]", gettext_noop("Set allowed DNS patterns for a connection-track mark."), NULL },
+   { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
+   { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
+   { LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
+@@ -647,6 +653,22 @@ static char *canonicalise_opt(char *s)
+   return ret;
+ }
+ 
++#ifdef HAVE_CONNTRACK
++static int strtoul_check(char *a, unsigned long *res)
++{
++  if (!a)
++    return 0;
++  
++  unhide_metas(a);
++  
++  *res = strtoul(a, NULL, 0);
++  if (errno)
++    return 0;
++  
++  return 1;
++}
++#endif
++
+ static int atoi_check(char *a, int *res)
+ {
+   char *p;
+@@ -2743,6 +2765,135 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
+       }
+ #endif
+       
++    case LOPT_CMARK_ALST_EN: /* --connmark-allowlist-enable */
++#ifndef HAVE_CONNTRACK
++      ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives"));
++      break;
++#else
++      {
++	unsigned long mask = UINT32_MAX;
++	
++	if (arg)
++	  if (!strtoul_check(arg, &mask) || mask < 1 || mask > UINT32_MAX)
++	    ret_err(gen_err);
++	
++	set_option_bool(OPT_CMARK_ALST_EN);
++	daemon->allowlist_mask = (uint32_t) mask;
++	break;
++      }
++#endif
++      
++    case LOPT_CMARK_ALST: /* --connmark-allowlist */
++#ifndef HAVE_CONNTRACK
++	ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives"));
++	break;
++#else
++      {
++	struct allowlist *allowlists;
++	char **patterns, **patterns_pos;
++	unsigned long mark, mask = UINT32_MAX;
++	size_t num_patterns = 0;
++	
++	char *c, *m = NULL;
++	char *separator;
++	unhide_metas(arg);
++	if (!arg)
++	  ret_err(gen_err);
++	c = arg;
++	if (*c < '0' || *c > '9')
++	  ret_err(gen_err);
++	while (*c && *c != ',')
++	  {
++	    if (*c == '/')
++	      {
++		if (m)
++		  ret_err(gen_err);
++	        *c = '\0';
++		m = ++c;
++	      }
++	    if (*c < '0' || *c > '9')
++	      ret_err(gen_err);
++	    c++;
++	  }
++	separator = c;
++	if (!*separator)
++	  break;
++	while (c && *c)
++	  {
++	    char *end = strchr(++c, '/');
++	    if (end)
++	      *end = '\0';
++	    if (strcmp(c, "*") && !is_valid_dns_name_pattern(c))
++	      ret_err(gen_err);
++	    if (end)
++	      *end = '/';
++	    if (num_patterns >= UINT16_MAX - 1)
++	      ret_err(gen_err);
++	    num_patterns++;
++	    c = end;
++	  }
++	
++	*separator = '\0';
++	if (!strtoul_check(arg, &mark) || mark < 1 || mark > UINT32_MAX)
++	  ret_err(gen_err);
++	if (m)
++	  if (!strtoul_check(m, &mask) || mask < 1 || mask > UINT32_MAX || (mark & ~mask))
++	    ret_err(gen_err);
++	if (num_patterns)
++	  *separator = ',';
++	for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
++	  if (allowlists->mark == (uint32_t) mark && allowlists->mask == (uint32_t) mask)
++	    ret_err(gen_err);
++	
++	patterns = opt_malloc((num_patterns + 1) * sizeof(char *));
++	if (!patterns)
++	  goto fail_cmark_allowlist;
++	patterns_pos = patterns;
++	c = separator;
++	while (c && *c)
++	{
++	  char *end = strchr(++c, '/');
++	  if (end)
++	    *end = '\0';
++	  if (!(*patterns_pos++ = opt_string_alloc(c)))
++	    goto fail_cmark_allowlist;
++	  if (end)
++	    *end = '/';
++	  c = end;
++	}
++	*patterns_pos++ = NULL;
++	
++	allowlists = opt_malloc(sizeof(struct allowlist));
++	if (!allowlists)
++	  goto fail_cmark_allowlist;
++	memset(allowlists, 0, sizeof(struct allowlist));
++	allowlists->mark = (uint32_t) mark;
++	allowlists->mask = (uint32_t) mask;
++	allowlists->patterns = patterns;
++	allowlists->next = daemon->allowlists;
++	daemon->allowlists = allowlists;
++	break;
++	
++      fail_cmark_allowlist:
++	if (patterns)
++	  {
++	    for (patterns_pos = patterns; *patterns_pos; patterns_pos++)
++	      {
++		free(*patterns_pos);
++		*patterns_pos = NULL;
++	      }
++	    free(patterns);
++	    patterns = NULL;
++	  }
++	if (allowlists)
++	  {
++	    free(allowlists);
++	    allowlists = NULL;
++	  }
++	ret_err(gen_err);
++      }
++#endif
++      
+     case 'c':  /* --cache-size */
+       {
+ 	int size;
+diff --git a/src/pattern.c b/src/pattern.c
+new file mode 100644
+index 0000000..74f5801
+--- /dev/null
++++ b/src/pattern.c
+@@ -0,0 +1,386 @@
++/* dnsmasq is Copyright (c) 2000-2021 Simon Kelley
++
++   This program is free software; you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; version 2 dated June, 1991, or
++   (at your option) version 3 dated 29 June, 2007.
++ 
++   This program is distributed in the hope that it will be useful,
++   but WITHOUT ANY WARRANTY; without even the implied warranty of
++   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++   GNU General Public License for more details.
++     
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <http://www.gnu.org/licenses/>.
++*/
++
++#include "dnsmasq.h"
++
++#ifdef HAVE_CONNTRACK
++
++#define LOG(...) \
++  do { \
++    my_syslog(LOG_WARNING, __VA_ARGS__); \
++  } while (0)
++
++#define ASSERT(condition) \
++  do { \
++    if (!(condition)) \
++      LOG("[pattern.c:%d] Assertion failure: %s", __LINE__, #condition); \
++  } while (0)
++
++/**
++ * Determines whether a given string value matches against a glob pattern
++ * which may contain zero-or-more-character wildcards denoted by '*'.
++ *
++ * Based on "Glob Matching Can Be Simple And Fast Too" by Russ Cox,
++ * See https://research.swtch.com/glob
++ *
++ * @param      value                A string value.
++ * @param      num_value_bytes      The number of bytes of the string value.
++ * @param      pattern              A glob pattern.
++ * @param      num_pattern_bytes    The number of bytes of the glob pattern.
++ *
++ * @return 1                        If the provided value matches against the glob pattern.
++ * @return 0                        Otherwise.
++ */
++static int is_string_matching_glob_pattern(
++  const char *value,
++  size_t num_value_bytes,
++  const char *pattern,
++  size_t num_pattern_bytes)
++{
++  ASSERT(value);
++  ASSERT(pattern);
++  
++  size_t value_index = 0;
++  size_t next_value_index = 0;
++  size_t pattern_index = 0;
++  size_t next_pattern_index = 0;
++  while (value_index < num_value_bytes || pattern_index < num_pattern_bytes)
++    {
++      if (pattern_index < num_pattern_bytes)
++	{
++	  char pattern_character = pattern[pattern_index];
++	  if ('a' <= pattern_character && pattern_character <= 'z')
++	    pattern_character -= 'a' - 'A';
++	  if (pattern_character == '*')
++	    {
++	      // zero-or-more-character wildcard
++	      // Try to match at value_index, otherwise restart at value_index + 1 next.
++	      next_pattern_index = pattern_index;
++	      pattern_index++;
++	      if (value_index < num_value_bytes)
++		next_value_index = value_index + 1;
++	      else
++		next_value_index = 0;
++	      continue;
++	    }
++	  else
++	    {
++	      // ordinary character
++	      if (value_index < num_value_bytes)
++	        {
++		  char value_character = value[value_index];
++		  if ('a' <= value_character && value_character <= 'z')
++		    value_character -= 'a' - 'A';
++		  if (value_character == pattern_character)
++		    {
++		      pattern_index++;
++		      value_index++;
++		      continue;
++		    }
++		}
++	    }
++	}
++      if (next_value_index)
++	{
++	  pattern_index = next_pattern_index;
++	  value_index = next_value_index;
++	  continue;
++	}
++      return 0;
++    }
++  return 1;
++}
++
++/**
++ * Determines whether a given string value represents a valid DNS name.
++ *
++ * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels
++ *   delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only
++ *   ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen.
++ *
++ * - A valid name 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.
++ *
++ * - Examples:
++ *   Valid: "example.com"
++ *   Invalid: "ipcamera", "ipcamera.local", "8.8.8.8"
++ *
++ * @param      value                A string value.
++ *
++ * @return 1                        If the provided string value is a valid DNS name.
++ * @return 0                        Otherwise.
++ */
++int is_valid_dns_name(const char *value)
++{
++  ASSERT(value);
++  
++  size_t num_bytes = 0;
++  size_t num_labels = 0;
++  const char *label = NULL;
++  int is_label_numeric = 1;
++  for (const char *c = value;; c++)
++    {
++      if (*c &&
++	  *c != '-' && *c != '.' &&
++	  (*c < '0' || *c > '9') &&
++	  (*c < 'A' || *c > 'Z') &&
++	  (*c < 'a' || *c > 'z'))
++	{
++	  LOG("Invalid DNS name: Invalid character %c.", *c);
++	  return 0;
++	}
++      if (*c)
++	num_bytes++;
++      if (!label)
++	{
++	  if (!*c || *c == '.')
++	    {
++	      LOG("Invalid DNS name: Empty label.");
++	      return 0;
++	    }
++	  if (*c == '-')
++	    {
++	      LOG("Invalid DNS name: Label starts with hyphen.");
++	      return 0;
++	    }
++	  label = c;
++	}
++      if (*c && *c != '.')
++	{
++	  if (*c < '0' || *c > '9')
++	    is_label_numeric = 0;
++	}
++      else
++	{
++	  if (c[-1] == '-')
++	    {
++	      LOG("Invalid DNS name: Label ends with hyphen.");
++	      return 0;
++	    }
++	  size_t num_label_bytes = (size_t) (c - label);
++	  if (num_label_bytes > 63)
++	    {
++	      LOG("Invalid DNS name: Label is too long (%zu).", num_label_bytes);
++	      return 0;
++	    }
++	  num_labels++;
++	  if (!*c)
++	    {
++	      if (num_labels < 2)
++		{
++		  LOG("Invalid DNS name: Not enough labels (%zu).", num_labels);
++		  return 0;
++		}
++	      if (is_label_numeric)
++		{
++		  LOG("Invalid DNS name: Final label is fully numeric.");
++		  return 0;
++		}
++	      if (num_label_bytes == 5 &&
++		  (label[0] == 'l' || label[0] == 'L') &&
++		  (label[1] == 'o' || label[1] == 'O') &&
++		  (label[2] == 'c' || label[2] == 'C') &&
++		  (label[3] == 'a' || label[3] == 'A') &&
++		  (label[4] == 'l' || label[4] == 'L'))
++		{
++		  LOG("Invalid DNS name: \"local\" pseudo-TLD.");
++		  return 0;
++		}
++	      if (num_bytes < 1 || num_bytes > 253)
++		{
++		  LOG("DNS name has invalid length (%zu).", num_bytes);
++		  return 0;
++		}
++	      return 1;
++	    }
++	  label = NULL;
++	  is_label_numeric = 1;
++	}
++    }
++}
++
++/**
++ * Determines whether a given string value represents a valid DNS name pattern.
++ *
++ * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels
++ *   delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only
++ *   ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen.
++ *
++ * - 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").
++ *
++ * - A valid name or pattern 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.
++ *
++ * - Examples:
++ *   Valid: "example.com", "*.example.com", "video*.example.com", "api*.*.example.com", "*-prod-*.example.com"
++ *   Invalid: "ipcamera", "ipcamera.local", "*", "*.com", "8.8.8.8"
++ *
++ * @param      value                A string value.
++ *
++ * @return 1                        If the provided string value is a valid DNS name pattern.
++ * @return 0                        Otherwise.
++ */
++int is_valid_dns_name_pattern(const char *value)
++{
++  ASSERT(value);
++  
++  size_t num_bytes = 0;
++  size_t num_labels = 0;
++  const char *label = NULL;
++  int is_label_numeric = 1;
++  size_t num_wildcards = 0;
++  int previous_label_has_wildcard = 1;
++  for (const char *c = value;; c++)
++    {
++      if (*c &&
++	  *c != '*' && // Wildcard.
++	  *c != '-' && *c != '.' &&
++	  (*c < '0' || *c > '9') &&
++	  (*c < 'A' || *c > 'Z') &&
++	  (*c < 'a' || *c > 'z'))
++	{
++	  LOG("Invalid DNS name pattern: Invalid character %c.", *c);
++	  return 0;
++	}
++      if (*c && *c != '*')
++	num_bytes++;
++      if (!label)
++	{
++	  if (!*c || *c == '.')
++	    {
++	      LOG("Invalid DNS name pattern: Empty label.");
++	      return 0;
++	    }
++	  if (*c == '-')
++	    {
++	      LOG("Invalid DNS name pattern: Label starts with hyphen.");
++	      return 0;
++	    }
++	  label = c;
++	}
++      if (*c && *c != '.')
++	{
++	  if (*c < '0' || *c > '9')
++	    is_label_numeric = 0;
++	  if (*c == '*')
++	    {
++	      if (num_wildcards >= 2)
++		{
++		  LOG("Invalid DNS name pattern: Wildcard character used more than twice per label.");
++		  return 0;
++		}
++	      num_wildcards++;
++	    }
++	}
++      else
++	{
++	  if (c[-1] == '-')
++	    {
++	      LOG("Invalid DNS name pattern: Label ends with hyphen.");
++	      return 0;
++	    }
++	  size_t num_label_bytes = (size_t) (c - label) - num_wildcards;
++	  if (num_label_bytes > 63)
++	    {
++	      LOG("Invalid DNS name pattern: Label is too long (%zu).", num_label_bytes);
++	      return 0;
++	    }
++	  num_labels++;
++	  if (!*c)
++	    {
++	      if (num_labels < 2)
++		{
++		  LOG("Invalid DNS name pattern: Not enough labels (%zu).", num_labels);
++		  return 0;
++		}
++	      if (num_wildcards != 0 || previous_label_has_wildcard)
++		{
++		  LOG("Invalid DNS name pattern: Wildcard within final two labels.");
++		  return 0;
++		}
++	      if (is_label_numeric)
++		{
++		  LOG("Invalid DNS name pattern: Final label is fully numeric.");
++		  return 0;
++		}
++	      if (num_label_bytes == 5 &&
++		  (label[0] == 'l' || label[0] == 'L') &&
++		  (label[1] == 'o' || label[1] == 'O') &&
++		  (label[2] == 'c' || label[2] == 'C') &&
++		  (label[3] == 'a' || label[3] == 'A') &&
++		  (label[4] == 'l' || label[4] == 'L'))
++		{
++		  LOG("Invalid DNS name pattern: \"local\" pseudo-TLD.");
++		  return 0;
++		}
++	      if (num_bytes < 1 || num_bytes > 253)
++		{
++		  LOG("DNS name pattern has invalid length after removing wildcards (%zu).", num_bytes);
++		  return 0;
++		}
++	      return 1;
++	    }
++	    label = NULL;
++	    is_label_numeric = 1;
++	    previous_label_has_wildcard = num_wildcards != 0;
++	    num_wildcards = 0;
++	  }
++    }
++}
++
++/**
++ * Determines whether a given DNS name matches against a DNS name pattern.
++ *
++ * @param      name                 A valid DNS name.
++ * @param      pattern              A valid DNS name pattern.
++ *
++ * @return 1                        If the provided DNS name matches against the DNS name pattern.
++ * @return 0                        Otherwise.
++ */
++int is_dns_name_matching_pattern(const char *name, const char *pattern)
++{
++  ASSERT(name);
++  ASSERT(is_valid_dns_name(name));
++  ASSERT(pattern);
++  ASSERT(is_valid_dns_name_pattern(pattern));
++  
++  const char *n = name;
++  const char *p = pattern;
++  
++  do {
++    const char *name_label = n;
++    while (*n && *n != '.')
++      n++;
++    const char *pattern_label = p;
++    while (*p && *p != '.')
++      p++;
++    if (!is_string_matching_glob_pattern(
++        name_label, (size_t) (n - name_label),
++        pattern_label, (size_t) (p - pattern_label)))
++      break;
++    if (*n)
++      n++;
++    if (*p)
++      p++;
++  } while (*n && *p);
++  
++  return !*n && !*p;
++}
++
++#endif
+diff --git a/src/rfc1035.c b/src/rfc1035.c
+index ea438c2..d60253d 100644
+--- a/src/rfc1035.c
++++ b/src/rfc1035.c
+@@ -884,6 +884,80 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
+   return 0;
+ }
+ 
++#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
++void report_addresses(struct dns_header *header, size_t len, uint32_t mark)
++{
++  unsigned char *p, *endrr;
++  int i;
++  unsigned long attl;
++  struct allowlist *allowlists;
++  char **pattern_pos;
++  
++  if (RCODE(header) != NOERROR)
++    return;
++  
++  for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
++    if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask))
++      for (pattern_pos = allowlists->patterns; *pattern_pos; pattern_pos++)
++	if (!strcmp(*pattern_pos, "*"))
++	  return;
++  
++  if (!(p = skip_questions(header, len)))
++    return;
++  for (i = ntohs(header->ancount); i != 0; i--)
++    {
++      int aqtype, aqclass, ardlen;
++      
++      if (!extract_name(header, len, &p, daemon->namebuff, 1, 10))
++	return;
++      
++      if (!CHECK_LEN(header, p, len, 10))
++	return;
++      GETSHORT(aqtype, p);
++      GETSHORT(aqclass, p);
++      GETLONG(attl, p);
++      GETSHORT(ardlen, p);
++      
++      if (!CHECK_LEN(header, p, len, ardlen))
++	return;
++      endrr = p+ardlen;
++      
++      if (aqclass == C_IN)
++	{
++	  if (aqtype == T_CNAME)
++	    {
++	      char namebuff[MAXDNAME];
++	      if (!extract_name(header, len, &p, namebuff, 1, 0))
++		return;
++	      ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, namebuff, attl);
++	    }
++	  if (aqtype == T_A)
++	    {
++	      struct in_addr addr;
++	      char ip[INET_ADDRSTRLEN];
++	      if (ardlen != INADDRSZ)
++		return;
++	      memcpy(&addr, p, ardlen);
++	      if (inet_ntop(AF_INET, &addr, ip, sizeof ip))
++		ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl);
++	    }
++	  else if (aqtype == T_AAAA)
++	    {
++	      struct in6_addr addr;
++	      char ip[INET6_ADDRSTRLEN];
++	      if (ardlen != IN6ADDRSZ)
++		return;
++	      memcpy(&addr, p, ardlen);
++	      if (inet_ntop(AF_INET6, &addr, ip, sizeof ip))
++		ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl);
++	    }
++	}
++      
++      p = endrr;
++    }
++}
++#endif
++
+ /* If the packet holds exactly one query
+    return F_IPV4 or F_IPV6  and leave the name from the query in name */
+ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep)
+@@ -894,7 +968,8 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
+   if (typep)
+     *typep = 0;
+ 
+-  if (ntohs(header->qdcount) != 1 || OPCODE(header) != QUERY)
++  if (ntohs(header->qdcount) != 1 || OPCODE(header) != QUERY ||
++      ntohs(header->ancount) != 0 || ntohs(header->nscount) != 0)
+     return 0; /* must be exactly one query. */
+   
+   if (!extract_name(header, qlen, &p, name, 1, 4))
+diff --git a/src/ubus.c b/src/ubus.c
+index 33c2783..00b14dd 100644
+--- a/src/ubus.c
++++ b/src/ubus.c
+@@ -28,10 +28,38 @@ static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj
+ 			       struct ubus_request_data *req, const char *method,
+ 			       struct blob_attr *msg);
+ 
++#ifdef HAVE_CONNTRACK
++enum {
++  SET_CONNMARK_ALLOWLIST_MARK,
++  SET_CONNMARK_ALLOWLIST_MASK,
++  SET_CONNMARK_ALLOWLIST_PATTERNS
++};
++static const struct blobmsg_policy set_connmark_allowlist_policy[] = {
++  [SET_CONNMARK_ALLOWLIST_MARK] = {
++    .name = "mark",
++    .type = BLOBMSG_TYPE_INT32
++  },
++  [SET_CONNMARK_ALLOWLIST_MASK] = {
++    .name = "mask",
++    .type = BLOBMSG_TYPE_INT32
++  },
++  [SET_CONNMARK_ALLOWLIST_PATTERNS] = {
++    .name = "patterns",
++    .type = BLOBMSG_TYPE_ARRAY
++  }
++};
++static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj,
++					      struct ubus_request_data *req, const char *method,
++					      struct blob_attr *msg);
++#endif
++
+ static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj);
+ 
+ static const struct ubus_method ubus_object_methods[] = {
+   UBUS_METHOD_NOARG("metrics", ubus_handle_metrics),
++#ifdef HAVE_CONNTRACK
++  UBUS_METHOD("set_connmark_allowlist", ubus_handle_set_connmark_allowlist, set_connmark_allowlist_policy),
++#endif
+ };
+ 
+ static struct ubus_object_type ubus_object_type =
+@@ -178,6 +206,122 @@ static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj
+   return ubus_send_reply(ctx, req, b.head);
+ }
+ 
++#ifdef HAVE_CONNTRACK
++static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj,
++					      struct ubus_request_data *req, const char *method,
++					      struct blob_attr *msg)
++{
++  const struct blobmsg_policy *policy = set_connmark_allowlist_policy;
++  size_t policy_len = countof(set_connmark_allowlist_policy);
++  struct allowlist *allowlists = NULL, **allowlists_pos;
++  char **patterns = NULL, **patterns_pos;
++  uint32_t mark, mask = UINT32_MAX;
++  size_t num_patterns = 0;
++  struct blob_attr *tb[policy_len];
++  struct blob_attr *attr;
++  
++  if (blobmsg_parse(policy, policy_len, tb, blob_data(msg), blob_len(msg)))
++    return UBUS_STATUS_INVALID_ARGUMENT;
++  
++  if (!tb[SET_CONNMARK_ALLOWLIST_MARK])
++    return UBUS_STATUS_INVALID_ARGUMENT;
++  mark = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MARK]);
++  if (!mark)
++    return UBUS_STATUS_INVALID_ARGUMENT;
++  
++  if (tb[SET_CONNMARK_ALLOWLIST_MASK])
++    {
++      mask = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MASK]);
++      if (!mask || (mark & ~mask))
++	return UBUS_STATUS_INVALID_ARGUMENT;
++    }
++  
++  if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS])
++    {
++      struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
++      size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
++      __blob_for_each_attr(attr, head, len)
++	{
++	  char *pattern;
++	  if (blob_id(attr) != BLOBMSG_TYPE_STRING)
++	    return UBUS_STATUS_INVALID_ARGUMENT;
++	  if (!(pattern = blobmsg_get_string(attr)))
++	    return UBUS_STATUS_INVALID_ARGUMENT;
++	  if (strcmp(pattern, "*") && !is_valid_dns_name_pattern(pattern))
++	    return UBUS_STATUS_INVALID_ARGUMENT;
++	  num_patterns++;
++	}
++    }
++  
++  for (allowlists_pos = &daemon->allowlists; *allowlists_pos; allowlists_pos = &(*allowlists_pos)->next)
++    if ((*allowlists_pos)->mark == mark && (*allowlists_pos)->mask == mask)
++      {
++	struct allowlist *allowlists_next = (*allowlists_pos)->next;
++	for (patterns_pos = (*allowlists_pos)->patterns; *patterns_pos; patterns_pos++)
++	  {
++	    free(*patterns_pos);
++	    *patterns_pos = NULL;
++	  }
++	free((*allowlists_pos)->patterns);
++	(*allowlists_pos)->patterns = NULL;
++	free(*allowlists_pos);
++	*allowlists_pos = allowlists_next;
++	break;
++      }
++  
++  if (!num_patterns)
++    return UBUS_STATUS_OK;
++  
++  patterns = whine_malloc((num_patterns + 1) * sizeof(char *));
++  if (!patterns)
++    goto fail;
++  patterns_pos = patterns;
++  if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS])
++    {
++      struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
++      size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
++      __blob_for_each_attr(attr, head, len)
++	{
++	  char *pattern;
++	  if (!(pattern = blobmsg_get_string(attr)))
++	    goto fail;
++	  if (!(*patterns_pos = whine_malloc(strlen(pattern) + 1)))
++	    goto fail;
++	  strcpy(*patterns_pos++, pattern);
++	}
++    }
++  
++  allowlists = whine_malloc(sizeof(struct allowlist));
++  if (!allowlists)
++    goto fail;
++  memset(allowlists, 0, sizeof(struct allowlist));
++  allowlists->mark = mark;
++  allowlists->mask = mask;
++  allowlists->patterns = patterns;
++  allowlists->next = daemon->allowlists;
++  daemon->allowlists = allowlists;
++  return UBUS_STATUS_OK;
++  
++fail:
++  if (patterns)
++    {
++      for (patterns_pos = patterns; *patterns_pos; patterns_pos++)
++	{
++	  free(*patterns_pos);
++	  *patterns_pos = NULL;
++	}
++      free(patterns);
++      patterns = NULL;
++    }
++  if (allowlists)
++    {
++      free(allowlists);
++      allowlists = NULL;
++    }
++  return UBUS_STATUS_UNKNOWN_ERROR;
++}
++#endif
++
+ void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface)
+ {
+   struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
+@@ -197,9 +341,47 @@ void ubus_event_bcast(const char *type, const char *mac, const char *ip, const c
+     blobmsg_add_string(&b, "interface", interface);
+   
+   ret = ubus_notify(ubus, &ubus_object, type, b.head, -1);
+-  if (!ret)
++  if (ret)
++    my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret));
++}
++
++#ifdef HAVE_CONNTRACK
++void ubus_event_bcast_connmark_allowlist_refused(uint32_t mark, const char *name)
++{
++  struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
++  int ret;
++
++  if (!ubus || !notify)
++    return;
++
++  blob_buf_init(&b, 0);
++  blobmsg_add_u32(&b, "mark", mark);
++  blobmsg_add_string(&b, "name", name);
++  
++  ret = ubus_notify(ubus, &ubus_object, "connmark-allowlist.refused", b.head, -1);
++  if (ret)
++    my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret));
++}
++
++void ubus_event_bcast_connmark_allowlist_resolved(uint32_t mark, const char *name, const char *value, uint32_t ttl)
++{
++  struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
++  int ret;
++
++  if (!ubus || !notify)
++    return;
++
++  blob_buf_init(&b, 0);
++  blobmsg_add_u32(&b, "mark", mark);
++  blobmsg_add_string(&b, "name", name);
++  blobmsg_add_string(&b, "value", value);
++  blobmsg_add_u32(&b, "ttl", ttl);
++  
++  ret = ubus_notify(ubus, &ubus_object, "connmark-allowlist.resolved", b.head, /* timeout: */ 1000);
++  if (ret)
+     my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret));
+ }
++#endif
+ 
+ 
+ #endif /* HAVE_UBUS */
+-- 
+2.30.1 (Apple Git-130)
+
-- 
2.30.1 (Apple Git-130)




More information about the Dnsmasq-discuss mailing list