mirror of
http://thekelleys.org.uk/git/dnsmasq.git
synced 2026-11-06 15:51:41 +00:00
Compare commits
16
Commits
v2.86test2
...
v2.86test3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1daf44954 | ||
|
|
be291d979d | ||
|
|
6d1edd8d32 | ||
|
|
25ff956c7d | ||
|
|
38179500f8 | ||
|
|
5f7be5f0d6 | ||
|
|
627056febb | ||
|
|
cbd76447fd | ||
|
|
a60a233329 | ||
|
|
a0a3b8ad3e | ||
|
|
d0ae3f5a4d | ||
|
|
6860cf932b | ||
|
|
0276e0805b | ||
|
|
06ff3d8a26 | ||
|
|
1a3b69aa56 | ||
|
|
8237d06ab7 |
No files matched your search
@@ -64,6 +64,18 @@ version 2.86
|
|||||||
queries. The requesting address and port have been removed from
|
queries. The requesting address and port have been removed from
|
||||||
DNSSEC logging lines, since this is no longer strictly defined.
|
DNSSEC logging lines, since this is no longer strictly defined.
|
||||||
|
|
||||||
|
Connection track mark based DNS query filtering. Thanks to
|
||||||
|
Etan Kissling for implementing this It 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.
|
||||||
|
|
||||||
|
|
||||||
version 2.85
|
version 2.85
|
||||||
Fix problem with DNS retries in 2.83/2.84.
|
Fix problem with DNS retries in 2.83/2.84.
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ Q: What network types are supported by the DHCP server?
|
|||||||
A: Ethernet (and 802.11 wireless) are supported on all platforms. On
|
A: Ethernet (and 802.11 wireless) are supported on all platforms. On
|
||||||
Linux all network types (including FireWire) are supported.
|
Linux all network types (including FireWire) are supported.
|
||||||
|
|
||||||
Q: What are these strange "bind-interface" and "bind-dynamic" options?
|
Q: What are these strange "bind-interfaces" and "bind-dynamic" options?
|
||||||
|
|
||||||
A: Dnsmasq from v2.63 can operate in one of three different "networking
|
A: Dnsmasq from v2.63 can operate in one of three different "networking
|
||||||
modes". This is unfortunate as it requires users configuring dnsmasq
|
modes". This is unfortunate as it requires users configuring dnsmasq
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ copts_conf = .copts_$(sum)
|
|||||||
objs = cache.o rfc1035.o util.o option.o forward.o network.o \
|
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 \
|
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 \
|
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 \
|
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 \
|
poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o \
|
||||||
metrics.o hash-questions.o domain-match.o
|
metrics.o hash-questions.o domain-match.o
|
||||||
|
|||||||
+30
-1
@@ -371,7 +371,10 @@ provides service at that name, rather than the default which is
|
|||||||
.TP
|
.TP
|
||||||
.B --enable-ubus[=<service-name>]
|
.B --enable-ubus[=<service-name>]
|
||||||
Enable dnsmasq UBus interface. It sends notifications via UBus on
|
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
|
Requires that dnsmasq has been built with UBus support. If the service
|
||||||
name is given, dnsmasq provides service at that namespace, rather than
|
name is given, dnsmasq provides service at that namespace, rather than
|
||||||
the default which is
|
the default which is
|
||||||
@@ -536,6 +539,32 @@ These IP sets must already exist. See
|
|||||||
.BR ipset (8)
|
.BR ipset (8)
|
||||||
for more details.
|
for more details.
|
||||||
.TP
|
.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>]
|
.B \-m, --mx-host=<mx name>[[,<hostname>],<preference>]
|
||||||
Return an MX record named <mx name> pointing to the given hostname (if
|
Return an MX record named <mx name> pointing to the given hostname (if
|
||||||
given), or
|
given), or
|
||||||
|
|||||||
@@ -135,6 +135,13 @@ int main (int argc, char **argv)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||||
|
/* CONNTRACK UBUS code uses this buffer, so if not allocated above,
|
||||||
|
we need to allocate it here. */
|
||||||
|
if (option_bool(OPT_CMARK_ALST_EN) && !daemon->workspacename)
|
||||||
|
daemon->workspacename = safe_malloc(MAXDNAME);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_DHCP
|
#ifdef HAVE_DHCP
|
||||||
if (!daemon->lease_file)
|
if (!daemon->lease_file)
|
||||||
{
|
{
|
||||||
|
|||||||
+31
-8
@@ -272,7 +272,8 @@ struct event_desc {
|
|||||||
#define OPT_LOG_DEBUG 62
|
#define OPT_LOG_DEBUG 62
|
||||||
#define OPT_UMBRELLA 63
|
#define OPT_UMBRELLA 63
|
||||||
#define OPT_UMBRELLA_DEVID 64
|
#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_BITS (sizeof(unsigned int)*8)
|
||||||
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
|
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
|
||||||
@@ -564,7 +565,7 @@ struct randfd_list {
|
|||||||
|
|
||||||
|
|
||||||
struct server {
|
struct server {
|
||||||
int flags;
|
u16 flags, domain_len;
|
||||||
char *domain;
|
char *domain;
|
||||||
struct server *next;
|
struct server *next;
|
||||||
int serial, arrayposn;
|
int serial, arrayposn;
|
||||||
@@ -583,23 +584,23 @@ struct server {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/* First three fields must match struct server in next three definitions.. */
|
/* First four fields must match struct server in next three definitions.. */
|
||||||
struct serv_addr4 {
|
struct serv_addr4 {
|
||||||
int flags;
|
u16 flags, domain_len;
|
||||||
char *domain;
|
char *domain;
|
||||||
struct server *next;
|
struct server *next;
|
||||||
struct in_addr addr;
|
struct in_addr addr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct serv_addr6 {
|
struct serv_addr6 {
|
||||||
int flags;
|
u16 flags, domain_len;
|
||||||
char *domain;
|
char *domain;
|
||||||
struct server *next;
|
struct server *next;
|
||||||
struct in6_addr addr;
|
struct in6_addr addr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct serv_local {
|
struct serv_local {
|
||||||
int flags;
|
u16 flags, domain_len;
|
||||||
char *domain;
|
char *domain;
|
||||||
struct server *next;
|
struct server *next;
|
||||||
};
|
};
|
||||||
@@ -610,6 +611,12 @@ struct ipsets {
|
|||||||
struct ipsets *next;
|
struct ipsets *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct allowlist {
|
||||||
|
u32 mark, mask;
|
||||||
|
char **patterns;
|
||||||
|
struct allowlist *next;
|
||||||
|
};
|
||||||
|
|
||||||
struct irec {
|
struct irec {
|
||||||
union mysockaddr addr;
|
union mysockaddr addr;
|
||||||
struct in_addr netmask; /* only valid for IPv4 */
|
struct in_addr netmask; /* only valid for IPv4 */
|
||||||
@@ -1086,6 +1093,8 @@ extern struct daemon {
|
|||||||
struct server *servers, *local_domains, **serverarray, *no_rebind;
|
struct server *servers, *local_domains, **serverarray, *no_rebind;
|
||||||
int serverarraysz;
|
int serverarraysz;
|
||||||
struct ipsets *ipsets;
|
struct ipsets *ipsets;
|
||||||
|
u32 allowlist_mask;
|
||||||
|
struct allowlist *allowlists;
|
||||||
int log_fac; /* log facility */
|
int log_fac; /* log facility */
|
||||||
char *log_file; /* optional log file */
|
char *log_file; /* optional log file */
|
||||||
int max_logs; /* queue limit */
|
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,
|
int extract_addresses(struct dns_header *header, size_t qlen, char *name,
|
||||||
time_t now, char **ipsets, int is_sign, int check_rebind,
|
time_t now, char **ipsets, int is_sign, int check_rebind,
|
||||||
int no_cache_dnssec, int secure, int *doctored);
|
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,
|
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
||||||
struct in_addr local_addr, struct in_addr local_netmask,
|
struct in_addr local_addr, struct in_addr local_netmask,
|
||||||
time_t now, int ad_reqd, int do_bit, int have_pseudoheader);
|
time_t now, int ad_reqd, int do_bit, int have_pseudoheader);
|
||||||
@@ -1381,7 +1393,7 @@ void set_option_bool(unsigned int opt);
|
|||||||
void reset_option_bool(unsigned int opt);
|
void reset_option_bool(unsigned int opt);
|
||||||
struct hostsfile *expand_filelist(struct hostsfile *list);
|
struct hostsfile *expand_filelist(struct hostsfile *list);
|
||||||
char *parse_server(char *arg, union mysockaddr *addr,
|
char *parse_server(char *arg, union mysockaddr *addr,
|
||||||
union mysockaddr *source_addr, char *interface, int *flags);
|
union mysockaddr *source_addr, char *interface, u16 *flags);
|
||||||
int option_read_dynfile(char *file, int flags);
|
int option_read_dynfile(char *file, int flags);
|
||||||
|
|
||||||
/* forward.c */
|
/* forward.c */
|
||||||
@@ -1546,6 +1558,10 @@ char *ubus_init(void);
|
|||||||
void set_ubus_listeners(void);
|
void set_ubus_listeners(void);
|
||||||
void check_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);
|
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
|
#endif
|
||||||
|
|
||||||
/* ipset.c */
|
/* 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);
|
int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove);
|
||||||
#endif
|
#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 */
|
/* helper.c */
|
||||||
#if defined(HAVE_SCRIPT)
|
#if defined(HAVE_SCRIPT)
|
||||||
int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd);
|
int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd);
|
||||||
@@ -1726,7 +1749,7 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout);
|
|||||||
int filter_servers(int seed, int flags, int *lowout, int *highout);
|
int filter_servers(int seed, int flags, int *lowout, int *highout);
|
||||||
int is_local_answer(time_t now, int first, char *name);
|
int is_local_answer(time_t now, int first, char *name);
|
||||||
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header,
|
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header,
|
||||||
char *name, int first, int last);
|
char *name, char *limit, int first, int last);
|
||||||
int server_samegroup(struct server *a, struct server *b);
|
int server_samegroup(struct server *a, struct server *b);
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp);
|
int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp);
|
||||||
|
|||||||
+112
-78
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
#include "dnsmasq.h"
|
#include "dnsmasq.h"
|
||||||
|
|
||||||
static int order(char *qdomain, int leading_dot, size_t qlen, struct server *serv);
|
static int order(char *qdomain, size_t qlen, struct server *serv);
|
||||||
static int order_qsort(const void *a, const void *b);
|
static int order_qsort(const void *a, const void *b);
|
||||||
static int order_servers(struct server *s, struct server *s2);
|
static int order_servers(struct server *s, struct server *s2);
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ void build_server_array(void)
|
|||||||
/* servers need the location in the array to find all the whole
|
/* servers need the location in the array to find all the whole
|
||||||
set of equivalent servers from a pointer to a single one. */
|
set of equivalent servers from a pointer to a single one. */
|
||||||
for (count = 0; count < daemon->serverarraysz; count++)
|
for (count = 0; count < daemon->serverarraysz; count++)
|
||||||
if (!(daemon->serverarray[count]->flags & SERV_LITERAL_ADDRESS))
|
if (!(daemon->serverarray[count]->flags & (SERV_LITERAL_ADDRESS | SERV_USE_RESOLV)))
|
||||||
daemon->serverarray[count]->arrayposn = count;
|
daemon->serverarray[count]->arrayposn = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,12 +75,14 @@ void build_server_array(void)
|
|||||||
A flag of F_DNSSECOK returns a DNSSEC capable server only and
|
A flag of F_DNSSECOK returns a DNSSEC capable server only and
|
||||||
also disables NODOTS servers from consideration.
|
also disables NODOTS servers from consideration.
|
||||||
A flag of F_DOMAINSRV returns a domain-specific server only.
|
A flag of F_DOMAINSRV returns a domain-specific server only.
|
||||||
|
A flag of F_CONFIG returns anything that generates a local
|
||||||
|
reply of IPv4 or IPV6.
|
||||||
return 0 if nothing found, 1 otherwise.
|
return 0 if nothing found, 1 otherwise.
|
||||||
*/
|
*/
|
||||||
int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
|
int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
|
||||||
{
|
{
|
||||||
int rc, nodots, leading_dot = 1;
|
int rc, crop_query, nodots;
|
||||||
ssize_t qlen, maxlen;
|
ssize_t qlen;
|
||||||
int try, high, low = 0;
|
int try, high, low = 0;
|
||||||
int nlow = 0, nhigh = 0;
|
int nlow = 0, nhigh = 0;
|
||||||
char *cp;
|
char *cp;
|
||||||
@@ -89,8 +91,6 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
|
|||||||
if (daemon->serverarraysz == 0)
|
if (daemon->serverarraysz == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
maxlen = strlen(daemon->serverarray[0]->domain);
|
|
||||||
|
|
||||||
/* find query length and presence of '.' */
|
/* find query length and presence of '.' */
|
||||||
for (cp = qdomain, nodots = 1, qlen = 0; *cp; qlen++, cp++)
|
for (cp = qdomain, nodots = 1, qlen = 0; *cp; qlen++, cp++)
|
||||||
if (*cp == '.')
|
if (*cp == '.')
|
||||||
@@ -101,56 +101,74 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
|
|||||||
if (qlen == 0 || flags & F_DNSSECOK)
|
if (qlen == 0 || flags & F_DNSSECOK)
|
||||||
nodots = 0;
|
nodots = 0;
|
||||||
|
|
||||||
/* No point trying to match more than the largest server domain */
|
|
||||||
if (qlen > maxlen)
|
|
||||||
{
|
|
||||||
qdomain += qlen - maxlen;
|
|
||||||
qlen = maxlen;
|
|
||||||
leading_dot = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Search shorter and shorter RHS substrings for a match */
|
/* Search shorter and shorter RHS substrings for a match */
|
||||||
while (qlen >= 0)
|
while (qlen >= 0)
|
||||||
{
|
{
|
||||||
/* Note that when we chop off a character, all the possible matches
|
/* Note that when we chop off a label, all the possible matches
|
||||||
MUST be at a larger index than the nearest failing match with one more
|
MUST be at a larger index than the nearest failing match with one more
|
||||||
character, since the array is sorted longest to smallest. Hence
|
character, since the array is sorted longest to smallest. Hence
|
||||||
we don't reset low here. */
|
we don't reset low to zero here, we can go further below and crop the
|
||||||
|
search string to the size of the largest remaining server
|
||||||
|
when this match fails. */
|
||||||
high = daemon->serverarraysz;
|
high = daemon->serverarraysz;
|
||||||
|
crop_query = 1;
|
||||||
|
|
||||||
/* binary search */
|
/* binary search */
|
||||||
do
|
while (1)
|
||||||
{
|
{
|
||||||
try = (low + high)/2;
|
try = (low + high)/2;
|
||||||
|
|
||||||
if ((rc = order(qdomain, leading_dot, qlen, daemon->serverarray[try])) == 0)
|
if ((rc = order(qdomain, qlen, daemon->serverarray[try])) == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
{
|
{
|
||||||
if (high == try)
|
if (high == try)
|
||||||
break;
|
{
|
||||||
|
/* qdomain is longer or same length as longest domain, and try == 0
|
||||||
|
crop the query to the longest domain. */
|
||||||
|
crop_query = qlen - daemon->serverarray[try]->domain_len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
high = try;
|
high = try;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (low == try)
|
if (low == try)
|
||||||
break;
|
{
|
||||||
|
/* try now points to the last domain that sorts before the query, so
|
||||||
|
we know that a substring of the query shorter than it is required to match, so
|
||||||
|
find the largest domain that's shorter than try. Note that just going to
|
||||||
|
try+1 is not optimal, consider searching bbb in (aaa,ccc,bb). try will point
|
||||||
|
to aaa, since ccc sorts after bbb, but the first domain that has a chance to
|
||||||
|
match is bb. So find the length of the first domain later than try which is
|
||||||
|
is shorter than it.
|
||||||
|
There's a nasty edge case when qdomain sorts before _any_ of the
|
||||||
|
server domains, where try _doesn't point_ to the last domain that sorts
|
||||||
|
before the query, since no such domain exists. In that case, the loop
|
||||||
|
exits via the rc < 0 && high == try path above and this code is
|
||||||
|
not executed. */
|
||||||
|
ssize_t len, old = daemon->serverarray[try]->domain_len;
|
||||||
|
while (++try != daemon->serverarraysz)
|
||||||
|
{
|
||||||
|
if (old != (len = daemon->serverarray[try]->domain_len))
|
||||||
|
{
|
||||||
|
crop_query = qlen - len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
low = try;
|
low = try;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
while (low != high);
|
|
||||||
|
|
||||||
if (rc == 0)
|
if (rc == 0)
|
||||||
{
|
{
|
||||||
/* We've matched a setting which says to use servers without a domain.
|
/* We've matched a setting which says to use servers without a domain.
|
||||||
Continue the search with empty query (the last character gets stripped
|
Continue the search with empty query */
|
||||||
by the loop. */
|
|
||||||
if (daemon->serverarray[try]->flags & SERV_USE_RESOLV)
|
if (daemon->serverarray[try]->flags & SERV_USE_RESOLV)
|
||||||
{
|
crop_query = qlen;
|
||||||
qdomain += qlen - 1;
|
|
||||||
qlen = 1;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* We have a match, but it may only be (say) an IPv6 address, and
|
/* We have a match, but it may only be (say) an IPv6 address, and
|
||||||
@@ -160,27 +178,30 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* crop_query must be at least one always. */
|
||||||
|
if (crop_query == 0)
|
||||||
|
crop_query = 1;
|
||||||
|
|
||||||
if (leading_dot)
|
/* strip chars off the query based on the largest possible remaining match,
|
||||||
leading_dot = 0;
|
then continue to the start of the next label. */
|
||||||
else
|
qlen -= crop_query;
|
||||||
{
|
qdomain += crop_query;
|
||||||
qlen--;
|
while (qlen > 0 && (*(qdomain-1) != '.'))
|
||||||
qdomain++;
|
qlen--, qdomain++;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* domain has no dots, and we have at least one server configured to handle such,
|
/* domain has no dots, and we have at least one server configured to handle such,
|
||||||
These servers always sort to the very end of the array.
|
These servers always sort to the very end of the array.
|
||||||
A configured server eg server=/lan/ will take precdence. */
|
A configured server eg server=/lan/ will take precdence. */
|
||||||
if (nodots &&
|
if (nodots &&
|
||||||
(daemon->serverarray[daemon->serverarraysz-1]->flags & SERV_FOR_NODOTS) &&
|
(daemon->serverarray[daemon->serverarraysz-1]->flags & SERV_FOR_NODOTS) &&
|
||||||
(nlow == nhigh || strlen(daemon->serverarray[nlow]->domain) == 0))
|
(nlow == nhigh || daemon->serverarray[nlow]->domain_len == 0))
|
||||||
filter_servers(daemon->serverarraysz-1, flags, &nlow, &nhigh);
|
filter_servers(daemon->serverarraysz-1, flags, &nlow, &nhigh);
|
||||||
|
|
||||||
/* F_DOMAINSRV returns only domain-specific servers, so if we got to a
|
/* F_DOMAINSRV returns only domain-specific servers, so if we got to a
|
||||||
general server, return empty set. */
|
general server, return empty set. */
|
||||||
if (nlow != nhigh && (flags & F_DOMAINSRV) && strlen(daemon->serverarray[nlow]->domain) == 0)
|
if (nlow != nhigh && (flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0)
|
||||||
nlow = nhigh;
|
nlow = nhigh;
|
||||||
|
|
||||||
if (lowout)
|
if (lowout)
|
||||||
@@ -222,47 +243,56 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
|
|||||||
|
|
||||||
See which of those match our query in that priority order and narrow (low, high) */
|
See which of those match our query in that priority order and narrow (low, high) */
|
||||||
|
|
||||||
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_6ADDR); i++);
|
#define SERV_LOCAL_ADDRESS (SERV_6ADDR | SERV_4ADDR | SERV_ALL_ZEROS)
|
||||||
|
|
||||||
if (i != nlow && (flags & F_IPV6))
|
for (i = nlow; (flags & F_CONFIG) && i < nhigh && (daemon->serverarray[i]->flags & SERV_LOCAL_ADDRESS); i++);
|
||||||
|
|
||||||
|
if (i != nlow)
|
||||||
nhigh = i;
|
nhigh = i;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nlow = i;
|
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_6ADDR); i++);
|
||||||
|
|
||||||
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_4ADDR); i++);
|
|
||||||
|
|
||||||
if (i != nlow && (flags & F_IPV4))
|
if (i != nlow && (flags & F_IPV6))
|
||||||
nhigh = i;
|
nhigh = i;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nlow = i;
|
nlow = i;
|
||||||
|
|
||||||
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_ALL_ZEROS); i++);
|
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_4ADDR); i++);
|
||||||
|
|
||||||
if (i != nlow && (flags & (F_IPV4 | F_IPV6)))
|
if (i != nlow && (flags & F_IPV4))
|
||||||
nhigh = i;
|
nhigh = i;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nlow = i;
|
nlow = i;
|
||||||
|
|
||||||
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
|
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_ALL_ZEROS); i++);
|
||||||
|
|
||||||
/* --local=/domain/, only return if we don't need a server. */
|
if (i != nlow && (flags & (F_IPV4 | F_IPV6)))
|
||||||
if (i != nlow && !(flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER)))
|
|
||||||
nhigh = i;
|
nhigh = i;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nlow = i;
|
nlow = i;
|
||||||
/* If we want a server that can do DNSSEC, and this one can't,
|
|
||||||
return nothing. */
|
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
|
||||||
if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC))
|
|
||||||
nlow = nhigh;
|
/* --local=/domain/, only return if we don't need a server. */
|
||||||
|
if (i != nlow && !(flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER)))
|
||||||
|
nhigh = i;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nlow = i;
|
||||||
|
/* If we want a server that can do DNSSEC, and this one can't,
|
||||||
|
return nothing. */
|
||||||
|
if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC))
|
||||||
|
nlow = nhigh;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*lowout = nlow;
|
*lowout = nlow;
|
||||||
*highout = nhigh;
|
*highout = nhigh;
|
||||||
|
|
||||||
@@ -273,7 +303,7 @@ int is_local_answer(time_t now, int first, char *name)
|
|||||||
{
|
{
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
|
||||||
if ((flags = daemon->serverarray[first]->flags) & SERV_LITERAL_ADDRESS)
|
if ((flags = daemon->serverarray[first]->flags) & SERV_LITERAL_ADDRESS)
|
||||||
{
|
{
|
||||||
if (flags & SERV_4ADDR)
|
if (flags & SERV_4ADDR)
|
||||||
@@ -283,13 +313,25 @@ int is_local_answer(time_t now, int first, char *name)
|
|||||||
else if (flags & SERV_ALL_ZEROS)
|
else if (flags & SERV_ALL_ZEROS)
|
||||||
rc = F_IPV4 | F_IPV6;
|
rc = F_IPV4 | F_IPV6;
|
||||||
else
|
else
|
||||||
rc = check_for_local_domain(name, now) ? F_NOERR : F_NXDOMAIN;
|
{
|
||||||
|
/* argument first is the first struct server which matches the query type;
|
||||||
|
now roll back to the server which is just the same domain, to check if that
|
||||||
|
provides an answer of a different type. */
|
||||||
|
|
||||||
|
for (;first > 0 && order_servers(daemon->serverarray[first-1], daemon->serverarray[first]) == 0; first--);
|
||||||
|
|
||||||
|
if ((daemon->serverarray[first]->flags & SERV_LOCAL_ADDRESS) ||
|
||||||
|
check_for_local_domain(name, now))
|
||||||
|
rc = F_NOERR;
|
||||||
|
else
|
||||||
|
rc = F_NXDOMAIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, int first, int last)
|
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, char *limit, int first, int last)
|
||||||
{
|
{
|
||||||
int trunc = 0;
|
int trunc = 0;
|
||||||
unsigned char *p;
|
unsigned char *p;
|
||||||
@@ -315,7 +357,7 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
|
|||||||
addr.addr4 = srv->addr;
|
addr.addr4 = srv->addr;
|
||||||
|
|
||||||
header->ancount = htons(ntohs(header->ancount) + 1);
|
header->ancount = htons(ntohs(header->ancount) + 1);
|
||||||
add_resource_record(header, ((char *)header) + 65536, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr);
|
add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr);
|
||||||
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL);
|
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,7 +372,7 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
|
|||||||
addr.addr6 = srv->addr;
|
addr.addr6 = srv->addr;
|
||||||
|
|
||||||
header->ancount = htons(ntohs(header->ancount) + 1);
|
header->ancount = htons(ntohs(header->ancount) + 1);
|
||||||
add_resource_record(header, ((char *)header) + 65536, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr);
|
add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr);
|
||||||
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL);
|
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,20 +414,16 @@ int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* order by size, then by dictionary order */
|
/* order by size, then by dictionary order */
|
||||||
static int order(char *qdomain, int leading_dot, size_t qlen, struct server *serv)
|
static int order(char *qdomain, size_t qlen, struct server *serv)
|
||||||
{
|
{
|
||||||
size_t dlen = 0;
|
size_t dlen = 0;
|
||||||
int rc;
|
|
||||||
|
|
||||||
/* servers for dotless names always sort last
|
/* servers for dotless names always sort last
|
||||||
searched for name is never dotless. */
|
searched for name is never dotless. */
|
||||||
if (serv->flags & SERV_FOR_NODOTS)
|
if (serv->flags & SERV_FOR_NODOTS)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (leading_dot)
|
dlen = serv->domain_len;
|
||||||
qlen++;
|
|
||||||
|
|
||||||
dlen = strlen(serv->domain);
|
|
||||||
|
|
||||||
if (qlen < dlen)
|
if (qlen < dlen)
|
||||||
return 1;
|
return 1;
|
||||||
@@ -393,22 +431,18 @@ static int order(char *qdomain, int leading_dot, size_t qlen, struct server *ser
|
|||||||
if (qlen > dlen)
|
if (qlen > dlen)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (leading_dot && (rc = '.' - serv->domain[0]) != 0)
|
return strcmp(qdomain, serv->domain);
|
||||||
return rc;
|
|
||||||
|
|
||||||
return strcmp(qdomain, leading_dot ? &serv->domain[1] : serv->domain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int order_servers(struct server *s1, struct server *s2)
|
static int order_servers(struct server *s1, struct server *s2)
|
||||||
{
|
{
|
||||||
size_t dlen = strlen(s1->domain);
|
/* need full comparison of dotless servers in
|
||||||
|
order_qsort() and filter_servers() */
|
||||||
|
|
||||||
/* need full comparison of dotless servers in
|
if (s1->flags & SERV_FOR_NODOTS)
|
||||||
order_qsort() and filter_servers() */
|
|
||||||
if (s1->flags & SERV_FOR_NODOTS)
|
|
||||||
return (s2->flags & SERV_FOR_NODOTS) ? 0 : 1;
|
return (s2->flags & SERV_FOR_NODOTS) ? 0 : 1;
|
||||||
|
|
||||||
return order(s1->domain, 0, dlen, s2);
|
return order(s1->domain, s1->domain_len, s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int order_qsort(const void *a, const void *b)
|
static int order_qsort(const void *a, const void *b)
|
||||||
|
|||||||
+166
-20
@@ -153,9 +153,8 @@ static int domain_no_rebind(char *domain)
|
|||||||
struct server *serv;
|
struct server *serv;
|
||||||
int dlen = (int)strlen(domain);
|
int dlen = (int)strlen(domain);
|
||||||
|
|
||||||
/* flags is misused to hold length of domain. */
|
|
||||||
for (serv = daemon->no_rebind; serv; serv = serv->next)
|
for (serv = daemon->no_rebind; serv; serv = serv->next)
|
||||||
if (dlen >= serv->flags && strcmp(serv->domain, &domain[dlen - serv->flags]) == 0)
|
if (dlen >= serv->domain_len && strcmp(serv->domain, &domain[dlen - serv->domain_len]) == 0)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -340,7 +339,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|||||||
|
|
||||||
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
|
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
|
||||||
plen = forward->stash_len;
|
plen = forward->stash_len;
|
||||||
|
/* get query for logging. */
|
||||||
|
extract_request(header, plen, daemon->namebuff, NULL);
|
||||||
|
|
||||||
if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign)
|
if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign)
|
||||||
PUTSHORT(SAFE_PKTSZ, pheader);
|
PUTSHORT(SAFE_PKTSZ, pheader);
|
||||||
|
|
||||||
@@ -524,12 +525,22 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|||||||
reply:
|
reply:
|
||||||
if (udpfd != -1)
|
if (udpfd != -1)
|
||||||
{
|
{
|
||||||
if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, first, last)))
|
if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, limit, first, last)))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (oph)
|
if (oph)
|
||||||
plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
|
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);
|
send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,16 +673,19 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
|||||||
int doctored = 0;
|
int doctored = 0;
|
||||||
|
|
||||||
if (rcode == NXDOMAIN &&
|
if (rcode == NXDOMAIN &&
|
||||||
extract_request(header, n, daemon->namebuff, NULL) &&
|
extract_request(header, n, daemon->namebuff, NULL))
|
||||||
check_for_local_domain(daemon->namebuff, now))
|
|
||||||
{
|
{
|
||||||
/* if we forwarded a query for a locally known name (because it was for
|
if (check_for_local_domain(daemon->namebuff, now) ||
|
||||||
an unknown type) and the answer is NXDOMAIN, convert that to NODATA,
|
lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL))
|
||||||
since we know that the domain exists, even if upstream doesn't */
|
{
|
||||||
munged = 1;
|
/* if we forwarded a query for a locally known name (because it was for
|
||||||
header->hb3 |= HB3_AA;
|
an unknown type) and the answer is NXDOMAIN, convert that to NODATA,
|
||||||
SET_RCODE(header, NOERROR);
|
since we know that the domain exists, even if upstream doesn't */
|
||||||
cache_secure = 0;
|
munged = 1;
|
||||||
|
header->hb3 |= HB3_AA;
|
||||||
|
SET_RCODE(header, NOERROR);
|
||||||
|
cache_secure = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored))
|
if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored))
|
||||||
@@ -1148,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);
|
dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source);
|
||||||
#endif
|
#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,
|
send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
|
||||||
&src->source, &src->dest, src->iface);
|
&src->source, &src->dest, src->iface);
|
||||||
|
|
||||||
@@ -1164,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)
|
void receive_query(struct listener *listen, time_t now)
|
||||||
{
|
{
|
||||||
struct dns_header *header = (struct dns_header *)daemon->packet;
|
struct dns_header *header = (struct dns_header *)daemon->packet;
|
||||||
@@ -1175,6 +1240,11 @@ void receive_query(struct listener *listen, time_t now)
|
|||||||
size_t m;
|
size_t m;
|
||||||
ssize_t n;
|
ssize_t n;
|
||||||
int if_index = 0, auth_dns = 0, do_bit = 0, have_pseudoheader = 0;
|
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
|
#ifdef HAVE_AUTH
|
||||||
int local_auth = 0;
|
int local_auth = 0;
|
||||||
#endif
|
#endif
|
||||||
@@ -1403,6 +1473,11 @@ void receive_query(struct listener *listen, time_t now)
|
|||||||
#ifdef HAVE_DUMPFILE
|
#ifdef HAVE_DUMPFILE
|
||||||
dump_packet(DUMP_QUERY, daemon->packet, (size_t)n, &source_addr, NULL);
|
dump_packet(DUMP_QUERY, daemon->packet, (size_t)n, &source_addr, NULL);
|
||||||
#endif
|
#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))
|
if (extract_request(header, (size_t)n, daemon->namebuff, &type))
|
||||||
{
|
{
|
||||||
@@ -1413,6 +1488,10 @@ void receive_query(struct listener *listen, time_t now)
|
|||||||
|
|
||||||
log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff,
|
log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff,
|
||||||
&source_addr, types);
|
&source_addr, types);
|
||||||
|
|
||||||
|
#ifdef HAVE_CONNTRACK
|
||||||
|
is_single_query = 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_AUTH
|
#ifdef HAVE_AUTH
|
||||||
/* find queries for zones we're authoritative for, and answer them directly */
|
/* find queries for zones we're authoritative for, and answer them directly */
|
||||||
@@ -1454,20 +1533,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 */
|
udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_CONNTRACK
|
||||||
#ifdef HAVE_AUTH
|
#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 (have_pseudoheader && m != 0)
|
||||||
|
m = add_pseudoheader(header, m, ((unsigned char *) header) + udp_size, daemon->edns_pktsz, 0, NULL, 0, do_bit, 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,
|
m = answer_auth(header, ((char *) header) + udp_size, (size_t)n, now, &source_addr,
|
||||||
local_auth, do_bit, have_pseudoheader);
|
local_auth, do_bit, have_pseudoheader);
|
||||||
if (m >= 1)
|
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),
|
send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
|
||||||
(char *)header, m, &source_addr, &dst_addr, if_index);
|
(char *)header, m, &source_addr, &dst_addr, if_index);
|
||||||
daemon->metrics[METRIC_DNS_AUTH_ANSWERED]++;
|
daemon->metrics[METRIC_DNS_AUTH_ANSWERED]++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
#endif
|
#endif
|
||||||
|
else
|
||||||
{
|
{
|
||||||
int ad_reqd = do_bit;
|
int ad_reqd = do_bit;
|
||||||
/* RFC 6840 5.7 */
|
/* RFC 6840 5.7 */
|
||||||
@@ -1479,6 +1588,10 @@ void receive_query(struct listener *listen, time_t now)
|
|||||||
|
|
||||||
if (m >= 1)
|
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),
|
send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
|
||||||
(char *)header, m, &source_addr, &dst_addr, if_index);
|
(char *)header, m, &source_addr, &dst_addr, if_index);
|
||||||
daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
|
daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
|
||||||
@@ -1685,6 +1798,9 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
{
|
{
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
int norebind;
|
int norebind;
|
||||||
|
#ifdef HAVE_CONNTRACK
|
||||||
|
int is_single_query = 0, allowed = 1;
|
||||||
|
#endif
|
||||||
#ifdef HAVE_AUTH
|
#ifdef HAVE_AUTH
|
||||||
int local_auth = 0;
|
int local_auth = 0;
|
||||||
#endif
|
#endif
|
||||||
@@ -1716,7 +1832,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
|
|
||||||
#ifdef HAVE_CONNTRACK
|
#ifdef HAVE_CONNTRACK
|
||||||
/* Get connection mark of incoming query to set on outgoing connections. */
|
/* 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;
|
union all_addr local;
|
||||||
|
|
||||||
@@ -1796,6 +1912,10 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff,
|
log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff,
|
||||||
&peer_addr, types);
|
&peer_addr, types);
|
||||||
|
|
||||||
|
#ifdef HAVE_CONNTRACK
|
||||||
|
is_single_query = 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_AUTH
|
#ifdef HAVE_AUTH
|
||||||
/* find queries for zones we're authoritative for, and answer them directly */
|
/* find queries for zones we're authoritative for, and answer them directly */
|
||||||
if (!auth_dns && !option_bool(OPT_LOCALISE))
|
if (!auth_dns && !option_bool(OPT_LOCALISE))
|
||||||
@@ -1829,13 +1949,31 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
if (flags & 0x8000)
|
if (flags & 0x8000)
|
||||||
do_bit = 1; /* do bit */
|
do_bit = 1; /* do bit */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_CONNTRACK
|
||||||
#ifdef HAVE_AUTH
|
#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);
|
||||||
|
|
||||||
|
if (have_pseudoheader && m != 0)
|
||||||
|
m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_AUTH
|
||||||
|
else if (auth_dns)
|
||||||
m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr,
|
m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr,
|
||||||
local_auth, do_bit, have_pseudoheader);
|
local_auth, do_bit, have_pseudoheader);
|
||||||
else
|
|
||||||
#endif
|
#endif
|
||||||
|
else
|
||||||
{
|
{
|
||||||
int ad_reqd = do_bit;
|
int ad_reqd = do_bit;
|
||||||
/* RFC 6840 5.7 */
|
/* RFC 6840 5.7 */
|
||||||
@@ -1950,7 +2088,8 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
/* In case of local answer or no connections made. */
|
/* In case of local answer or no connections made. */
|
||||||
if (m == 0)
|
if (m == 0)
|
||||||
{
|
{
|
||||||
if (!(m = make_local_answer(flags, gotname, size, header, daemon->namebuff, first, last)))
|
if (!(m = make_local_answer(flags, gotname, size, header, daemon->namebuff,
|
||||||
|
((char *) header) + 65536, first, last)))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (have_pseudoheader)
|
if (have_pseudoheader)
|
||||||
@@ -1961,6 +2100,13 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
|
|
||||||
*length = htons(m);
|
*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))
|
if (!read_write(confd, packet, m + sizeof(u16), 0))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1617,7 +1617,7 @@ void add_update_server(int flags,
|
|||||||
|
|
||||||
serv->flags = flags;
|
serv->flags = flags;
|
||||||
serv->domain = domain_str;
|
serv->domain = domain_str;
|
||||||
|
serv->domain_len = strlen(domain_str);
|
||||||
|
|
||||||
if (!(flags & SERV_IS_LOCAL))
|
if (!(flags & SERV_IS_LOCAL))
|
||||||
{
|
{
|
||||||
|
|||||||
+150
-6
@@ -171,6 +171,8 @@ struct myoption {
|
|||||||
#define LOPT_DYNHOST 362
|
#define LOPT_DYNHOST 362
|
||||||
#define LOPT_LOG_DEBUG 363
|
#define LOPT_LOG_DEBUG 363
|
||||||
#define LOPT_UMBRELLA 364
|
#define LOPT_UMBRELLA 364
|
||||||
|
#define LOPT_CMARK_ALST_EN 365
|
||||||
|
#define LOPT_CMARK_ALST 366
|
||||||
|
|
||||||
#ifdef HAVE_GETOPT_LONG
|
#ifdef HAVE_GETOPT_LONG
|
||||||
static const struct option opts[] =
|
static const struct option opts[] =
|
||||||
@@ -324,6 +326,8 @@ static const struct myoption opts[] =
|
|||||||
{ "auth-sec-servers", 1, 0, LOPT_AUTHSFS },
|
{ "auth-sec-servers", 1, 0, LOPT_AUTHSFS },
|
||||||
{ "auth-peer", 1, 0, LOPT_AUTHPEER },
|
{ "auth-peer", 1, 0, LOPT_AUTHPEER },
|
||||||
{ "ipset", 1, 0, LOPT_IPSET },
|
{ "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 },
|
{ "synth-domain", 1, 0, LOPT_SYNTH },
|
||||||
{ "dnssec", 0, 0, LOPT_SEC_VALID },
|
{ "dnssec", 0, 0, LOPT_SEC_VALID },
|
||||||
{ "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
|
{ "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_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_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_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_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_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 },
|
{ 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)
|
static int strtoul_check(char *a, u32 *res)
|
||||||
{
|
{
|
||||||
|
unsigned long x;
|
||||||
|
|
||||||
if (!numeric_check(a))
|
if (!numeric_check(a))
|
||||||
return 0;
|
return 0;
|
||||||
*res = strtoul(a, NULL, 10);
|
x = strtoul(a, NULL, 10);
|
||||||
if (errno == ERANGE) {
|
if (errno || x > UINT32_MAX) {
|
||||||
errno = 0;
|
errno = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
*res = (u32)x;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -812,7 +821,7 @@ static char *parse_mysockaddr(char *arg, union mysockaddr *addr)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, int *flags)
|
char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, u16 *flags)
|
||||||
{
|
{
|
||||||
int source_port = 0, serv_port = NAMESERVER_PORT;
|
int source_port = 0, serv_port = NAMESERVER_PORT;
|
||||||
char *portno, *source;
|
char *portno, *source;
|
||||||
@@ -2617,7 +2626,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
comma = split_chr(arg, '/');
|
comma = split_chr(arg, '/');
|
||||||
new = opt_malloc(sizeof(struct serv_local));
|
new = opt_malloc(sizeof(struct serv_local));
|
||||||
new->domain = opt_string_alloc(arg);
|
new->domain = opt_string_alloc(arg);
|
||||||
new->flags = strlen(arg);
|
new->domain_len = strlen(arg);
|
||||||
new->next = daemon->no_rebind;
|
new->next = daemon->no_rebind;
|
||||||
daemon->no_rebind = new;
|
daemon->no_rebind = new;
|
||||||
arg = comma;
|
arg = comma;
|
||||||
@@ -2634,7 +2643,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
size_t size;
|
size_t size;
|
||||||
char *lastdomain = NULL, *domain = "";
|
char *lastdomain = NULL, *domain = "";
|
||||||
char *alloc_domain;
|
char *alloc_domain;
|
||||||
int flags = 0;
|
u16 flags = 0;
|
||||||
char *err;
|
char *err;
|
||||||
struct in_addr addr4;
|
struct in_addr addr4;
|
||||||
struct in6_addr addr6;
|
struct in6_addr addr6;
|
||||||
@@ -2647,8 +2656,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
char *last;
|
char *last;
|
||||||
|
|
||||||
arg++;
|
arg++;
|
||||||
domain = lastdomain = arg;
|
/* elide leading dots - they are implied in the search algorithm */
|
||||||
|
while (*arg == '.') arg++;
|
||||||
|
|
||||||
|
domain = lastdomain = arg;
|
||||||
|
|
||||||
while ((last = split_chr(arg, '/')))
|
while ((last = split_chr(arg, '/')))
|
||||||
{
|
{
|
||||||
lastdomain = arg;
|
lastdomain = arg;
|
||||||
@@ -2730,6 +2742,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
}
|
}
|
||||||
|
|
||||||
new->domain = alloc_domain;
|
new->domain = alloc_domain;
|
||||||
|
new->domain_len = strlen(alloc_domain);
|
||||||
|
|
||||||
/* server=//1.2.3.4 is special. */
|
/* server=//1.2.3.4 is special. */
|
||||||
if (strlen(domain) == 0 && lastdomain)
|
if (strlen(domain) == 0 && lastdomain)
|
||||||
@@ -2751,6 +2764,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
new = opt_malloc(size);
|
new = opt_malloc(size);
|
||||||
memcpy(new, last, size);
|
memcpy(new, last, size);
|
||||||
new->domain = alloc_domain;
|
new->domain = alloc_domain;
|
||||||
|
new->domain_len = strlen(alloc_domain);
|
||||||
|
|
||||||
if (flags & (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS))
|
if (flags & (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS))
|
||||||
{
|
{
|
||||||
new->next = daemon->local_domains;
|
new->next = daemon->local_domains;
|
||||||
@@ -2877,6 +2892,135 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
}
|
}
|
||||||
#endif
|
#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 */
|
case 'c': /* --cache-size */
|
||||||
{
|
{
|
||||||
int size;
|
int size;
|
||||||
|
|||||||
+386
@@ -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
|
||||||
+88
-1
@@ -884,6 +884,92 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||||
|
/* Don't pass control chars and weird escapes to UBus. */
|
||||||
|
static int safe_name(char *name)
|
||||||
|
{
|
||||||
|
unsigned char *r;
|
||||||
|
|
||||||
|
for (r = (unsigned char *)name; *r; r++)
|
||||||
|
if (!isprint((int)*r))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (!extract_name(header, len, &p, daemon->workspacename, 1, 0))
|
||||||
|
return;
|
||||||
|
if (safe_name(daemon->namebuff) && safe_name(daemon->workspacename))
|
||||||
|
ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, daemon->workspacename, 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) && safe_name(daemon->namebuff))
|
||||||
|
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) && safe_name(daemon->namebuff))
|
||||||
|
ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p = endrr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* If the packet holds exactly one query
|
/* If the packet holds exactly one query
|
||||||
return F_IPV4 or F_IPV6 and leave the name from the query in name */
|
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)
|
unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep)
|
||||||
@@ -896,7 +982,8 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
|
|||||||
|
|
||||||
*name = 0; /* return empty name if no query found. */
|
*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. */
|
return 0; /* must be exactly one query. */
|
||||||
|
|
||||||
if (!extract_name(header, qlen, &p, name, 1, 4))
|
if (!extract_name(header, qlen, &p, name, 1, 4))
|
||||||
|
|||||||
+184
-2
@@ -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 ubus_request_data *req, const char *method,
|
||||||
struct blob_attr *msg);
|
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 void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj);
|
||||||
|
|
||||||
static const struct ubus_method ubus_object_methods[] = {
|
static const struct ubus_method ubus_object_methods[] = {
|
||||||
UBUS_METHOD_NOARG("metrics", ubus_handle_metrics),
|
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 =
|
static struct ubus_object_type ubus_object_type =
|
||||||
@@ -89,7 +117,7 @@ char *ubus_init()
|
|||||||
if (ret)
|
if (ret)
|
||||||
{
|
{
|
||||||
ubus_destroy(ubus);
|
ubus_destroy(ubus);
|
||||||
return ubus_strerror(ret);
|
return (char *)ubus_strerror(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
ubus->connection_lost = ubus_disconnect_cb;
|
ubus->connection_lost = ubus_disconnect_cb;
|
||||||
@@ -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);
|
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)
|
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;
|
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);
|
blobmsg_add_string(&b, "interface", interface);
|
||||||
|
|
||||||
ret = ubus_notify(ubus, &ubus_object, type, b.head, -1);
|
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));
|
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 */
|
#endif /* HAVE_UBUS */
|
||||||
Reference in new issue
Block a user