mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 18:28:25 +00:00
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@apple.com> (addressed reviewer feedback) Signed-off-by: Etan Kissling <etan.kissling@gmail.com>
This commit is contained in:
committed by
Simon Kelley
parent
cbd76447fd
commit
627056febb
@@ -272,7 +272,8 @@ struct event_desc {
|
||||
#define OPT_LOG_DEBUG 62
|
||||
#define OPT_UMBRELLA 63
|
||||
#define OPT_UMBRELLA_DEVID 64
|
||||
#define OPT_LAST 65
|
||||
#define OPT_CMARK_ALST_EN 65
|
||||
#define OPT_LAST 66
|
||||
|
||||
#define OPTION_BITS (sizeof(unsigned int)*8)
|
||||
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
|
||||
@@ -610,6 +611,12 @@ struct ipsets {
|
||||
struct ipsets *next;
|
||||
};
|
||||
|
||||
struct allowlist {
|
||||
u32 mark, mask;
|
||||
char **patterns;
|
||||
struct allowlist *next;
|
||||
};
|
||||
|
||||
struct irec {
|
||||
union mysockaddr addr;
|
||||
struct in_addr netmask; /* only valid for IPv4 */
|
||||
@@ -1086,6 +1093,8 @@ extern struct daemon {
|
||||
struct server *servers, *local_domains, **serverarray, *no_rebind;
|
||||
int serverarraysz;
|
||||
struct ipsets *ipsets;
|
||||
u32 allowlist_mask;
|
||||
struct allowlist *allowlists;
|
||||
int log_fac; /* log facility */
|
||||
char *log_file; /* optional log file */
|
||||
int max_logs; /* queue limit */
|
||||
@@ -1275,6 +1284,9 @@ void setup_reply(struct dns_header *header, unsigned int flags);
|
||||
int extract_addresses(struct dns_header *header, size_t qlen, char *name,
|
||||
time_t now, char **ipsets, int is_sign, int check_rebind,
|
||||
int no_cache_dnssec, int secure, int *doctored);
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
void report_addresses(struct dns_header *header, size_t len, u32 mark);
|
||||
#endif
|
||||
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
||||
struct in_addr local_addr, struct in_addr local_netmask,
|
||||
time_t now, int ad_reqd, int do_bit, int have_pseudoheader);
|
||||
@@ -1546,6 +1558,10 @@ char *ubus_init(void);
|
||||
void set_ubus_listeners(void);
|
||||
void check_ubus_listeners(void);
|
||||
void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface);
|
||||
# ifdef HAVE_CONNTRACK
|
||||
void ubus_event_bcast_connmark_allowlist_refused(u32 mark, const char *name);
|
||||
void ubus_event_bcast_connmark_allowlist_resolved(u32 mark, const char *pattern, const char *ip, u32 ttl);
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* ipset.c */
|
||||
@@ -1554,6 +1570,13 @@ void ipset_init(void);
|
||||
int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove);
|
||||
#endif
|
||||
|
||||
/* pattern.c */
|
||||
#ifdef HAVE_CONNTRACK
|
||||
int is_valid_dns_name(const char *value);
|
||||
int is_valid_dns_name_pattern(const char *value);
|
||||
int is_dns_name_matching_pattern(const char *name, const char *pattern);
|
||||
#endif
|
||||
|
||||
/* helper.c */
|
||||
#if defined(HAVE_SCRIPT)
|
||||
int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd);
|
||||
|
||||
145
src/forward.c
145
src/forward.c
@@ -531,6 +531,16 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
if (oph)
|
||||
plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
|
||||
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
if (option_bool(OPT_CMARK_ALST_EN))
|
||||
{
|
||||
unsigned int mark;
|
||||
int have_mark = get_incoming_mark(udpaddr, dst_addr, /* istcp: */ 0, &mark);
|
||||
if (have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
report_addresses(header, plen, mark);
|
||||
}
|
||||
#endif
|
||||
|
||||
send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface);
|
||||
}
|
||||
|
||||
@@ -1152,6 +1162,16 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
|
||||
dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
if (option_bool(OPT_CMARK_ALST_EN))
|
||||
{
|
||||
unsigned int mark;
|
||||
int have_mark = get_incoming_mark(&src->source, &src->dest, /* istcp: */ 0, &mark);
|
||||
if (have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
report_addresses(header, nn, mark);
|
||||
}
|
||||
#endif
|
||||
|
||||
send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
|
||||
&src->source, &src->dest, src->iface);
|
||||
|
||||
@@ -1168,6 +1188,47 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
|
||||
}
|
||||
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
static int is_query_allowed_for_mark(u32 mark, const char *name)
|
||||
{
|
||||
int is_allowable_name, did_validate_name = 0;
|
||||
struct allowlist *allowlists;
|
||||
char **patterns_pos;
|
||||
|
||||
for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
|
||||
if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask))
|
||||
for (patterns_pos = allowlists->patterns; *patterns_pos; patterns_pos++)
|
||||
{
|
||||
if (!strcmp(*patterns_pos, "*"))
|
||||
return 1;
|
||||
if (!did_validate_name)
|
||||
{
|
||||
is_allowable_name = name ? is_valid_dns_name(name) : 0;
|
||||
did_validate_name = 1;
|
||||
}
|
||||
if (is_allowable_name && is_dns_name_matching_pattern(name, *patterns_pos))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t answer_disallowed(struct dns_header *header, size_t qlen, u32 mark, const char *name)
|
||||
{
|
||||
unsigned char *p;
|
||||
|
||||
#ifdef HAVE_UBUS
|
||||
if (name)
|
||||
ubus_event_bcast_connmark_allowlist_refused(mark, name);
|
||||
#endif
|
||||
|
||||
setup_reply(header, /* flags: */ 0);
|
||||
|
||||
if (!(p = skip_questions(header, qlen)))
|
||||
return 0;
|
||||
return p - (unsigned char *)header;
|
||||
}
|
||||
#endif
|
||||
|
||||
void receive_query(struct listener *listen, time_t now)
|
||||
{
|
||||
struct dns_header *header = (struct dns_header *)daemon->packet;
|
||||
@@ -1179,6 +1240,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
|
||||
@@ -1407,6 +1473,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))
|
||||
{
|
||||
@@ -1417,6 +1488,10 @@ void receive_query(struct listener *listen, time_t now)
|
||||
|
||||
log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff,
|
||||
&source_addr, types);
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
is_single_query = 1;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_AUTH
|
||||
/* find queries for zones we're authoritative for, and answer them directly */
|
||||
@@ -1458,20 +1533,47 @@ void receive_query(struct listener *listen, time_t now)
|
||||
udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */
|
||||
}
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
#ifdef HAVE_AUTH
|
||||
if (auth_dns)
|
||||
if (!auth_dns || local_auth)
|
||||
#endif
|
||||
if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
allowed = is_query_allowed_for_mark((u32)mark, is_single_query ? daemon->namebuff : NULL);
|
||||
#endif
|
||||
|
||||
if (0);
|
||||
#ifdef HAVE_CONNTRACK
|
||||
else if (!allowed)
|
||||
{
|
||||
m = answer_disallowed(header, (size_t)n, (u32)mark, is_single_query ? daemon->namebuff : NULL);
|
||||
|
||||
if (m >= 1)
|
||||
{
|
||||
send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
|
||||
(char *)header, m, &source_addr, &dst_addr, if_index);
|
||||
daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_AUTH
|
||||
else if (auth_dns)
|
||||
{
|
||||
m = answer_auth(header, ((char *) header) + udp_size, (size_t)n, now, &source_addr,
|
||||
local_auth, do_bit, have_pseudoheader);
|
||||
if (m >= 1)
|
||||
{
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
if (local_auth)
|
||||
if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
report_addresses(header, m, mark);
|
||||
#endif
|
||||
send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
|
||||
(char *)header, m, &source_addr, &dst_addr, if_index);
|
||||
daemon->metrics[METRIC_DNS_AUTH_ANSWERED]++;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
else
|
||||
{
|
||||
int ad_reqd = do_bit;
|
||||
/* RFC 6840 5.7 */
|
||||
@@ -1483,6 +1585,10 @@ void receive_query(struct listener *listen, time_t now)
|
||||
|
||||
if (m >= 1)
|
||||
{
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
report_addresses(header, m, mark);
|
||||
#endif
|
||||
send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
|
||||
(char *)header, m, &source_addr, &dst_addr, if_index);
|
||||
daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
|
||||
@@ -1689,6 +1795,9 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
{
|
||||
size_t size = 0;
|
||||
int norebind;
|
||||
#ifdef HAVE_CONNTRACK
|
||||
int is_single_query = 0, allowed = 1;
|
||||
#endif
|
||||
#ifdef HAVE_AUTH
|
||||
int local_auth = 0;
|
||||
#endif
|
||||
@@ -1720,7 +1829,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;
|
||||
|
||||
@@ -1800,6 +1909,10 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff,
|
||||
&peer_addr, types);
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
is_single_query = 1;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_AUTH
|
||||
/* find queries for zones we're authoritative for, and answer them directly */
|
||||
if (!auth_dns && !option_bool(OPT_LOCALISE))
|
||||
@@ -1833,13 +1946,26 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
if (flags & 0x8000)
|
||||
do_bit = 1; /* do bit */
|
||||
}
|
||||
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
#ifdef HAVE_AUTH
|
||||
if (auth_dns)
|
||||
if (!auth_dns || local_auth)
|
||||
#endif
|
||||
if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
allowed = is_query_allowed_for_mark((u32)mark, is_single_query ? daemon->namebuff : NULL);
|
||||
#endif
|
||||
|
||||
if (0);
|
||||
#ifdef HAVE_CONNTRACK
|
||||
else if (!allowed)
|
||||
m = answer_disallowed(header, size, (u32)mark, is_single_query ? daemon->namebuff : NULL);
|
||||
#endif
|
||||
#ifdef HAVE_AUTH
|
||||
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 */
|
||||
@@ -1965,6 +2091,13 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
|
||||
*length = htons(m);
|
||||
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
#ifdef HAVE_AUTH
|
||||
if (!auth_dns || local_auth)
|
||||
#endif
|
||||
if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
|
||||
report_addresses(header, m, mark);
|
||||
#endif
|
||||
if (!read_write(confd, packet, m + sizeof(u16), 0))
|
||||
break;
|
||||
}
|
||||
|
||||
142
src/option.c
142
src/option.c
@@ -171,6 +171,8 @@ struct myoption {
|
||||
#define LOPT_DYNHOST 362
|
||||
#define LOPT_LOG_DEBUG 363
|
||||
#define LOPT_UMBRELLA 364
|
||||
#define LOPT_CMARK_ALST_EN 365
|
||||
#define LOPT_CMARK_ALST 366
|
||||
|
||||
#ifdef HAVE_GETOPT_LONG
|
||||
static const struct option opts[] =
|
||||
@@ -324,6 +326,8 @@ static const struct myoption opts[] =
|
||||
{ "auth-sec-servers", 1, 0, LOPT_AUTHSFS },
|
||||
{ "auth-peer", 1, 0, LOPT_AUTHPEER },
|
||||
{ "ipset", 1, 0, LOPT_IPSET },
|
||||
{ "connmark-allowlist-enable", 2, 0, LOPT_CMARK_ALST_EN },
|
||||
{ "connmark-allowlist", 1, 0, LOPT_CMARK_ALST },
|
||||
{ "synth-domain", 1, 0, LOPT_SYNTH },
|
||||
{ "dnssec", 0, 0, LOPT_SEC_VALID },
|
||||
{ "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
|
||||
@@ -508,6 +512,8 @@ static struct {
|
||||
{ LOPT_AUTHSFS, ARG_DUP, "<NS>[,<NS>...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL },
|
||||
{ LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL },
|
||||
{ LOPT_IPSET, ARG_DUP, "/<domain>[/<domain>...]/<ipset>...", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
|
||||
{ LOPT_CMARK_ALST_EN, ARG_ONE, "[=<mask>]", gettext_noop("Enable filtering of DNS queries with connection-track marks."), NULL },
|
||||
{ LOPT_CMARK_ALST, ARG_DUP, "<connmark>[/<mask>][,<pattern>[/<pattern>...]]", gettext_noop("Set allowed DNS patterns for a connection-track mark."), NULL },
|
||||
{ LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
|
||||
{ LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
|
||||
{ LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
|
||||
@@ -685,13 +691,16 @@ static int atoi_check(char *a, int *res)
|
||||
|
||||
static int strtoul_check(char *a, u32 *res)
|
||||
{
|
||||
unsigned long x;
|
||||
|
||||
if (!numeric_check(a))
|
||||
return 0;
|
||||
*res = strtoul(a, NULL, 10);
|
||||
if (errno == ERANGE) {
|
||||
x = strtoul(a, NULL, 10);
|
||||
if (errno || x > UINT32_MAX) {
|
||||
errno = 0;
|
||||
return 0;
|
||||
}
|
||||
*res = (u32)x;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -2880,6 +2889,135 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
}
|
||||
#endif
|
||||
|
||||
case LOPT_CMARK_ALST_EN: /* --connmark-allowlist-enable */
|
||||
#ifndef HAVE_CONNTRACK
|
||||
ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives"));
|
||||
break;
|
||||
#else
|
||||
{
|
||||
u32 mask = UINT32_MAX;
|
||||
|
||||
if (arg)
|
||||
if (!strtoul_check(arg, &mask) || mask < 1)
|
||||
ret_err(gen_err);
|
||||
|
||||
set_option_bool(OPT_CMARK_ALST_EN);
|
||||
daemon->allowlist_mask = mask;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
case LOPT_CMARK_ALST: /* --connmark-allowlist */
|
||||
#ifndef HAVE_CONNTRACK
|
||||
ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives"));
|
||||
break;
|
||||
#else
|
||||
{
|
||||
struct allowlist *allowlists;
|
||||
char **patterns, **patterns_pos;
|
||||
u32 mark, mask = UINT32_MAX;
|
||||
size_t num_patterns = 0;
|
||||
|
||||
char *c, *m = NULL;
|
||||
char *separator;
|
||||
unhide_metas(arg);
|
||||
if (!arg)
|
||||
ret_err(gen_err);
|
||||
c = arg;
|
||||
if (*c < '0' || *c > '9')
|
||||
ret_err(gen_err);
|
||||
while (*c && *c != ',')
|
||||
{
|
||||
if (*c == '/')
|
||||
{
|
||||
if (m)
|
||||
ret_err(gen_err);
|
||||
*c = '\0';
|
||||
m = ++c;
|
||||
}
|
||||
if (*c < '0' || *c > '9')
|
||||
ret_err(gen_err);
|
||||
c++;
|
||||
}
|
||||
separator = c;
|
||||
if (!*separator)
|
||||
break;
|
||||
while (c && *c)
|
||||
{
|
||||
char *end = strchr(++c, '/');
|
||||
if (end)
|
||||
*end = '\0';
|
||||
if (strcmp(c, "*") && !is_valid_dns_name_pattern(c))
|
||||
ret_err(gen_err);
|
||||
if (end)
|
||||
*end = '/';
|
||||
if (num_patterns >= UINT16_MAX - 1)
|
||||
ret_err(gen_err);
|
||||
num_patterns++;
|
||||
c = end;
|
||||
}
|
||||
|
||||
*separator = '\0';
|
||||
if (!strtoul_check(arg, &mark) || mark < 1 || mark > UINT32_MAX)
|
||||
ret_err(gen_err);
|
||||
if (m)
|
||||
if (!strtoul_check(m, &mask) || mask < 1 || mask > UINT32_MAX || (mark & ~mask))
|
||||
ret_err(gen_err);
|
||||
if (num_patterns)
|
||||
*separator = ',';
|
||||
for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
|
||||
if (allowlists->mark == mark && allowlists->mask == mask)
|
||||
ret_err(gen_err);
|
||||
|
||||
patterns = opt_malloc((num_patterns + 1) * sizeof(char *));
|
||||
if (!patterns)
|
||||
goto fail_cmark_allowlist;
|
||||
patterns_pos = patterns;
|
||||
c = separator;
|
||||
while (c && *c)
|
||||
{
|
||||
char *end = strchr(++c, '/');
|
||||
if (end)
|
||||
*end = '\0';
|
||||
if (!(*patterns_pos++ = opt_string_alloc(c)))
|
||||
goto fail_cmark_allowlist;
|
||||
if (end)
|
||||
*end = '/';
|
||||
c = end;
|
||||
}
|
||||
*patterns_pos++ = NULL;
|
||||
|
||||
allowlists = opt_malloc(sizeof(struct allowlist));
|
||||
if (!allowlists)
|
||||
goto fail_cmark_allowlist;
|
||||
memset(allowlists, 0, sizeof(struct allowlist));
|
||||
allowlists->mark = mark;
|
||||
allowlists->mask = mask;
|
||||
allowlists->patterns = patterns;
|
||||
allowlists->next = daemon->allowlists;
|
||||
daemon->allowlists = allowlists;
|
||||
break;
|
||||
|
||||
fail_cmark_allowlist:
|
||||
if (patterns)
|
||||
{
|
||||
for (patterns_pos = patterns; *patterns_pos; patterns_pos++)
|
||||
{
|
||||
free(*patterns_pos);
|
||||
*patterns_pos = NULL;
|
||||
}
|
||||
free(patterns);
|
||||
patterns = NULL;
|
||||
}
|
||||
if (allowlists)
|
||||
{
|
||||
free(allowlists);
|
||||
allowlists = NULL;
|
||||
}
|
||||
ret_err(gen_err);
|
||||
}
|
||||
#endif
|
||||
|
||||
case 'c': /* --cache-size */
|
||||
{
|
||||
int size;
|
||||
|
||||
386
src/pattern.c
Normal file
386
src/pattern.c
Normal file
@@ -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
|
||||
@@ -884,6 +884,80 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
void report_addresses(struct dns_header *header, size_t len, u32 mark)
|
||||
{
|
||||
unsigned char *p, *endrr;
|
||||
int i;
|
||||
unsigned long attl;
|
||||
struct allowlist *allowlists;
|
||||
char **pattern_pos;
|
||||
|
||||
if (RCODE(header) != NOERROR)
|
||||
return;
|
||||
|
||||
for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
|
||||
if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask))
|
||||
for (pattern_pos = allowlists->patterns; *pattern_pos; pattern_pos++)
|
||||
if (!strcmp(*pattern_pos, "*"))
|
||||
return;
|
||||
|
||||
if (!(p = skip_questions(header, len)))
|
||||
return;
|
||||
for (i = ntohs(header->ancount); i != 0; i--)
|
||||
{
|
||||
int aqtype, aqclass, ardlen;
|
||||
|
||||
if (!extract_name(header, len, &p, daemon->namebuff, 1, 10))
|
||||
return;
|
||||
|
||||
if (!CHECK_LEN(header, p, len, 10))
|
||||
return;
|
||||
GETSHORT(aqtype, p);
|
||||
GETSHORT(aqclass, p);
|
||||
GETLONG(attl, p);
|
||||
GETSHORT(ardlen, p);
|
||||
|
||||
if (!CHECK_LEN(header, p, len, ardlen))
|
||||
return;
|
||||
endrr = p+ardlen;
|
||||
|
||||
if (aqclass == C_IN)
|
||||
{
|
||||
if (aqtype == T_CNAME)
|
||||
{
|
||||
char namebuff[MAXDNAME];
|
||||
if (!extract_name(header, len, &p, namebuff, 1, 0))
|
||||
return;
|
||||
ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, namebuff, attl);
|
||||
}
|
||||
if (aqtype == T_A)
|
||||
{
|
||||
struct in_addr addr;
|
||||
char ip[INET_ADDRSTRLEN];
|
||||
if (ardlen != INADDRSZ)
|
||||
return;
|
||||
memcpy(&addr, p, ardlen);
|
||||
if (inet_ntop(AF_INET, &addr, ip, sizeof ip))
|
||||
ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl);
|
||||
}
|
||||
else if (aqtype == T_AAAA)
|
||||
{
|
||||
struct in6_addr addr;
|
||||
char ip[INET6_ADDRSTRLEN];
|
||||
if (ardlen != IN6ADDRSZ)
|
||||
return;
|
||||
memcpy(&addr, p, ardlen);
|
||||
if (inet_ntop(AF_INET6, &addr, ip, sizeof ip))
|
||||
ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl);
|
||||
}
|
||||
}
|
||||
|
||||
p = endrr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* If the packet holds exactly one query
|
||||
return F_IPV4 or F_IPV6 and leave the name from the query in name */
|
||||
unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep)
|
||||
@@ -896,7 +970,8 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
|
||||
|
||||
*name = 0; /* return empty name if no query found. */
|
||||
|
||||
if (ntohs(header->qdcount) != 1 || OPCODE(header) != QUERY)
|
||||
if (ntohs(header->qdcount) != 1 || OPCODE(header) != QUERY ||
|
||||
ntohs(header->ancount) != 0 || ntohs(header->nscount) != 0)
|
||||
return 0; /* must be exactly one query. */
|
||||
|
||||
if (!extract_name(header, qlen, &p, name, 1, 4))
|
||||
|
||||
184
src/ubus.c
184
src/ubus.c
@@ -28,10 +28,38 @@ static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg);
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
enum {
|
||||
SET_CONNMARK_ALLOWLIST_MARK,
|
||||
SET_CONNMARK_ALLOWLIST_MASK,
|
||||
SET_CONNMARK_ALLOWLIST_PATTERNS
|
||||
};
|
||||
static const struct blobmsg_policy set_connmark_allowlist_policy[] = {
|
||||
[SET_CONNMARK_ALLOWLIST_MARK] = {
|
||||
.name = "mark",
|
||||
.type = BLOBMSG_TYPE_INT32
|
||||
},
|
||||
[SET_CONNMARK_ALLOWLIST_MASK] = {
|
||||
.name = "mask",
|
||||
.type = BLOBMSG_TYPE_INT32
|
||||
},
|
||||
[SET_CONNMARK_ALLOWLIST_PATTERNS] = {
|
||||
.name = "patterns",
|
||||
.type = BLOBMSG_TYPE_ARRAY
|
||||
}
|
||||
};
|
||||
static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg);
|
||||
#endif
|
||||
|
||||
static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj);
|
||||
|
||||
static const struct ubus_method ubus_object_methods[] = {
|
||||
UBUS_METHOD_NOARG("metrics", ubus_handle_metrics),
|
||||
#ifdef HAVE_CONNTRACK
|
||||
UBUS_METHOD("set_connmark_allowlist", ubus_handle_set_connmark_allowlist, set_connmark_allowlist_policy),
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct ubus_object_type ubus_object_type =
|
||||
@@ -163,6 +191,122 @@ static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj
|
||||
return ubus_send_reply(ctx, req, b.head);
|
||||
}
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
const struct blobmsg_policy *policy = set_connmark_allowlist_policy;
|
||||
size_t policy_len = countof(set_connmark_allowlist_policy);
|
||||
struct allowlist *allowlists = NULL, **allowlists_pos;
|
||||
char **patterns = NULL, **patterns_pos;
|
||||
u32 mark, mask = UINT32_MAX;
|
||||
size_t num_patterns = 0;
|
||||
struct blob_attr *tb[policy_len];
|
||||
struct blob_attr *attr;
|
||||
|
||||
if (blobmsg_parse(policy, policy_len, tb, blob_data(msg), blob_len(msg)))
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
if (!tb[SET_CONNMARK_ALLOWLIST_MARK])
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
mark = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MARK]);
|
||||
if (!mark)
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
if (tb[SET_CONNMARK_ALLOWLIST_MASK])
|
||||
{
|
||||
mask = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MASK]);
|
||||
if (!mask || (mark & ~mask))
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS])
|
||||
{
|
||||
struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
|
||||
size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
|
||||
__blob_for_each_attr(attr, head, len)
|
||||
{
|
||||
char *pattern;
|
||||
if (blob_id(attr) != BLOBMSG_TYPE_STRING)
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
if (!(pattern = blobmsg_get_string(attr)))
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
if (strcmp(pattern, "*") && !is_valid_dns_name_pattern(pattern))
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
num_patterns++;
|
||||
}
|
||||
}
|
||||
|
||||
for (allowlists_pos = &daemon->allowlists; *allowlists_pos; allowlists_pos = &(*allowlists_pos)->next)
|
||||
if ((*allowlists_pos)->mark == mark && (*allowlists_pos)->mask == mask)
|
||||
{
|
||||
struct allowlist *allowlists_next = (*allowlists_pos)->next;
|
||||
for (patterns_pos = (*allowlists_pos)->patterns; *patterns_pos; patterns_pos++)
|
||||
{
|
||||
free(*patterns_pos);
|
||||
*patterns_pos = NULL;
|
||||
}
|
||||
free((*allowlists_pos)->patterns);
|
||||
(*allowlists_pos)->patterns = NULL;
|
||||
free(*allowlists_pos);
|
||||
*allowlists_pos = allowlists_next;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!num_patterns)
|
||||
return UBUS_STATUS_OK;
|
||||
|
||||
patterns = whine_malloc((num_patterns + 1) * sizeof(char *));
|
||||
if (!patterns)
|
||||
goto fail;
|
||||
patterns_pos = patterns;
|
||||
if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS])
|
||||
{
|
||||
struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
|
||||
size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
|
||||
__blob_for_each_attr(attr, head, len)
|
||||
{
|
||||
char *pattern;
|
||||
if (!(pattern = blobmsg_get_string(attr)))
|
||||
goto fail;
|
||||
if (!(*patterns_pos = whine_malloc(strlen(pattern) + 1)))
|
||||
goto fail;
|
||||
strcpy(*patterns_pos++, pattern);
|
||||
}
|
||||
}
|
||||
|
||||
allowlists = whine_malloc(sizeof(struct allowlist));
|
||||
if (!allowlists)
|
||||
goto fail;
|
||||
memset(allowlists, 0, sizeof(struct allowlist));
|
||||
allowlists->mark = mark;
|
||||
allowlists->mask = mask;
|
||||
allowlists->patterns = patterns;
|
||||
allowlists->next = daemon->allowlists;
|
||||
daemon->allowlists = allowlists;
|
||||
return UBUS_STATUS_OK;
|
||||
|
||||
fail:
|
||||
if (patterns)
|
||||
{
|
||||
for (patterns_pos = patterns; *patterns_pos; patterns_pos++)
|
||||
{
|
||||
free(*patterns_pos);
|
||||
*patterns_pos = NULL;
|
||||
}
|
||||
free(patterns);
|
||||
patterns = NULL;
|
||||
}
|
||||
if (allowlists)
|
||||
{
|
||||
free(allowlists);
|
||||
allowlists = NULL;
|
||||
}
|
||||
return UBUS_STATUS_UNKNOWN_ERROR;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface)
|
||||
{
|
||||
struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
|
||||
@@ -182,9 +326,47 @@ void ubus_event_bcast(const char *type, const char *mac, const char *ip, const c
|
||||
blobmsg_add_string(&b, "interface", interface);
|
||||
|
||||
ret = ubus_notify(ubus, &ubus_object, type, b.head, -1);
|
||||
if (!ret)
|
||||
if (ret)
|
||||
my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret));
|
||||
}
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
void ubus_event_bcast_connmark_allowlist_refused(u32 mark, const char *name)
|
||||
{
|
||||
struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
|
||||
int ret;
|
||||
|
||||
if (!ubus || !notify)
|
||||
return;
|
||||
|
||||
blob_buf_init(&b, 0);
|
||||
blobmsg_add_u32(&b, "mark", mark);
|
||||
blobmsg_add_string(&b, "name", name);
|
||||
|
||||
ret = ubus_notify(ubus, &ubus_object, "connmark-allowlist.refused", b.head, -1);
|
||||
if (ret)
|
||||
my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret));
|
||||
}
|
||||
|
||||
void ubus_event_bcast_connmark_allowlist_resolved(u32 mark, const char *name, const char *value, u32 ttl)
|
||||
{
|
||||
struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
|
||||
int ret;
|
||||
|
||||
if (!ubus || !notify)
|
||||
return;
|
||||
|
||||
blob_buf_init(&b, 0);
|
||||
blobmsg_add_u32(&b, "mark", mark);
|
||||
blobmsg_add_string(&b, "name", name);
|
||||
blobmsg_add_string(&b, "value", value);
|
||||
blobmsg_add_u32(&b, "ttl", ttl);
|
||||
|
||||
ret = ubus_notify(ubus, &ubus_object, "connmark-allowlist.resolved", b.head, /* timeout: */ 1000);
|
||||
if (ret)
|
||||
my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* HAVE_UBUS */
|
||||
|
||||
Reference in New Issue
Block a user