From 8705058ce99b52380b440ff9ca39ebf1ba116915 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Wed, 21 Jul 2021 10:21:24 +0000 Subject: [PATCH] import dnsmasq-2.79-18.el8 --- .../dnsmasq-2.79-mixed-family-failed.patch | 79 ++ SOURCES/dnsmasq-2.81-netlink-table.patch | 45 + .../dnsmasq-2.84-bind-dynamic-netlink.patch | 237 ++++ SOURCES/dnsmasq-2.85-CVE-2021-3448.patch | 1056 +++++++++++++++++ SPECS/dnsmasq.spec | 23 +- 5 files changed, 1439 insertions(+), 1 deletion(-) create mode 100644 SOURCES/dnsmasq-2.79-mixed-family-failed.patch create mode 100644 SOURCES/dnsmasq-2.81-netlink-table.patch create mode 100644 SOURCES/dnsmasq-2.84-bind-dynamic-netlink.patch create mode 100644 SOURCES/dnsmasq-2.85-CVE-2021-3448.patch 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.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..9d1a962 100644 --- a/SPECS/dnsmasq.spec +++ b/SPECS/dnsmasq.spec @@ -13,7 +13,7 @@ Name: dnsmasq Version: 2.79 -Release: 15%{?extraversion:.%{extraversion}}%{?dist} +Release: 18%{?extraversion:.%{extraversion}}%{?dist} Summary: A lightweight DHCP/caching DNS server License: GPLv2 or GPLv3 @@ -57,6 +57,14 @@ 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 # This is workaround to nettle bug #1549190 # https://bugzilla.redhat.com/show_bug.cgi?id=1549190 @@ -116,6 +124,10 @@ 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 # 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 +227,15 @@ install -Dpm 644 %{SOURCE2} %{buildroot}%{_sysusersdir}/dnsmasq.conf %{_mandir}/man1/dhcp_* %changelog +* 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)