Compare commits

...
22 Commits
Author SHA1 Message Date
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 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
Simon Kelley 1c9f136b57 Man page update, lease times can be given in days or weeks. 2021-06-15 22:07:59 +01:00
Simon Kelley 5ab7e4a475 Improve efficiency of DNSSEC.
The sharing point for DNSSEC RR data used to be when it entered the
cache, having been validated. After that queries requiring the KEY or
DS records would share the cached values. There is a common case in
dual-stack hosts that queries for A and AAAA records for the same
domain are made simultaneously.  If required keys were not in the
cache, this would result in two requests being sent upstream for the
same key data (and all the subsequent chain-of-trust queries.) Now we
combine these requests and elide the duplicates, resulting in fewer
queries upstream and better performance. To keep a better handle on
what's going on, the "extra" logging mode has been modified to
associate queries and answers for DNSSEC queries in the same way as
ordinary queries. The requesting address and port have been removed
from DNSSEC logging lines, since this is no longer strictly defined.
2021-06-15 15:27:29 +01:00
Simon Kelley 3236f358f8 Revise resource handling for number of concurrent DNS queries.
This used to have a global limit, but that has a problem when using
different servers for different upstream domains. Queries which are
routed by domain to an upstream server which is not responding will
build up and trigger the limit, which breaks DNS service for all other
domains which could be handled by other servers. The change is to make
the limit per server-group, where a server group is the set of servers
configured for a particular domain. In the common case, where only
default servers are declared, there is no effective change.
2021-06-13 21:29:22 +01:00
Simon Kelley 4a6550d69a Move make_local_answer() to domain-match.c 2021-06-10 21:40:52 +01:00
Simon Kelley ff523d0c67 Fix TCP replies with --domain-needed. 2021-06-10 21:31:38 +01:00
Simon Kelley 3c93e8eb41 Re-order UBus initialisation to avoid logging before logs set up. 2021-06-08 23:13:48 +01:00
15 changed files with 1532 additions and 459 deletions

No files matched your search

+38
View File
@@ -38,7 +38,45 @@ version 2.86
Finally, some of the oldest and gnarliest code in dnsmasq has had
a significant clean-up. It's far from perfect, but it _is_ better.
Revise resource handling for number of concurrent DNS queries. This
used to have a global limit, but that has a problem when using
different servers for different upstream domains. Queries which are
routed by domain to an upstream server which is not responding will
build up and trigger the limit, which breaks DNS service for
all other domains which could be handled by other servers. The
change is to make the limit per server-group, where a server group
is the set of servers configured for a particular domain. In the
common case, where only default servers are declared, there is
no effective change.
Improve efficiency of DNSSEC. The sharing point for DNSSEC RR data
used to be when it entered the cache, having been validated. After
that queries requiring the KEY or DS records would share the cached
values. There is a common case in dual-stack hosts that queries for
A and AAAA records for the same domain are made simultaneously.
If required keys were not in the cache, this would result in two
requests being sent upstream for the same key data (and all the
subsequent chain-of-trust queries.) Now we combine these requests
and elide the duplicates, resulting in fewer queries upstream
and better performance. To keep a better handle on what's
going on, the "extra" logging mode has been modified to associate
queries and answers for DNSSEC queries in the same way as ordinary
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.
The new logic in 2.83/2.84 which merges distinct requests
+1 -1
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
+1 -1
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
+33 -3
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
@@ -731,7 +760,8 @@ identical queries without forwarding them again.
Set the maximum number of concurrent DNS queries. The default value is
150, which should be fine for most setups. The only known situation
where this needs to be increased is when using web-server log file
resolvers, which can generate large numbers of concurrent queries.
resolvers, which can generate large numbers of concurrent queries. This
parameter actually controls the number of concurrent queries per server group, where a server group is the set of server(s) associated with a single domain. So if a domain has it's own server via --server=/example.com/1.2.3.4 and 1.2.3.4 is not responding, but queries for *.example.com cannot go elsewhere, then other queries will not be affected. On configurations with many such server groups and tight resources, this value may need to be reduced.
.TP
.B --dnssec
Validate DNS replies and cache DNSSEC data. When forwarding DNS queries, dnsmasq requests the
@@ -875,7 +905,7 @@ in
.B --dhcp-host
options. If the lease time is given, then leases
will be given for that length of time. The lease time is in seconds,
or minutes (eg 45m) or hours (eg 1h) or "infinite". If not given,
or minutes (eg 45m) or hours (eg 1h) or days (2d) or weeks (1w) or "infinite". If not given,
the default lease time is one hour for IPv4 and one day for IPv6. The
minimum lease time is two minutes. For IPv6 ranges, the lease time
maybe "deprecated"; this sets the preferred lifetime sent in a DHCP
+5 -3
View File
@@ -1965,11 +1965,13 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg)
if (option_bool(OPT_EXTRALOG))
{
int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2);
if (flags & F_NOEXTRA)
my_syslog(LOG_INFO, "* %s/%u %s %s %s %s", daemon->addrbuff2, port, source, name, verb, dest);
my_syslog(LOG_INFO, "%u %s %s %s %s", daemon->log_display_id, source, name, verb, dest);
else
my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest);
{
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);
}
}
else
my_syslog(LOG_INFO, "%s %s %s %s", source, name, verb, dest);
+1 -1
View File
@@ -23,7 +23,7 @@
#define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */
#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
#define DNSSEC_WORK 50 /* Max number of queries to validate one question */
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
#define FORWARD_TEST 50 /* try all servers every 50 queries */
#define FORWARD_TIME 20 /* or 20 seconds */
#define UDP_TEST_TIME 60 /* How often to reset our idea of max packet size. */
+29 -32
View File
@@ -24,7 +24,7 @@ struct daemon *daemon;
static volatile pid_t pid = 0;
static volatile int pipewrite;
static int set_dns_listeners(time_t now);
static void set_dns_listeners(void);
static void check_dns_listeners(time_t now);
static void sig_handler(int sig);
static void async_event(int pipe, time_t now);
@@ -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)
{
@@ -442,8 +449,10 @@ int main (int argc, char **argv)
if (option_bool(OPT_UBUS))
#ifdef HAVE_UBUS
{
char *err;
daemon->ubus = NULL;
ubus_init();
if ((err = ubus_init()))
die(_("UBus error: %s"), err, EC_MISC);
}
#else
die(_("UBus not available: set HAVE_UBUS in src/config.h"), NULL, EC_BADCONF);
@@ -1040,16 +1049,10 @@ int main (int argc, char **argv)
while (1)
{
int t, timeout = -1;
int timeout = -1;
poll_reset();
/* if we are out of resources, find how long we have to wait
for some to come free, we'll loop around then and restart
listening for queries */
if ((t = set_dns_listeners(now)) != 0)
timeout = t * 1000;
/* Whilst polling for the dbus, or doing a tftp transfer, wake every quarter second */
if (daemon->tftp_trans ||
(option_bool(OPT_DBUS) && !daemon->dbus))
@@ -1059,6 +1062,8 @@ int main (int argc, char **argv)
else if (is_dad_listeners())
timeout = 1000;
set_dns_listeners();
#ifdef HAVE_DBUS
set_dbus_listeners();
#endif
@@ -1195,20 +1200,20 @@ int main (int argc, char **argv)
if (daemon->dbus)
my_syslog(LOG_INFO, _("connected to system DBus"));
}
check_dbus_listeners();
check_dbus_listeners();
#endif
#ifdef HAVE_UBUS
if (option_bool(OPT_UBUS))
/* if we didn't create a UBus connection, retry now. */
if (option_bool(OPT_UBUS) && !daemon->ubus)
{
/* if we didn't create a UBus connection, retry now. */
if (!daemon->ubus)
{
ubus_init();
}
check_ubus_listeners();
}
char *err;
if ((err = ubus_init()))
my_syslog(LOG_WARNING, _("UBus error: %s"), err);
if (daemon->ubus)
my_syslog(LOG_INFO, _("connected to system UBus"));
}
check_ubus_listeners();
#endif
check_dns_listeners(now);
@@ -1683,12 +1688,12 @@ void clear_cache_and_reload(time_t now)
#endif
}
static int set_dns_listeners(time_t now)
static void set_dns_listeners(void)
{
struct serverfd *serverfdp;
struct listener *listener;
struct randfd_list *rfl;
int wait = 0, i;
int i;
#ifdef HAVE_TFTP
int tftp = 0;
@@ -1701,10 +1706,6 @@ static int set_dns_listeners(time_t now)
}
#endif
/* will we be able to get memory? */
if (daemon->port != 0)
get_new_frec(now, &wait, NULL);
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
poll_listen(serverfdp->fd, POLLIN);
@@ -1723,10 +1724,9 @@ static int set_dns_listeners(time_t now)
for (listener = daemon->listeners; listener; listener = listener->next)
{
/* only listen for queries if we have resources */
if (listener->fd != -1 && wait == 0)
if (listener->fd != -1)
poll_listen(listener->fd, POLLIN);
/* Only listen for TCP connections when a process slot
is available. Death of a child goes through the select loop, so
we don't need to explicitly arrange to wake up here,
@@ -1739,15 +1739,12 @@ static int set_dns_listeners(time_t now)
if (tftp <= daemon->tftp_max && listener->tftpfd != -1)
poll_listen(listener->tftpfd, POLLIN);
#endif
}
if (!option_bool(OPT_DEBUG))
for (i = 0; i < MAX_PROCS; i++)
if (daemon->tcp_pipes[i] != -1)
poll_listen(daemon->tcp_pipes[i], POLLIN);
return wait;
}
static void check_dns_listeners(time_t now)
@@ -2098,7 +2095,7 @@ int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id)
poll_reset();
if (fd != -1)
poll_listen(fd, POLLIN);
set_dns_listeners(now);
set_dns_listeners();
set_log_writer();
#ifdef HAVE_DHCP6
+35 -10
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) )
@@ -564,7 +565,7 @@ struct randfd_list {
struct server {
int flags;
u16 flags, domain_len;
char *domain;
struct server *next;
int serial, arrayposn;
@@ -583,23 +584,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 +611,12 @@ struct ipsets {
struct ipsets *next;
};
struct allowlist {
u32 mark, mask;
char **patterns;
struct allowlist *next;
};
struct irec {
union mysockaddr addr;
struct in_addr netmask; /* only valid for IPv4 */
@@ -690,7 +697,6 @@ struct hostsfile {
#define STAT_SECURE_WILDCARD 7
#define STAT_OK 8
#define STAT_ABANDONED 9
#define STAT_INPROGRESS 10
#define FREC_NOREBIND 1
#define FREC_CHECKING_DISABLED 2
@@ -727,6 +733,7 @@ struct frec {
struct blockdata *stash; /* Saved reply, whilst we validate */
size_t stash_len;
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
struct frec *next_dependent; /* list of above. */
struct frec *blocking_query; /* Query which is blocking us. */
#endif
struct frec *next;
@@ -1086,6 +1093,8 @@ extern struct daemon {
struct server *servers, *local_domains, **serverarray, *no_rebind;
int serverarraysz;
struct ipsets *ipsets;
u32 allowlist_mask;
struct allowlist *allowlists;
int log_fac; /* log facility */
char *log_file; /* optional log file */
int max_logs; /* queue limit */
@@ -1275,6 +1284,9 @@ void setup_reply(struct dns_header *header, unsigned int flags);
int extract_addresses(struct dns_header *header, size_t qlen, char *name,
time_t now, char **ipsets, int is_sign, int check_rebind,
int no_cache_dnssec, int secure, int *doctored);
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
void report_addresses(struct dns_header *header, size_t len, u32 mark);
#endif
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
struct in_addr local_addr, struct in_addr local_netmask,
time_t now, int ad_reqd, int do_bit, int have_pseudoheader);
@@ -1381,7 +1393,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 */
@@ -1390,7 +1402,6 @@ void receive_query(struct listener *listen, time_t now);
unsigned char *tcp_request(int confd, time_t now,
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
void server_gone(struct server *server);
struct frec *get_new_frec(time_t now, int *wait, struct frec *force);
int send_from(int fd, int nowild, char *packet, size_t len,
union mysockaddr *to, union all_addr *source,
unsigned int iface);
@@ -1543,10 +1554,14 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname);
/* ubus.c */
#ifdef HAVE_UBUS
void ubus_init(void);
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 */
@@ -1555,6 +1570,13 @@ void ipset_init(void);
int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove);
#endif
/* pattern.c */
#ifdef HAVE_CONNTRACK
int is_valid_dns_name(const char *value);
int is_valid_dns_name_pattern(const char *value);
int is_dns_name_matching_pattern(const char *name, const char *pattern);
#endif
/* helper.c */
#if defined(HAVE_SCRIPT)
int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd);
@@ -1726,6 +1748,9 @@ void build_server_array(void);
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, char *limit, int first, int last);
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
+165 -75
View File
@@ -16,7 +16,7 @@
#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);
@@ -62,7 +62,7 @@ void build_server_array(void)
/* 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_LITERAL_ADDRESS | SERV_USE_RESOLV)))
daemon->serverarray[count]->arrayposn = count;
}
@@ -75,12 +75,14 @@ void build_server_array(void)
A flag of F_DNSSECOK returns a DNSSEC capable server only and
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 +91,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 +101,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 +178,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)
@@ -195,6 +216,11 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
return 1;
}
/* Return first server in group of equivalent servers; this is the "master" record. */
int server_samegroup(struct server *a, struct server *b)
{
return order_servers(a, b) == 0;
}
int filter_servers(int seed, int flags, int *lowout, int *highout)
{
@@ -217,47 +243,56 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
See which of those match our query in that priority order and narrow (low, high) */
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;
@@ -268,7 +303,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)
@@ -278,12 +313,75 @@ 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, char *limit, int first, int last)
{
int trunc = 0;
unsigned char *p;
int start;
union all_addr addr;
if (flags & (F_NXDOMAIN | F_NOERR))
log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL);
setup_reply(header, flags);
if (!(p = skip_questions(header, size)))
return 0;
if (flags & gotname & F_IPV4)
for (start = first; start != last; start++)
{
struct serv_addr4 *srv = (struct serv_addr4 *)daemon->serverarray[start];
if (srv->flags & SERV_ALL_ZEROS)
memset(&addr, 0, sizeof(addr));
else
addr.addr4 = srv->addr;
header->ancount = htons(ntohs(header->ancount) + 1);
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);
}
if (flags & gotname & F_IPV6)
for (start = first; start != last; start++)
{
struct serv_addr6 *srv = (struct serv_addr6 *)daemon->serverarray[start];
if (srv->flags & SERV_ALL_ZEROS)
memset(&addr, 0, sizeof(addr));
else
addr.addr6 = srv->addr;
header->ancount = htons(ntohs(header->ancount) + 1);
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);
}
if (trunc)
header->hb3 |= HB3_TC;
return p - (unsigned char *)header;
}
#ifdef HAVE_DNSSEC
int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp)
{
@@ -316,20 +414,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;
@@ -337,22 +431,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)
+408 -301
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1617,7 +1617,7 @@ void add_update_server(int flags,
serv->flags = flags;
serv->domain = domain_str;
serv->domain_len = strlen(domain_str);
if (!(flags & SERV_IS_LOCAL))
{
+150 -6
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;
@@ -2617,7 +2626,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;
@@ -2634,7 +2643,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
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;
@@ -2647,8 +2656,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;
@@ -2730,6 +2742,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
new->domain = alloc_domain;
new->domain_len = strlen(alloc_domain);
/* server=//1.2.3.4 is special. */
if (strlen(domain) == 0 && lastdomain)
@@ -2751,6 +2764,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
new = opt_malloc(size);
memcpy(new, last, size);
new->domain = alloc_domain;
new->domain_len = strlen(alloc_domain);
if (flags & (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS))
{
new->next = daemon->local_domains;
@@ -2877,6 +2892,135 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
#endif
case LOPT_CMARK_ALST_EN: /* --connmark-allowlist-enable */
#ifndef HAVE_CONNTRACK
ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives"));
break;
#else
{
u32 mask = UINT32_MAX;
if (arg)
if (!strtoul_check(arg, &mask) || mask < 1)
ret_err(gen_err);
set_option_bool(OPT_CMARK_ALST_EN);
daemon->allowlist_mask = mask;
break;
}
#endif
case LOPT_CMARK_ALST: /* --connmark-allowlist */
#ifndef HAVE_CONNTRACK
ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives"));
break;
#else
{
struct allowlist *allowlists;
char **patterns, **patterns_pos;
u32 mark, mask = UINT32_MAX;
size_t num_patterns = 0;
char *c, *m = NULL;
char *separator;
unhide_metas(arg);
if (!arg)
ret_err(gen_err);
c = arg;
if (*c < '0' || *c > '9')
ret_err(gen_err);
while (*c && *c != ',')
{
if (*c == '/')
{
if (m)
ret_err(gen_err);
*c = '\0';
m = ++c;
}
if (*c < '0' || *c > '9')
ret_err(gen_err);
c++;
}
separator = c;
if (!*separator)
break;
while (c && *c)
{
char *end = strchr(++c, '/');
if (end)
*end = '\0';
if (strcmp(c, "*") && !is_valid_dns_name_pattern(c))
ret_err(gen_err);
if (end)
*end = '/';
if (num_patterns >= UINT16_MAX - 1)
ret_err(gen_err);
num_patterns++;
c = end;
}
*separator = '\0';
if (!strtoul_check(arg, &mark) || mark < 1 || mark > UINT32_MAX)
ret_err(gen_err);
if (m)
if (!strtoul_check(m, &mask) || mask < 1 || mask > UINT32_MAX || (mark & ~mask))
ret_err(gen_err);
if (num_patterns)
*separator = ',';
for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
if (allowlists->mark == mark && allowlists->mask == mask)
ret_err(gen_err);
patterns = opt_malloc((num_patterns + 1) * sizeof(char *));
if (!patterns)
goto fail_cmark_allowlist;
patterns_pos = patterns;
c = separator;
while (c && *c)
{
char *end = strchr(++c, '/');
if (end)
*end = '\0';
if (!(*patterns_pos++ = opt_string_alloc(c)))
goto fail_cmark_allowlist;
if (end)
*end = '/';
c = end;
}
*patterns_pos++ = NULL;
allowlists = opt_malloc(sizeof(struct allowlist));
if (!allowlists)
goto fail_cmark_allowlist;
memset(allowlists, 0, sizeof(struct allowlist));
allowlists->mark = mark;
allowlists->mask = mask;
allowlists->patterns = patterns;
allowlists->next = daemon->allowlists;
daemon->allowlists = allowlists;
break;
fail_cmark_allowlist:
if (patterns)
{
for (patterns_pos = patterns; *patterns_pos; patterns_pos++)
{
free(*patterns_pos);
*patterns_pos = NULL;
}
free(patterns);
patterns = NULL;
}
if (allowlists)
{
free(allowlists);
allowlists = NULL;
}
ret_err(gen_err);
}
#endif
case 'c': /* --cache-size */
{
int size;
+386
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
+88 -1
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))
+191 -24
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 =
@@ -76,42 +104,27 @@ static void ubus_disconnect_cb(struct ubus_context *ubus)
}
}
void ubus_init()
char *ubus_init()
{
struct ubus_context *ubus = NULL;
int ret = 0;
ubus = ubus_connect(NULL);
if (!ubus)
{
if (!error_logged)
{
my_syslog(LOG_ERR, _("Cannot initialize UBus: connection failed"));
error_logged = 1;
}
ubus_destroy(ubus);
return;
}
if (!(ubus = ubus_connect(NULL)))
return NULL;
ubus_object.name = daemon->ubus_name;
ret = ubus_add_object(ubus, &ubus_object);
if (ret)
{
if (!error_logged)
{
my_syslog(LOG_ERR, _("Cannot add object to UBus: %s"), ubus_strerror(ret));
error_logged = 1;
}
ubus_destroy(ubus);
return;
}
return (char *)ubus_strerror(ret);
}
ubus->connection_lost = ubus_disconnect_cb;
daemon->ubus = ubus;
error_logged = 0;
my_syslog(LOG_INFO, _("Connected to system UBus"));
return NULL;
}
void set_ubus_listeners()
@@ -178,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;
@@ -197,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 */