[Dnsmasq-discuss] [PATCH v9] Connection track mark based DNS query filtering.

Etan Kissling etan.kissling at gmail.com
Wed Jun 16 21:56:17 UTC 2021


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.
v8: Rebase to v2.85 (update options numbers).
v9: Rebase to v2.86test2 (options, setup_reply, uint32_t -> u32).
    Fix strtoul_check for sizeof(long) > sizeof(u32), and generic errno.

 Makefile      |   2 +-
 man/dnsmasq.8 |  31 +++-
 src/dnsmasq.h |  25 +++-
 src/forward.c | 143 ++++++++++++++++++-
 src/option.c  | 142 ++++++++++++++++++-
 src/pattern.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/rfc1035.c |  77 +++++++++-
 src/ubus.c    | 184 +++++++++++++++++++++++-
 8 files changed, 978 insertions(+), 12 deletions(-)
 create mode 100644 src/pattern.c

diff --git a/Makefile b/Makefile
index 367cd26..0cd592e 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 domain-match.o
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index e6bc6f0..ea8457b 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 696408a..a35e7f3 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -272,7 +272,8 @@ struct event_desc {
 #define OPT_LOG_DEBUG      62
 #define OPT_UMBRELLA       63
 #define OPT_UMBRELLA_DEVID 64
-#define OPT_LAST           65
+#define OPT_CMARK_ALST_EN  65
+#define OPT_LAST           66
 
 #define OPTION_BITS (sizeof(unsigned int)*8)
 #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -610,6 +611,12 @@ struct ipsets {
   struct ipsets *next;
 };
 
+struct allowlist {
+  u32 mark, mask;
+  char **patterns;
+  struct allowlist *next;
+};
+
 struct irec {
   union mysockaddr addr;
   struct in_addr netmask; /* only valid for IPv4 */
@@ -1086,6 +1093,8 @@ extern struct daemon {
   struct server *servers, *local_domains, **serverarray, *no_rebind;
   int serverarraysz;
   struct ipsets *ipsets;
+  u32 allowlist_mask;
+  struct allowlist *allowlists;
   int log_fac; /* log facility */
   char *log_file; /* optional log file */
   int max_logs;  /* queue limit */
@@ -1275,6 +1284,9 @@ void setup_reply(struct dns_header *header, unsigned int flags);
 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, u32 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);
@@ -1546,6 +1558,10 @@ char *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(u32 mark, const char *name);
+void ubus_event_bcast_connmark_allowlist_resolved(u32 mark, const char *pattern, const char *ip, u32 ttl);
+#  endif
 #endif
 
 /* ipset.c */
@@ -1554,6 +1570,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 de36ff2..9f48b07 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -530,6 +530,16 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
       if (oph)
 	plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
       
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+      if (option_bool(OPT_CMARK_ALST_EN))
+	{
+	  unsigned int mark;
+	  int have_mark = get_incoming_mark(udpaddr, dst_addr, /* istcp: */ 0, &mark);
+	  if (have_mark && ((u32)mark & daemon->allowlist_mask))
+	    report_addresses(header, plen, mark);
+	}
+#endif
+      
       send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface);
     }
 	  
@@ -1148,6 +1158,16 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
 	  dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source);
 #endif
 	  
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+	  if (option_bool(OPT_CMARK_ALST_EN))
+	    {
+	      unsigned int mark;
+	      int have_mark = get_incoming_mark(&src->source, &src->dest, /* istcp: */ 0, &mark);
+	      if (have_mark && ((u32)mark & daemon->allowlist_mask))
+		report_addresses(header, nn, mark);
+	    }
+#endif
+	  
 	  send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, 
 		    &src->source, &src->dest, src->iface);
 	  
@@ -1164,6 +1184,47 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
 }
 
 
+#ifdef HAVE_CONNTRACK
+static int is_query_allowed_for_mark(u32 mark, const char *name)
+{
+  int is_allowable_name, did_validate_name = 0;
+  struct allowlist *allowlists;
+  char **patterns_pos;
+  
+  for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
+    if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask))
+      for (patterns_pos = allowlists->patterns; *patterns_pos; patterns_pos++)
+	{
+	  if (!strcmp(*patterns_pos, "*"))
+	    return 1;
+	  if (!did_validate_name)
+	    {
+	      is_allowable_name = name ? is_valid_dns_name(name) : 0;
+	      did_validate_name = 1;
+	    }
+	  if (is_allowable_name && is_dns_name_matching_pattern(name, *patterns_pos))
+	    return 1;
+	}
+  return 0;
+}
+
+static size_t answer_disallowed(struct dns_header *header, size_t qlen, u32 mark, const char *name)
+{
+  unsigned char *p;
+  
+#ifdef HAVE_UBUS
+  if (name)
+    ubus_event_bcast_connmark_allowlist_refused(mark, name);
+#endif
+  
+  setup_reply(header, /* flags: */ 0);
+  
+  if (!(p = skip_questions(header, qlen)))
+    return 0;
+  return p - (unsigned char *)header;
+}
+#endif
+
 void receive_query(struct listener *listen, time_t now)
 {
   struct dns_header *header = (struct dns_header *)daemon->packet;
@@ -1175,6 +1236,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
@@ -1403,6 +1469,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))
     {
@@ -1413,6 +1484,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 */
@@ -1454,20 +1529,47 @@ void receive_query(struct listener *listen, time_t now)
 	udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */
     }
 
+#ifdef HAVE_CONNTRACK
 #ifdef HAVE_AUTH
-  if (auth_dns)
+  if (!auth_dns || local_auth)
+#endif
+    if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+      allowed = is_query_allowed_for_mark((u32)mark, is_single_query ? daemon->namebuff : NULL);
+#endif
+  
+  if (0);
+#ifdef HAVE_CONNTRACK
+  else if (!allowed)
+    {
+      m = answer_disallowed(header, (size_t)n, (u32)mark, is_single_query ? daemon->namebuff : NULL);
+      
+      if (m >= 1)
+	{
+	  send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
+		    (char *)header, m, &source_addr, &dst_addr, if_index);
+	  daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
+	}
+    }
+#endif
+#ifdef HAVE_AUTH
+  else if (auth_dns)
     {
       m = answer_auth(header, ((char *) header) + udp_size, (size_t)n, now, &source_addr, 
 		      local_auth, do_bit, have_pseudoheader);
       if (m >= 1)
 	{
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+	  if (local_auth)
+	    if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+	      report_addresses(header, m, mark);
+#endif
 	  send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
 		    (char *)header, m, &source_addr, &dst_addr, if_index);
 	  daemon->metrics[METRIC_DNS_AUTH_ANSWERED]++;
 	}
     }
-  else
 #endif
+  else
     {
       int ad_reqd = do_bit;
        /* RFC 6840 5.7 */
@@ -1479,6 +1581,10 @@ void receive_query(struct listener *listen, time_t now)
       
       if (m >= 1)
 	{
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+	  if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+	    report_addresses(header, m, mark);
+#endif
 	  send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
 		    (char *)header, m, &source_addr, &dst_addr, if_index);
 	  daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
@@ -1685,6 +1791,9 @@ unsigned char *tcp_request(int confd, time_t now,
 {
   size_t size = 0;
   int norebind;
+#ifdef HAVE_CONNTRACK
+  int is_single_query = 0, allowed = 1;
+#endif
 #ifdef HAVE_AUTH
   int local_auth = 0;
 #endif
@@ -1716,7 +1825,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;
 		      
@@ -1796,6 +1905,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))
@@ -1829,13 +1942,26 @@ unsigned char *tcp_request(int confd, time_t now,
 	  if (flags & 0x8000)
 	    do_bit = 1; /* do bit */ 
 	}
+      
+#ifdef HAVE_CONNTRACK
+#ifdef HAVE_AUTH
+      if (!auth_dns || local_auth)
+#endif
+	if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+	  allowed = is_query_allowed_for_mark((u32)mark, is_single_query ? daemon->namebuff : NULL);
+#endif
 
+      if (0);
+#ifdef HAVE_CONNTRACK
+      else if (!allowed)
+	m = answer_disallowed(header, size, (u32)mark, is_single_query ? daemon->namebuff : NULL);
+#endif
 #ifdef HAVE_AUTH
-      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 */
@@ -1961,6 +2087,13 @@ unsigned char *tcp_request(int confd, time_t now,
       
       *length = htons(m);
       
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+#ifdef HAVE_AUTH
+      if (!auth_dns || local_auth)
+#endif
+	if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+	  report_addresses(header, m, mark);
+#endif
       if (!read_write(confd, packet, m + sizeof(u16), 0))
 	break;
     }
diff --git a/src/option.c b/src/option.c
index cacfaa6..a134b35 100644
--- a/src/option.c
+++ b/src/option.c
@@ -171,6 +171,8 @@ struct myoption {
 #define LOPT_DYNHOST       362
 #define LOPT_LOG_DEBUG     363
 #define LOPT_UMBRELLA	   364
+#define LOPT_CMARK_ALST_EN 365
+#define LOPT_CMARK_ALST    366
  
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -324,6 +326,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 },
@@ -508,6 +512,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 },
@@ -685,13 +691,16 @@ static int atoi_check(char *a, int *res)
 
 static int strtoul_check(char *a, u32 *res)
 {
+  unsigned long x;
+  
   if (!numeric_check(a))
     return 0;
-  *res = strtoul(a, NULL, 10);
-  if (errno == ERANGE) {
+  x = strtoul(a, NULL, 10);
+  if (errno || x > UINT32_MAX) {
     errno = 0;
     return 0;
   }
+  *res = (u32)x;
   return 1;
 }
 
@@ -2877,6 +2886,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
+      {
+	u32 mask = UINT32_MAX;
+	
+	if (arg)
+	  if (!strtoul_check(arg, &mask) || mask < 1)
+	    ret_err(gen_err);
+	
+	set_option_bool(OPT_CMARK_ALST_EN);
+	daemon->allowlist_mask = 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;
+	u32 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 == mark && allowlists->mask == 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 = mark;
+	allowlists->mask = 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 9bc5ef2..a70e1c3 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, u32 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)
@@ -896,7 +970,8 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
 
   *name = 0; /* return empty name if no query found. */
   
-  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 4c166e0..6a4b34c 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 =
@@ -163,6 +191,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;
+  u32 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;
@@ -182,9 +326,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(u32 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(u32 mark, const char *name, const char *value, u32 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)




More information about the Dnsmasq-discuss mailing list