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

Etan Kissling etan_kissling at apple.com
Wed Jan 13 10:03:21 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>
---
 Makefile      |   2 +-
 man/dnsmasq.8 |  31 +++-
 src/dnsmasq.h |  25 +++-
 src/forward.c | 125 +++++++++++++++-
 src/option.c  | 134 ++++++++++++++++++
 src/pattern.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/rfc1035.c |  82 +++++++++++
 src/ubus.c    | 182 ++++++++++++++++++++++++
 8 files changed, 959 insertions(+), 8 deletions(-)
 create mode 100644 src/pattern.c

diff --git a/Makefile b/Makefile
index 78e25f0..8c64438 100644
--- a/Makefile
+++ b/Makefile
@@ -75,7 +75,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
 
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 7c6b405..a0dfe96 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -368,7 +368,10 @@ provides service at that name, rather than the default which is
 .TP 
 .B --enable-ubus[=<service-name>]
 Enable dnsmasq UBus interface. It sends notifications via UBus on
-DHCPACK and DHCPRELEASE events. Furthermore it offers metrics.
+DHCPACK and DHCPRELEASE events. Furthermore it offers metrics
+and allows configuration of Linux connection track mark based filtering.
+When DNS query filtering based on Linux connection track marks is enabled
+UBus notifications are generated for each resolved or filtered DNS query.
 Requires that dnsmasq has been built with UBus support. If the service
 name is given, dnsmasq provides service at that namespace, rather than
 the default which is
@@ -533,6 +536,32 @@ These IP sets must already exist. See
 .BR ipset (8)
 for more details.
 .TP
+.B --connmark-allowlist-enable[=<mask>]
+Enables filtering of incoming DNS queries with associated Linux connection track marks
+according to individual allowlists configured via a series of \fB--connmark-allowlist\fP
+options. Disallowed queries are not forwarded; they are rejected with a REFUSED error code.
+DNS queries are only allowed if they do not have an associated Linux connection
+track mark, or if the queried domains match the configured DNS patterns for the
+associated Linux connection track mark. If no allowlist is configured for a
+Linux connection track mark, all DNS queries associated with that mark are rejected.
+If a mask is specified, Linux connection track marks are first bitwise ANDed
+with the given mask before being processed.
+.TP
+.B --connmark-allowlist=<connmark>[/<mask>][,<pattern>[/<pattern>...]]
+Configures the DNS patterns that are allowed in DNS queries associated with
+the given Linux connection track mark.
+If a mask is specified, Linux connection track marks are first bitwise ANDed
+with the given mask before they are compared to the given connection track mark.
+Patterns follow the syntax of DNS names, but additionally allow the wildcard
+character "*" to be used up to twice per label to match 0 or more characters
+within that label. Note that the wildcard never matches a dot (e.g., "*.example.com"
+matches "api.example.com" but not "api.us.example.com"). Patterns must be
+fully qualified, i.e., consist of at least two labels. The final label must not be
+fully numeric, and must not be the "local" pseudo-TLD. A pattern must end with at least
+two literal (non-wildcard) labels.
+Instead of a pattern, "*" can be specified to disable allowlist filtering
+for a given Linux connection track mark entirely.
+.TP
 .B \-m, --mx-host=<mx name>[[,<hostname>],<preference>]
 Return an MX record named <mx name> pointing to the given hostname (if
 given), or
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 4d78c37..dcbe0ef 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -269,7 +269,8 @@ struct event_desc {
 #define OPT_IGNORE_CLID    59
 #define OPT_SINGLE_PORT    60
 #define OPT_LEASE_RENEW    61
-#define OPT_LAST           62
+#define OPT_CMARK_ALST_EN  62
+#define OPT_LAST           63
 
 #define OPTION_BITS (sizeof(unsigned int)*8)
 #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -563,6 +564,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 */
@@ -1035,6 +1042,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 */
@@ -1222,6 +1231,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);
+#ifdef HAVE_CONNTRACK
+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);
@@ -1493,6 +1505,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 */
@@ -1501,6 +1517,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 9c2b2c6..77ebc7b 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -562,9 +562,20 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
   /* could not send on, return empty answer or address if known for whole domain */
   if (udpfd != -1)
     {
+#ifdef HAVE_CONNTRACK
+      unsigned int mark;
+      int have_mark = 0;
+      if (option_bool(OPT_CMARK_ALST_EN))
+	have_mark = get_incoming_mark(udpaddr, dst_addr, /* istcp: */ 0, &mark);
+#endif
+      
       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);
+#ifdef HAVE_CONNTRACK
+      if (option_bool(OPT_CMARK_ALST_EN) && 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);
     }
 
@@ -1226,6 +1237,13 @@ void reply_query(int fd, int family, time_t now)
 			      forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, 
 			      forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source)))
 	{
+#ifdef HAVE_CONNTRACK
+	  unsigned int mark;
+	  int have_mark = 0;
+	  if (option_bool(OPT_CMARK_ALST_EN))
+	    have_mark = get_incoming_mark(&forward->source, &forward->dest, /* istcp: */ 0, &mark);
+#endif
+	  
 	  header->id = htons(forward->orig_id);
 	  header->hb4 |= HB4_RA; /* recursion if available */
 #ifdef HAVE_DNSSEC
@@ -1246,6 +1264,10 @@ void reply_query(int fd, int family, time_t now)
 	  dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &forward->source);
 #endif
 	  
+#ifdef HAVE_CONNTRACK
+	  if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) mark & daemon->allowlist_mask))
+	    report_addresses(header, nn, mark);
+#endif
 	  send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, 
 		    &forward->source, &forward->dest, forward->iface);
 	}
@@ -1265,6 +1287,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
@@ -1493,6 +1520,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))
     {
@@ -1507,6 +1539,10 @@ void receive_query(struct listener *listen, time_t now)
       else
 	log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, 
 		  (union all_addr *)&source_addr.in6.sin6_addr, types);
+      
+#ifdef HAVE_CONNTRACK
+      is_single_query = 1;
+#endif
 
 #ifdef HAVE_AUTH
       /* find queries for zones we're authoritative for, and answer them directly */
@@ -1548,20 +1584,58 @@ 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
+  if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) mark & daemon->allowlist_mask))
+    {
+      struct allowlist *allowlists;
+      char **pattern_pos;
+      allowed = 0;
+      for (allowlists = daemon->allowlists; allowlists && !allowed; allowlists = allowlists->next)
+	if (allowlists->mark == ((uint32_t) mark & daemon->allowlist_mask & allowlists->mask))
+	  for (pattern_pos = allowlists->patterns; *pattern_pos && !allowed; pattern_pos++)
+	    if (!strcmp(*pattern_pos, "*") ||
+		(is_single_query && !ntohs(header->ancount) && !ntohs(header->nscount) &&
+		 is_valid_dns_name(daemon->namebuff) &&
+		 is_dns_name_matching_pattern(daemon->namebuff, *pattern_pos)))
+	      allowed = 1;
+#ifdef HAVE_UBUS
+      if (!allowed)
+	ubus_event_bcast_connmark_allowlist_refused(mark, daemon->namebuff);
+#endif
+    }
+#endif
+  
+  if (0);
+#ifdef HAVE_CONNTRACK
+  else if (!allowed)
+    {
+      m = setup_reply(header, n, /* addrp: */ NULL, /* flags: */ 0, /* ttl: */ 0);
+      if (m >= 1)
+	{
+	  send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
+		    (char *)header, m, &source_addr, &dst_addr, if_index);
+	  daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
+	}
+    }
+#endif
 #ifdef HAVE_AUTH
-  if (auth_dns)
+  else if (auth_dns)
     {
       m = answer_auth(header, ((char *) header) + udp_size, (size_t)n, now, &source_addr, 
 		      local_auth, do_bit, have_pseudoheader);
       if (m >= 1)
 	{
+#ifdef HAVE_CONNTRACK
+	  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 */
@@ -1573,6 +1647,10 @@ void receive_query(struct listener *listen, time_t now)
       
       if (m >= 1)
 	{
+#ifdef HAVE_CONNTRACK
+	  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]++;
@@ -1763,6 +1841,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
@@ -1795,7 +1876,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;
 		      
@@ -1879,6 +1960,10 @@ unsigned char *tcp_request(int confd, time_t now,
 	    log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, 
 		      (union all_addr *)&peer_addr.in6.sin6_addr, types);
 	  
+#ifdef HAVE_CONNTRACK
+	  is_single_query = 1;
+#endif
+	  
 #ifdef HAVE_AUTH
 	  /* find queries for zones we're authoritative for, and answer them directly */
 	  if (!auth_dns && !option_bool(OPT_LOCALISE))
@@ -1910,13 +1995,39 @@ unsigned char *tcp_request(int confd, time_t now,
 	  if (flags & 0x8000)
 	    do_bit = 1; /* do bit */ 
 	}
+      
+#ifdef HAVE_CONNTRACK
+      if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) mark & daemon->allowlist_mask))
+	{
+	  struct allowlist *allowlists;
+	  char **pattern_pos;
+	  allowed = 0;
+	  for (allowlists = daemon->allowlists; allowlists && !allowed; allowlists = allowlists->next)
+	    if (allowlists->mark == ((uint32_t) mark & daemon->allowlist_mask & allowlists->mask))
+	      for (pattern_pos = allowlists->patterns; *pattern_pos && !allowed; pattern_pos++)
+		if (!strcmp(*pattern_pos, "*") ||
+		    (is_single_query && !ntohs(header->ancount) && !ntohs(header->nscount) &&
+		     is_valid_dns_name(daemon->namebuff) &&
+		     is_dns_name_matching_pattern(daemon->namebuff, *pattern_pos)))
+		  allowed = 1;
+#ifdef HAVE_UBUS
+	  if (!allowed)
+	    ubus_event_bcast_connmark_allowlist_refused(mark, daemon->namebuff);
+#endif
+	}
+#endif
 
+      if (0);
+#ifdef HAVE_CONNTRACK
+      else if (!allowed)
+	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 */
@@ -2154,6 +2265,10 @@ unsigned char *tcp_request(int confd, time_t now,
       
       *length = htons(m);
            
+#ifdef HAVE_CONNTRACK
+      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 316d9c8..cf3f840 100644
--- a/src/option.c
+++ b/src/option.c
@@ -168,6 +168,8 @@ struct myoption {
 #define LOPT_SINGLE_PORT   359
 #define LOPT_SCRIPT_TIME   360
 #define LOPT_PXE_VENDOR    361
+#define LOPT_CMARK_ALST_EN 362
+#define LOPT_CMARK_ALST    363
  
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -321,6 +323,8 @@ static const struct myoption opts[] =
     { "auth-sec-servers", 1, 0, LOPT_AUTHSFS },
     { "auth-peer", 1, 0, LOPT_AUTHPEER }, 
     { "ipset", 1, 0, LOPT_IPSET },
+    { "connmark-allowlist-enable", 2, 0, LOPT_CMARK_ALST_EN },
+    { "connmark-allowlist", 1, 0, LOPT_CMARK_ALST },
     { "synth-domain", 1, 0, LOPT_SYNTH },
     { "dnssec", 0, 0, LOPT_SEC_VALID },
     { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
@@ -501,6 +505,8 @@ static struct {
   { LOPT_AUTHSFS, ARG_DUP, "<NS>[,<NS>...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL },
   { LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL },
   { LOPT_IPSET, ARG_DUP, "/<domain>[/<domain>...]/<ipset>...", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
+  { LOPT_CMARK_ALST_EN, ARG_ONE, "[=<mask>]", gettext_noop("Enable filtering of DNS queries with connection-track marks."), NULL },
+  { LOPT_CMARK_ALST, ARG_DUP, "<connmark>[/<mask>][,<pattern>[/<pattern>...]]", gettext_noop("Set allowed DNS patterns for a connection-track mark."), NULL },
   { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
   { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
   { LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
@@ -2743,6 +2749,134 @@ 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
+      {
+	int mask = UINT32_MAX;
+	
+	if (arg)
+	  if (!atoi_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;
+	int mark, mask = UINT32_MAX;
+	size_t num_patterns = 0;
+	
+	char *c, *m = NULL;
+	char *separator;
+	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 (!atoi_check(arg, &mark) || mark < 1 || mark > UINT32_MAX)
+	  ret_err(gen_err);
+	if (m)
+	  if (!atoi_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..7d15c5c
--- /dev/null
+++ b/src/pattern.c
@@ -0,0 +1,386 @@
+/* dnsmasq is Copyright (c) 2000-2020 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("[patterns:%d] Assertion failure: %s", __LINE__, #condition); \
+    } \
+  } while (0)
+
+/**
+ * Determines whether a given string value matches against a glob pattern
+ * which may contains 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 fefe63d..988f355 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -933,6 +933,88 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
   return 0;
 }
 
+#ifdef HAVE_CONNTRACK
+void report_addresses(struct dns_header *header, size_t len, uint32_t mark)
+{
+  unsigned char *p, *endrr;
+  int i, cname_count = CNAME_CHAIN;
+  unsigned long attl;
+  struct allowlist *allowlists;
+  char **pattern_pos, **pattern_pos1;
+  
+  if (RCODE(header) != NOERROR)
+    return;
+  
+  for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
+    if (allowlists->mark == ((uint32_t) mark & daemon->allowlist_mask & allowlists->mask))
+      for (pattern_pos = allowlists->patterns; *pattern_pos; pattern_pos++)
+	if (!strcmp(*pattern_pos, "*"))
+	  return;
+      
+  if (!extract_request(header, len, daemon->namebuff, /* typep: */ NULL))
+    return;
+  
+cname_loop:
+  if (!(p = skip_questions(header, len)))
+    return;
+  for (i = ntohs(header->ancount); i != 0; i--)
+    {
+      int aqtype, aqclass, ardlen, res;
+      
+      if (!(res = extract_name(header, len, &p, daemon->namebuff, 0, 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 && res != 2)
+	{
+	  if (aqtype == T_CNAME)
+	    {
+	      char namebuff[MAXDNAME];
+	      if (!cname_count--)
+		return;
+	      if (!extract_name(header, len, &p, namebuff, 1, 0))
+		return;
+	      ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, namebuff, attl);
+	      strcpy(daemon->namebuff, namebuff);
+	      goto cname_loop;
+	    }
+	  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)
diff --git a/src/ubus.c b/src/ubus.c
index 5f81287..ee9e7da 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;
@@ -201,5 +345,43 @@ void ubus_event_bcast(const char *type, const char *mac, const char *ip, const c
     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.21.1 (Apple Git-122.3)






More information about the Dnsmasq-discuss mailing list