[Dnsmasq-discuss] [OpenWrt] Integration of connmark based DNS filtering
Etan Kissling
etan_kissling at apple.com
Fri Feb 26 15:45:50 UTC 2021
>From 7694255ba440a1f53faeaae6cd034d0e1256e8a9 Mon Sep 17 00:00:00 2001
From: Etan Kissling <etan_kissling at apple.com>
Date: Mon, 20 Apr 2020 16:39:24 +0200
Subject: [PATCH] openwrt: Integration of connmark based DNS filtering
This integrates the proposed Dnsmasq patch from email:
- [PATCH v5] dnsmasq: connection track mark based DNS query filtering
into OpenWrt 21.02.
Signed-off-by: Etan Kissling <etan_kissling at apple.com>
---
This patch uses OpenWrt 21.02 as basis and may be useful for testing
on OpenWrt (Ubus event monitoring, and Uci based configuration).
.../services/dnsmasq/files/dnsmasq.init | 12 +
...track-mark-based-DNS-query-filtering.patch | 1262 +++++++++++++++++
2 files changed, 1274 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..4758100
--- /dev/null
+++ b/package/network/services/dnsmasq/patches/300-Connection-track-mark-based-DNS-query-filtering.patch
@@ -0,0 +1,1262 @@
+From e403e6dfabd9b9c4d4b132a940987f1cf3595278 Mon Sep 17 00:00:00 2001
+From: Etan Kissling <etan_kissling at apple.com>
+Date: Tue, 12 Jan 2021 10:51:21 +0100
+Subject: [PATCH v5] Connection track mark based DNS query filtering.
+
+This extends query filtering support beyond what is currently possible
+with the `--ipset` configuration option, by adding support for:
+1) Specifying allowlists on a per-client basis, based on their
+ associated Linux connection track mark.
+2) Dynamic configuration of allowlists via Ubus.
+3) Reporting when a DNS query resolves or is rejected via Ubus.
+4) DNS name patterns containing wildcards.
+
+Disallowed queries are not forwarded; they are rejected
+with a REFUSED error code.
+
+Signed-off-by: Etan Kissling <etan_kissling at apple.com>
+---
+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).
+
+ Makefile | 2 +-
+ man/dnsmasq.8 | 31 +++-
+ src/dnsmasq.h | 25 +++-
+ src/forward.c | 121 +++++++++++++++-
+ src/option.c | 134 ++++++++++++++++++
+ src/pattern.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ src/rfc1035.c | 82 +++++++++++
+ src/ubus.c | 184 +++++++++++++++++++++++-
+ 8 files changed, 956 insertions(+), 9 deletions(-)
+ create mode 100644 src/pattern.c
+
+diff --git a/Makefile b/Makefile
+index e4c3f5c..506e56b 100644
+--- a/Makefile
++++ b/Makefile
+@@ -79,7 +79,7 @@ copts_conf = .copts_$(sum)
+ objs = cache.o rfc1035.o util.o option.o forward.o network.o \
+ dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
+ helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
+- dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
++ dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o pattern.o \
+ domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \
+ poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o \
+ metrics.o hash_questions.o
+diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
+index ac7c9fa..04d666d 100644
+--- a/man/dnsmasq.8
++++ b/man/dnsmasq.8
+@@ -368,7 +368,10 @@ provides service at that name, rather than the default which is
+ .TP
+ .B --enable-ubus[=<service-name>]
+ Enable dnsmasq UBus interface. It sends notifications via UBus on
+-DHCPACK and DHCPRELEASE events. Furthermore it offers metrics.
++DHCPACK and DHCPRELEASE events. Furthermore it offers metrics
++and allows configuration of Linux connection track mark based filtering.
++When DNS query filtering based on Linux connection track marks is enabled
++UBus notifications are generated for each resolved or filtered DNS query.
+ Requires that dnsmasq has been built with UBus support. If the service
+ name is given, dnsmasq provides service at that namespace, rather than
+ the default which is
+@@ -533,6 +536,32 @@ These IP sets must already exist. See
+ .BR ipset (8)
+ for more details.
+ .TP
++.B --connmark-allowlist-enable[=<mask>]
++Enables filtering of incoming DNS queries with associated Linux connection track marks
++according to individual allowlists configured via a series of \fB--connmark-allowlist\fP
++options. Disallowed queries are not forwarded; they are rejected with a REFUSED error code.
++DNS queries are only allowed if they do not have an associated Linux connection
++track mark, or if the queried domains match the configured DNS patterns for the
++associated Linux connection track mark. If no allowlist is configured for a
++Linux connection track mark, all DNS queries associated with that mark are rejected.
++If a mask is specified, Linux connection track marks are first bitwise ANDed
++with the given mask before being processed.
++.TP
++.B --connmark-allowlist=<connmark>[/<mask>][,<pattern>[/<pattern>...]]
++Configures the DNS patterns that are allowed in DNS queries associated with
++the given Linux connection track mark.
++If a mask is specified, Linux connection track marks are first bitwise ANDed
++with the given mask before they are compared to the given connection track mark.
++Patterns follow the syntax of DNS names, but additionally allow the wildcard
++character "*" to be used up to twice per label to match 0 or more characters
++within that label. Note that the wildcard never matches a dot (e.g., "*.example.com"
++matches "api.example.com" but not "api.us.example.com"). Patterns must be
++fully qualified, i.e., consist of at least two labels. The final label must not be
++fully numeric, and must not be the "local" pseudo-TLD. A pattern must end with at least
++two literal (non-wildcard) labels.
++Instead of a pattern, "*" can be specified to disable allowlist filtering
++for a given Linux connection track mark entirely.
++.TP
+ .B \-m, --mx-host=<mx name>[[,<hostname>],<preference>]
+ Return an MX record named <mx name> pointing to the given hostname (if
+ given), or
+diff --git a/src/dnsmasq.h b/src/dnsmasq.h
+index e770454..b48e433 100644
+--- a/src/dnsmasq.h
++++ b/src/dnsmasq.h
+@@ -273,7 +273,8 @@ struct event_desc {
+ #define OPT_IGNORE_CLID 59
+ #define OPT_SINGLE_PORT 60
+ #define OPT_LEASE_RENEW 61
+-#define OPT_LAST 62
++#define OPT_CMARK_ALST_EN 62
++#define OPT_LAST 63
+
+ #define OPTION_BITS (sizeof(unsigned int)*8)
+ #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
+@@ -567,6 +568,12 @@ struct ipsets {
+ struct ipsets *next;
+ };
+
++struct allowlist {
++ uint32_t mark, mask;
++ char **patterns;
++ struct allowlist *next;
++};
++
+ struct irec {
+ union mysockaddr addr;
+ struct in_addr netmask; /* only valid for IPv4 */
+@@ -1042,6 +1049,8 @@ extern struct daemon {
+ struct bogus_addr *bogus_addr, *ignore_addr;
+ struct server *servers;
+ struct ipsets *ipsets;
++ uint32_t allowlist_mask;
++ struct allowlist *allowlists;
+ int log_fac; /* log facility */
+ char *log_file; /* optional log file */
+ int max_logs; /* queue limit */
+@@ -1231,6 +1240,9 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
+ int extract_addresses(struct dns_header *header, size_t qlen, char *name,
+ time_t now, char **ipsets, int is_sign, int check_rebind,
+ int no_cache_dnssec, int secure, int *doctored);
++#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
++void report_addresses(struct dns_header *header, size_t len, uint32_t mark);
++#endif
+ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+ struct in_addr local_addr, struct in_addr local_netmask,
+ time_t now, int ad_reqd, int do_bit, int have_pseudoheader);
+@@ -1504,6 +1516,10 @@ void ubus_init(void);
+ void set_ubus_listeners(void);
+ void check_ubus_listeners(void);
+ void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface);
++# ifdef HAVE_CONNTRACK
++void ubus_event_bcast_connmark_allowlist_refused(uint32_t mark, const char *name);
++void ubus_event_bcast_connmark_allowlist_resolved(uint32_t mark, const char *pattern, const char *ip, uint32_t ttl);
++# endif
+ #endif
+
+ /* ipset.c */
+@@ -1512,6 +1528,13 @@ void ipset_init(void);
+ int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove);
+ #endif
+
++/* pattern.c */
++#ifdef HAVE_CONNTRACK
++int is_valid_dns_name(const char *value);
++int is_valid_dns_name_pattern(const char *value);
++int is_dns_name_matching_pattern(const char *name, const char *pattern);
++#endif
++
+ /* helper.c */
+ #if defined(HAVE_SCRIPT)
+ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd);
+diff --git a/src/forward.c b/src/forward.c
+index e82e14a..8bc92bc 100644
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -621,6 +621,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);
+ }
+
+@@ -1308,6 +1317,15 @@ void reply_query(int fd, int family, time_t now)
+ dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source);
+ #endif
+
++#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
++ if (option_bool(OPT_CMARK_ALST_EN))
++ {
++ unsigned int mark;
++ int have_mark = get_incoming_mark(&src->source, &src->dest, /* istcp: */ 0, &mark);
++ if (have_mark && ((uint32_t) mark & daemon->allowlist_mask))
++ report_addresses(header, nn, mark);
++ }
++#endif
+ send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
+ &src->source, &src->dest, src->iface);
+
+@@ -1336,6 +1354,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
+@@ -1564,6 +1587,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))
+ {
+@@ -1578,6 +1606,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 */
+@@ -1619,20 +1651,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)
+ {
++#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
++ if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) mark & daemon->allowlist_mask))
++ report_addresses(header, m, mark);
++#endif
+ send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
+ (char *)header, m, &source_addr, &dst_addr, if_index);
+ daemon->metrics[METRIC_DNS_AUTH_ANSWERED]++;
+ }
+ }
+- else
+ #endif
++ else
+ {
+ int ad_reqd = do_bit;
+ /* RFC 6840 5.7 */
+@@ -1644,6 +1714,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]++;
+@@ -1834,6 +1908,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
+@@ -1866,7 +1943,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;
+
+@@ -1950,6 +2027,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))
+@@ -1981,13 +2062,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 */
+@@ -2215,6 +2322,10 @@ unsigned char *tcp_request(int confd, time_t now,
+
+ *length = htons(m);
+
++#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
++ if (m != 0 && option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) mark & daemon->allowlist_mask))
++ report_addresses(header, m, mark);
++#endif
+ if (m == 0 || !read_write(confd, packet, m + sizeof(u16), 0))
+ return packet;
+ }
+diff --git a/src/option.c b/src/option.c
+index 0a72406..8527848 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 || (unsigned int) 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 || (unsigned int) mark > UINT32_MAX)
++ ret_err(gen_err);
++ if (m)
++ if (!atoi_check(m, &mask) || mask < 1 || (unsigned int) 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..a068719
+--- /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("[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 ea438c2..0d0b000 100644
+--- a/src/rfc1035.c
++++ b/src/rfc1035.c
+@@ -884,6 +884,88 @@ 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, cname_count = CNAME_CHAIN;
++ 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 == ((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 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.24.3 (Apple Git-128)
+
--
2.24.3 (Apple Git-128)
More information about the Dnsmasq-discuss
mailing list