Compare commits

...

25 Commits

Author SHA1 Message Date
Simon Kelley
66b863c989 Fix problem with re-allocation of serverarray. 2021-06-26 21:13:41 +01:00
Simon Kelley
c9efe8e5e1 Rationalise domain parsing for --rev-server and --domain. 2021-06-26 18:51:05 +01:00
Simon Kelley
d515223bb5 Don't re-use datastructures for --address and --local.
Doing so makes the loading process quadratic, which is a problem
when there are a large number.
2021-06-26 01:00:37 +01:00
Simon Kelley
b908f4334b Merge branch 'extended-error' 2021-06-26 00:38:55 +01:00
Simon Kelley
6261aba026 Initial implementation of RFC-8914 extended DNS errors. 2021-06-26 00:38:01 +01:00
Simon Kelley
85bc7534da Rationalise --server parsing and datastructure building.
Use add_update_server for everything.
2021-06-25 22:09:08 +01:00
Simon Kelley
1b30fd1732 Merge branch 'master' of ssh://thekelleys.org.uk/var/local/git/dnsmasq 2021-06-25 10:46:53 +01:00
Simon Kelley
8c9196bff8 Correct domain search algorithm.
For reasons unknown, I (srk) assumed that the orginal
substring domain matching algorithm was still in use,
where example.com would match eg. sexample.com

In fact the far more sensible label-based match, where
example.com (or .example.com) matches example.com and
www.example.com, but not sexample.com, has been in use
since release 2.22. This commit implements the 2.22 to 2.85
behaviour in the new domain-search code.

Thanks to Kevin Darbyshire-Bryant for spotting my mistake.
2021-06-25 10:46:06 +01:00
Simon Kelley
b1daf44954 Correct domain search algorithm.
For reasons unknown, I (srk) assumed that the orginal
substring domain matching algorithm was still in use,
where example.comKevin Darbyshire-Bryant <kevin@darbyshire-bryant.me.uk> would match eg. sexample.com

In fact the far more sensible label-based match, where
example.com (or .example.com) matches example.com and
www.example.com, but not sexample.com, has been in use
since release 2.22. This commit implements the 2.22 to 2.85
behaviour in the new domain-search code.

Thanks to Kevin Darbyshire-Bryant for spotting my mistake.
2021-06-24 23:28:47 +01:00
Simon Kelley
11c52d032b Initial changes for extended DNS error codes. 2021-06-21 17:37:46 +01:00
Simon Kelley
be291d979d Include EDNS0 in connmark REFUSED replies. 2021-06-21 16:59:42 +01:00
Simon Kelley
6d1edd8d32 Use correct packet-size limit in make_local_answer() 2021-06-21 15:59:07 +01:00
Simon Kelley
25ff956c7d Tidy up name buffer use in report_addresses().
Buffer may need to be twice MAXDNAME is escaping is
enabled in extract_name. The name may include weird characters.
2021-06-21 15:05:28 +01:00
Simon Kelley
38179500f8 CHANGELOG entry for new connmark code. 2021-06-21 14:35:36 +01:00
Simon Kelley
5f7be5f0d6 Fix compiler warning. 2021-06-21 14:31:54 +01:00
Etan Kissling
627056febb 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>
2021-06-21 14:14:55 +01:00
Simon Kelley
cbd76447fd Further work from a0a3b8ad3e
When query longer than longest domain, crop directly to length
of longest domain.
2021-06-21 00:01:51 +01:00
Simon Kelley
a60a233329 Fix bug introduced in 6860cf932b
Breakage 0f --no-rebind-domain due to incomplete edit.

Thanks to Kevin Darbyshire-Bryant for spotting this.
2021-06-20 23:02:54 +01:00
Simon Kelley
a0a3b8ad3e Fix bug in 6860cf932b
The optimisation based on the assumption that
"try now points to the last domain that sorts before the query"
fails in the specific edge case that the query sorts before
_any_ of the server domains. Handle this case.

Thanks to Xingcong Li for finding a test case for this bug.
2021-06-20 22:57:54 +01:00
Simon Kelley
d0ae3f5a4d Fix specific NOERR/NXDOMAIN confusion.
In the specific case of configuring an A record for a domain

address=/example.com/1.2.3.4

queries for *example.com for any other type will now return
NOERR, and not the previous erroneous NXDOMAIN. The same thing
applies for

address=/example.com/::1:2:3:4
address=/example.com/#
2021-06-17 23:11:17 +01:00
Simon Kelley
6860cf932b Optimise lokkup_domain() 2021-06-17 21:30:40 +01:00
Simon Kelley
0276e0805b merge development machines.
Merge branch 'master' of ssh://thekelleys.org.uk/var/local/git/dnsmasq
2021-06-16 14:05:49 +01:00
Simon Kelley
06ff3d8a26 Log the correct name when we retry a DNSSEC query.
If we retry a DNSSEC query because our client retries on us, and
we have an answer but are waiting on a DNSSEC query to validate it,
log the name of the DNSSEC query, not the client's query.
2021-06-16 13:59:57 +01:00
Simon Kelley
1a3b69aa56 Fix error in new domain-search code.
SERV_USE_RESOLV set implies struct serv_local,
so don't can't set ->arrayposn

Thanks to Xingcong Li for the cod review which led to this.
2021-06-16 09:57:41 +01:00
Simon Kelley
8237d06ab7 Typo in FAQ.
Reported by Alexander Traud.
2021-06-15 23:14:59 +01:00
18 changed files with 1779 additions and 586 deletions

View File

@@ -64,6 +64,18 @@ version 2.86
queries. The requesting address and port have been removed from
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
Fix problem with DNS retries in 2.83/2.84.

2
FAQ
View File

@@ -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
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
modes". This is unfortunate as it requires users configuring dnsmasq

View File

@@ -79,7 +79,7 @@ copts_conf = .copts_$(sum)
objs = cache.o rfc1035.o util.o option.o forward.o network.o \
dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o pattern.o \
domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \
poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o \
metrics.o hash-questions.o domain-match.o

View File

@@ -371,7 +371,10 @@ provides service at that name, rather than the default which is
.TP
.B --enable-ubus[=<service-name>]
Enable dnsmasq UBus interface. It sends notifications via UBus on
DHCPACK and DHCPRELEASE events. Furthermore it offers metrics.
DHCPACK and DHCPRELEASE events. Furthermore it offers metrics
and allows configuration of Linux connection track mark based filtering.
When DNS query filtering based on Linux connection track marks is enabled
UBus notifications are generated for each resolved or filtered DNS query.
Requires that dnsmasq has been built with UBus support. If the service
name is given, dnsmasq provides service at that namespace, rather than
the default which is
@@ -536,6 +539,32 @@ These IP sets must already exist. See
.BR ipset (8)
for more details.
.TP
.B --connmark-allowlist-enable[=<mask>]
Enables filtering of incoming DNS queries with associated Linux connection track marks
according to individual allowlists configured via a series of \fB--connmark-allowlist\fP
options. Disallowed queries are not forwarded; they are rejected with a REFUSED error code.
DNS queries are only allowed if they do not have an associated Linux connection
track mark, or if the queried domains match the configured DNS patterns for the
associated Linux connection track mark. If no allowlist is configured for a
Linux connection track mark, all DNS queries associated with that mark are rejected.
If a mask is specified, Linux connection track marks are first bitwise ANDed
with the given mask before being processed.
.TP
.B --connmark-allowlist=<connmark>[/<mask>][,<pattern>[/<pattern>...]]
Configures the DNS patterns that are allowed in DNS queries associated with
the given Linux connection track mark.
If a mask is specified, Linux connection track marks are first bitwise ANDed
with the given mask before they are compared to the given connection track mark.
Patterns follow the syntax of DNS names, but additionally allow the wildcard
character "*" to be used up to twice per label to match 0 or more characters
within that label. Note that the wildcard never matches a dot (e.g., "*.example.com"
matches "api.example.com" but not "api.us.example.com"). Patterns must be
fully qualified, i.e., consist of at least two labels. The final label must not be
fully numeric, and must not be the "local" pseudo-TLD. A pattern must end with at least
two literal (non-wildcard) labels.
Instead of a pattern, "*" can be specified to disable allowlist filtering
for a given Linux connection track mark entirely.
.TP
.B \-m, --mx-host=<mx name>[[,<hostname>],<preference>]
Return an MX record named <mx name> pointing to the given hostname (if
given), or

View File

@@ -1861,10 +1861,44 @@ char *querystr(char *desc, unsigned short type)
return buff ? buff : "";
}
static char *edestr(int ede)
{
switch (ede)
{
case EDE_OTHER: return "other";
case EDE_USUPDNSKEY: return "unsupported DNSKEY algorithm";
case EDE_USUPDS: return "unsupported DS digest";
case EDE_STALE: return "stale answer";
case EDE_FORGED: return "forged";
case EDE_DNSSEC_IND: return "DNSSEC indeterminate";
case EDE_DNSSEC_BOGUS: return "DNSSEC bogus";
case EDE_SIG_EXP: return "DNSSEC signature expired";
case EDE_SIG_NYV: return "DNSSEC sig not yet valid";
case EDE_NO_DNSKEY: return "DNSKEY missing";
case EDE_NO_RRSIG: return "RRSIG missing";
case EDE_NO_ZONEKEY: return "no zone key bit set";
case EDE_NO_NSEC: return "NSEC(3) missing";
case EDE_CACHED_ERR: return "cached error";
case EDE_NOT_READY: return "not ready";
case EDE_BLOCKED: return "blocked";
case EDE_CENSORED: return "censored";
case EDE_FILTERED: return "filtered";
case EDE_PROHIBITED: return "prohibited";
case EDE_STALE_NXD: return "stale NXDOMAIN";
case EDE_NOT_AUTH: return "not authoritative";
case EDE_NOT_SUP: return "not supported";
case EDE_NO_AUTH: return "no reachable authority";
case EDE_NETERR: return "network error";
case EDE_INVALID_DATA: return "invalid data";
default: return "unknown";
}
}
void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg)
{
char *source, *dest = daemon->addrbuff;
char *verb = "is";
char *extra = "";
if (!option_bool(OPT_LOG))
return;
@@ -1887,6 +1921,12 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg)
dest = "not implemented";
else
sprintf(daemon->addrbuff, "%u", rcode);
if (addr->log.ede != -1)
{
extra = daemon->addrbuff;
sprintf(extra, " (EDE:%s)", edestr(addr->log.ede));
}
}
else
inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
@@ -1932,7 +1972,15 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg)
else if (flags & F_UPSTREAM)
source = "reply";
else if (flags & F_SECSTAT)
source = "validation";
{
if (addr && addr->log.ede != -1)
{
extra = daemon->addrbuff;
sprintf(extra, " (EDE:%s)", edestr(addr->log.ede));
}
source = "validation";
dest = arg;
}
else if (flags & F_AUTH)
source = "auth";
else if (flags & F_SERVER)
@@ -1966,11 +2014,11 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg)
if (option_bool(OPT_EXTRALOG))
{
if (flags & F_NOEXTRA)
my_syslog(LOG_INFO, "%u %s %s %s %s", daemon->log_display_id, source, name, verb, dest);
my_syslog(LOG_INFO, "%u %s %s %s %s%s", daemon->log_display_id, source, name, verb, dest, extra);
else
{
int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2);
my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest);
my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s%s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest, extra);
}
}
else

View File

@@ -215,13 +215,14 @@ static void dbus_read_servers(DBusMessage *message)
domain = NULL;
if (!skip)
add_update_server(SERV_FROM_DBUS, &addr, &source_addr, NULL, domain);
add_update_server(SERV_FROM_DBUS, &addr, &source_addr, NULL, domain, NULL);
} while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING);
}
/* unlink and free anything still marked. */
cleanup_servers();
check_servers(0);
}
#ifdef HAVE_LOOP
@@ -361,10 +362,6 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
strcpy(str_addr, str);
}
memset(&addr, 0, sizeof(addr));
memset(&source_addr, 0, sizeof(source_addr));
memset(&interface, 0, sizeof(interface));
/* parse the IP address */
if ((addr_err = parse_server(str_addr, &addr, &source_addr, (char *) &interface, &flags)))
{
@@ -392,7 +389,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
else
p = NULL;
add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str_domain);
add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str_domain, NULL);
} while ((str_domain = p));
}
else
@@ -407,7 +404,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
dbus_message_iter_get_basic(&string_iter, &str);
dbus_message_iter_next (&string_iter);
add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str);
add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str, NULL);
} while (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING);
}
@@ -416,7 +413,8 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
}
cleanup_servers();
check_servers(0);
if (dup)
free(dup);

View File

@@ -80,10 +80,41 @@
#define EDNS0_OPTION_MAC 65001 /* dyndns.org temporary assignment */
#define EDNS0_OPTION_CLIENT_SUBNET 8 /* IANA */
#define EDNS0_OPTION_EDE 15 /* IANA - RFC 8914 */
#define EDNS0_OPTION_NOMDEVICEID 65073 /* Nominum temporary assignment */
#define EDNS0_OPTION_NOMCPEID 65074 /* Nominum temporary assignment */
#define EDNS0_OPTION_UMBRELLA 20292 /* Cisco Umbrella temporary assignment */
/* RFC-8914 extended errors */
#define EDE_OTHER 0 /* Other */
#define EDE_USUPDNSKEY 1 /* Unsupported DNSKEY algo */
#define EDE_USUPDS 2 /* Unsupported DS Digest */
#define EDE_STALE 3 /* Stale answer */
#define EDE_FORGED 4 /* Forged answer */
#define EDE_DNSSEC_IND 5 /* DNSSEC Indeterminate */
#define EDE_DNSSEC_BOGUS 6 /* DNSSEC Bogus */
#define EDE_SIG_EXP 7 /* Signature Expired */
#define EDE_SIG_NYV 8 /* Signature Not Yet Valid */
#define EDE_NO_DNSKEY 9 /* DNSKEY missing */
#define EDE_NO_RRSIG 10 /* RRSIGs missing */
#define EDE_NO_ZONEKEY 11 /* No Zone Key Bit Set */
#define EDE_NO_NSEC 12 /* NSEC Missing */
#define EDE_CACHED_ERR 13 /* Cached Error */
#define EDE_NOT_READY 14 /* Not Ready */
#define EDE_BLOCKED 15 /* Blocked */
#define EDE_CENSORED 16 /* Censored */
#define EDE_FILTERED 17 /* Filtered */
#define EDE_PROHIBITED 18 /* Prohibited */
#define EDE_STALE_NXD 19 /* Stale NXDOMAIN */
#define EDE_NOT_AUTH 20 /* Not Authoritative */
#define EDE_NOT_SUP 21 /* Not Supported */
#define EDE_NO_AUTH 22 /* No Reachable Authority */
#define EDE_NETERR 23 /* Network error */
#define EDE_INVALID_DATA 24 /* Invalid Data */
struct dns_header {
u16 id;
u8 hb3,hb4;

View File

@@ -135,6 +135,13 @@ int main (int argc, char **argv)
}
#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
if (!daemon->lease_file)
{
@@ -1027,7 +1034,7 @@ int main (int argc, char **argv)
close(err_pipe[1]);
if (daemon->port != 0)
check_servers();
check_servers(0);
pid = getpid();
@@ -1439,7 +1446,7 @@ static void async_event(int pipe, time_t now)
}
if (check)
check_servers();
check_servers(0);
}
#ifdef HAVE_DHCP
@@ -1638,7 +1645,7 @@ static void poll_resolv(int force, int do_reload, time_t now)
{
my_syslog(LOG_INFO, _("reading %s"), latest->name);
warned = 0;
check_servers();
check_servers(0);
if (option_bool(OPT_RELOAD) && do_reload)
clear_cache_and_reload(now);
}

View File

@@ -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) )
@@ -322,6 +323,7 @@ union all_addr {
/* for log_query */
struct {
unsigned short keytag, algo, digest, rcode;
int ede;
} log;
};
@@ -564,7 +566,7 @@ struct randfd_list {
struct server {
int flags;
u16 flags, domain_len;
char *domain;
struct server *next;
int serial, arrayposn;
@@ -583,23 +585,23 @@ struct server {
#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 {
int flags;
u16 flags, domain_len;
char *domain;
struct server *next;
struct in_addr addr;
};
struct serv_addr6 {
int flags;
u16 flags, domain_len;
char *domain;
struct server *next;
struct in6_addr addr;
};
struct serv_local {
int flags;
u16 flags, domain_len;
char *domain;
struct server *next;
};
@@ -610,6 +612,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 */
@@ -679,17 +687,28 @@ struct hostsfile {
#define DUMP_BOGUS 0x0040
#define DUMP_SEC_BOGUS 0x0080
/* DNSSEC status values. */
#define STAT_SECURE 1
#define STAT_INSECURE 2
#define STAT_BOGUS 3
#define STAT_NEED_DS 4
#define STAT_NEED_KEY 5
#define STAT_TRUNCATED 6
#define STAT_SECURE_WILDCARD 7
#define STAT_OK 8
#define STAT_ABANDONED 9
#define STAT_SECURE 0x10000
#define STAT_INSECURE 0x20000
#define STAT_BOGUS 0x30000
#define STAT_NEED_DS 0x40000
#define STAT_NEED_KEY 0x50000
#define STAT_TRUNCATED 0x60000
#define STAT_SECURE_WILDCARD 0x70000
#define STAT_OK 0x80000
#define STAT_ABANDONED 0x90000
#define DNSSEC_FAIL_NYV 0x0001 /* key not yet valid */
#define DNSSEC_FAIL_EXP 0x0002 /* key expired */
#define DNSSEC_FAIL_INDET 0x0004 /* indetermined */
#define DNSSEC_FAIL_NOKEYSUP 0x0008 /* no supported key algo. */
#define DNSSEC_FAIL_NOSIG 0x0010 /* No RRsigs */
#define DNSSEC_FAIL_NOZONE 0x0020 /* No Zone bit set */
#define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */
#define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */
#define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */
#define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b))
#define FREC_NOREBIND 1
#define FREC_CHECKING_DISABLED 2
@@ -1084,8 +1103,10 @@ extern struct daemon {
struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces;
struct bogus_addr *bogus_addr, *ignore_addr;
struct server *servers, *local_domains, **serverarray, *no_rebind;
int serverarraysz;
int serverarraysz, serverarrayhwm;
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 */
@@ -1271,10 +1292,13 @@ unsigned char *skip_questions(struct dns_header *header, size_t plen);
unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen);
unsigned int extract_request(struct dns_header *header, size_t qlen,
char *name, unsigned short *typep);
void setup_reply(struct dns_header *header, unsigned int flags);
void setup_reply(struct dns_header *header, unsigned int flags, int ede);
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);
@@ -1299,6 +1323,7 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
#endif
/* dnssec.c */
#ifdef HAVE_DNSSEC
size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz);
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
@@ -1307,6 +1332,8 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
size_t filter_rrsigs(struct dns_header *header, size_t plen);
int setup_timestamp(void);
int errflags_to_ede(int status);
#endif
/* hash_questions.c */
void hash_questions_init(void);
@@ -1381,7 +1408,7 @@ void set_option_bool(unsigned int opt);
void reset_option_bool(unsigned int opt);
struct hostsfile *expand_filelist(struct hostsfile *list);
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);
/* forward.c */
@@ -1402,14 +1429,7 @@ int indextoname(int fd, int index, char *name);
int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp);
void pre_allocate_sfds(void);
int reload_servers(char *fname);
void mark_servers(int flag);
void cleanup_servers(void);
void add_update_server(int flags,
union mysockaddr *addr,
union mysockaddr *source_addr,
const char *interface,
const char *domain);
void check_servers(void);
void check_servers(int no_loop_call);
int enumerate_interfaces(int reset);
void create_wildcard_listeners(void);
void create_bound_listeners(int dienow);
@@ -1546,6 +1566,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 +1578,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);
@@ -1726,9 +1757,16 @@ int lookup_domain(char *qdomain, 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);
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 ede);
int server_samegroup(struct server *a, struct server *b);
#ifdef HAVE_DNSSEC
int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp);
#endif
void mark_servers(int flag);
void cleanup_servers(void);
int add_update_server(int flags,
union mysockaddr *addr,
union mysockaddr *source_addr,
const char *interface,
const char *domain,
union all_addr *local_addr);

View File

@@ -215,14 +215,6 @@ static int is_check_date(unsigned long curtime)
return !daemon->dnssec_no_time_check;
}
/* Check whether today/now is between date_start and date_end */
static int check_date_range(unsigned long curtime, u32 date_start, u32 date_end)
{
/* We must explicitly check against wanted values, because of SERIAL_UNDEF */
return serial_compare_32(curtime, date_start) == SERIAL_GT
&& serial_compare_32(curtime, date_end) == SERIAL_LT;
}
/* Return bytes of canonicalised rrdata one by one.
Init state->ip with the RR, and state->end with the end of same.
Init state->op to NULL.
@@ -534,7 +526,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
struct crec *crecp = NULL;
u16 *rr_desc = rrfilter_desc(type);
u32 sig_expiration, sig_inception;
int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
unsigned long curtime = time(0);
int time_check = is_check_date(curtime);
@@ -557,6 +550,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
void *ctx;
char *name_start;
u32 nsigttl, ttl, orig_ttl;
failflags &= ~DNSSEC_FAIL_NOSIG;
p = sigs[j];
GETLONG(ttl, p);
@@ -574,12 +569,31 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
if (!extract_name(header, plen, &p, keyname, 1, 0))
return STAT_BOGUS;
if ((time_check && !check_date_range(curtime, sig_inception, sig_expiration)) ||
labels > name_labels ||
!(hash = hash_find(algo_digest_name(algo))) ||
if (!time_check)
failflags &= ~(DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP);
else
{
/* We must explicitly check against wanted values, because of SERIAL_UNDEF */
if (serial_compare_32(curtime, sig_inception) == SERIAL_LT)
continue;
else
failflags &= ~DNSSEC_FAIL_NYV;
if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT)
continue;
else
failflags &= ~DNSSEC_FAIL_EXP;
}
if (!(hash = hash_find(algo_digest_name(algo))))
continue;
else
failflags &= ~DNSSEC_FAIL_NOKEYSUP;
if (labels > name_labels ||
!hash_init(hash, &ctx, &digest))
continue;
/* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
return STAT_NEED_KEY;
@@ -730,7 +744,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
}
}
return STAT_BOGUS;
return STAT_BOGUS | failflags;
}
@@ -751,17 +765,18 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
unsigned long ttl, sig_ttl;
struct blockdata *key;
union all_addr a;
int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE | DNSSEC_FAIL_NOKEY;
if (ntohs(header->qdcount) != 1 ||
RCODE(header) == SERVFAIL || RCODE(header) == REFUSED ||
!extract_name(header, plen, &p, name, 1, 4))
return STAT_BOGUS;
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
return STAT_BOGUS;
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
/* See if we have cached a DS record which validates this key */
if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
@@ -795,14 +810,17 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
GETSHORT(flags, p);
if (*p++ != 3)
return STAT_BOGUS;
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
algo = *p++;
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
key = NULL;
/* key must have zone key flag set */
if (flags & 0x100)
key = blockdata_alloc((char*)p, rdlen - 4);
{
key = blockdata_alloc((char*)p, rdlen - 4);
failflags &= ~DNSSEC_FAIL_NOZONE;
}
p = psave;
@@ -823,15 +841,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
unsigned char *digest, *ds_digest;
const struct nettle_hash *hash;
int sigcnt, rrcnt;
int wire_len;
if (recp1->addr.ds.algo == algo &&
recp1->addr.ds.keytag == keytag &&
recp1->uid == (unsigned int)class &&
(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) &&
hash_init(hash, &ctx, &digest))
recp1->uid == (unsigned int)class)
{
int wire_len = to_wire(name);
failflags &= ~DNSSEC_FAIL_NOKEY;
if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))))
continue;
else
failflags &= ~DNSSEC_FAIL_NODSSUP;
if (!hash_init(hash, &ctx, &digest))
continue;
wire_len = to_wire(name);
/* Note that digest may be different between DSs, so
we can't move this outside the loop. */
@@ -846,12 +872,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
(ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) &&
memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
sigcnt != 0 && rrcnt != 0 &&
validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
NULL, key, rdlen - 4, algo, keytag, &sig_ttl) == STAT_SECURE)
rrcnt != 0)
{
valid = 1;
break;
if (sigcnt == 0)
continue;
else
failflags &= ~DNSSEC_FAIL_NOSIG;
rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
NULL, key, rdlen - 4, algo, keytag, &sig_ttl);
failflags &= rc;
if (STAT_ISEQUAL(rc, STAT_SECURE))
{
valid = 1;
break;
}
}
}
}
@@ -936,7 +973,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
}
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
return STAT_BOGUS;
return STAT_BOGUS | failflags;
}
/* The DNS packet is expected to contain the answer to a DS query
@@ -971,10 +1008,11 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
else
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl);
if (rc == STAT_INSECURE)
if (STAT_ISEQUAL(rc, STAT_INSECURE))
{
my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
rc = STAT_BOGUS;
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure");
return STAT_BOGUS | DNSSEC_FAIL_INDET;
}
p = (unsigned char *)(header+1);
@@ -984,13 +1022,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
/* If the key needed to validate the DS is on the same domain as the DS, we'll
loop getting nowhere. Stop that now. This can happen of the DS answer comes
from the DS's zone, and not the parent zone. */
if (rc == STAT_BOGUS || (rc == STAT_NEED_KEY && hostname_isequal(name, keyname)))
if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
{
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS");
return STAT_BOGUS;
}
if (rc != STAT_SECURE)
if (!STAT_ISEQUAL(rc, STAT_SECURE))
return rc;
if (!neganswer)
@@ -1456,7 +1494,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
if (!(p = skip_name(nsecs[i], header, plen, 15)))
return 0; /* bad packet */
p += 10; /* type, class, TTL, rdlen */
p += 10; /* type, class, TTL, rdlen */
algo = *p++;
if ((hash = hash_find(nsec3_digest_name(algo))))
@@ -1959,15 +1997,15 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if (check_unsigned && i < ntohs(header->ancount))
{
rc = zone_status(name, class1, keyname, now);
if (rc == STAT_SECURE)
rc = STAT_BOGUS;
if (STAT_ISEQUAL(rc, STAT_SECURE))
rc = STAT_BOGUS | DNSSEC_FAIL_NOSIG;
if (class)
*class = class1; /* Class for NEED_DS or NEED_KEY */
}
else
rc = STAT_INSECURE;
if (rc != STAT_INSECURE)
if (!STAT_ISEQUAL(rc, STAT_INSECURE))
return rc;
}
}
@@ -1978,7 +2016,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
strcpy(daemon->workspacename, keyname);
rc = zone_status(daemon->workspacename, class1, keyname, now);
if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS))
{
if (class)
*class = class1; /* Class for NEED_DS or NEED_KEY */
@@ -1986,13 +2024,13 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
}
/* Zone is insecure, don't need to validate RRset */
if (rc == STAT_SECURE)
if (STAT_ISEQUAL(rc, STAT_SECURE))
{
unsigned long sig_ttl;
rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl);
if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS))
{
if (class)
*class = class1; /* Class for DS or DNSKEY */
@@ -2025,21 +2063,21 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
Note that we may not yet have validated the NSEC/NSEC3 RRsets.
That's not a problem since if the RRsets later fail
we'll return BOGUS then. */
if (rc == STAT_SECURE_WILDCARD &&
if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
!prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL))
return STAT_BOGUS;
return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
rc = STAT_SECURE;
}
}
}
if (rc == STAT_INSECURE)
if (STAT_ISEQUAL(rc, STAT_INSECURE))
secure = STAT_INSECURE;
}
/* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
if (secure == STAT_SECURE)
if (STAT_ISEQUAL(secure, STAT_SECURE))
for (j = 0; j <targetidx; j++)
if ((p2 = targets[j]))
{
@@ -2057,16 +2095,16 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
{
/* Empty DS without NSECS */
if (qtype == T_DS)
return STAT_BOGUS;
return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE)
if (STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
{
if (class)
*class = qclass; /* Class for NEED_DS or NEED_KEY */
return rc;
}
return STAT_BOGUS; /* signed zone, no NSECs */
return STAT_BOGUS | DNSSEC_FAIL_NONSEC; /* signed zone, no NSECs */
}
}
@@ -2130,4 +2168,31 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char
return ret;
}
int errflags_to_ede(int status)
{
/* We can end up with more than one flag set for some errors,
so this encodes a rough priority so the (eg) No sig is reported
before no-unexpired-sig. */
if (status & DNSSEC_FAIL_NYV)
return EDE_SIG_NYV;
else if (status & DNSSEC_FAIL_EXP)
return EDE_SIG_EXP;
else if (status & DNSSEC_FAIL_NOKEYSUP)
return EDE_USUPDNSKEY;
else if (status & DNSSEC_FAIL_NOZONE)
return EDE_NO_ZONEKEY;
else if (status & DNSSEC_FAIL_NOKEY)
return EDE_NO_DNSKEY;
else if (status & DNSSEC_FAIL_NODSSUP)
return EDE_USUPDS;
else if (status & DNSSEC_FAIL_NONSEC)
return EDE_NO_NSEC;
else if (status & DNSSEC_FAIL_INDET)
return EDE_DNSSEC_IND;
else if (status & DNSSEC_FAIL_NOSIG)
return EDE_NO_RRSIG;
else
return -1;
}
#endif /* HAVE_DNSSEC */

View File

@@ -16,53 +16,66 @@
#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_servers(struct server *s, struct server *s2);
/* If the server is USE_RESOLV or LITERAL_ADDRES, it lives on the local_domains chain. */
#define SERV_IS_LOCAL (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS)
void build_server_array(void)
{
struct server *serv;
int count = 0;
for (serv = daemon->servers; serv; serv = serv->next)
count++;
#ifdef HAVE_LOOP
if (!(serv->flags & SERV_LOOP))
#endif
count++;
for (serv = daemon->local_domains; serv; serv = serv->next)
count++;
if (count > daemon->serverarraysz)
daemon->serverarraysz = count;
if (count > daemon->serverarrayhwm)
{
struct server **new;
count += 10; /* A few extra without re-allocating. */
if ((new = whine_malloc(count * sizeof(struct server *))))
{
if (daemon->serverarray)
free(daemon->serverarray);
daemon->serverarray = new;
daemon->serverarraysz = count;
daemon->serverarrayhwm = count;
}
}
count = 0;
for (serv = daemon->servers; serv; serv = serv->next, count++)
{
daemon->serverarray[count] = serv;
serv->serial = count;
serv->last_server = -1;
}
#ifdef HAVE_LOOP
if (!(serv->flags & SERV_LOOP))
#endif
{
daemon->serverarray[count] = serv;
serv->serial = count;
serv->last_server = -1;
}
for (serv = daemon->local_domains; serv; serv = serv->next, count++)
daemon->serverarray[count] = serv;
qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort);
/* servers need the location in the array to find all the whole
set of equivalent servers from a pointer to a single one. */
for (count = 0; count < daemon->serverarraysz; count++)
if (!(daemon->serverarray[count]->flags & SERV_LITERAL_ADDRESS))
if (!(daemon->serverarray[count]->flags & SERV_IS_LOCAL))
daemon->serverarray[count]->arrayposn = count;
}
@@ -75,12 +88,14 @@ void build_server_array(void)
A flag of F_DNSSECOK returns a DNSSEC capable server only and
also disables NODOTS servers from consideration.
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.
*/
int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
{
int rc, nodots, leading_dot = 1;
ssize_t qlen, maxlen;
int rc, crop_query, nodots;
ssize_t qlen;
int try, high, low = 0;
int nlow = 0, nhigh = 0;
char *cp;
@@ -89,8 +104,6 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
if (daemon->serverarraysz == 0)
return 0;
maxlen = strlen(daemon->serverarray[0]->domain);
/* find query length and presence of '.' */
for (cp = qdomain, nodots = 1, qlen = 0; *cp; qlen++, cp++)
if (*cp == '.')
@@ -101,56 +114,74 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
if (qlen == 0 || flags & F_DNSSECOK)
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 */
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
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;
crop_query = 1;
/* binary search */
do
while (1)
{
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;
if (rc < 0)
if (rc < 0)
{
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;
}
else
{
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;
}
}
while (low != high);
};
if (rc == 0)
{
/* We've matched a setting which says to use servers without a domain.
Continue the search with empty query (the last character gets stripped
by the loop. */
Continue the search with empty query */
if (daemon->serverarray[try]->flags & SERV_USE_RESOLV)
{
qdomain += qlen - 1;
qlen = 1;
}
crop_query = qlen;
else
{
/* We have a match, but it may only be (say) an IPv6 address, and
@@ -160,27 +191,30 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
break;
}
}
/* crop_query must be at least one always. */
if (crop_query == 0)
crop_query = 1;
if (leading_dot)
leading_dot = 0;
else
{
qlen--;
qdomain++;
}
/* strip chars off the query based on the largest possible remaining match,
then continue to the start of the next label. */
qlen -= crop_query;
qdomain += crop_query;
while (qlen > 0 && (*(qdomain-1) != '.'))
qlen--, qdomain++;
}
/* 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.
A configured server eg server=/lan/ will take precdence. */
if (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);
/* F_DOMAINSRV returns only domain-specific servers, so if we got to a
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;
if (lowout)
@@ -222,47 +256,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) */
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_6ADDR); i++);
if (i != nlow && (flags & F_IPV6))
#define SERV_LOCAL_ADDRESS (SERV_6ADDR | SERV_4ADDR | SERV_ALL_ZEROS)
for (i = nlow; (flags & F_CONFIG) && i < nhigh && (daemon->serverarray[i]->flags & SERV_LOCAL_ADDRESS); i++);
if (i != nlow)
nhigh = i;
else
{
nlow = i;
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_4ADDR); i++);
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_6ADDR); i++);
if (i != nlow && (flags & F_IPV4))
if (i != nlow && (flags & F_IPV6))
nhigh = i;
else
{
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;
else
{
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_DNSSECOK | F_DOMAINSRV | F_SERVER)))
if (i != nlow && (flags & (F_IPV4 | F_IPV6)))
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;
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
/* --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;
*highout = nhigh;
@@ -273,7 +316,7 @@ int is_local_answer(time_t now, int first, char *name)
{
int flags = 0;
int rc = 0;
if ((flags = daemon->serverarray[first]->flags) & SERV_LITERAL_ADDRESS)
{
if (flags & SERV_4ADDR)
@@ -283,13 +326,25 @@ int is_local_answer(time_t now, int first, char *name)
else if (flags & SERV_ALL_ZEROS)
rc = F_IPV4 | F_IPV6;
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;
}
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 ede)
{
int trunc = 0;
unsigned char *p;
@@ -299,7 +354,7 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
if (flags & (F_NXDOMAIN | F_NOERR))
log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL);
setup_reply(header, flags);
setup_reply(header, flags, ede);
if (!(p = skip_questions(header, size)))
return 0;
@@ -315,7 +370,7 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
addr.addr4 = srv->addr;
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);
}
@@ -330,7 +385,7 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
addr.addr6 = srv->addr;
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);
}
@@ -372,20 +427,16 @@ int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp)
#endif
/* 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;
int rc;
/* servers for dotless names always sort last
searched for name is never dotless. */
if (serv->flags & SERV_FOR_NODOTS)
return -1;
if (leading_dot)
qlen++;
dlen = strlen(serv->domain);
dlen = serv->domain_len;
if (qlen < dlen)
return 1;
@@ -393,22 +444,18 @@ static int order(char *qdomain, int leading_dot, size_t qlen, struct server *ser
if (qlen > dlen)
return -1;
if (leading_dot && (rc = '.' - serv->domain[0]) != 0)
return rc;
return strcmp(qdomain, leading_dot ? &serv->domain[1] : serv->domain);
return strcmp(qdomain, serv->domain);
}
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
order_qsort() and filter_servers() */
if (s1->flags & SERV_FOR_NODOTS)
if (s1->flags & SERV_FOR_NODOTS)
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)
@@ -433,3 +480,150 @@ static int order_qsort(const void *a, const void *b)
return rc;
}
void mark_servers(int flag)
{
struct server *serv;
/* mark everything with argument flag */
for (serv = daemon->servers; serv; serv = serv->next)
if (serv->flags & flag)
serv->flags |= SERV_MARK;
for (serv = daemon->local_domains; serv; serv = serv->next)
if (serv->flags & flag)
serv->flags |= SERV_MARK;
}
void cleanup_servers(void)
{
struct server *serv, *tmp, **up;
/* unlink and free anything still marked. */
for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp)
{
tmp = serv->next;
if (serv->flags & SERV_MARK)
{
server_gone(serv);
*up = serv->next;
free(serv->domain);
free(serv);
}
else
up = &serv->next;
}
for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = tmp)
{
tmp = serv->next;
if (serv->flags & SERV_MARK)
{
*up = serv->next;
free(serv->domain);
free(serv);
}
else
up = &serv->next;
}
}
int add_update_server(int flags,
union mysockaddr *addr,
union mysockaddr *source_addr,
const char *interface,
const char *domain,
union all_addr *local_addr)
{
struct server *serv = NULL;
char *alloc_domain;
if (!domain || strlen(domain) == 0)
alloc_domain = whine_malloc(1);
else if (!(alloc_domain = canonicalise((char *)domain, NULL)))
return 0;
/* See if there is a suitable candidate, and unmark
only do this for forwarding servers, not
address or local, to avoid delays on large numbers. */
if (flags & SERV_IS_LOCAL)
for (serv = daemon->servers; serv; serv = serv->next)
if ((serv->flags & SERV_MARK) &&
hostname_isequal(alloc_domain, serv->domain))
break;
if (serv)
{
free(alloc_domain);
alloc_domain = serv->domain;
}
else
{
size_t size;
if (flags & SERV_LITERAL_ADDRESS)
{
if (flags & SERV_6ADDR)
size = sizeof(struct serv_addr6);
else if (flags & SERV_4ADDR)
size = sizeof(struct serv_addr4);
else
size = sizeof(struct serv_local);
}
else
size = sizeof(struct server);
if (!(serv = whine_malloc(size)))
return 0;
if (flags & SERV_IS_LOCAL)
{
serv->next = daemon->local_domains;
daemon->local_domains = serv;
}
else
{
struct server *s;
/* Add to the end of the chain, for order */
if (!daemon->servers)
daemon->servers = serv;
else
{
for (s = daemon->servers; s->next; s = s->next);
s->next = serv;
}
serv->next = NULL;
}
}
if (!(flags & SERV_IS_LOCAL))
memset(serv, 0, sizeof(struct server));
serv->flags = flags;
serv->domain = alloc_domain;
serv->domain_len = strlen(alloc_domain);
if (flags & SERV_4ADDR)
((struct serv_addr4*)serv)->addr = local_addr->addr4;
if (flags & SERV_6ADDR)
((struct serv_addr6*)serv)->addr = local_addr->addr6;
if (!(flags & SERV_IS_LOCAL))
{
#ifdef HAVE_LOOP
serv->uid = rand32();
#endif
if (interface)
safe_strncpy(serv->interface, interface, sizeof(serv->interface));
if (addr)
serv->addr = *addr;
if (source_addr)
serv->source_addr = *source_addr;
}
return 1;
}

View File

@@ -153,9 +153,8 @@ static int domain_no_rebind(char *domain)
struct server *serv;
int dlen = (int)strlen(domain);
/* flags is misused to hold length of domain. */
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 0;
@@ -178,6 +177,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
int subnet, cacheable, forwarded = 0;
size_t edns0_len;
unsigned char *pheader;
int ede = -1;
(void)do_bit;
if (header->hb4 & HB4_CD)
@@ -271,7 +271,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
/* no available server. */
if (!lookup_domain(daemon->namebuff, gotname, &first, &last))
goto reply;
{
ede = EDE_NOT_READY;
goto reply;
}
/* Configured answer. */
if ((flags = is_local_answer(now, first, daemon->namebuff)))
@@ -340,7 +343,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
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)
PUTSHORT(SAFE_PKTSZ, pheader);
@@ -520,15 +525,33 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
/* could not send on, prepare to return */
header->id = htons(forward->frec_src.orig_id);
free_frec(forward); /* cancel */
ede = EDE_NETERR;
reply:
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, ede)))
return 0;
if (oph)
plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
{
u16 swap = htons((u16)ede);
if (ede != -1)
plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
else
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);
}
@@ -538,14 +561,14 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind,
int no_cache, int cache_secure, int bogusanswer, int ad_reqd, int do_bit, int added_pheader,
int check_subnet, union mysockaddr *query_source)
int check_subnet, union mysockaddr *query_source, unsigned char *limit, int ede)
{
unsigned char *pheader, *sizep;
char **sets = 0;
int munged = 0, is_sign;
unsigned int rcode = RCODE(header);
size_t plen;
(void)ad_reqd;
(void)do_bit;
(void)bogusanswer;
@@ -593,11 +616,10 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
}
else
{
unsigned short udpsz;
/* If upstream is advertising a larger UDP packet size
than we allow, trim it so that we don't get overlarge
requests for the client. We can't do this for signed packets. */
unsigned short udpsz;
GETSHORT(udpsz, sizep);
if (udpsz > daemon->edns_pktsz)
{
@@ -634,6 +656,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
{
union all_addr a;
a.log.rcode = rcode;
a.log.ede = ede;
log_query(F_UPSTREAM | F_RCODE, "error", &a, NULL);
return resize_packet(header, n, pheader, plen);
@@ -656,22 +679,26 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
SET_RCODE(header, NXDOMAIN);
header->hb3 &= ~HB3_AA;
cache_secure = 0;
ede = EDE_BLOCKED;
}
else
{
int doctored = 0;
if (rcode == NXDOMAIN &&
extract_request(header, n, daemon->namebuff, NULL) &&
check_for_local_domain(daemon->namebuff, now))
extract_request(header, n, daemon->namebuff, NULL))
{
/* if we forwarded a query for a locally known name (because it was for
an unknown type) and the answer is NXDOMAIN, convert that to NODATA,
since we know that the domain exists, even if upstream doesn't */
munged = 1;
header->hb3 |= HB3_AA;
SET_RCODE(header, NOERROR);
cache_secure = 0;
if (check_for_local_domain(daemon->namebuff, now) ||
lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL))
{
/* if we forwarded a query for a locally known name (because it was for
an unknown type) and the answer is NXDOMAIN, convert that to NODATA,
since we know that the domain exists, even if upstream doesn't */
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))
@@ -679,6 +706,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
munged = 1;
cache_secure = 0;
ede = EDE_BLOCKED;
}
if (doctored)
@@ -706,9 +734,14 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
}
#endif
if (pheader && ede != -1)
{
u16 swap = htons((u16)ede);
n = add_pseudoheader(header, n, limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 1);
}
/* do this after extract_addresses. Ensure NODATA reply and remove
nameserver info. */
if (munged)
{
header->ancount = htons(0);
@@ -748,7 +781,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
/* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
would invite infinite loops, since the answers to DNSKEY and DS queries
will not be cached, so they'll be repeated. */
if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED)
if (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(status, STAT_ABANDONED))
{
if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class);
@@ -759,7 +792,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
!option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC),
NULL, NULL, NULL);
#ifdef HAVE_DUMPFILE
if (status == STAT_BOGUS)
if (STAT_ISEQUAL(status, STAT_BOGUS))
dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS,
header, (size_t)plen, &forward->sentto->addr, NULL);
#endif
@@ -767,7 +800,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
/* Can't validate, as we're missing key data. Put this
answer aside, whilst we get that. */
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
if (STAT_ISEQUAL(status, STAT_NEED_DS) || STAT_ISEQUAL(status, STAT_NEED_KEY))
{
struct frec *new = NULL;
int serverind;
@@ -786,9 +819,9 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
/* validate routines leave name of required record in daemon->keyname */
nn = dnssec_generate_query(header, ((unsigned char *) header) + server->edns_pktsz,
daemon->keyname, forward->class,
status == STAT_NEED_KEY ? T_DNSKEY : T_DS, server->edns_pktsz);
STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz);
flags = (status == STAT_NEED_KEY) ? FREC_DNSKEY_QUERY : FREC_DS_QUERY;
flags = STAT_ISEQUAL(status, STAT_NEED_KEY) ? FREC_DNSKEY_QUERY : FREC_DS_QUERY;
hash = hash_questions(header, nn, daemon->namebuff);
if ((new = lookup_frec_by_query(hash, flags, FREC_DNSKEY_QUERY | FREC_DS_QUERY)))
@@ -858,7 +891,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
#endif
server_send_log(server, fd, header, nn, DUMP_SEC_QUERY,
F_NOEXTRA | F_DNSSEC, daemon->keyname,
querystr("dnssec-query", status == STAT_NEED_KEY ? T_DNSKEY : T_DS));
querystr("dnssec-query", STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS));
server->queries++;
}
@@ -1059,6 +1092,7 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
{
int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
size_t nn;
int ede = -1;
(void)status;
@@ -1071,36 +1105,40 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
no_cache_dnssec = 1;
#ifdef HAVE_DNSSEC
if (status != STAT_OK)
if (!STAT_ISEQUAL(status, STAT_OK))
{
/* status is STAT_OK when validation not turned on. */
no_cache_dnssec = 0;
if (status == STAT_TRUNCATED)
if (STAT_ISEQUAL(status, STAT_TRUNCATED))
header->hb3 |= HB3_TC;
else
{
char *result, *domain = "result";
if (status == STAT_ABANDONED)
union all_addr a;
a.log.ede = ede = errflags_to_ede(status);
if (STAT_ISEQUAL(status, STAT_ABANDONED))
{
result = "ABANDONED";
status = STAT_BOGUS;
}
else
result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS"));
if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL))
domain = daemon->namebuff;
if (STAT_ISEQUAL(status, STAT_SECURE))
cache_secure = 1;
else if (STAT_ISEQUAL(status, STAT_BOGUS))
{
no_cache_dnssec = 1;
bogusanswer = 1;
if (extract_request(header, n, daemon->namebuff, NULL))
domain = daemon->namebuff;
}
log_query(F_SECSTAT, domain, NULL, result);
}
if (status == STAT_SECURE)
cache_secure = 1;
else if (status == STAT_BOGUS)
{
no_cache_dnssec = 1;
bogusanswer = 1;
log_query(F_SECSTAT, domain, &a, result);
}
}
#endif
@@ -1121,7 +1159,8 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer,
forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION,
forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source)))
forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source,
((unsigned char *)header) + daemon->edns_pktsz, ede)))
{
struct frec_src *src;
@@ -1148,6 +1187,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);
@@ -1164,6 +1213,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, EDE_BLOCKED);
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;
@@ -1175,6 +1265,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
@@ -1403,6 +1498,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))
{
@@ -1413,6 +1513,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 */
@@ -1454,20 +1558,53 @@ 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)
{
u16 swap = htons(EDE_BLOCKED);
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,
EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, 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,
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 */
@@ -1479,6 +1616,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]++;
@@ -1618,16 +1759,16 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
/* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
if (--(*keycount) == 0)
new_status = STAT_ABANDONED;
else if (status == STAT_NEED_KEY)
else if (STAT_ISEQUAL(status, STAT_NEED_KEY))
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
else if (status == STAT_NEED_DS)
else if (STAT_ISEQUAL(status, STAT_NEED_DS))
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
else
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
!option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC),
NULL, NULL, NULL);
if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY)
if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY))
break;
/* Can't validate because we need a key/DS whose name now in keyname.
@@ -1645,7 +1786,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
}
m = dnssec_generate_query(new_header, ((unsigned char *) new_header) + 65536, keyname, class,
new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, server->edns_pktsz);
STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz);
if ((start = dnssec_server(server, daemon->keyname, &first, &last)) == -1 ||
(m = tcp_talk(first, last, start, packet, m, have_mark, mark, &server)) == 0)
@@ -1658,13 +1799,13 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
daemon->log_display_id = ++daemon->log_id;
log_query_mysockaddr(F_NOEXTRA | F_DNSSEC, keyname, &server->addr,
querystr("dnssec-query", new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS));
querystr("dnssec-query", STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS));
new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount);
daemon->log_display_id = log_save;
if (new_status != STAT_OK)
if (!STAT_ISEQUAL(new_status, STAT_OK))
break;
}
@@ -1685,6 +1826,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
@@ -1710,13 +1854,13 @@ unsigned char *tcp_request(int confd, time_t now,
int have_mark = 0;
int first, last;
unsigned int flags = 0;
if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
return packet;
#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;
@@ -1761,6 +1905,8 @@ unsigned char *tcp_request(int confd, time_t now,
while (1)
{
int ede = -1;
if (query_count == TCP_MAX_QUERIES ||
!packet ||
!read_write(confd, &c1, 1, 1) || !read_write(confd, &c2, 1, 1) ||
@@ -1796,6 +1942,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))
@@ -1829,13 +1979,34 @@ 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)
{
u16 swap = htons(EDE_BLOCKED);
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,
EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
}
#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 */
@@ -1860,7 +2031,9 @@ unsigned char *tcp_request(int confd, time_t now,
!strchr(daemon->namebuff, '.') &&
strlen(daemon->namebuff) != 0)
flags = F_NOERR;
else if (lookup_domain(daemon->namebuff, gotname, &first, &last) && !(flags = is_local_answer(now, first, daemon->namebuff)))
else if (!lookup_domain(daemon->namebuff, gotname, &first, &last))
ede = EDE_NOT_READY; /* No configured servers */
else if (!(flags = is_local_answer(now, first, daemon->namebuff)))
{
master = daemon->serverarray[first];
@@ -1890,7 +2063,10 @@ unsigned char *tcp_request(int confd, time_t now,
/* Loop round available servers until we succeed in connecting to one. */
if ((m = tcp_talk(first, last, start, packet, size, have_mark, mark, &serv)) == 0)
break;
{
ede = EDE_NETERR;
break;
}
/* get query name again for logging - may have been overwritten */
if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype)))
@@ -1904,28 +2080,30 @@ unsigned char *tcp_request(int confd, time_t now,
int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname,
serv, have_mark, mark, &keycount);
char *result, *domain = "result";
if (status == STAT_ABANDONED)
union all_addr a;
a.log.ede = ede = errflags_to_ede(status);
if (STAT_ISEQUAL(status, STAT_ABANDONED))
{
result = "ABANDONED";
status = STAT_BOGUS;
}
else
result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS"));
if (status == STAT_BOGUS && extract_request(header, m, daemon->namebuff, NULL))
domain = daemon->namebuff;
log_query(F_SECSTAT, domain, NULL, result);
if (status == STAT_BOGUS)
if (STAT_ISEQUAL(status, STAT_SECURE))
cache_secure = 1;
else if (STAT_ISEQUAL(status, STAT_BOGUS))
{
no_cache_dnssec = 1;
bogusanswer = 1;
if (extract_request(header, m, daemon->namebuff, NULL))
domain = daemon->namebuff;
}
if (status == STAT_SECURE)
cache_secure = 1;
log_query(F_SECSTAT, domain, &a, result);
}
#endif
@@ -1942,7 +2120,7 @@ unsigned char *tcp_request(int confd, time_t now,
m = process_reply(header, now, serv, (unsigned int)m,
option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer,
ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr);
ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr, ((unsigned char *)header) + 65536, ede);
}
}
}
@@ -1950,17 +2128,32 @@ unsigned char *tcp_request(int confd, time_t now,
/* In case of local answer or no connections made. */
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, ede)))
break;
if (have_pseudoheader)
m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
{
u16 swap = htons((u16)ede);
if (ede != -1)
m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
else
m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
}
}
check_log_writer(1);
*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;
}

View File

@@ -31,11 +31,13 @@ void loop_send_probes()
identifiable, via the uid. If we see that query back again, then the server is looping, and we should not use it. */
for (serv = daemon->servers; serv; serv = serv->next)
if (strlen(serv->domain) == 0 &&
!(serv->flags & (SERV_FOR_NODOTS | SERV_LOOP)))
!(serv->flags & (SERV_FOR_NODOTS)))
{
ssize_t len = loop_make_probe(serv->uid);
int fd;
serv->flags &= ~SERV_LOOP;
if ((fd = allocate_rfd(&rfds, serv)) == -1)
continue;
@@ -101,7 +103,7 @@ int detect_loop(char *query, int type)
uid == serv->uid)
{
serv->flags |= SERV_LOOP;
check_servers(); /* log new state */
check_servers(1); /* log new state - don't send more probes. */
return 1;
}

View File

@@ -1494,149 +1494,7 @@ void pre_allocate_sfds(void)
}
}
void mark_servers(int flag)
{
struct server *serv;
/* mark everything with argument flag */
for (serv = daemon->servers; serv; serv = serv->next)
{
if (serv->flags & flag)
serv->flags |= SERV_MARK;
#ifdef HAVE_LOOP
/* Give looped servers another chance */
serv->flags &= ~SERV_LOOP;
#endif
}
for (serv = daemon->local_domains; serv; serv = serv->next)
if (serv->flags & flag)
serv->flags |= SERV_MARK;
}
void cleanup_servers(void)
{
struct server *serv, *tmp, **up;
/* unlink and free anything still marked. */
for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp)
{
tmp = serv->next;
if (serv->flags & SERV_MARK)
{
server_gone(serv);
*up = serv->next;
free(serv->domain);
free(serv);
}
else
up = &serv->next;
}
for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = tmp)
{
tmp = serv->next;
if (serv->flags & SERV_MARK)
{
*up = serv->next;
free(serv->domain);
free(serv);
}
else
up = &serv->next;
}
#ifdef HAVE_LOOP
/* Now we have a new set of servers, test for loops. */
loop_send_probes();
#endif
}
void add_update_server(int flags,
union mysockaddr *addr,
union mysockaddr *source_addr,
const char *interface,
const char *domain)
{
struct server *serv;
char *domain_str;
if (!domain)
domain = "";
/* If the server is USE_RESOLV or LITERAL_ADDRES, it lives on the local_domains chain.
NOTE that we can get local=/domain/ here, but NOT address=/domain/1.2.3.4 */
#define SERV_IS_LOCAL (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS)
/* See if there is a suitable candidate, and unmark */
for (serv = (flags & SERV_IS_LOCAL) ? daemon->local_domains : daemon->servers; serv; serv = serv->next)
if ((serv->flags & SERV_MARK) && hostname_isequal(domain, serv->domain))
break;
if (serv)
domain_str = serv->domain;
else if ((serv = whine_malloc(sizeof (struct server))))
{
/* Not found, create a new one. */
if (!(domain_str = whine_malloc(strlen(domain)+1)))
{
free(serv);
serv = NULL;
}
else
{
strcpy(domain_str, domain);
if (flags & SERV_IS_LOCAL)
{
serv->next = daemon->local_domains;
daemon->local_domains = serv;
}
else
{
struct server *s;
/* Add to the end of the chain, for order */
if (!daemon->servers)
daemon->servers = serv;
else
{
for (s = daemon->servers; s->next; s = s->next);
s->next = serv;
}
serv->next = NULL;
}
}
}
if (serv)
{
if (!(flags & SERV_IS_LOCAL))
memset(serv, 0, sizeof(struct server));
serv->flags = flags;
serv->domain = domain_str;
if (!(flags & SERV_IS_LOCAL))
{
serv->queries = serv->failed_queries = 0;
#ifdef HAVE_LOOP
serv->uid = rand32();
#endif
if (interface)
safe_strncpy(serv->interface, interface, sizeof(serv->interface));
if (addr)
serv->addr = *addr;
if (source_addr)
serv->source_addr = *source_addr;
}
}
}
void check_servers(void)
void check_servers(int no_loop_check)
{
struct irec *iface;
struct server *serv;
@@ -1644,7 +1502,12 @@ void check_servers(void)
int port = 0, count;
int locals = 0;
/* interface may be new since startup */
#ifdef HAVE_LOOP
if (!no_loop_check)
loop_send_probes();
#endif
/* interface may be new since startup */
if (!option_bool(OPT_NOWILD))
enumerate_interfaces(0);
@@ -1786,8 +1649,8 @@ void check_servers(void)
up = &sfd->next;
}
cleanup_servers();
build_server_array();
cleanup_servers(); /* remove servers we just deleted. */
build_server_array();
}
/* Return zero if no servers found, in that case we keep polling.
@@ -1860,7 +1723,7 @@ int reload_servers(char *fname)
continue;
}
add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, NULL, NULL);
add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, NULL, NULL, NULL);
gotone = 1;
}

View File

@@ -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;
}
@@ -812,7 +821,7 @@ static char *parse_mysockaddr(char *arg, union mysockaddr *addr)
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;
char *portno, *source;
@@ -820,6 +829,8 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
int scope_index = 0;
char *scope_id;
*interface = 0;
if (strcmp(arg, "#") == 0)
{
if (flags)
@@ -921,58 +932,52 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
return NULL;
}
static struct server *add_rev4(struct in_addr addr, int msize)
static int domain_rev4(char *domain, struct in_addr addr, int msize)
{
struct server *serv = opt_malloc(sizeof(struct server));
in_addr_t a = ntohl(addr.s_addr);
char *p;
memset(serv, 0, sizeof(struct server));
p = serv->domain = opt_malloc(29); /* strlen("xxx.yyy.zzz.ttt.in-addr.arpa")+1 */
*domain = 0;
switch (msize)
{
case 32:
p += sprintf(p, "%u.", a & 0xff);
domain += sprintf(domain, "%u.", a & 0xff);
/* fall through */
case 24:
p += sprintf(p, "%d.", (a >> 8) & 0xff);
domain += sprintf(domain, "%d.", (a >> 8) & 0xff);
/* fall through */
case 16:
p += sprintf(p, "%d.", (a >> 16) & 0xff);
domain += sprintf(domain, "%d.", (a >> 16) & 0xff);
/* fall through */
case 8:
p += sprintf(p, "%d.", (a >> 24) & 0xff);
domain += sprintf(domain, "%d.", (a >> 24) & 0xff);
break;
default:
free(serv->domain);
free(serv);
return NULL;
return 0;
}
p += sprintf(p, "in-addr.arpa");
return serv;
domain += sprintf(domain, "in-addr.arpa");
return 1;
}
static struct server *add_rev6(struct in6_addr *addr, int msize)
static int domain_rev6(char *domain, struct in6_addr *addr, int msize)
{
struct server *serv = opt_malloc(sizeof(struct server));
char *p;
int i;
memset(serv, 0, sizeof(struct server));
p = serv->domain = opt_malloc(73); /* strlen("32*<n.>ip6.arpa")+1 */
if (msize%4)
return 0;
*domain = 0;
for (i = msize-1; i >= 0; i -= 4)
{
int dig = ((unsigned char *)addr)[i>>3];
p += sprintf(p, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4);
domain += sprintf(domain, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4);
}
p += sprintf(p, "ip6.arpa");
domain += sprintf(domain, "ip6.arpa");
return serv;
return 1;
}
#ifdef HAVE_DHCP
@@ -2247,7 +2252,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
set_option_bool(OPT_RESOLV_DOMAIN);
else
{
char *d;
char *d, *d_raw = arg;
comma = split(arg);
if (!(d = canonicalise_opt(arg)))
ret_err(gen_err);
@@ -2288,23 +2293,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
ret_err_free(gen_err, new);
else
{
/* generate the equivalent of
local=/xxx.yyy.zzz.in-addr.arpa/ */
struct server *serv = add_rev4(new->start, msize);
if (!serv)
ret_err_free(_("bad prefix"), new);
serv->flags |= SERV_LITERAL_ADDRESS;
serv->next = daemon->local_domains;
daemon->local_domains = serv;
char domain[29]; /* strlen("xxx.yyy.zzz.ttt.in-addr.arpa")+1 */
/* local=/xxx.yyy.zzz.in-addr.arpa/ */
/* domain_rev4 can't fail here, msize checked above. */
domain_rev4(domain, new->start, msize);
add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, domain, NULL);
/* local=/<domain>/ */
serv = opt_malloc(sizeof(struct server));
memset(serv, 0, sizeof(struct server));
serv->domain = d;
serv->flags = SERV_LITERAL_ADDRESS;
serv->next = daemon->local_domains;
daemon->local_domains = serv;
/* d_raw can't failed to canonicalise here, checked above. */
add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL);
}
}
}
@@ -2336,20 +2333,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
ret_err_free(gen_err, new);
else
{
char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */
/* generate the equivalent of
local=/xxx.yyy.zzz.ip6.arpa/ */
struct server *serv = add_rev6(&new->start6, msize);
serv->flags |= SERV_LITERAL_ADDRESS;
serv->next = daemon->local_domains;
daemon->local_domains = serv;
domain_rev6(domain, &new->start6, msize);
add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, domain, NULL);
/* local=/<domain>/ */
serv = opt_malloc(sizeof(struct server));
memset(serv, 0, sizeof(struct server));
serv->domain = d;
serv->flags = SERV_LITERAL_ADDRESS;
serv->next = daemon->local_domains;
daemon->local_domains = serv;
/* d_raw can't failed to canonicalise here, checked above. */
add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL);
}
}
}
@@ -2617,7 +2609,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
comma = split_chr(arg, '/');
new = opt_malloc(sizeof(struct serv_local));
new->domain = opt_string_alloc(arg);
new->flags = strlen(arg);
new->domain_len = strlen(arg);
new->next = daemon->no_rebind;
daemon->no_rebind = new;
arg = comma;
@@ -2630,14 +2622,12 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
case LOPT_LOCAL: /* --local */
case 'A': /* --address */
{
struct server *new;
size_t size;
char *lastdomain = NULL, *domain = "";
char *alloc_domain;
int flags = 0;
u16 flags = 0;
char *err;
struct in_addr addr4;
struct in6_addr addr6;
union all_addr addr;
union mysockaddr serv_addr, source_addr;
char interface[IF_NAMESIZE+1];
unhide_metas(arg);
@@ -2647,8 +2637,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
char *last;
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, '/')))
{
lastdomain = arg;
@@ -2656,113 +2649,45 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
}
if (servers_only && option == 'S')
flags |= SERV_FROM_FILE;
if (!arg || !*arg)
flags = SERV_LITERAL_ADDRESS;
else if (option == 'A')
{
/* # as literal address means return zero address for 4 and 6 */
if (strcmp(arg, "#") == 0)
flags |= SERV_ALL_ZEROS | SERV_LITERAL_ADDRESS;
else if (inet_pton(AF_INET, arg, &addr4) > 0)
flags |= SERV_4ADDR | SERV_LITERAL_ADDRESS;
else if (inet_pton(AF_INET6, arg, &addr6) > 0)
flags |= SERV_6ADDR | SERV_LITERAL_ADDRESS;
flags = SERV_ALL_ZEROS | SERV_LITERAL_ADDRESS;
else if (inet_pton(AF_INET, arg, &addr.addr4) > 0)
flags = SERV_4ADDR | SERV_LITERAL_ADDRESS;
else if (inet_pton(AF_INET6, arg, &addr.addr6) > 0)
flags = SERV_6ADDR | SERV_LITERAL_ADDRESS;
else
ret_err(_("Bad address in --address"));
}
if (!(alloc_domain = canonicalise_opt(domain)))
ret_err(gen_err);
if (flags & SERV_LITERAL_ADDRESS)
{
if (flags & SERV_6ADDR)
{
size = sizeof(struct serv_addr6);
new = opt_malloc(sizeof(struct serv_addr6));
((struct serv_addr6*)new)->addr = addr6;
}
else if (flags & SERV_4ADDR)
{
size = sizeof(struct serv_addr4);
new = opt_malloc(sizeof(struct serv_addr4));
((struct serv_addr4*)new)->addr = addr4;
}
else
{
size = sizeof(struct serv_local);
new = opt_malloc(sizeof(struct serv_local));
}
new->next = daemon->local_domains;
daemon->local_domains = new;
}
else
{
size = sizeof(struct server);
new = opt_malloc(sizeof(struct server));
if ((err = parse_server(arg, &serv_addr, &source_addr, interface, &flags)))
ret_err(err);
#ifdef HAVE_LOOP
new->uid = rand32();
#endif
if ((err = parse_server(arg, &new->addr, &new->source_addr, new->interface, &flags)))
{
free(new);
ret_err(err);
}
/* server=//1.2.3.4 is special. */
if (strlen(domain) == 0 && lastdomain)
flags |= SERV_FOR_NODOTS;
}
if (servers_only && option == 'S')
flags |= SERV_FROM_FILE;
while (1)
{
if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, &addr))
ret_err(gen_err);
/* Since domains that use standard servers don't have the
network stuff, it's easier to treat them as local. */
if (flags & SERV_USE_RESOLV)
{
new->next = daemon->local_domains;
daemon->local_domains = new;
}
else
{
new->next = daemon->servers;
daemon->servers = new;
}
if (!lastdomain || domain == lastdomain)
break;
domain += strlen(domain) + 1;
}
new->domain = alloc_domain;
/* server=//1.2.3.4 is special. */
if (strlen(domain) == 0 && lastdomain)
flags |= SERV_FOR_NODOTS;
new->flags = flags;
/* If we have more than one domain, copy and iterate */
if (lastdomain)
while (domain != lastdomain)
{
struct server *last = new;
domain += strlen(domain) + 1;
if (!(alloc_domain = canonicalise_opt(domain)))
ret_err(gen_err);
new = opt_malloc(size);
memcpy(new, last, size);
new->domain = alloc_domain;
if (flags & (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS))
{
new->next = daemon->local_domains;
daemon->local_domains = new;
}
else
{
new->next = daemon->servers;
daemon->servers = new;
}
}
break;
}
@@ -2770,10 +2695,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
{
char *string;
int size;
struct server *serv;
u16 flags = 0;
char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */
struct in_addr addr4;
struct in6_addr addr6;
union mysockaddr serv_addr, source_addr;
char interface[IF_NAMESIZE+1];
unhide_metas(arg);
if (!arg)
ret_err(gen_err);
@@ -2785,28 +2713,27 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
if (inet_pton(AF_INET, arg, &addr4))
{
serv = add_rev4(addr4, size);
if (!serv)
ret_err(_("bad prefix"));
serv->next = daemon->servers;
daemon->servers = serv;
if (!domain_rev4(domain, addr4, size))
ret_err(_("bad IPv4 prefix"));
}
else if (inet_pton(AF_INET6, arg, &addr6))
{
serv = add_rev6(&addr6, size);
serv->next = daemon->servers;
daemon->servers = serv;
if (!domain_rev6(domain, &addr6, size))
ret_err(_("bad IPv6 prefix"));
}
else
ret_err(gen_err);
string = parse_server(comma, &serv->addr, &serv->source_addr, serv->interface, &serv->flags);
if (string)
if (!comma)
flags |= SERV_LITERAL_ADDRESS;
else if ((string = parse_server(comma, &serv_addr, &source_addr, interface, &flags)))
ret_err(string);
if (servers_only)
serv->flags |= SERV_FROM_FILE;
flags |= SERV_FROM_FILE;
if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL))
ret_err(gen_err);
break;
}
@@ -2877,6 +2804,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;
@@ -4595,8 +4651,7 @@ err:
}
else
{
int nomem;
char *canon = canonicalise(arg, &nomem);
char *canon = canonicalise_opt(arg);
struct name_list *nl;
if (!canon)
{
@@ -5049,9 +5104,9 @@ void read_servers_file(void)
}
mark_servers(SERV_FROM_FILE);
cleanup_servers();
read_file(daemon->servers_file, f, LOPT_REV_SERV);
cleanup_servers();
check_servers(0);
}

386
src/pattern.c Normal file
View 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

View File

@@ -884,6 +884,92 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
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
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 +982,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))
@@ -918,17 +1005,19 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
return F_IPV4 | F_IPV6;
}
#ifdef HAVE_DNSSEC
/* F_DNSSECOK as agument to search_servers() inhibits forwarding
to servers for domains without a trust anchor. This make the
behaviour for DS and DNSKEY queries we forward the same
as for DS and DNSKEY queries we originate. */
if (qtype == T_DS || qtype == T_DNSKEY)
if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DS || qtype == T_DNSKEY))
return F_DNSSECOK;
#endif
return F_QUERY;
}
void setup_reply(struct dns_header *header, unsigned int flags)
void setup_reply(struct dns_header *header, unsigned int flags, int ede)
{
/* clear authoritative and truncated flags, set QR flag */
header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC )) | HB3_QR;
@@ -951,6 +1040,7 @@ void setup_reply(struct dns_header *header, unsigned int flags)
{
union all_addr a;
a.log.rcode = REFUSED;
a.log.ede = ede;
log_query(F_CONFIG | F_RCODE, "error", &a, NULL);
SET_RCODE(header, REFUSED);
}

View File

@@ -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 =
@@ -89,7 +117,7 @@ char *ubus_init()
if (ret)
{
ubus_destroy(ubus);
return ubus_strerror(ret);
return (char *)ubus_strerror(ret);
}
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);
}
#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 */