This integrates the proposed Dnsmasq patch from email: - [PATCH v5] dnsmasq: connection track mark based DNS query filtering into OpenWrt 21.02.
Signed-off-by: Etan Kissling <etan_kissl...@apple.com> (updated to latest patch) Signed-off-by: Etan Kissling <etan.kissl...@gmail.com> --- Etan Kissling <etan.kissl...@gmail.com>: v2: Update to v6 of underlying dnsmasq patch. v3: Update to v7 of underlying dnsmasq patch. .../services/dnsmasq/files/dnsmasq.init | 12 + ...track-mark-based-DNS-query-filtering.patch | 1320 +++++++++++++++++ 2 files changed, 1332 insertions(+) create mode 100644 package/network/services/dnsmasq/patches/300-Connection-track-mark-based-DNS-query-filtering.patch diff --git a/package/network/services/dnsmasq/files/dnsmasq.init b/package/network/services/dnsmasq/files/dnsmasq.init index 680e72f..b46988f 100644 --- a/package/network/services/dnsmasq/files/dnsmasq.init +++ b/package/network/services/dnsmasq/files/dnsmasq.init @@ -172,6 +172,10 @@ append_ipset() { xappend "--ipset=$1" } +append_connmark_allowlist() { + xappend "--connmark-allowlist=$1" +} + append_interface() { network_get_device ifname "$1" || ifname="$1" xappend "--interface=$ifname" @@ -913,6 +917,14 @@ dnsmasq_start() config_list_foreach "$cfg" "rev_server" append_rev_server config_list_foreach "$cfg" "address" append_address config_list_foreach "$cfg" "ipset" append_ipset + + local connmark_allowlist_enable + config_get connmark_allowlist_enable "$cfg" connmark_allowlist_enable 0 + [ "$connmark_allowlist_enable" -gt 0 ] && { + append_parm "$cfg" "connmark_allowlist_enable" "--connmark-allowlist-enable" + config_list_foreach "$cfg" "connmark_allowlist" append_connmark_allowlist + } + [ -n "$BOOT" ] || { config_list_foreach "$cfg" "interface" append_interface config_list_foreach "$cfg" "notinterface" append_notinterface diff --git a/package/network/services/dnsmasq/patches/300-Connection-track-mark-based-DNS-query-filtering.patch b/package/network/services/dnsmasq/patches/300-Connection-track-mark-based-DNS-query-filtering.patch new file mode 100644 index 0000000..bb3533e --- /dev/null +++ b/package/network/services/dnsmasq/patches/300-Connection-track-mark-based-DNS-query-filtering.patch @@ -0,0 +1,1320 @@ +From 462371dc003ac17f2fb36935c68314063e2c5dfd Mon Sep 17 00:00:00 2001 +From: Etan Kissling <etan_kissl...@apple.com> +Date: Tue, 12 Jan 2021 10:51:21 +0100 +Subject: [PATCH v7] Connection track mark based DNS query filtering. + +This extends query filtering support beyond what is currently possible +with the `--ipset` configuration option, by adding support for: +1) Specifying allowlists on a per-client basis, based on their + associated Linux connection track mark. +2) Dynamic configuration of allowlists via Ubus. +3) Reporting when a DNS query resolves or is rejected via Ubus. +4) DNS name patterns containing wildcards. + +Disallowed queries are not forwarded; they are rejected +with a REFUSED error code. + +Signed-off-by: Etan Kissling <etan_kissl...@apple.com> +(addressed reviewer feedback) +Signed-off-by: Etan Kissling <etan.kissl...@gmail.com> +--- +v2: Rebase to v2.83, and fix compilation when HAVE_UBUS not present. +v3: Rebase to v2.84test2. +v4: Rebase to v2.84rc2 (update copyright notice). +v5: Correct logging of `ubus_notify` errors (also in existing code). + +Etan Kissling <etan.kissl...@gmail.com>: +v6: Integrate checks for weird queries into `extract_request`. + Skip Ubus reporting when daemon->namebuff is not initialized. + Fix options parsing for mark / mask with bit 31 set. + Disable filtering for external queries (`auth_dns && !local_auth`). + Report all CNAME RRs via Ubus instead of just a (potential) subset. + Avoid redundant `is_valid_dns_name` evaluations. + Unify DNS name pattern matching logic across transports (UDP / TCP). +v7: Fix typos and adjust code style to project. + + Makefile | 2 +- + man/dnsmasq.8 | 31 +++- + src/dnsmasq.h | 25 +++- + src/forward.c | 134 +++++++++++++++++- + src/option.c | 151 ++++++++++++++++++++ + src/pattern.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++ + src/rfc1035.c | 77 +++++++++- + src/ubus.c | 184 +++++++++++++++++++++++- + 8 files changed, 980 insertions(+), 10 deletions(-) + create mode 100644 src/pattern.c + +diff --git a/Makefile b/Makefile +index e4c3f5c..506e56b 100644 +--- a/Makefile ++++ b/Makefile +@@ -79,7 +79,7 @@ copts_conf = .copts_$(sum) + objs = cache.o rfc1035.o util.o option.o forward.o network.o \ + dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \ + helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \ +- dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \ ++ dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o pattern.o \ + domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \ + poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o \ + metrics.o hash_questions.o +diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 +index ac7c9fa..04d666d 100644 +--- a/man/dnsmasq.8 ++++ b/man/dnsmasq.8 +@@ -368,7 +368,10 @@ provides service at that name, rather than the default which is + .TP + .B --enable-ubus[=<service-name>] + Enable dnsmasq UBus interface. It sends notifications via UBus on +-DHCPACK and DHCPRELEASE events. Furthermore it offers metrics. ++DHCPACK and DHCPRELEASE events. Furthermore it offers metrics ++and allows configuration of Linux connection track mark based filtering. ++When DNS query filtering based on Linux connection track marks is enabled ++UBus notifications are generated for each resolved or filtered DNS query. + Requires that dnsmasq has been built with UBus support. If the service + name is given, dnsmasq provides service at that namespace, rather than + the default which is +@@ -533,6 +536,32 @@ These IP sets must already exist. See + .BR ipset (8) + for more details. + .TP ++.B --connmark-allowlist-enable[=<mask>] ++Enables filtering of incoming DNS queries with associated Linux connection track marks ++according to individual allowlists configured via a series of \fB--connmark-allowlist\fP ++options. Disallowed queries are not forwarded; they are rejected with a REFUSED error code. ++DNS queries are only allowed if they do not have an associated Linux connection ++track mark, or if the queried domains match the configured DNS patterns for the ++associated Linux connection track mark. If no allowlist is configured for a ++Linux connection track mark, all DNS queries associated with that mark are rejected. ++If a mask is specified, Linux connection track marks are first bitwise ANDed ++with the given mask before being processed. ++.TP ++.B --connmark-allowlist=<connmark>[/<mask>][,<pattern>[/<pattern>...]] ++Configures the DNS patterns that are allowed in DNS queries associated with ++the given Linux connection track mark. ++If a mask is specified, Linux connection track marks are first bitwise ANDed ++with the given mask before they are compared to the given connection track mark. ++Patterns follow the syntax of DNS names, but additionally allow the wildcard ++character "*" to be used up to twice per label to match 0 or more characters ++within that label. Note that the wildcard never matches a dot (e.g., "*.example.com" ++matches "api.example.com" but not "api.us.example.com"). Patterns must be ++fully qualified, i.e., consist of at least two labels. The final label must not be ++fully numeric, and must not be the "local" pseudo-TLD. A pattern must end with at least ++two literal (non-wildcard) labels. ++Instead of a pattern, "*" can be specified to disable allowlist filtering ++for a given Linux connection track mark entirely. ++.TP + .B \-m, --mx-host=<mx name>[[,<hostname>],<preference>] + Return an MX record named <mx name> pointing to the given hostname (if + given), or +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index e770454..b48e433 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -273,7 +273,8 @@ struct event_desc { + #define OPT_IGNORE_CLID 59 + #define OPT_SINGLE_PORT 60 + #define OPT_LEASE_RENEW 61 +-#define OPT_LAST 62 ++#define OPT_CMARK_ALST_EN 62 ++#define OPT_LAST 63 + + #define OPTION_BITS (sizeof(unsigned int)*8) + #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) ) +@@ -567,6 +568,12 @@ struct ipsets { + struct ipsets *next; + }; + ++struct allowlist { ++ uint32_t mark, mask; ++ char **patterns; ++ struct allowlist *next; ++}; ++ + struct irec { + union mysockaddr addr; + struct in_addr netmask; /* only valid for IPv4 */ +@@ -1042,6 +1049,8 @@ extern struct daemon { + struct bogus_addr *bogus_addr, *ignore_addr; + struct server *servers; + struct ipsets *ipsets; ++ uint32_t allowlist_mask; ++ struct allowlist *allowlists; + int log_fac; /* log facility */ + char *log_file; /* optional log file */ + int max_logs; /* queue limit */ +@@ -1231,6 +1240,9 @@ size_t setup_reply(struct dns_header *header, size_t qlen, + int extract_addresses(struct dns_header *header, size_t qlen, char *name, + time_t now, char **ipsets, int is_sign, int check_rebind, + int no_cache_dnssec, int secure, int *doctored); ++#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) ++void report_addresses(struct dns_header *header, size_t len, uint32_t mark); ++#endif + size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + struct in_addr local_addr, struct in_addr local_netmask, + time_t now, int ad_reqd, int do_bit, int have_pseudoheader); +@@ -1504,6 +1516,10 @@ void ubus_init(void); + void set_ubus_listeners(void); + void check_ubus_listeners(void); + void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface); ++# ifdef HAVE_CONNTRACK ++void ubus_event_bcast_connmark_allowlist_refused(uint32_t mark, const char *name); ++void ubus_event_bcast_connmark_allowlist_resolved(uint32_t mark, const char *pattern, const char *ip, uint32_t ttl); ++# endif + #endif + + /* ipset.c */ +@@ -1512,6 +1528,13 @@ void ipset_init(void); + int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove); + #endif + ++/* pattern.c */ ++#ifdef HAVE_CONNTRACK ++int is_valid_dns_name(const char *value); ++int is_valid_dns_name_pattern(const char *value); ++int is_dns_name_matching_pattern(const char *name, const char *pattern); ++#endif ++ + /* helper.c */ + #if defined(HAVE_SCRIPT) + int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd); +diff --git a/src/forward.c b/src/forward.c +index 8fb0327..61592a8 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -614,6 +614,15 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl); + if (oph) + plen = add_pseudoheader(header, plen, ((unsigned char *) header) + PACKETSZ, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); ++#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) ++ if (option_bool(OPT_CMARK_ALST_EN)) ++ { ++ unsigned int mark; ++ int have_mark = get_incoming_mark(udpaddr, dst_addr, /* istcp: */ 0, &mark); ++ if (have_mark && ((uint32_t) mark & daemon->allowlist_mask)) ++ report_addresses(header, plen, mark); ++ } ++#endif + send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface); + } + +@@ -1301,6 +1310,15 @@ void reply_query(int fd, int family, time_t now) + dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source); + #endif + ++#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) ++ if (option_bool(OPT_CMARK_ALST_EN)) ++ { ++ unsigned int mark; ++ int have_mark = get_incoming_mark(&src->source, &src->dest, /* istcp: */ 0, &mark); ++ if (have_mark && ((uint32_t) mark & daemon->allowlist_mask)) ++ report_addresses(header, nn, mark); ++ } ++#endif + send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, + &src->source, &src->dest, src->iface); + +@@ -1318,6 +1336,31 @@ void reply_query(int fd, int family, time_t now) + } + + ++#ifdef HAVE_CONNTRACK ++static int is_query_allowed_for_mark(uint32_t mark, const char *name) ++{ ++ int is_allowable_name, did_validate_name = 0; ++ struct allowlist *allowlists; ++ char **patterns_pos; ++ ++ for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next) ++ if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask)) ++ for (patterns_pos = allowlists->patterns; *patterns_pos; patterns_pos++) ++ { ++ if (!strcmp(*patterns_pos, "*")) ++ return 1; ++ if (!did_validate_name) ++ { ++ is_allowable_name = name ? is_valid_dns_name(name) : 0; ++ did_validate_name = 1; ++ } ++ if (is_allowable_name && is_dns_name_matching_pattern(name, *patterns_pos)) ++ return 1; ++ } ++ return 0; ++} ++#endif ++ + void receive_query(struct listener *listen, time_t now) + { + struct dns_header *header = (struct dns_header *)daemon->packet; +@@ -1329,6 +1372,11 @@ void receive_query(struct listener *listen, time_t now) + size_t m; + ssize_t n; + int if_index = 0, auth_dns = 0, do_bit = 0, have_pseudoheader = 0; ++#ifdef HAVE_CONNTRACK ++ unsigned int mark = 0; ++ int have_mark = 0; ++ int is_single_query = 0, allowed = 1; ++#endif + #ifdef HAVE_AUTH + int local_auth = 0; + #endif +@@ -1557,6 +1605,11 @@ void receive_query(struct listener *listen, time_t now) + #ifdef HAVE_DUMPFILE + dump_packet(DUMP_QUERY, daemon->packet, (size_t)n, &source_addr, NULL); + #endif ++ ++#ifdef HAVE_CONNTRACK ++ if (option_bool(OPT_CMARK_ALST_EN)) ++ have_mark = get_incoming_mark(&source_addr, &dst_addr, /* istcp: */ 0, &mark); ++#endif + + if (extract_request(header, (size_t)n, daemon->namebuff, &type)) + { +@@ -1571,6 +1624,10 @@ void receive_query(struct listener *listen, time_t now) + else + log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, + (union all_addr *)&source_addr.in6.sin6_addr, types); ++ ++#ifdef HAVE_CONNTRACK ++ is_single_query = 1; ++#endif + + #ifdef HAVE_AUTH + /* find queries for zones we're authoritative for, and answer them directly */ +@@ -1612,20 +1669,50 @@ void receive_query(struct listener *listen, time_t now) + udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */ + } + ++#ifdef HAVE_CONNTRACK + #ifdef HAVE_AUTH +- if (auth_dns) ++ if (!auth_dns || local_auth) ++#endif ++ if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) mark & daemon->allowlist_mask)) ++ allowed = is_query_allowed_for_mark((uint32_t) mark, is_single_query ? daemon->namebuff : NULL); ++#endif ++ ++ if (0); ++#ifdef HAVE_CONNTRACK ++ else if (!allowed) ++ { ++#ifdef HAVE_UBUS ++ if (is_single_query) ++ ubus_event_bcast_connmark_allowlist_refused(mark, daemon->namebuff); ++#endif ++ m = setup_reply(header, n, /* addrp: */ NULL, /* flags: */ 0, /* ttl: */ 0); ++ if (m >= 1) ++ { ++ send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), ++ (char *)header, m, &source_addr, &dst_addr, if_index); ++ daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++; ++ } ++ } ++#endif ++#ifdef HAVE_AUTH ++ else if (auth_dns) + { + m = answer_auth(header, ((char *) header) + udp_size, (size_t)n, now, &source_addr, + local_auth, do_bit, have_pseudoheader); + if (m >= 1) + { ++#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) ++ if (local_auth) ++ if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) mark & daemon->allowlist_mask)) ++ report_addresses(header, m, mark); ++#endif + send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), + (char *)header, m, &source_addr, &dst_addr, if_index); + daemon->metrics[METRIC_DNS_AUTH_ANSWERED]++; + } + } +- else + #endif ++ else + { + int ad_reqd = do_bit; + /* RFC 6840 5.7 */ +@@ -1637,6 +1724,10 @@ void receive_query(struct listener *listen, time_t now) + + if (m >= 1) + { ++#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) ++ if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) mark & daemon->allowlist_mask)) ++ report_addresses(header, m, mark); ++#endif + send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), + (char *)header, m, &source_addr, &dst_addr, if_index); + daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++; +@@ -1827,6 +1918,9 @@ unsigned char *tcp_request(int confd, time_t now, + { + size_t size = 0; + int norebind = 0; ++#ifdef HAVE_CONNTRACK ++ int is_single_query = 0, allowed = 1; ++#endif + #ifdef HAVE_AUTH + int local_auth = 0; + #endif +@@ -1859,7 +1953,7 @@ unsigned char *tcp_request(int confd, time_t now, + + #ifdef HAVE_CONNTRACK + /* Get connection mark of incoming query to set on outgoing connections. */ +- if (option_bool(OPT_CONNTRACK)) ++ if (option_bool(OPT_CONNTRACK) || option_bool(OPT_CMARK_ALST_EN)) + { + union all_addr local; + +@@ -1943,6 +2037,10 @@ unsigned char *tcp_request(int confd, time_t now, + log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, + (union all_addr *)&peer_addr.in6.sin6_addr, types); + ++#ifdef HAVE_CONNTRACK ++ is_single_query = 1; ++#endif ++ + #ifdef HAVE_AUTH + /* find queries for zones we're authoritative for, and answer them directly */ + if (!auth_dns && !option_bool(OPT_LOCALISE)) +@@ -1974,13 +2072,32 @@ unsigned char *tcp_request(int confd, time_t now, + if (flags & 0x8000) + do_bit = 1; /* do bit */ + } ++ ++#ifdef HAVE_CONNTRACK ++#ifdef HAVE_AUTH ++ if (!auth_dns || local_auth) ++#endif ++ if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) mark & daemon->allowlist_mask)) ++ allowed = is_query_allowed_for_mark((uint32_t) mark, is_single_query ? daemon->namebuff : NULL); ++#endif + ++ if (0); ++#ifdef HAVE_CONNTRACK ++ else if (!allowed) ++ { ++#ifdef HAVE_UBUS ++ if (is_single_query) ++ ubus_event_bcast_connmark_allowlist_refused(mark, daemon->namebuff); ++#endif ++ m = setup_reply(header, size, /* addrp: */ NULL, /* flags: */ 0, /* ttl: */ 0); ++ } ++#endif + #ifdef HAVE_AUTH +- if (auth_dns) ++ else if (auth_dns) + m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr, + local_auth, do_bit, have_pseudoheader); +- else + #endif ++ else + { + int ad_reqd = do_bit; + /* RFC 6840 5.7 */ +@@ -2208,6 +2325,13 @@ unsigned char *tcp_request(int confd, time_t now, + + *length = htons(m); + ++#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) ++#ifdef HAVE_AUTH ++ if (!auth_dns || local_auth) ++#endif ++ if (m != 0 && option_bool(OPT_CMARK_ALST_EN) && have_mark && ((uint32_t) mark & daemon->allowlist_mask)) ++ report_addresses(header, m, mark); ++#endif + if (m == 0 || !read_write(confd, packet, m + sizeof(u16), 0)) + return packet; + } +diff --git a/src/option.c b/src/option.c +index 0a72406..312d15f 100644 +--- a/src/option.c ++++ b/src/option.c +@@ -168,6 +168,8 @@ struct myoption { + #define LOPT_SINGLE_PORT 359 + #define LOPT_SCRIPT_TIME 360 + #define LOPT_PXE_VENDOR 361 ++#define LOPT_CMARK_ALST_EN 362 ++#define LOPT_CMARK_ALST 363 + + #ifdef HAVE_GETOPT_LONG + static const struct option opts[] = +@@ -321,6 +323,8 @@ static const struct myoption opts[] = + { "auth-sec-servers", 1, 0, LOPT_AUTHSFS }, + { "auth-peer", 1, 0, LOPT_AUTHPEER }, + { "ipset", 1, 0, LOPT_IPSET }, ++ { "connmark-allowlist-enable", 2, 0, LOPT_CMARK_ALST_EN }, ++ { "connmark-allowlist", 1, 0, LOPT_CMARK_ALST }, + { "synth-domain", 1, 0, LOPT_SYNTH }, + { "dnssec", 0, 0, LOPT_SEC_VALID }, + { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR }, +@@ -501,6 +505,8 @@ static struct { + { LOPT_AUTHSFS, ARG_DUP, "<NS>[,<NS>...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL }, + { LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL }, + { LOPT_IPSET, ARG_DUP, "/<domain>[/<domain>...]/<ipset>...", gettext_noop("Specify ipsets to which matching domains should be added"), NULL }, ++ { LOPT_CMARK_ALST_EN, ARG_ONE, "[=<mask>]", gettext_noop("Enable filtering of DNS queries with connection-track marks."), NULL }, ++ { LOPT_CMARK_ALST, ARG_DUP, "<connmark>[/<mask>][,<pattern>[/<pattern>...]]", gettext_noop("Set allowed DNS patterns for a connection-track mark."), NULL }, + { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL }, + { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL }, + { LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL }, +@@ -647,6 +653,22 @@ static char *canonicalise_opt(char *s) + return ret; + } + ++#ifdef HAVE_CONNTRACK ++static int strtoul_check(char *a, unsigned long *res) ++{ ++ if (!a) ++ return 0; ++ ++ unhide_metas(a); ++ ++ *res = strtoul(a, NULL, 0); ++ if (errno) ++ return 0; ++ ++ return 1; ++} ++#endif ++ + static int atoi_check(char *a, int *res) + { + char *p; +@@ -2743,6 +2765,135 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + } + #endif + ++ case LOPT_CMARK_ALST_EN: /* --connmark-allowlist-enable */ ++#ifndef HAVE_CONNTRACK ++ ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives")); ++ break; ++#else ++ { ++ unsigned long mask = UINT32_MAX; ++ ++ if (arg) ++ if (!strtoul_check(arg, &mask) || mask < 1 || mask > UINT32_MAX) ++ ret_err(gen_err); ++ ++ set_option_bool(OPT_CMARK_ALST_EN); ++ daemon->allowlist_mask = (uint32_t) mask; ++ break; ++ } ++#endif ++ ++ case LOPT_CMARK_ALST: /* --connmark-allowlist */ ++#ifndef HAVE_CONNTRACK ++ ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives")); ++ break; ++#else ++ { ++ struct allowlist *allowlists; ++ char **patterns, **patterns_pos; ++ unsigned long mark, mask = UINT32_MAX; ++ size_t num_patterns = 0; ++ ++ char *c, *m = NULL; ++ char *separator; ++ unhide_metas(arg); ++ if (!arg) ++ ret_err(gen_err); ++ c = arg; ++ if (*c < '0' || *c > '9') ++ ret_err(gen_err); ++ while (*c && *c != ',') ++ { ++ if (*c == '/') ++ { ++ if (m) ++ ret_err(gen_err); ++ *c = '\0'; ++ m = ++c; ++ } ++ if (*c < '0' || *c > '9') ++ ret_err(gen_err); ++ c++; ++ } ++ separator = c; ++ if (!*separator) ++ break; ++ while (c && *c) ++ { ++ char *end = strchr(++c, '/'); ++ if (end) ++ *end = '\0'; ++ if (strcmp(c, "*") && !is_valid_dns_name_pattern(c)) ++ ret_err(gen_err); ++ if (end) ++ *end = '/'; ++ if (num_patterns >= UINT16_MAX - 1) ++ ret_err(gen_err); ++ num_patterns++; ++ c = end; ++ } ++ ++ *separator = '\0'; ++ if (!strtoul_check(arg, &mark) || mark < 1 || mark > UINT32_MAX) ++ ret_err(gen_err); ++ if (m) ++ if (!strtoul_check(m, &mask) || mask < 1 || mask > UINT32_MAX || (mark & ~mask)) ++ ret_err(gen_err); ++ if (num_patterns) ++ *separator = ','; ++ for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next) ++ if (allowlists->mark == (uint32_t) mark && allowlists->mask == (uint32_t) mask) ++ ret_err(gen_err); ++ ++ patterns = opt_malloc((num_patterns + 1) * sizeof(char *)); ++ if (!patterns) ++ goto fail_cmark_allowlist; ++ patterns_pos = patterns; ++ c = separator; ++ while (c && *c) ++ { ++ char *end = strchr(++c, '/'); ++ if (end) ++ *end = '\0'; ++ if (!(*patterns_pos++ = opt_string_alloc(c))) ++ goto fail_cmark_allowlist; ++ if (end) ++ *end = '/'; ++ c = end; ++ } ++ *patterns_pos++ = NULL; ++ ++ allowlists = opt_malloc(sizeof(struct allowlist)); ++ if (!allowlists) ++ goto fail_cmark_allowlist; ++ memset(allowlists, 0, sizeof(struct allowlist)); ++ allowlists->mark = (uint32_t) mark; ++ allowlists->mask = (uint32_t) mask; ++ allowlists->patterns = patterns; ++ allowlists->next = daemon->allowlists; ++ daemon->allowlists = allowlists; ++ break; ++ ++ fail_cmark_allowlist: ++ if (patterns) ++ { ++ for (patterns_pos = patterns; *patterns_pos; patterns_pos++) ++ { ++ free(*patterns_pos); ++ *patterns_pos = NULL; ++ } ++ free(patterns); ++ patterns = NULL; ++ } ++ if (allowlists) ++ { ++ free(allowlists); ++ allowlists = NULL; ++ } ++ ret_err(gen_err); ++ } ++#endif ++ + case 'c': /* --cache-size */ + { + int size; +diff --git a/src/pattern.c b/src/pattern.c +new file mode 100644 +index 0000000..74f5801 +--- /dev/null ++++ b/src/pattern.c +@@ -0,0 +1,386 @@ ++/* dnsmasq is Copyright (c) 2000-2021 Simon Kelley ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; version 2 dated June, 1991, or ++ (at your option) version 3 dated 29 June, 2007. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see <http://www.gnu.org/licenses/>. ++*/ ++ ++#include "dnsmasq.h" ++ ++#ifdef HAVE_CONNTRACK ++ ++#define LOG(...) \ ++ do { \ ++ my_syslog(LOG_WARNING, __VA_ARGS__); \ ++ } while (0) ++ ++#define ASSERT(condition) \ ++ do { \ ++ if (!(condition)) \ ++ LOG("[pattern.c:%d] Assertion failure: %s", __LINE__, #condition); \ ++ } while (0) ++ ++/** ++ * Determines whether a given string value matches against a glob pattern ++ * which may contain zero-or-more-character wildcards denoted by '*'. ++ * ++ * Based on "Glob Matching Can Be Simple And Fast Too" by Russ Cox, ++ * See https://research.swtch.com/glob ++ * ++ * @param value A string value. ++ * @param num_value_bytes The number of bytes of the string value. ++ * @param pattern A glob pattern. ++ * @param num_pattern_bytes The number of bytes of the glob pattern. ++ * ++ * @return 1 If the provided value matches against the glob pattern. ++ * @return 0 Otherwise. ++ */ ++static int is_string_matching_glob_pattern( ++ const char *value, ++ size_t num_value_bytes, ++ const char *pattern, ++ size_t num_pattern_bytes) ++{ ++ ASSERT(value); ++ ASSERT(pattern); ++ ++ size_t value_index = 0; ++ size_t next_value_index = 0; ++ size_t pattern_index = 0; ++ size_t next_pattern_index = 0; ++ while (value_index < num_value_bytes || pattern_index < num_pattern_bytes) ++ { ++ if (pattern_index < num_pattern_bytes) ++ { ++ char pattern_character = pattern[pattern_index]; ++ if ('a' <= pattern_character && pattern_character <= 'z') ++ pattern_character -= 'a' - 'A'; ++ if (pattern_character == '*') ++ { ++ // zero-or-more-character wildcard ++ // Try to match at value_index, otherwise restart at value_index + 1 next. ++ next_pattern_index = pattern_index; ++ pattern_index++; ++ if (value_index < num_value_bytes) ++ next_value_index = value_index + 1; ++ else ++ next_value_index = 0; ++ continue; ++ } ++ else ++ { ++ // ordinary character ++ if (value_index < num_value_bytes) ++ { ++ char value_character = value[value_index]; ++ if ('a' <= value_character && value_character <= 'z') ++ value_character -= 'a' - 'A'; ++ if (value_character == pattern_character) ++ { ++ pattern_index++; ++ value_index++; ++ continue; ++ } ++ } ++ } ++ } ++ if (next_value_index) ++ { ++ pattern_index = next_pattern_index; ++ value_index = next_value_index; ++ continue; ++ } ++ return 0; ++ } ++ return 1; ++} ++ ++/** ++ * Determines whether a given string value represents a valid DNS name. ++ * ++ * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels ++ * delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only ++ * ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen. ++ * ++ * - A valid name must be fully qualified, i.e., consist of at least two labels. ++ * The final label must not be fully numeric, and must not be the "local" pseudo-TLD. ++ * ++ * - Examples: ++ * Valid: "example.com" ++ * Invalid: "ipcamera", "ipcamera.local", "8.8.8.8" ++ * ++ * @param value A string value. ++ * ++ * @return 1 If the provided string value is a valid DNS name. ++ * @return 0 Otherwise. ++ */ ++int is_valid_dns_name(const char *value) ++{ ++ ASSERT(value); ++ ++ size_t num_bytes = 0; ++ size_t num_labels = 0; ++ const char *label = NULL; ++ int is_label_numeric = 1; ++ for (const char *c = value;; c++) ++ { ++ if (*c && ++ *c != '-' && *c != '.' && ++ (*c < '0' || *c > '9') && ++ (*c < 'A' || *c > 'Z') && ++ (*c < 'a' || *c > 'z')) ++ { ++ LOG("Invalid DNS name: Invalid character %c.", *c); ++ return 0; ++ } ++ if (*c) ++ num_bytes++; ++ if (!label) ++ { ++ if (!*c || *c == '.') ++ { ++ LOG("Invalid DNS name: Empty label."); ++ return 0; ++ } ++ if (*c == '-') ++ { ++ LOG("Invalid DNS name: Label starts with hyphen."); ++ return 0; ++ } ++ label = c; ++ } ++ if (*c && *c != '.') ++ { ++ if (*c < '0' || *c > '9') ++ is_label_numeric = 0; ++ } ++ else ++ { ++ if (c[-1] == '-') ++ { ++ LOG("Invalid DNS name: Label ends with hyphen."); ++ return 0; ++ } ++ size_t num_label_bytes = (size_t) (c - label); ++ if (num_label_bytes > 63) ++ { ++ LOG("Invalid DNS name: Label is too long (%zu).", num_label_bytes); ++ return 0; ++ } ++ num_labels++; ++ if (!*c) ++ { ++ if (num_labels < 2) ++ { ++ LOG("Invalid DNS name: Not enough labels (%zu).", num_labels); ++ return 0; ++ } ++ if (is_label_numeric) ++ { ++ LOG("Invalid DNS name: Final label is fully numeric."); ++ return 0; ++ } ++ if (num_label_bytes == 5 && ++ (label[0] == 'l' || label[0] == 'L') && ++ (label[1] == 'o' || label[1] == 'O') && ++ (label[2] == 'c' || label[2] == 'C') && ++ (label[3] == 'a' || label[3] == 'A') && ++ (label[4] == 'l' || label[4] == 'L')) ++ { ++ LOG("Invalid DNS name: \"local\" pseudo-TLD."); ++ return 0; ++ } ++ if (num_bytes < 1 || num_bytes > 253) ++ { ++ LOG("DNS name has invalid length (%zu).", num_bytes); ++ return 0; ++ } ++ return 1; ++ } ++ label = NULL; ++ is_label_numeric = 1; ++ } ++ } ++} ++ ++/** ++ * Determines whether a given string value represents a valid DNS name pattern. ++ * ++ * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels ++ * delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only ++ * ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen. ++ * ++ * - Patterns follow the syntax of DNS names, but additionally allow the wildcard character "*" to be used up to ++ * twice per label to match 0 or more characters within that label. Note that the wildcard never matches a dot ++ * (e.g., "*.example.com" matches "api.example.com" but not "api.us.example.com"). ++ * ++ * - A valid name or pattern must be fully qualified, i.e., consist of at least two labels. ++ * The final label must not be fully numeric, and must not be the "local" pseudo-TLD. ++ * A pattern must end with at least two literal (non-wildcard) labels. ++ * ++ * - Examples: ++ * Valid: "example.com", "*.example.com", "video*.example.com", "api*.*.example.com", "*-prod-*.example.com" ++ * Invalid: "ipcamera", "ipcamera.local", "*", "*.com", "8.8.8.8" ++ * ++ * @param value A string value. ++ * ++ * @return 1 If the provided string value is a valid DNS name pattern. ++ * @return 0 Otherwise. ++ */ ++int is_valid_dns_name_pattern(const char *value) ++{ ++ ASSERT(value); ++ ++ size_t num_bytes = 0; ++ size_t num_labels = 0; ++ const char *label = NULL; ++ int is_label_numeric = 1; ++ size_t num_wildcards = 0; ++ int previous_label_has_wildcard = 1; ++ for (const char *c = value;; c++) ++ { ++ if (*c && ++ *c != '*' && // Wildcard. ++ *c != '-' && *c != '.' && ++ (*c < '0' || *c > '9') && ++ (*c < 'A' || *c > 'Z') && ++ (*c < 'a' || *c > 'z')) ++ { ++ LOG("Invalid DNS name pattern: Invalid character %c.", *c); ++ return 0; ++ } ++ if (*c && *c != '*') ++ num_bytes++; ++ if (!label) ++ { ++ if (!*c || *c == '.') ++ { ++ LOG("Invalid DNS name pattern: Empty label."); ++ return 0; ++ } ++ if (*c == '-') ++ { ++ LOG("Invalid DNS name pattern: Label starts with hyphen."); ++ return 0; ++ } ++ label = c; ++ } ++ if (*c && *c != '.') ++ { ++ if (*c < '0' || *c > '9') ++ is_label_numeric = 0; ++ if (*c == '*') ++ { ++ if (num_wildcards >= 2) ++ { ++ LOG("Invalid DNS name pattern: Wildcard character used more than twice per label."); ++ return 0; ++ } ++ num_wildcards++; ++ } ++ } ++ else ++ { ++ if (c[-1] == '-') ++ { ++ LOG("Invalid DNS name pattern: Label ends with hyphen."); ++ return 0; ++ } ++ size_t num_label_bytes = (size_t) (c - label) - num_wildcards; ++ if (num_label_bytes > 63) ++ { ++ LOG("Invalid DNS name pattern: Label is too long (%zu).", num_label_bytes); ++ return 0; ++ } ++ num_labels++; ++ if (!*c) ++ { ++ if (num_labels < 2) ++ { ++ LOG("Invalid DNS name pattern: Not enough labels (%zu).", num_labels); ++ return 0; ++ } ++ if (num_wildcards != 0 || previous_label_has_wildcard) ++ { ++ LOG("Invalid DNS name pattern: Wildcard within final two labels."); ++ return 0; ++ } ++ if (is_label_numeric) ++ { ++ LOG("Invalid DNS name pattern: Final label is fully numeric."); ++ return 0; ++ } ++ if (num_label_bytes == 5 && ++ (label[0] == 'l' || label[0] == 'L') && ++ (label[1] == 'o' || label[1] == 'O') && ++ (label[2] == 'c' || label[2] == 'C') && ++ (label[3] == 'a' || label[3] == 'A') && ++ (label[4] == 'l' || label[4] == 'L')) ++ { ++ LOG("Invalid DNS name pattern: \"local\" pseudo-TLD."); ++ return 0; ++ } ++ if (num_bytes < 1 || num_bytes > 253) ++ { ++ LOG("DNS name pattern has invalid length after removing wildcards (%zu).", num_bytes); ++ return 0; ++ } ++ return 1; ++ } ++ label = NULL; ++ is_label_numeric = 1; ++ previous_label_has_wildcard = num_wildcards != 0; ++ num_wildcards = 0; ++ } ++ } ++} ++ ++/** ++ * Determines whether a given DNS name matches against a DNS name pattern. ++ * ++ * @param name A valid DNS name. ++ * @param pattern A valid DNS name pattern. ++ * ++ * @return 1 If the provided DNS name matches against the DNS name pattern. ++ * @return 0 Otherwise. ++ */ ++int is_dns_name_matching_pattern(const char *name, const char *pattern) ++{ ++ ASSERT(name); ++ ASSERT(is_valid_dns_name(name)); ++ ASSERT(pattern); ++ ASSERT(is_valid_dns_name_pattern(pattern)); ++ ++ const char *n = name; ++ const char *p = pattern; ++ ++ do { ++ const char *name_label = n; ++ while (*n && *n != '.') ++ n++; ++ const char *pattern_label = p; ++ while (*p && *p != '.') ++ p++; ++ if (!is_string_matching_glob_pattern( ++ name_label, (size_t) (n - name_label), ++ pattern_label, (size_t) (p - pattern_label))) ++ break; ++ if (*n) ++ n++; ++ if (*p) ++ p++; ++ } while (*n && *p); ++ ++ return !*n && !*p; ++} ++ ++#endif +diff --git a/src/rfc1035.c b/src/rfc1035.c +index ea438c2..d60253d 100644 +--- a/src/rfc1035.c ++++ b/src/rfc1035.c +@@ -884,6 +884,80 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t + return 0; + } + ++#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) ++void report_addresses(struct dns_header *header, size_t len, uint32_t mark) ++{ ++ unsigned char *p, *endrr; ++ int i; ++ unsigned long attl; ++ struct allowlist *allowlists; ++ char **pattern_pos; ++ ++ if (RCODE(header) != NOERROR) ++ return; ++ ++ for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next) ++ if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask)) ++ for (pattern_pos = allowlists->patterns; *pattern_pos; pattern_pos++) ++ if (!strcmp(*pattern_pos, "*")) ++ return; ++ ++ if (!(p = skip_questions(header, len))) ++ return; ++ for (i = ntohs(header->ancount); i != 0; i--) ++ { ++ int aqtype, aqclass, ardlen; ++ ++ if (!extract_name(header, len, &p, daemon->namebuff, 1, 10)) ++ return; ++ ++ if (!CHECK_LEN(header, p, len, 10)) ++ return; ++ GETSHORT(aqtype, p); ++ GETSHORT(aqclass, p); ++ GETLONG(attl, p); ++ GETSHORT(ardlen, p); ++ ++ if (!CHECK_LEN(header, p, len, ardlen)) ++ return; ++ endrr = p+ardlen; ++ ++ if (aqclass == C_IN) ++ { ++ if (aqtype == T_CNAME) ++ { ++ char namebuff[MAXDNAME]; ++ if (!extract_name(header, len, &p, namebuff, 1, 0)) ++ return; ++ ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, namebuff, attl); ++ } ++ if (aqtype == T_A) ++ { ++ struct in_addr addr; ++ char ip[INET_ADDRSTRLEN]; ++ if (ardlen != INADDRSZ) ++ return; ++ memcpy(&addr, p, ardlen); ++ if (inet_ntop(AF_INET, &addr, ip, sizeof ip)) ++ ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl); ++ } ++ else if (aqtype == T_AAAA) ++ { ++ struct in6_addr addr; ++ char ip[INET6_ADDRSTRLEN]; ++ if (ardlen != IN6ADDRSZ) ++ return; ++ memcpy(&addr, p, ardlen); ++ if (inet_ntop(AF_INET6, &addr, ip, sizeof ip)) ++ ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl); ++ } ++ } ++ ++ p = endrr; ++ } ++} ++#endif ++ + /* If the packet holds exactly one query + return F_IPV4 or F_IPV6 and leave the name from the query in name */ + unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep) +@@ -894,7 +968,8 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, + if (typep) + *typep = 0; + +- if (ntohs(header->qdcount) != 1 || OPCODE(header) != QUERY) ++ if (ntohs(header->qdcount) != 1 || OPCODE(header) != QUERY || ++ ntohs(header->ancount) != 0 || ntohs(header->nscount) != 0) + return 0; /* must be exactly one query. */ + + if (!extract_name(header, qlen, &p, name, 1, 4)) +diff --git a/src/ubus.c b/src/ubus.c +index 33c2783..00b14dd 100644 +--- a/src/ubus.c ++++ b/src/ubus.c +@@ -28,10 +28,38 @@ static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj + struct ubus_request_data *req, const char *method, + struct blob_attr *msg); + ++#ifdef HAVE_CONNTRACK ++enum { ++ SET_CONNMARK_ALLOWLIST_MARK, ++ SET_CONNMARK_ALLOWLIST_MASK, ++ SET_CONNMARK_ALLOWLIST_PATTERNS ++}; ++static const struct blobmsg_policy set_connmark_allowlist_policy[] = { ++ [SET_CONNMARK_ALLOWLIST_MARK] = { ++ .name = "mark", ++ .type = BLOBMSG_TYPE_INT32 ++ }, ++ [SET_CONNMARK_ALLOWLIST_MASK] = { ++ .name = "mask", ++ .type = BLOBMSG_TYPE_INT32 ++ }, ++ [SET_CONNMARK_ALLOWLIST_PATTERNS] = { ++ .name = "patterns", ++ .type = BLOBMSG_TYPE_ARRAY ++ } ++}; ++static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj, ++ struct ubus_request_data *req, const char *method, ++ struct blob_attr *msg); ++#endif ++ + static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj); + + static const struct ubus_method ubus_object_methods[] = { + UBUS_METHOD_NOARG("metrics", ubus_handle_metrics), ++#ifdef HAVE_CONNTRACK ++ UBUS_METHOD("set_connmark_allowlist", ubus_handle_set_connmark_allowlist, set_connmark_allowlist_policy), ++#endif + }; + + static struct ubus_object_type ubus_object_type = +@@ -178,6 +206,122 @@ static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj + return ubus_send_reply(ctx, req, b.head); + } + ++#ifdef HAVE_CONNTRACK ++static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj, ++ struct ubus_request_data *req, const char *method, ++ struct blob_attr *msg) ++{ ++ const struct blobmsg_policy *policy = set_connmark_allowlist_policy; ++ size_t policy_len = countof(set_connmark_allowlist_policy); ++ struct allowlist *allowlists = NULL, **allowlists_pos; ++ char **patterns = NULL, **patterns_pos; ++ uint32_t mark, mask = UINT32_MAX; ++ size_t num_patterns = 0; ++ struct blob_attr *tb[policy_len]; ++ struct blob_attr *attr; ++ ++ if (blobmsg_parse(policy, policy_len, tb, blob_data(msg), blob_len(msg))) ++ return UBUS_STATUS_INVALID_ARGUMENT; ++ ++ if (!tb[SET_CONNMARK_ALLOWLIST_MARK]) ++ return UBUS_STATUS_INVALID_ARGUMENT; ++ mark = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MARK]); ++ if (!mark) ++ return UBUS_STATUS_INVALID_ARGUMENT; ++ ++ if (tb[SET_CONNMARK_ALLOWLIST_MASK]) ++ { ++ mask = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MASK]); ++ if (!mask || (mark & ~mask)) ++ return UBUS_STATUS_INVALID_ARGUMENT; ++ } ++ ++ if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS]) ++ { ++ struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]); ++ size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]); ++ __blob_for_each_attr(attr, head, len) ++ { ++ char *pattern; ++ if (blob_id(attr) != BLOBMSG_TYPE_STRING) ++ return UBUS_STATUS_INVALID_ARGUMENT; ++ if (!(pattern = blobmsg_get_string(attr))) ++ return UBUS_STATUS_INVALID_ARGUMENT; ++ if (strcmp(pattern, "*") && !is_valid_dns_name_pattern(pattern)) ++ return UBUS_STATUS_INVALID_ARGUMENT; ++ num_patterns++; ++ } ++ } ++ ++ for (allowlists_pos = &daemon->allowlists; *allowlists_pos; allowlists_pos = &(*allowlists_pos)->next) ++ if ((*allowlists_pos)->mark == mark && (*allowlists_pos)->mask == mask) ++ { ++ struct allowlist *allowlists_next = (*allowlists_pos)->next; ++ for (patterns_pos = (*allowlists_pos)->patterns; *patterns_pos; patterns_pos++) ++ { ++ free(*patterns_pos); ++ *patterns_pos = NULL; ++ } ++ free((*allowlists_pos)->patterns); ++ (*allowlists_pos)->patterns = NULL; ++ free(*allowlists_pos); ++ *allowlists_pos = allowlists_next; ++ break; ++ } ++ ++ if (!num_patterns) ++ return UBUS_STATUS_OK; ++ ++ patterns = whine_malloc((num_patterns + 1) * sizeof(char *)); ++ if (!patterns) ++ goto fail; ++ patterns_pos = patterns; ++ if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS]) ++ { ++ struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]); ++ size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]); ++ __blob_for_each_attr(attr, head, len) ++ { ++ char *pattern; ++ if (!(pattern = blobmsg_get_string(attr))) ++ goto fail; ++ if (!(*patterns_pos = whine_malloc(strlen(pattern) + 1))) ++ goto fail; ++ strcpy(*patterns_pos++, pattern); ++ } ++ } ++ ++ allowlists = whine_malloc(sizeof(struct allowlist)); ++ if (!allowlists) ++ goto fail; ++ memset(allowlists, 0, sizeof(struct allowlist)); ++ allowlists->mark = mark; ++ allowlists->mask = mask; ++ allowlists->patterns = patterns; ++ allowlists->next = daemon->allowlists; ++ daemon->allowlists = allowlists; ++ return UBUS_STATUS_OK; ++ ++fail: ++ if (patterns) ++ { ++ for (patterns_pos = patterns; *patterns_pos; patterns_pos++) ++ { ++ free(*patterns_pos); ++ *patterns_pos = NULL; ++ } ++ free(patterns); ++ patterns = NULL; ++ } ++ if (allowlists) ++ { ++ free(allowlists); ++ allowlists = NULL; ++ } ++ return UBUS_STATUS_UNKNOWN_ERROR; ++} ++#endif ++ + void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface) + { + struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; +@@ -197,9 +341,47 @@ void ubus_event_bcast(const char *type, const char *mac, const char *ip, const c + blobmsg_add_string(&b, "interface", interface); + + ret = ubus_notify(ubus, &ubus_object, type, b.head, -1); +- if (!ret) ++ if (ret) ++ my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret)); ++} ++ ++#ifdef HAVE_CONNTRACK ++void ubus_event_bcast_connmark_allowlist_refused(uint32_t mark, const char *name) ++{ ++ struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; ++ int ret; ++ ++ if (!ubus || !notify) ++ return; ++ ++ blob_buf_init(&b, 0); ++ blobmsg_add_u32(&b, "mark", mark); ++ blobmsg_add_string(&b, "name", name); ++ ++ ret = ubus_notify(ubus, &ubus_object, "connmark-allowlist.refused", b.head, -1); ++ if (ret) ++ my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret)); ++} ++ ++void ubus_event_bcast_connmark_allowlist_resolved(uint32_t mark, const char *name, const char *value, uint32_t ttl) ++{ ++ struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; ++ int ret; ++ ++ if (!ubus || !notify) ++ return; ++ ++ blob_buf_init(&b, 0); ++ blobmsg_add_u32(&b, "mark", mark); ++ blobmsg_add_string(&b, "name", name); ++ blobmsg_add_string(&b, "value", value); ++ blobmsg_add_u32(&b, "ttl", ttl); ++ ++ ret = ubus_notify(ubus, &ubus_object, "connmark-allowlist.resolved", b.head, /* timeout: */ 1000); ++ if (ret) + my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret)); + } ++#endif + + + #endif /* HAVE_UBUS */ +-- +2.30.1 (Apple Git-130) + -- 2.30.1 (Apple Git-130) _______________________________________________ Dnsmasq-discuss mailing list Dnsmasq-discuss@lists.thekelleys.org.uk https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss