diff --git a/SOURCES/dnsmasq-2.79-mixed-family-failed.patch b/SOURCES/dnsmasq-2.79-mixed-family-failed.patch new file mode 100644 index 0000000..c241d15 --- /dev/null +++ b/SOURCES/dnsmasq-2.79-mixed-family-failed.patch @@ -0,0 +1,79 @@ +From 4348c43be45d20aba87ee5564ecdde10aff7e5e7 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Fri, 22 Jan 2021 16:49:12 +0000 +Subject: [PATCH] Move fd into frec_src, fixes + 15b60ddf935a531269bb8c68198de012a4967156 + +If identical queries from IPv4 and IPv6 sources are combined by the +new code added in 15b60ddf935a531269bb8c68198de012a4967156 then replies +can end up being sent via the wrong family of socket. The ->fd +should be per query, not per-question. + +In bind-interfaces mode, this could also result in replies being sent +via the wrong socket even when IPv4/IPV6 issues are not in play. + +(cherry picked from commit 04490bf622ac84891aad6f2dd2edf83725decdee) + +Fix for 12af2b171de0d678d98583e2190789e544440e02 + +(cherry picked from commit 3f535da79e7a42104543ef5c7b5fa2bed819a78b) +--- + src/dnsmasq.h | 3 ++- + src/forward.c | 5 +++-- + 2 files changed, 5 insertions(+), 3 deletions(-) + +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index f3bbb4e..e7e1693 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -632,6 +632,7 @@ struct frec { + union mysockaddr source; + struct all_addr dest; + unsigned int iface, log_id; ++ int fd; + unsigned short orig_id; + struct frec_src *next; + } frec_src; +@@ -641,7 +642,7 @@ struct frec { + struct randfd *rfd6; + #endif + unsigned short new_id; +- int fd, forwardall, flags; ++ int forwardall, flags; + time_t time; + unsigned char *hash[HASH_SIZE]; + #ifdef HAVE_DNSSEC +diff --git a/src/forward.c b/src/forward.c +index 9d249c0..82dd850 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -368,6 +368,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + new->dest = *dst_addr; + new->log_id = daemon->log_id; + new->iface = dst_iface; ++ new->fd = udpfd; + } + + return 1; +@@ -392,8 +393,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + forward->frec_src.dest = *dst_addr; + forward->frec_src.iface = dst_iface; + forward->frec_src.next = NULL; ++ forward->frec_src.fd = udpfd; + forward->new_id = get_id(); +- forward->fd = udpfd; + memcpy(forward->hash, hash, HASH_SIZE); + forward->forwardall = 0; + forward->flags = fwd_flags; +@@ -1175,7 +1176,7 @@ void reply_query(int fd, int family, time_t now) + { + header->id = htons(src->orig_id); + +- send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, ++ send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, + &src->source, &src->dest, src->iface); + + if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src) +-- +2.26.2 + diff --git a/SOURCES/dnsmasq-2.80-man-nameing.patch b/SOURCES/dnsmasq-2.80-man-nameing.patch new file mode 100644 index 0000000..3318a4a --- /dev/null +++ b/SOURCES/dnsmasq-2.80-man-nameing.patch @@ -0,0 +1,25 @@ +From 03212e533b1e07aba30d2f4112009dc3af867ea5 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Tue, 4 Sep 2018 17:52:28 +0100 +Subject: [PATCH] Manpage typo. + +--- + man/dnsmasq.8 | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 +index ebfadba..a62860e 100644 +--- a/man/dnsmasq.8 ++++ b/man/dnsmasq.8 +@@ -27,7 +27,7 @@ TFTP server to allow net/PXE boot of DHCP hosts and also supports BOOTP. The PXE + .PP + The dnsmasq DHCPv6 server provides the same set of features as the + DHCPv4 server, and in addition, it includes router advertisements and +-a neat feature which allows nameing for clients which use DHCPv4 and ++a neat feature which allows naming for clients which use DHCPv4 and + stateless autoconfiguration only for IPv6 configuration. There is support for doing address allocation (both DHCPv6 and RA) from subnets which are dynamically delegated via DHCPv6 prefix delegation. + .PP + Dnsmasq is coded with small embedded systems in mind. It aims for the smallest possible memory footprint compatible with the supported functions, and allows unneeded functions to be omitted from the compiled binary. +-- +2.31.1 + diff --git a/SOURCES/dnsmasq-2.81-netlink-table.patch b/SOURCES/dnsmasq-2.81-netlink-table.patch new file mode 100644 index 0000000..05d22cf --- /dev/null +++ b/SOURCES/dnsmasq-2.81-netlink-table.patch @@ -0,0 +1,45 @@ +From 595b2e2e87f152c4ade7e2d66cb78915096f60c2 Mon Sep 17 00:00:00 2001 +From: Donald Sharp +Date: Mon, 2 Mar 2020 11:23:36 -0500 +Subject: [PATCH] Ignore routes in non-main tables + +Route lookup in Linux is bounded by `ip rules` as well +as the contents of specific routing tables. With the +advent of vrf's(l3mdev's) non-default tables are regularly being +used for routing purposes. + +dnsmasq listens to all route changes on the box and responds +to each one with an event. This is *expensive* when a full +BGP routing table is placed into the linux kernel, moreso +when dnsmasq is responding to events in tables that it will +never actually need to respond to, since dnsmasq at this +point in time has no concept of vrf's and would need +to be programmed to understand them. Help alleviate this load +by reducing the set of data that dnsmasq pays attention to +when we know there are events that are not useful at this +point in time. + +Signed-off-by: Donald Sharp +(cherry picked from commit b2ed691eb3ca6488a8878f5f3dd950a07b14a9db) +--- + src/netlink.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/netlink.c b/src/netlink.c +index 8cd51af..0a3da3e 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -363,7 +363,9 @@ static void nl_async(struct nlmsghdr *h) + failing. */ + struct rtmsg *rtm = NLMSG_DATA(h); + +- if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK) ++ if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK && ++ (rtm->rtm_table == RT_TABLE_MAIN || ++ rtm->rtm_table == RT_TABLE_LOCAL)) + queue_event(EVENT_NEWROUTE); + } + else if (h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) +-- +2.26.2 + diff --git a/SOURCES/dnsmasq-2.84-bind-dynamic-netlink.patch b/SOURCES/dnsmasq-2.84-bind-dynamic-netlink.patch new file mode 100644 index 0000000..f325e6d --- /dev/null +++ b/SOURCES/dnsmasq-2.84-bind-dynamic-netlink.patch @@ -0,0 +1,237 @@ +From 5010c42c47b7b5a3d68d83369d6c17ed0bc11cff Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Wed, 17 Feb 2021 11:47:28 +0100 +Subject: [PATCH] Correct occasional --bind-dynamic synchronization break + +Request only one re-read of addresses and/or routes + +Previous implementation re-reads systemd addresses exactly the same +number of time equal number of notifications received. +This is not necessary, we need just notification of change, then re-read +the current state and adapt listeners. Repeated re-reading slows netlink +processing and highers CPU usage on mass interface changes. + +Continue reading multicast events from netlink, even when ENOBUFS +arrive. Broadcasts are not trusted anyway and refresh would be done in +iface_enumerate. Save queued events sent again. + +Remove sleeping on netlink ENOBUFS + +With reduced number of written events netlink should receive ENOBUFS +rarely. It does not make sense to wait if it is received. It is just a +signal some packets got missing. Fast reading all pending packets is required, +seq checking ensures it already. Finishes changes by +commit 1d07667ac77c55b9de56b1b2c385167e0e0ec27a. + +Move restart from iface_enumerate to enumerate_interfaces + +When ENOBUFS is received, restart of reading addresses is done. But +previously found addresses might not have been found this time. In order +to catch this, restart both IPv4 and IPv6 enumeration with clearing +found interfaces first. It should deliver up-to-date state also after +ENOBUFS. + +Read all netlink messages before netlink restart + +Before writing again into netlink socket, try fetching all pending +messages. They would be ignored, only might trigger new address +synchronization. Should ensure new try has better chance to succeed. + +Request sending ENOBUFS again + +ENOBUFS error handling was improved. Netlink is correctly drained before +sending a new request again. It seems ENOBUFS supression is no longer +necessary or wanted. Let kernel tell us when it failed and handle it a +good way. +--- + src/netlink.c | 67 ++++++++++++++++++++++++++++++++++++--------------- + src/network.c | 11 +++++++-- + 2 files changed, 57 insertions(+), 21 deletions(-) + +diff --git a/src/netlink.c b/src/netlink.c +index ac1a1c5..f95f3e8 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -32,13 +32,21 @@ + + #ifndef NDA_RTA + # define NDA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) +-#endif ++#endif ++ ++/* Used to request refresh of addresses or routes just once, ++ * when multiple changes might be announced. */ ++enum async_states { ++ STATE_NEWADDR = (1 << 0), ++ STATE_NEWROUTE = (1 << 1), ++}; + + + static struct iovec iov; + static u32 netlink_pid; + +-static void nl_async(struct nlmsghdr *h); ++static unsigned nl_async(struct nlmsghdr *h, unsigned state); ++static void nl_multicast_state(unsigned state); + + void netlink_init(void) + { +@@ -135,7 +143,9 @@ static ssize_t netlink_recv(void) + + + /* family = AF_UNSPEC finds ARP table entries. +- family = AF_LOCAL finds MAC addresses. */ ++ family = AF_LOCAL finds MAC addresses. ++ returns 0 on failure, 1 on success, -1 when restart is required ++*/ + int iface_enumerate(int family, void *parm, int (*callback)()) + { + struct sockaddr_nl addr; +@@ -143,6 +153,7 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + ssize_t len; + static unsigned int seq = 0; + int callback_ok = 1; ++ unsigned state = 0; + + struct { + struct nlmsghdr nlh; +@@ -154,7 +165,6 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + addr.nl_groups = 0; + addr.nl_pid = 0; /* address to kernel */ + +- again: + if (family == AF_UNSPEC) + req.nlh.nlmsg_type = RTM_GETNEIGH; + else if (family == AF_LOCAL) +@@ -181,8 +191,8 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + { + if (errno == ENOBUFS) + { +- sleep(1); +- goto again; ++ nl_multicast_state(state); ++ return -1; + } + return 0; + } +@@ -191,7 +201,7 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + if (h->nlmsg_pid != netlink_pid || h->nlmsg_type == NLMSG_ERROR) + { + /* May be multicast arriving async */ +- nl_async(h); ++ state = nl_async(h, state); + } + else if (h->nlmsg_seq != seq) + { +@@ -327,26 +337,36 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + } + } + +-void netlink_multicast(void) ++static void nl_multicast_state(unsigned state) + { + ssize_t len; + struct nlmsghdr *h; + int flags; +- +- /* don't risk blocking reading netlink messages here. */ ++ + if ((flags = fcntl(daemon->netlinkfd, F_GETFL)) == -1 || + fcntl(daemon->netlinkfd, F_SETFL, flags | O_NONBLOCK) == -1) + return; ++ ++ do { ++ /* don't risk blocking reading netlink messages here. */ ++ while ((len = netlink_recv()) != -1) + +- if ((len = netlink_recv()) != -1) +- for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) +- nl_async(h); +- ++ for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) ++ state = nl_async(h, state); ++ } while (errno == ENOBUFS); ++ + /* restore non-blocking status */ + fcntl(daemon->netlinkfd, F_SETFL, flags); + } + +-static void nl_async(struct nlmsghdr *h) ++void netlink_multicast(void) ++{ ++ unsigned state = 0; ++ nl_multicast_state(state); ++} ++ ++ ++static unsigned nl_async(struct nlmsghdr *h, unsigned state) + { + if (h->nlmsg_type == NLMSG_ERROR) + { +@@ -354,7 +374,8 @@ static void nl_async(struct nlmsghdr *h) + if (err->error != 0) + my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error))); + } +- else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE) ++ else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE && ++ (state & STATE_NEWROUTE)==0) + { + /* We arrange to receive netlink multicast messages whenever the network route is added. + If this happens and we still have a DNS packet in the buffer, we re-send it. +@@ -366,10 +387,18 @@ static void nl_async(struct nlmsghdr *h) + if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK && + (rtm->rtm_table == RT_TABLE_MAIN || + rtm->rtm_table == RT_TABLE_LOCAL)) +- queue_event(EVENT_NEWROUTE); ++ { ++ queue_event(EVENT_NEWROUTE); ++ state |= STATE_NEWROUTE; ++ } ++ } ++ else if ((h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) && ++ (state & STATE_NEWADDR)==0) ++ { ++ queue_event(EVENT_NEWADDR); ++ state |= STATE_NEWADDR; + } +- else if (h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) +- queue_event(EVENT_NEWADDR); ++ return state; + } + #endif + +diff --git a/src/network.c b/src/network.c +index c6e7d89..47caf38 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -656,7 +656,8 @@ int enumerate_interfaces(int reset) + + if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return 0; +- ++ ++again: + /* Mark interfaces for garbage collection */ + for (iface = daemon->interfaces; iface; iface = iface->next) + iface->found = 0; +@@ -709,10 +710,16 @@ int enumerate_interfaces(int reset) + + #ifdef HAVE_IPV6 + ret = iface_enumerate(AF_INET6, ¶m, iface_allowed_v6); ++ if (ret < 0) ++ goto again; + #endif + + if (ret) +- ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4); ++ { ++ ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4); ++ if (ret < 0) ++ goto again; ++ } + + errsave = errno; + close(param.fd); +-- +2.26.2 + diff --git a/SOURCES/dnsmasq-2.85-CVE-2021-3448.patch b/SOURCES/dnsmasq-2.85-CVE-2021-3448.patch new file mode 100644 index 0000000..a95c6df --- /dev/null +++ b/SOURCES/dnsmasq-2.85-CVE-2021-3448.patch @@ -0,0 +1,1056 @@ +From d88dc5e696f1b8b95e416890ac831eb0c26250ff Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Mon, 15 Mar 2021 21:59:51 +0000 +Subject: [PATCH] Use random source ports where possible if source + addresses/interfaces in use. + +CVE-2021-3448 applies. + +It's possible to specify the source address or interface to be +used when contacting upstream nameservers: server=8.8.8.8@1.2.3.4 +or server=8.8.8.8@1.2.3.4#66 or server=8.8.8.8@eth0, and all of +these have, until now, used a single socket, bound to a fixed +port. This was originally done to allow an error (non-existent +interface, or non-local address) to be detected at start-up. This +means that any upstream servers specified in such a way don't use +random source ports, and are more susceptible to cache-poisoning +attacks. + +We now use random ports where possible, even when the +source is specified, so server=8.8.8.8@1.2.3.4 or +server=8.8.8.8@eth0 will use random source +ports. server=8.8.8.8@1.2.3.4#66 or any use of --query-port will +use the explicitly configured port, and should only be done with +understanding of the security implications. +Note that this change changes non-existing interface, or non-local +source address errors from fatal to run-time. The error will be +logged and communiction with the server not possible. +--- + man/dnsmasq.8 | 4 +- + src/dnsmasq.c | 31 +++-- + src/dnsmasq.h | 28 ++-- + src/forward.c | 373 +++++++++++++++++++++++++++++++------------------- + src/loop.c | 20 +-- + src/network.c | 100 ++++---------- + src/option.c | 3 +- + src/tftp.c | 6 +- + src/util.c | 2 +- + 9 files changed, 310 insertions(+), 257 deletions(-) + +diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 +index 45d2273..7f4c62e 100644 +--- a/man/dnsmasq.8 ++++ b/man/dnsmasq.8 +@@ -419,7 +419,7 @@ Tells dnsmasq to never forward A or AAAA queries for plain names, without dots + or domain parts, to upstream nameservers. If the name is not known + from /etc/hosts or DHCP then a "not found" answer is returned. + .TP +-.B \-S, --local, --server=[/[]/[domain/]][[#][@|[#]] ++.B \-S, --local, --server=[/[]/[domain/]][[#]][@][@[#]] + Specify IP address of upstream servers directly. Setting this flag does + not suppress reading of /etc/resolv.conf, use -R to do that. If one or + more +@@ -481,7 +481,7 @@ source address specified but the port may be specified directly as + part of the source address. Forcing queries to an interface is not + implemented on all platforms supported by dnsmasq. + .TP +-.B --rev-server=/,[#][@|[#]] ++.B --rev-server=/,[#][@|@[#]] + This is functionally the same as + .B --server, + but provides some syntactic sugar to make specifying address-to-name queries easier. For example +diff --git a/src/dnsmasq.c b/src/dnsmasq.c +index b7f0a29..3a1f65e 100644 +--- a/src/dnsmasq.c ++++ b/src/dnsmasq.c +@@ -1538,6 +1538,7 @@ static int set_dns_listeners(time_t now) + { + struct serverfd *serverfdp; + struct listener *listener; ++ struct randfd_list *rfl; + int wait = 0, i; + + #ifdef HAVE_TFTP +@@ -1557,11 +1558,14 @@ static int set_dns_listeners(time_t now) + for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) + poll_listen(serverfdp->fd, POLLIN); + +- if (daemon->port != 0 && !daemon->osport) +- for (i = 0; i < RANDOM_SOCKS; i++) +- if (daemon->randomsocks[i].refcount != 0) +- poll_listen(daemon->randomsocks[i].fd, POLLIN); +- ++ for (i = 0; i < RANDOM_SOCKS; i++) ++ if (daemon->randomsocks[i].refcount != 0) ++ poll_listen(daemon->randomsocks[i].fd, POLLIN); ++ ++ /* Check overflow random sockets too. */ ++ for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next) ++ poll_listen(rfl->rfd->fd, POLLIN); ++ + for (listener = daemon->listeners; listener; listener = listener->next) + { + /* only listen for queries if we have resources */ +@@ -1592,17 +1596,22 @@ static void check_dns_listeners(time_t now) + { + struct serverfd *serverfdp; + struct listener *listener; ++ struct randfd_list *rfl; + int i; + + for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) + if (poll_check(serverfdp->fd, POLLIN)) +- reply_query(serverfdp->fd, serverfdp->source_addr.sa.sa_family, now); ++ reply_query(serverfdp->fd, now); + +- if (daemon->port != 0 && !daemon->osport) +- for (i = 0; i < RANDOM_SOCKS; i++) +- if (daemon->randomsocks[i].refcount != 0 && +- poll_check(daemon->randomsocks[i].fd, POLLIN)) +- reply_query(daemon->randomsocks[i].fd, daemon->randomsocks[i].family, now); ++ for (i = 0; i < RANDOM_SOCKS; i++) ++ if (daemon->randomsocks[i].refcount != 0 && ++ poll_check(daemon->randomsocks[i].fd, POLLIN)) ++ reply_query(daemon->randomsocks[i].fd, now); ++ ++ /* Check overflow random sockets too. */ ++ for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next) ++ if (poll_check(rfl->rfd->fd, POLLIN)) ++ reply_query(rfl->rfd->fd, now); + + for (listener = daemon->listeners; listener; listener = listener->next) + { +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 221f788..4beef35 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -521,13 +521,20 @@ struct serverfd { + }; + + struct randfd { ++ struct server *serv; + int fd; +- unsigned short refcount, family; ++ unsigned short refcount; /* refcount == 0xffff means overflow record. */ + }; +- ++ ++struct randfd_list { ++ struct randfd *rfd; ++ struct randfd_list *next; ++}; ++ + struct server { + union mysockaddr addr, source_addr; + char interface[IF_NAMESIZE+1]; ++ unsigned int ifindex; /* corresponding to interface, above */ + struct serverfd *sfd; + char *domain; /* set if this server only handles a domain. */ + int flags, tcpfd, edns_pktsz; +@@ -640,10 +647,7 @@ struct frec { + struct frec_src *next; + } frec_src; + struct server *sentto; /* NULL means free */ +- struct randfd *rfd4; +-#ifdef HAVE_IPV6 +- struct randfd *rfd6; +-#endif ++ struct randfd_list *rfds; + unsigned short new_id; + int forwardall, flags; + time_t time; +@@ -1062,9 +1066,10 @@ extern struct daemon { + int forwardcount; + struct server *srv_save; /* Used for resend on DoD */ + size_t packet_len; /* " " */ +- struct randfd *rfd_save; /* " " */ ++ int fd_save; /* " " */ + pid_t tcp_pids[MAX_PROCS]; + struct randfd randomsocks[RANDOM_SOCKS]; ++ struct randfd_list *rfl_spare, *rfl_poll; + int v6pktinfo; + struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */ + int log_id, log_display_id; /* ids of transactions for logging */ +@@ -1227,7 +1232,7 @@ void safe_strncpy(char *dest, const char *src, size_t size); + void safe_pipe(int *fd, int read_noblock); + void *whine_malloc(size_t size); + int sa_len(union mysockaddr *addr); +-int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2); ++int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2); + int hostname_isequal(const char *a, const char *b); + time_t dnsmasq_time(void); + int netmask_length(struct in_addr mask); +@@ -1276,7 +1281,7 @@ char *parse_server(char *arg, union mysockaddr *addr, + int option_read_dynfile(char *file, int flags); + + /* forward.c */ +-void reply_query(int fd, int family, time_t now); ++void reply_query(int fd, time_t now); + 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); +@@ -1286,13 +1291,12 @@ int send_from(int fd, int nowild, char *packet, size_t len, + union mysockaddr *to, struct all_addr *source, + unsigned int iface); + void resend_query(void); +-struct randfd *allocate_rfd(int family); +-void free_rfd(struct randfd *rfd); ++int allocate_rfd(struct randfd_list **fdlp, struct server *serv); ++void free_rfds(struct randfd_list **fdlp); + + /* network.c */ + int indextoname(int fd, int index, char *name); + int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp); +-int random_sock(int family); + void pre_allocate_sfds(void); + int reload_servers(char *fname); + void mark_servers(int flag); +diff --git a/src/forward.c b/src/forward.c +index 82dd850..11e0310 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -16,7 +16,7 @@ + + #include "dnsmasq.h" + +-static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash); ++static struct frec *lookup_frec(unsigned short id, int fd, void *hash); + static struct frec *lookup_frec_by_sender(unsigned short id, + union mysockaddr *addr, + void *hash); +@@ -291,29 +291,19 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) + PUTSHORT(SAFE_PKTSZ, pheader); + +- if (forward->sentto->addr.sa.sa_family == AF_INET) +- log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); +-#ifdef HAVE_IPV6 +- else +- log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (struct all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); +-#endif +- +- if (forward->sentto->sfd) +- fd = forward->sentto->sfd->fd; +- else ++ if ((fd = allocate_rfd(&forward->rfds, forward->sentto)) != -1) + { ++ if (forward->sentto->addr.sa.sa_family == AF_INET) ++ log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); + #ifdef HAVE_IPV6 +- if (forward->sentto->addr.sa.sa_family == AF_INET6) +- fd = forward->rfd6->fd; + else ++ log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (struct all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); + #endif +- fd = forward->rfd4->fd; ++ while (retry_send(sendto(fd, (char *)header, plen, 0, ++ &forward->sentto->addr.sa, ++ sa_len(&forward->sentto->addr)))); + } +- +- while (retry_send( sendto(fd, (char *)header, plen, 0, +- &forward->sentto->addr.sa, +- sa_len(&forward->sentto->addr)))); +- ++ + return 1; + } + #endif +@@ -490,50 +480,26 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + + while (1) + { ++ int fd; ++ + /* only send to servers dealing with our domain. + domain may be NULL, in which case server->domain + must be NULL also. */ + + if (type == (start->flags & SERV_TYPE) && + (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && +- !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) ++ !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) && ++ ((fd = allocate_rfd(&forward->rfds, start)) != -1)) + { +- int fd; +- +- /* find server socket to use, may need to get random one. */ +- if (start->sfd) +- fd = start->sfd->fd; +- else +- { +-#ifdef HAVE_IPV6 +- if (start->addr.sa.sa_family == AF_INET6) +- { +- if (!forward->rfd6 && +- !(forward->rfd6 = allocate_rfd(AF_INET6))) +- break; +- daemon->rfd_save = forward->rfd6; +- fd = forward->rfd6->fd; +- } +- else +-#endif +- { +- if (!forward->rfd4 && +- !(forward->rfd4 = allocate_rfd(AF_INET))) +- break; +- daemon->rfd_save = forward->rfd4; +- fd = forward->rfd4->fd; +- } +- + #ifdef HAVE_CONNTRACK +- /* Copy connection mark of incoming query to outgoing connection. */ +- if (option_bool(OPT_CONNTRACK)) +- { +- unsigned int mark; +- if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark)) +- setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); +- } +-#endif ++ /* Copy connection mark of incoming query to outgoing connection. */ ++ if (option_bool(OPT_CONNTRACK)) ++ { ++ unsigned int mark; ++ if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark)) ++ setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } ++#endif + + #ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER)) +@@ -561,6 +527,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + /* Keep info in case we want to re-send this packet */ + daemon->srv_save = start; + daemon->packet_len = plen; ++ daemon->fd_save = fd; + + if (!gotname) + strcpy(daemon->namebuff, "query"); +@@ -579,7 +546,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + break; + forward->forwardall++; + } +- } ++ } + + if (!(start = start->next)) + start = daemon->servers; +@@ -779,7 +746,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server + } + + /* sets new last_server */ +-void reply_query(int fd, int family, time_t now) ++void reply_query(int fd, time_t now) + { + /* packet from peer server, extract data for cache, and send to + original requester */ +@@ -794,9 +761,8 @@ void reply_query(int fd, int family, time_t now) + + /* packet buffer overwritten */ + daemon->srv_save = NULL; +- ++ + /* Determine the address of the server replying so that we can mark that as good */ +- serveraddr.sa.sa_family = family; + #ifdef HAVE_IPV6 + if (serveraddr.sa.sa_family == AF_INET6) + serveraddr.in6.sin6_flowinfo = 0; +@@ -822,7 +788,7 @@ void reply_query(int fd, int family, time_t now) + + hash = hash_questions(header, n, daemon->namebuff); + +- if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash))) ++ if (!(forward = lookup_frec(ntohs(header->id), fd, hash))) + return; + + /* log_query gets called indirectly all over the place, so +@@ -1027,9 +993,8 @@ void reply_query(int fd, int family, time_t now) + } + + new->sentto = server; +- new->rfd4 = NULL; ++ new->rfds = NULL; + #ifdef HAVE_IPV6 +- new->rfd6 = NULL; + #endif + new->frec_src.next = NULL; + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY); +@@ -1059,26 +1024,7 @@ void reply_query(int fd, int family, time_t now) + /* Don't resend this. */ + daemon->srv_save = NULL; + +- if (server->sfd) +- fd = server->sfd->fd; +- else +- { +- fd = -1; +-#ifdef HAVE_IPV6 +- if (server->addr.sa.sa_family == AF_INET6) +- { +- if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) +- fd = new->rfd6->fd; +- } +- else +-#endif +- { +- if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) +- fd = new->rfd4->fd; +- } +- } +- +- if (fd != -1) ++ if ((fd = allocate_rfd(&new->rfds, server)) != -1) + { + #ifdef HAVE_CONNTRACK + /* Copy connection mark of incoming query to outgoing connection. */ +@@ -1234,7 +1180,7 @@ void receive_query(struct listener *listen, time_t now) + + /* packet buffer overwritten */ + daemon->srv_save = NULL; +- ++ + dst_addr_4.s_addr = dst_addr.addr.addr4.s_addr = 0; + netmask.s_addr = 0; + +@@ -2066,10 +2012,9 @@ static struct frec *allocate_frec(time_t now) + f->next = daemon->frec_list; + f->time = now; + f->sentto = NULL; +- f->rfd4 = NULL; ++ f->rfds = NULL; + f->flags = 0; + #ifdef HAVE_IPV6 +- f->rfd6 = NULL; + #endif + #ifdef HAVE_DNSSEC + f->dependent = NULL; +@@ -2082,46 +2027,192 @@ static struct frec *allocate_frec(time_t now) + return f; + } + +-struct randfd *allocate_rfd(int family) ++/* return a UDP socket bound to a random port, have to cope with straying into ++ occupied port nos and reserved ones. */ ++static int random_sock(struct server *s) + { +- static int finger = 0; +- int i; ++ int fd; ++ ++ if ((fd = socket(s->source_addr.sa.sa_family, SOCK_DGRAM, 0)) != -1) ++ { ++ if (local_bind(fd, &s->source_addr, s->interface, s->ifindex, 0)) ++ return fd; + ++ if (s->interface[0] == 0) ++ (void)prettyprint_addr(&s->source_addr, daemon->namebuff); ++ else ++ strcpy(daemon->namebuff, s->interface); ++ ++ my_syslog(LOG_ERR, _("failed to bind server socket to %s: %s"), ++ daemon->namebuff, strerror(errno)); ++ close(fd); ++ } ++ ++ return -1; ++} ++ ++/* compare source addresses and interface, serv2 can be null. */ ++static int server_isequal(const struct server *serv1, ++ const struct server *serv2) ++{ ++ return (serv2 && ++ serv2->ifindex == serv1->ifindex && ++ sockaddr_isequal(&serv2->source_addr, &serv1->source_addr) && ++ strncmp(serv2->interface, serv1->interface, IF_NAMESIZE) == 0); ++} ++ ++/* fdlp points to chain of randomfds already in use by transaction. ++ If there's already a suitable one, return it, else allocate a ++ new one and add it to the list. ++ ++ Not leaking any resources in the face of allocation failures ++ is rather convoluted here. ++ ++ Note that rfd->serv may be NULL, when a server goes away. ++*/ ++int allocate_rfd(struct randfd_list **fdlp, struct server *serv) ++{ ++ static int finger = 0; ++ int i, j = 0; ++ struct randfd_list *rfl; ++ struct randfd *rfd = NULL; ++ int fd = 0; ++ ++ /* If server has a pre-allocated fd, use that. */ ++ if (serv->sfd) ++ return serv->sfd->fd; ++ ++ /* existing suitable random port socket linked to this transaction? */ ++ for (rfl = *fdlp; rfl; rfl = rfl->next) ++ if (server_isequal(serv, rfl->rfd->serv)) ++ return rfl->rfd->fd; ++ ++ /* No. need new link. */ ++ if ((rfl = daemon->rfl_spare)) ++ daemon->rfl_spare = rfl->next; ++ else if (!(rfl = whine_malloc(sizeof(struct randfd_list)))) ++ return -1; ++ + /* limit the number of sockets we have open to avoid starvation of + (eg) TFTP. Once we have a reasonable number, randomness should be OK */ +- + for (i = 0; i < RANDOM_SOCKS; i++) + if (daemon->randomsocks[i].refcount == 0) + { +- if ((daemon->randomsocks[i].fd = random_sock(family)) == -1) +- break; +- +- daemon->randomsocks[i].refcount = 1; +- daemon->randomsocks[i].family = family; +- return &daemon->randomsocks[i]; ++ if ((fd = random_sock(serv)) != -1) ++ { ++ rfd = &daemon->randomsocks[i]; ++ rfd->serv = serv; ++ rfd->fd = fd; ++ rfd->refcount = 1; ++ } ++ break; + } +- ++ + /* No free ones or cannot get new socket, grab an existing one */ +- for (i = 0; i < RANDOM_SOCKS; i++) ++ if (!rfd) ++ for (j = 0; j < RANDOM_SOCKS; j++) ++ { ++ i = (j + finger) % RANDOM_SOCKS; ++ if (daemon->randomsocks[i].refcount != 0 && ++ server_isequal(serv, daemon->randomsocks[i].serv) && ++ daemon->randomsocks[i].refcount != 0xfffe) ++ { ++ finger = i + 1; ++ rfd = &daemon->randomsocks[i]; ++ rfd->refcount++; ++ break; ++ } ++ } ++ ++ if (j == RANDOM_SOCKS) + { +- int j = (i+finger) % RANDOM_SOCKS; +- if (daemon->randomsocks[j].refcount != 0 && +- daemon->randomsocks[j].family == family && +- daemon->randomsocks[j].refcount != 0xffff) ++ struct randfd_list *rfl_poll; ++ ++ /* there are no free slots, and non with the same parameters we can piggy-back on. ++ We're going to have to allocate a new temporary record, distinguished by ++ refcount == 0xffff. This will exist in the frec randfd list, never be shared, ++ and be freed when no longer in use. It will also be held on ++ the daemon->rfl_poll list so the poll system can find it. */ ++ ++ if ((rfl_poll = daemon->rfl_spare)) ++ daemon->rfl_spare = rfl_poll->next; ++ else ++ rfl_poll = whine_malloc(sizeof(struct randfd_list)); ++ ++ if (!rfl_poll || ++ !(rfd = whine_malloc(sizeof(struct randfd))) || ++ (fd = random_sock(serv)) == -1) + { +- finger = j; +- daemon->randomsocks[j].refcount++; +- return &daemon->randomsocks[j]; ++ ++ /* Don't leak anything we may already have */ ++ rfl->next = daemon->rfl_spare; ++ daemon->rfl_spare = rfl; ++ ++ if (rfl_poll) ++ { ++ rfl_poll->next = daemon->rfl_spare; ++ daemon->rfl_spare = rfl_poll; ++ } ++ ++ if (rfd) ++ free(rfd); ++ ++ return -1; /* doom */ + } +- } + +- return NULL; /* doom */ ++ /* Note rfd->serv not set here, since it's not reused */ ++ rfd->fd = fd; ++ rfd->refcount = 0xffff; /* marker for temp record */ ++ ++ rfl_poll->rfd = rfd; ++ rfl_poll->next = daemon->rfl_poll; ++ daemon->rfl_poll = rfl_poll; ++ } ++ ++ rfl->rfd = rfd; ++ rfl->next = *fdlp; ++ *fdlp = rfl; ++ ++ return rfl->rfd->fd; + } + +-void free_rfd(struct randfd *rfd) ++void free_rfds(struct randfd_list **fdlp) + { +- if (rfd && --(rfd->refcount) == 0) +- close(rfd->fd); ++ struct randfd_list *tmp, *rfl, *poll, *next, **up; ++ ++ for (rfl = *fdlp; rfl; rfl = tmp) ++ { ++ if (rfl->rfd->refcount == 0xffff || --(rfl->rfd->refcount) == 0) ++ close(rfl->rfd->fd); ++ ++ /* temporary overflow record */ ++ if (rfl->rfd->refcount == 0xffff) ++ { ++ free(rfl->rfd); ++ ++ /* go through the link of all these by steam to delete. ++ This list is expected to be almost always empty. */ ++ for (poll = daemon->rfl_poll, up = &daemon->rfl_poll; poll; poll = next) ++ { ++ next = poll->next; ++ ++ if (poll->rfd == rfl->rfd) ++ { ++ *up = poll->next; ++ poll->next = daemon->rfl_spare; ++ daemon->rfl_spare = poll; ++ } ++ else ++ up = &poll->next; ++ } ++ } ++ ++ tmp = rfl->next; ++ rfl->next = daemon->rfl_spare; ++ daemon->rfl_spare = rfl; ++ } ++ ++ *fdlp = NULL; + } + + static void free_frec(struct frec *f) +@@ -2137,14 +2228,11 @@ static void free_frec(struct frec *f) + } + + f->frec_src.next = NULL; +- free_rfd(f->rfd4); +- f->rfd4 = NULL; ++ free_rfds(&f->rfds); + f->sentto = NULL; + f->flags = 0; + + #ifdef HAVE_IPV6 +- free_rfd(f->rfd6); +- f->rfd6 = NULL; + #endif + + #ifdef HAVE_DNSSEC +@@ -2252,26 +2340,39 @@ struct frec *get_new_frec(time_t now, int *wait, int force) + } + + /* crc is all-ones if not known. */ +-static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash) ++static struct frec *lookup_frec(unsigned short id, int fd, void *hash) + { + struct frec *f; +- ++ struct server *s; ++ int type; ++ struct randfd_list *fdl; ++ + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->new_id == id && + (memcmp(hash, f->hash, HASH_SIZE) == 0)) + { + /* sent from random port */ +- if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd) +- return f; +- +- if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd) +- return f; +- +- /* sent to upstream from bound socket. */ +- if (f->sentto->sfd && f->sentto->sfd->fd == fd) ++ for (fdl = f->rfds; fdl; fdl = fdl->next) ++ if (fdl->rfd->fd == fd) + return f; ++ ++ /* Sent to upstream from socket associated with a server. ++ Note we have to iterate over all the possible servers, since they may ++ have different bound sockets. */ ++ type = f->sentto->flags & SERV_TYPE; ++ s = f->sentto; ++ do { ++ if ((type == (s->flags & SERV_TYPE)) && ++ (type != SERV_HAS_DOMAIN || ++ (s->domain && hostname_isequal(f->sentto->domain, s->domain))) && ++ !(s->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) && ++ s->sfd && s->sfd->fd == fd) ++ return f; ++ ++ s = s->next ? s->next : daemon->servers; ++ } while (s != f->sentto); + } +- ++ + return NULL; + } + +@@ -2317,31 +2418,27 @@ static struct frec *lookup_frec_by_query(void *hash, unsigned int flags) + void resend_query() + { + if (daemon->srv_save) +- { +- int fd; +- +- if (daemon->srv_save->sfd) +- fd = daemon->srv_save->sfd->fd; +- else if (daemon->rfd_save && daemon->rfd_save->refcount != 0) +- fd = daemon->rfd_save->fd; +- else +- return; +- +- while(retry_send(sendto(fd, daemon->packet, daemon->packet_len, 0, +- &daemon->srv_save->addr.sa, +- sa_len(&daemon->srv_save->addr)))); +- } ++ while(retry_send(sendto(daemon->fd_save, daemon->packet, daemon->packet_len, 0, ++ &daemon->srv_save->addr.sa, ++ sa_len(&daemon->srv_save->addr)))); + } + + /* A server record is going away, remove references to it */ + void server_gone(struct server *server) + { + struct frec *f; ++ int i; + + for (f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->sentto == server) + free_frec(f); +- ++ ++ /* If any random socket refers to this server, NULL the reference. ++ No more references to the socket will be created in the future. */ ++ for (i = 0; i < RANDOM_SOCKS; i++) ++ if (daemon->randomsocks[i].refcount != 0 && daemon->randomsocks[i].serv == server) ++ daemon->randomsocks[i].serv = NULL; ++ + if (daemon->last_server == server) + daemon->last_server = NULL; + +diff --git a/src/loop.c b/src/loop.c +index 0b47a2f..98d0b9e 100644 +--- a/src/loop.c ++++ b/src/loop.c +@@ -22,6 +22,7 @@ static ssize_t loop_make_probe(u32 uid); + void loop_send_probes() + { + struct server *serv; ++ struct randfd_list *rfds = NULL; + + if (!option_bool(OPT_LOOP_DETECT)) + return; +@@ -34,29 +35,22 @@ void loop_send_probes() + { + ssize_t len = loop_make_probe(serv->uid); + int fd; +- struct randfd *rfd = NULL; + +- if (serv->sfd) +- fd = serv->sfd->fd; +- else +- { +- if (!(rfd = allocate_rfd(serv->addr.sa.sa_family))) +- continue; +- fd = rfd->fd; +- } +- ++ if ((fd = allocate_rfd(&rfds, serv)) == -1) ++ continue; ++ + while (retry_send(sendto(fd, daemon->packet, len, 0, + &serv->addr.sa, sa_len(&serv->addr)))); +- +- free_rfd(rfd); + } ++ ++ free_rfds(&rfds); + } + + static ssize_t loop_make_probe(u32 uid) + { + struct dns_header *header = (struct dns_header *)daemon->packet; + unsigned char *p = (unsigned char *)(header+1); +- ++ + /* packet buffer overwritten */ + daemon->srv_save = NULL; + +diff --git a/src/network.c b/src/network.c +index 47caf38..4eda1fd 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -639,7 +639,8 @@ int enumerate_interfaces(int reset) + #ifdef HAVE_AUTH + struct auth_zone *zone; + #endif +- ++ struct server *serv; ++ + /* Do this max once per select cycle - also inhibits netlink socket use + in TCP child processes. */ + +@@ -657,6 +658,13 @@ int enumerate_interfaces(int reset) + if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return 0; + ++ /* iface indexes can change when interfaces are created/destroyed. ++ We use them in the main forwarding control path, when the path ++ to a server is specified by an interface, so cache them. ++ Update the cache here. */ ++ for (serv = daemon->servers; serv; serv = serv->next) ++ serv->ifindex = if_nametoindex(serv->interface); ++ + again: + /* Mark interfaces for garbage collection */ + for (iface = daemon->interfaces; iface; iface = iface->next) +@@ -754,7 +762,7 @@ again: + + errno = errsave; + spare = param.spare; +- ++ + return ret; + } + +@@ -893,10 +901,10 @@ int tcp_interface(int fd, int af) + /* use mshdr so that the CMSDG_* macros are available */ + msg.msg_control = daemon->packet; + msg.msg_controllen = len = daemon->packet_buff_sz; +- ++ + /* we overwrote the buffer... */ +- daemon->srv_save = NULL; +- ++ daemon->srv_save = NULL; ++ + if (af == AF_INET) + { + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 && +@@ -1228,61 +1236,6 @@ void join_multicast(int dienow) + } + #endif + +-/* return a UDP socket bound to a random port, have to cope with straying into +- occupied port nos and reserved ones. */ +-int random_sock(int family) +-{ +- int fd; +- +- if ((fd = socket(family, SOCK_DGRAM, 0)) != -1) +- { +- union mysockaddr addr; +- unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1; +- int tries = ports_avail < 30 ? 3 * ports_avail : 100; +- +- memset(&addr, 0, sizeof(addr)); +- addr.sa.sa_family = family; +- +- /* don't loop forever if all ports in use. */ +- +- if (fix_fd(fd)) +- while(tries--) +- { +- unsigned short port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); +- +- if (family == AF_INET) +- { +- addr.in.sin_addr.s_addr = INADDR_ANY; +- addr.in.sin_port = port; +-#ifdef HAVE_SOCKADDR_SA_LEN +- addr.in.sin_len = sizeof(struct sockaddr_in); +-#endif +- } +-#ifdef HAVE_IPV6 +- else +- { +- addr.in6.sin6_addr = in6addr_any; +- addr.in6.sin6_port = port; +-#ifdef HAVE_SOCKADDR_SA_LEN +- addr.in6.sin6_len = sizeof(struct sockaddr_in6); +-#endif +- } +-#endif +- +- if (bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == 0) +- return fd; +- +- if (errno != EADDRINUSE && errno != EACCES) +- break; +- } +- +- close(fd); +- } +- +- return -1; +-} +- +- + int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp) + { + union mysockaddr addr_copy = *addr; +@@ -1328,39 +1281,34 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind + return 1; + } + +-static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) ++static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsigned int ifindex) + { + struct serverfd *sfd; +- unsigned int ifindex = 0; + int errsave; + + /* when using random ports, servers which would otherwise use +- the INADDR_ANY/port0 socket have sfd set to NULL */ +- if (!daemon->osport && intname[0] == 0) ++ the INADDR_ANY/port0 socket have sfd set to NULL, this is ++ anything without an explictly set source port. */ ++ if (!daemon->osport) + { + errno = 0; + + if (addr->sa.sa_family == AF_INET && +- addr->in.sin_addr.s_addr == INADDR_ANY && + addr->in.sin_port == htons(0)) + return NULL; + + #ifdef HAVE_IPV6 + if (addr->sa.sa_family == AF_INET6 && +- memcmp(&addr->in6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 && + addr->in6.sin6_port == htons(0)) + return NULL; + #endif + } + +- if (intname && strlen(intname) != 0) +- ifindex = if_nametoindex(intname); /* index == 0 when not binding to an interface */ +- + /* may have a suitable one already */ + for (sfd = daemon->sfds; sfd; sfd = sfd->next ) +- if (sockaddr_isequal(&sfd->source_addr, addr) && +- strcmp(intname, sfd->interface) == 0 && +- ifindex == sfd->ifindex) ++ if (ifindex == sfd->ifindex && ++ sockaddr_isequal(&sfd->source_addr, addr) && ++ strcmp(intname, sfd->interface) == 0) + return sfd; + + /* need to make a new one. */ +@@ -1408,7 +1356,7 @@ void pre_allocate_sfds(void) + #ifdef HAVE_SOCKADDR_SA_LEN + addr.in.sin_len = sizeof(struct sockaddr_in); + #endif +- allocate_sfd(&addr, ""); ++ allocate_sfd(&addr, "", 0); + #ifdef HAVE_IPV6 + memset(&addr, 0, sizeof(addr)); + addr.in6.sin6_family = AF_INET6; +@@ -1417,13 +1365,13 @@ void pre_allocate_sfds(void) + #ifdef HAVE_SOCKADDR_SA_LEN + addr.in6.sin6_len = sizeof(struct sockaddr_in6); + #endif +- allocate_sfd(&addr, ""); ++ allocate_sfd(&addr, "", 0); + #endif + } + + for (srv = daemon->servers; srv; srv = srv->next) + if (!(srv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)) && +- !allocate_sfd(&srv->source_addr, srv->interface) && ++ !allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) && + errno != 0 && + option_bool(OPT_NOWILD)) + { +@@ -1631,7 +1579,7 @@ void check_servers(void) + + /* Do we need a socket set? */ + if (!serv->sfd && +- !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface)) && ++ !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) && + errno != 0) + { + my_syslog(LOG_WARNING, +diff --git a/src/option.c b/src/option.c +index 79122df..abc5a48 100644 +--- a/src/option.c ++++ b/src/option.c +@@ -795,7 +795,8 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a + if (interface_opt) + { + #if defined(SO_BINDTODEVICE) +- safe_strncpy(interface, interface_opt, IF_NAMESIZE); ++ safe_strncpy(interface, source, IF_NAMESIZE); ++ source = interface_opt; + #else + return _("interface binding not supported"); + #endif +diff --git a/src/tftp.c b/src/tftp.c +index f2eccbc..ba9833e 100644 +--- a/src/tftp.c ++++ b/src/tftp.c +@@ -96,7 +96,7 @@ void tftp_request(struct listener *listen, time_t now) + + if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2) + return; +- ++ + /* Can always get recvd interface for IPv6 */ + if (!check_dest) + { +@@ -566,7 +566,7 @@ void check_tftp_listeners(time_t now) + { + /* we overwrote the buffer... */ + daemon->srv_save = NULL; +- ++ + if ((len = recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)) >= (ssize_t)sizeof(struct ack)) + { + if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block) +@@ -609,7 +609,7 @@ void check_tftp_listeners(time_t now) + + /* we overwrote the buffer... */ + daemon->srv_save = NULL; +- ++ + if ((len = get_block(daemon->packet, transfer)) == -1) + { + len = tftp_err_oops(daemon->packet, transfer->file->filename); +diff --git a/src/util.c b/src/util.c +index 6287529..d016db6 100644 +--- a/src/util.c ++++ b/src/util.c +@@ -311,7 +311,7 @@ void *whine_malloc(size_t size) + return ret; + } + +-int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2) ++int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2) + { + if (s1->sa.sa_family == s2->sa.sa_family) + { +-- +2.26.2 + diff --git a/SPECS/dnsmasq.spec b/SPECS/dnsmasq.spec index 37482e0..3f116de 100644 --- a/SPECS/dnsmasq.spec +++ b/SPECS/dnsmasq.spec @@ -13,7 +13,7 @@ Name: dnsmasq Version: 2.79 -Release: 15%{?extraversion:.%{extraversion}}%{?dist} +Release: 19%{?extraversion:.%{extraversion}}%{?dist} Summary: A lightweight DHCP/caching DNS server License: GPLv2 or GPLv3 @@ -57,6 +57,16 @@ Patch23: dnsmasq-2.79-CVE-2020-25684.patch Patch24: dnsmasq-2.79-CVE-2020-25685.patch Patch25: dnsmasq-2.79-CVE-2020-25686.patch Patch26: dnsmasq-2.79-CVE-2020-25686-2.patch +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=3f535da79e7a42104543ef5c7b5fa2bed819a78b +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=04490bf622ac84891aad6f2dd2edf83725decdee +Patch27: dnsmasq-2.79-mixed-family-failed.patch +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=b2ed691eb3ca6488a8878f5f3dd950a07b14a9db +Patch28: dnsmasq-2.81-netlink-table.patch +Patch29: dnsmasq-2.84-bind-dynamic-netlink.patch +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=74d4fcd756a85bc1823232ea74334f7ccfb9d5d2 +Patch30: dnsmasq-2.85-CVE-2021-3448.patch +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=03212e533b1e07aba30d2f4112009dc3af867ea5 +Patch31: dnsmasq-2.80-man-nameing.patch # This is workaround to nettle bug #1549190 # https://bugzilla.redhat.com/show_bug.cgi?id=1549190 @@ -116,6 +126,11 @@ server's leases. %patch24 -p1 -b .CVE-2020-25685 %patch25 -p1 -b .CVE-2020-25686 %patch26 -p1 -b .CVE-2020-25686-2 +%patch27 -p1 -b .rh1921153 +%patch28 -p1 -b .rh1887649-table +%patch29 -p1 -b .rh1887649 +%patch30 -p1 -b .CVE-2021-3448 +%patch31 -p1 -b .rh1947039 # use /var/lib/dnsmasq instead of /var/lib/misc for file in dnsmasq.conf.example man/dnsmasq.8 man/es/dnsmasq.8 src/config.h; do @@ -215,6 +230,18 @@ install -Dpm 644 %{SOURCE2} %{buildroot}%{_sysusersdir}/dnsmasq.conf %{_mandir}/man1/dhcp_* %changelog +* Tue Jun 29 2021 Petr Menšík - 2.79-19 +- Correct typo in man page (#1947039) + +* Thu Mar 18 2021 Petr Menšík - 2.79-18 +- Properly randomize outgoing ports also with bound interface (CVE-2021-3448) + +* Fri Feb 12 2021 Petr Menšík - 2.79-17 +- Fix sporadic bind-dynamic failures (#1887649) + +* Wed Jan 27 2021 Petr Menšík - 2.79-16 +- Fix network errors on queries both from ipv4 and ipv6 (#1921153) + * Wed Nov 25 2020 Petr Menšík - 2.79-15 - Fix various issues in dnssec validation (CVE-2020-25681) - Accept responses only on correct sockets (CVE-2020-25684)