diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 30429df..6d37360 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -2254,6 +2254,10 @@ example command to query this, using the utility would be dig +short chaos txt cachesize.bind +.TP +.B --max-tcp-connections= +The maximum number of concurrent TCP connections. The application forks to +handle each TCP request. The default maximum is 20. .SH CONFIG FILE At startup, dnsmasq reads diff --git a/src/cache.c b/src/cache.c index 1c0e250..5342ce2 100644 --- a/src/cache.c +++ b/src/cache.c @@ -124,6 +124,7 @@ static const struct { { 258, "AVC" }, /* Application Visibility and Control [Wolfgang_Riedel] AVC/avc-completed-template 2016-02-26*/ { 259, "DOA" }, /* Digital Object Architecture [draft-durand-doa-over-dns] DOA/doa-completed-template 2017-08-30*/ { 260, "AMTRELAY" }, /* Automatic Multicast Tunneling Relay [RFC8777] AMTRELAY/amtrelay-completed-template 2019-02-06*/ + { 261, "RESINFO" }, /* Resolver Information as Key/Value Pairs https://datatracker.ietf.org/doc/draft-ietf-add-resolver-info/06/ */ { 32768, "TA" }, /* DNSSEC Trust Authorities [Sam_Weiler][http://cameo.library.cmu.edu/][ Deploying DNSSEC Without a Signed Root. Technical Report 1999-19, Information Networking Institute, Carnegie Mellon University, April 2004.] 2005-12-13*/ { 32769, "DLV" }, /* DNSSEC Lookaside Validation (OBSOLETE) [RFC8749][RFC4431] */ }; @@ -424,18 +425,21 @@ unsigned int cache_remove_uid(const unsigned int uid) { int i; unsigned int removed = 0; - struct crec *crecp, **up; + struct crec *crecp, *tmp, **up; for (i = 0; i < hash_size; i++) - for (crecp = hash_table[i], up = &hash_table[i]; crecp; crecp = crecp->hash_next) - if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && crecp->uid == uid) - { - *up = crecp->hash_next; - free(crecp); - removed++; - } - else - up = &crecp->hash_next; + for (crecp = hash_table[i], up = &hash_table[i]; crecp; crecp = tmp) + { + tmp = crecp->hash_next; + if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && crecp->uid == uid) + { + *up = tmp; + free(crecp); + removed++; + } + else + up = &crecp->hash_next; + } return removed; } @@ -629,8 +633,8 @@ static struct crec *really_insert(char *name, union all_addr *addr, unsigned sho if (insert_error) return NULL; - /* we don't cache zero-TTL records. */ - if (ttl == 0) + /* we don't cache zero-TTL records unless we're doing stale-caching. */ + if (daemon->cache_max_expiry == 0 && ttl == 0) { insert_error = 1; return NULL; diff --git a/src/config.h b/src/config.h index 88cf72e..cc1c465 100644 --- a/src/config.h +++ b/src/config.h @@ -15,7 +15,7 @@ */ #define FTABSIZ 150 /* max number of outstanding requests (default) */ -#define MAX_PROCS 20 /* max no children for TCP requests */ +#define MAX_PROCS 20 /* default max no children for TCP requests */ #define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */ #define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */ #define TCP_BACKLOG 32 /* kernel backlog limit for TCP connections */ diff --git a/src/dhcp6.c b/src/dhcp6.c index 7eeef03..87ad50b 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -92,7 +92,7 @@ void dhcp6_packet(time_t now) struct iface_param parm; struct cmsghdr *cmptr; struct msghdr msg; - int if_index = 0; + uint32_t if_index = 0; union { struct cmsghdr align; /* this ensures alignment */ char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; @@ -118,11 +118,6 @@ void dhcp6_packet(time_t now) if ((sz = recv_dhcp_packet(daemon->dhcp6fd, &msg)) == -1) return; -#ifdef HAVE_DUMPFILE - dump_packet_udp(DUMP_DHCPV6, (void *)daemon->dhcp_packet.iov_base, sz, - (union mysockaddr *)&from, NULL, daemon->dhcp6fd); -#endif - for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) { @@ -138,6 +133,34 @@ void dhcp6_packet(time_t now) if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name)) return; + +#ifdef HAVE_LINUX_NETWORK + /* This works around a possible Linux kernel bug when using interfaces + enslaved to a VRF. The scope_id in the source address gets set + to the index of the VRF interface, not the slave. Fortunately, + the interface index returned by packetinfo is correct so we use + that instead. Log this once, so if it triggers in other circumstances + we've not anticipated and breaks things, we get some clues. */ + if (from.sin6_scope_id != if_index) + { + static int logged = 0; + + if (!logged) + { + my_syslog(MS_DHCP | LOG_WARNING, + _("Working around kernel bug: faulty source address scope for VRF slave %s"), + ifr.ifr_name); + logged = 1; + } + + from.sin6_scope_id = if_index; + } +#endif + +#ifdef HAVE_DUMPFILE + dump_packet_udp(DUMP_DHCPV6, (void *)daemon->dhcp_packet.iov_base, sz, + (union mysockaddr *)&from, NULL, daemon->dhcp6fd); +#endif if (relay_reply6(&from, sz, ifr.ifr_name)) { diff --git a/src/dnsmasq.c b/src/dnsmasq.c index bc6644c..65ba334 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -30,12 +30,14 @@ static volatile pid_t pid = 0; static volatile int pipewrite; static void set_dns_listeners(void); +static void set_tftp_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); static void fatal_event(struct event_desc *ev, char *msg); static int read_event(int fd, struct event_desc *evp, char **msg); static void poll_resolv(int force, int do_reload, time_t now); +static void tcp_init(void); int main (int argc, char **argv) { @@ -415,6 +417,8 @@ int main (int argc, char **argv) daemon->numrrand = max_fd/3; /* safe_malloc returns zero'd memory */ daemon->randomsocks = safe_malloc(daemon->numrrand * sizeof(struct randfd)); + + tcp_init(); } #ifdef HAVE_INOTIFY @@ -1043,8 +1047,10 @@ int main (int argc, char **argv) pid = getpid(); daemon->pipe_to_parent = -1; - for (i = 0; i < MAX_PROCS; i++) - daemon->tcp_pipes[i] = -1; + + if (daemon->port != 0) + for (i = 0; i < daemon->max_procs; i++) + daemon->tcp_pipes[i] = -1; #ifdef HAVE_INOTIFY /* Using inotify, have to select a resolv file at startup */ @@ -1067,7 +1073,12 @@ int main (int argc, char **argv) (timeout == -1 || timeout > 1000)) timeout = 1000; - set_dns_listeners(); + if (daemon->port != 0) + set_dns_listeners(); + +#ifdef HAVE_TFTP + set_tftp_listeners(); +#endif #ifdef HAVE_DBUS if (option_bool(OPT_DBUS)) @@ -1252,8 +1263,9 @@ int main (int argc, char **argv) check_ubus_listeners(); } #endif - - check_dns_listeners(now); + + if (daemon->port != 0) + check_dns_listeners(now); #ifdef HAVE_TFTP check_tftp_listeners(now); @@ -1519,8 +1531,8 @@ static void async_event(int pipe, time_t now) if (errno != EINTR) break; } - else - for (i = 0 ; i < MAX_PROCS; i++) + else if (daemon->port != 0) + for (i = 0 ; i < daemon->max_procs; i++) if (daemon->tcp_pids[i] == p) daemon->tcp_pids[i] = 0; break; @@ -1584,9 +1596,10 @@ static void async_event(int pipe, time_t now) case EVENT_TERM: /* Knock all our children on the head. */ - for (i = 0; i < MAX_PROCS; i++) - if (daemon->tcp_pids[i] != 0) - kill(daemon->tcp_pids[i], SIGALRM); + if (daemon->port != 0) + for (i = 0; i < daemon->max_procs; i++) + if (daemon->tcp_pids[i] != 0) + kill(daemon->tcp_pids[i], SIGALRM); #if defined(HAVE_SCRIPT) && defined(HAVE_DHCP) /* handle pending lease transitions */ @@ -1731,23 +1744,33 @@ void clear_cache_and_reload(time_t now) #endif } -static void set_dns_listeners(void) -{ - struct serverfd *serverfdp; - struct listener *listener; - struct randfd_list *rfl; - int i; - #ifdef HAVE_TFTP +static void set_tftp_listeners(void) +{ int tftp = 0; struct tftp_transfer *transfer; + struct listener *listener; + if (!option_bool(OPT_SINGLE_PORT)) for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next) { tftp++; poll_listen(transfer->sockfd, POLLIN); } + + for (listener = daemon->listeners; listener; listener = listener->next) + /* tftp == 0 in single-port mode. */ + if (tftp <= daemon->tftp_max && listener->tftpfd != -1) + poll_listen(listener->tftpfd, POLLIN); +} #endif + +static void set_dns_listeners(void) +{ + struct serverfd *serverfdp; + struct listener *listener; + struct randfd_list *rfl; + int i; for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) poll_listen(serverfdp->fd, POLLIN); @@ -1761,7 +1784,7 @@ static void set_dns_listeners(void) poll_listen(rfl->rfd->fd, POLLIN); /* check to see if we have free tcp process slots. */ - for (i = MAX_PROCS - 1; i >= 0; i--) + for (i = daemon->max_procs - 1; i >= 0; i--) if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1) break; @@ -1776,16 +1799,10 @@ static void set_dns_listeners(void) we'll be called again when a slot becomes available. */ if (listener->tcpfd != -1 && i >= 0) poll_listen(listener->tcpfd, POLLIN); - -#ifdef HAVE_TFTP - /* tftp == 0 in single-port mode. */ - 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++) + for (i = 0; i < daemon->max_procs; i++) if (daemon->tcp_pipes[i] != -1) poll_listen(daemon->tcp_pipes[i], POLLIN); } @@ -1820,7 +1837,7 @@ static void check_dns_listeners(time_t now) to free the process slot. Once the child process has gone, poll() returns POLLHUP, not POLLIN, so have to check for both here. */ if (!option_bool(OPT_DEBUG)) - for (i = 0; i < MAX_PROCS; i++) + for (i = 0; i < daemon->max_procs; i++) if (daemon->tcp_pipes[i] != -1 && poll_check(daemon->tcp_pipes[i], POLLIN | POLLHUP) && !cache_recv_insert(now, daemon->tcp_pipes[i])) @@ -1834,17 +1851,12 @@ static void check_dns_listeners(time_t now) if (listener->fd != -1 && poll_check(listener->fd, POLLIN)) receive_query(listener, now); -#ifdef HAVE_TFTP - if (listener->tftpfd != -1 && poll_check(listener->tftpfd, POLLIN)) - tftp_request(listener, now); -#endif - /* check to see if we have a free tcp process slot. Note that we can't assume that because we had at least one a poll() time, that we still do. There may be more waiting connections after poll() returns then free process slots. */ - for (i = MAX_PROCS - 1; i >= 0; i--) + for (i = daemon->max_procs - 1; i >= 0; i--) if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1) break; @@ -2135,7 +2147,11 @@ 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(); + if (daemon->port != 0) + set_dns_listeners(); +#ifdef HAVE_TFTP + set_tftp_listeners(); +#endif set_log_writer(); #ifdef HAVE_DHCP6 @@ -2153,7 +2169,8 @@ int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id) now = dnsmasq_time(); check_log_writer(0); - check_dns_listeners(now); + if (daemon->port != 0) + check_dns_listeners(now); #ifdef HAVE_DHCP6 if (daemon->doing_ra && poll_check(daemon->icmp6fd, POLLIN)) @@ -2186,3 +2203,9 @@ int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id) return 0; } #endif /* HAVE_DHCP */ + +void tcp_init(void) +{ + daemon->tcp_pids = safe_malloc(daemon->max_procs*sizeof(pid_t)); + daemon->tcp_pipes = safe_malloc(daemon->max_procs*sizeof(int)); +} diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 2f95c12..67c083b 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1252,8 +1252,8 @@ extern struct daemon { struct server *srv_save; /* Used for resend on DoD */ size_t packet_len; /* " " */ int fd_save; /* " " */ - pid_t tcp_pids[MAX_PROCS]; - int tcp_pipes[MAX_PROCS]; + pid_t *tcp_pids; + int *tcp_pipes; int pipe_to_parent; int numrrand; struct randfd *randomsocks; @@ -1313,6 +1313,7 @@ extern struct daemon { /* file for packet dumps. */ int dumpfd; #endif + int max_procs; } *daemon; struct server_details { diff --git a/src/option.c b/src/option.c index 286f06b..9423582 100644 --- a/src/option.c +++ b/src/option.c @@ -190,6 +190,7 @@ struct myoption { #define LOPT_FILTER_RR 381 #define LOPT_NO_DHCP6 382 #define LOPT_NO_DHCP4 383 +#define LOPT_MAX_PROCS 384 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -384,6 +385,7 @@ static const struct myoption opts[] = { "fast-dns-retry", 2, 0, LOPT_FAST_RETRY }, { "use-stale-cache", 2, 0 , LOPT_STALE_CACHE }, { "no-ident", 0, 0, LOPT_NO_IDENT }, + { "max-tcp-connections", 1, 0, LOPT_MAX_PROCS }, { NULL, 0, 0, 0 } }; @@ -585,6 +587,7 @@ static struct { { LOPT_NORR, OPT_NORR, NULL, gettext_noop("Suppress round-robin ordering of DNS records."), NULL }, { LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL }, { LOPT_CACHE_RR, ARG_DUP, "", gettext_noop("Cache this DNS resource record type."), NULL }, + { LOPT_MAX_PROCS, ARG_ONE, "", gettext_noop("Maximum number of concurrent tcp connections."), NULL }, { 0, 0, NULL, NULL, NULL } }; @@ -5313,7 +5316,17 @@ err: break; } #endif - + + case LOPT_MAX_PROCS: /* --max-tcp-connections */ + { + int max_procs; + /* Don't accept numbers less than 1. */ + if (!atoi_check(arg, &max_procs) || max_procs < 1) + ret_err(gen_err); + daemon->max_procs = max_procs; + break; + } + default: ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)")); @@ -5841,6 +5854,7 @@ void read_opts(int argc, char **argv, char *compile_opts) daemon->soa_expiry = SOA_EXPIRY; daemon->randport_limit = 1; daemon->host_index = SRC_AH; + daemon->max_procs = MAX_PROCS; /* See comment above make_servers(). Optimises server-read code. */ mark_servers(0); diff --git a/src/rfc1035.c b/src/rfc1035.c index da8713a..ae6b46a 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -847,11 +847,18 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t int len; if (!extract_name(header, qlen, &p1, name, 1, 0)) - return 2; + { + blockdata_free(addr.rrblock.rrdata); + return 2; + } len = to_wire(name); if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, name, len)) - return 0; + { + blockdata_free(addr.rrblock.rrdata); + return 0; + } + addr.rrblock.datalen += len; } else @@ -859,8 +866,13 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t /* desc is length of a block of data to be used as-is */ if (desc > endrr - p1) desc = endrr - p1; + if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, desc)) - return 0; + { + blockdata_free(addr.rrblock.rrdata); + return 0; + } + addr.rrblock.datalen += desc; p1 += desc; } @@ -868,7 +880,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t /* we overwrote the original name, so get it back here. */ if (!extract_name(header, qlen, &tmp, name, 1, 0)) - return 2; + { + blockdata_free(addr.rrblock.rrdata); + return 2; + } } } else if (flags & (F_IPV4 | F_IPV6)) @@ -914,6 +929,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t cpp->addr.cname.uid = newc->uid; } cpp = NULL; + + /* cache insert failed, don't leak blockdata. */ + if (!newc && (flags & F_RR) && (flags & F_KEYTAG)) + blockdata_free(addr.rrblock.rrdata); } if (aqtype == T_TXT) diff --git a/src/rfc3315.c b/src/rfc3315.c index c2e2692..bd448c7 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -1074,7 +1074,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu case DHCP6CONFIRM: { - int good_addr = 0; + int good_addr = 0, bad_addr = 0; /* set reply message type */ outmsgtype = DHCP6REPLY; @@ -1096,26 +1096,24 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu if (!address6_valid(state->context, &req_addr, tagif, 1)) { - o1 = new_opt6(OPTION6_STATUS_CODE); - put_opt6_short(DHCP6NOTONLINK); - put_opt6_string(_("confirm failed")); - end_opt6(o1); + bad_addr = 1; log6_quiet(state, "DHCPREPLY", &req_addr, _("confirm failed")); - return 1; } - - good_addr = 1; - log6_quiet(state, "DHCPREPLY", &req_addr, state->hostname); + else + { + good_addr = 1; + log6_quiet(state, "DHCPREPLY", &req_addr, state->hostname); + } } } /* No addresses, no reply: RFC 3315 18.2.2 */ - if (!good_addr) + if (!good_addr && !bad_addr) return 0; o1 = new_opt6(OPTION6_STATUS_CODE); - put_opt6_short(DHCP6SUCCESS ); - put_opt6_string(_("all addresses still on link")); + put_opt6_short(bad_addr ? DHCP6NOTONLINK : DHCP6SUCCESS); + put_opt6_string(bad_addr ? (_("confirm failed")) : (_("all addresses still on link"))); end_opt6(o1); break; } diff --git a/src/tftp.c b/src/tftp.c index f036297..d98bfca 100644 --- a/src/tftp.c +++ b/src/tftp.c @@ -585,8 +585,13 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *c void check_tftp_listeners(time_t now) { + struct listener *listener; struct tftp_transfer *transfer, *tmp, **up; + for (listener = daemon->listeners; listener; listener = listener->next) + if (listener->tftpfd != -1 && poll_check(listener->tftpfd, POLLIN)) + tftp_request(listener, now); + /* In single port mode, all packets come via port 69 and tftp_request() */ if (!option_bool(OPT_SINGLE_PORT)) for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next)