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_kissl...@apple.com>
(updated to latest patch)
Signed-off-by: Etan Kissling <etan.kissl...@gmail.com>
---
Etan Kissling <etan.kissl...@gmail.com>:
v2: Update to v6 of underlying dnsmasq patch.
v3: Update to v7 of underlying dnsmasq patch.
v4: Update to v8 of underlying dnsmasq patch.

 .../services/dnsmasq/files/dnsmasq.init       |   12 +
 ...track-mark-based-DNS-query-filtering.patch | 1321 +++++++++++++++++
 2 files changed, 1333 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..3e592b5
--- /dev/null
+++ 
b/package/network/services/dnsmasq/patches/300-Connection-track-mark-based-DNS-query-filtering.patch
@@ -0,0 +1,1321 @@
+From 01673a781ed5f9a9e3d431a9f45ce5cc85ac52a6 Mon Sep 17 00:00:00 2001
+From: Etan Kissling <etan_kissl...@apple.com>
+Date: Tue, 12 Jan 2021 10:51:21 +0100
+Subject: [PATCH v8] 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_kissl...@apple.com>
+(addressed reviewer feedback)
+Signed-off-by: Etan Kissling <etan.kissl...@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.kissl...@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.
+v8: Rebase to v2.85 (update options numbers).
+
+ 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 fce580f..8fe2534 100644
+--- a/man/dnsmasq.8
++++ b/man/dnsmasq.8
+@@ -371,7 +371,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
+@@ -536,6 +539,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 1e21005..2fedbcc 100644
+--- a/src/dnsmasq.h
++++ b/src/dnsmasq.h
+@@ -270,7 +270,8 @@ struct event_desc {
+ #define OPT_SINGLE_PORT    60
+ #define OPT_LEASE_RENEW    61
+ #define OPT_LOG_DEBUG      62
+-#define OPT_LAST           63
++#define OPT_CMARK_ALST_EN  63
++#define OPT_LAST           64
+ 
+ #define OPTION_BITS (sizeof(unsigned int)*8)
+ #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
+@@ -580,6 +581,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 */
+@@ -1054,6 +1061,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 */
+@@ -1245,6 +1254,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);
+@@ -1517,6 +1529,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 */
+@@ -1525,6 +1541,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 40c9723..72876e0 100644
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -640,6 +640,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);
+     }
+ 
+@@ -1257,6 +1266,15 @@ void reply_query(int fd, 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);
+ 
+@@ -1274,6 +1292,31 @@ void reply_query(int fd, 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;
+@@ -1285,6 +1328,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
+@@ -1513,6 +1561,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))
+     {
+@@ -1523,6 +1576,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 */
+@@ -1564,20 +1621,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 */
+@@ -1589,6 +1676,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]++;
+@@ -1771,6 +1862,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
+@@ -1803,7 +1897,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;
+                     
+@@ -1883,6 +1977,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))
+@@ -1914,13 +2012,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 */
+@@ -2141,6 +2258,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 6de5914..88b1dd4 100644
+--- a/src/option.c
++++ b/src/option.c
+@@ -170,6 +170,8 @@ struct myoption {
+ #define LOPT_PXE_VENDOR    361
+ #define LOPT_DYNHOST       362
+ #define LOPT_LOG_DEBUG     363
++#define LOPT_CMARK_ALST_EN 364
++#define LOPT_CMARK_ALST    365
+  
+ #ifdef HAVE_GETOPT_LONG
+ static const struct option opts[] =  
+@@ -323,6 +325,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 },
+@@ -506,6 +510,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 },
+@@ -653,6 +659,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;
+@@ -2755,6 +2777,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 5a961b8..bf97c43 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)


_______________________________________________
Dnsmasq-discuss mailing list
Dnsmasq-discuss@lists.thekelleys.org.uk
https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss

Reply via email to