From d571d74b63382f52572f2b060c8caf867dea76dc Mon Sep 17 00:00:00 2001 From: Petr Mensik Date: Wed, 31 Jul 2019 17:23:45 +0200 Subject: [PATCH] Fix TCP listener after interface is recreated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed commit of the following: commit 023433cad60a47bf83037cd8f8d403d1086163e0 Author: Petr Menšík Date: Mon Jul 15 17:16:44 2019 +0200 Remove duplicate address family from listener Since address already contain family, remove separate family from listener. Use now family from address itself. commit d9b9235139b15a953ba9220e1d33a62d853f4e73 Author: Petr Menšík Date: Mon Jul 15 17:13:12 2019 +0200 Handle listening on duplicate addresses Save listening address into listener. Use it to find existing listeners before creating new one. If it exist, increase just used counter. Release only listeners not already used. Duplicates family in listener. commit a9836313966ecb0689c52bbc4ddbc7a78f7bb677 Author: Petr Mensik Date: Tue Jul 9 14:05:59 2019 +0200 Cleanup interfaces no longer available Clean addresses and interfaces not found after enumerate. Free unused records to speed up checking active interfaces and reduce used memory. commit 1474c5146b6278fc61df385a8e08b23ccc11b1ab Author: Petr Mensik Date: Wed Jul 3 17:02:16 2019 +0200 Compare address and interface index for allowed interface If interface is recreated with the same address but different index, it would not change any other parameter. Test also address family on incoming TCP queries. commit 94b2f5d33e043652a00b8c70e573994925cd26fe Author: Petr Mensik Date: Thu Jul 4 20:28:08 2019 +0200 Log listening on new interfaces Log in debug mode listening on interfaces. They can be dynamically found, include interface number, since it is checked on TCP connections. Print also addresses found on them. --- src/dnsmasq.c | 3 +- src/dnsmasq.h | 3 +- src/forward.c | 27 +++++----- src/network.c | 147 +++++++++++++++++++++++++++++++++++++++++--------- src/tftp.c | 29 +++++----- 5 files changed, 155 insertions(+), 54 deletions(-) diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 769e063..4755125 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -1820,7 +1820,8 @@ static void check_dns_listeners(time_t now) addr.addr4 = tcp_addr.in.sin_addr; for (iface = daemon->interfaces; iface; iface = iface->next) - if (iface->index == if_index) + if (iface->index == if_index && + iface->addr.sa.sa_family == tcp_addr.sa.sa_family) break; if (!iface && !loopback_exception(listener->tcpfd, tcp_addr.sa.sa_family, &addr, intr_name)) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index c46bfeb..17b5f4e 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -569,7 +569,8 @@ struct irec { }; struct listener { - int fd, tcpfd, tftpfd, family; + int fd, tcpfd, tftpfd, used; + union mysockaddr addr; struct irec *iface; /* only sometimes valid for non-wildcard */ struct listener *next; }; diff --git a/src/forward.c b/src/forward.c index 77059ed..043c2e2 100644 --- a/src/forward.c +++ b/src/forward.c @@ -1279,8 +1279,9 @@ void receive_query(struct listener *listen, time_t now) CMSG_SPACE(sizeof(struct sockaddr_dl))]; #endif } control_u; + int family = listen->addr.sa.sa_family; /* Can always get recvd interface for IPv6 */ - int check_dst = !option_bool(OPT_NOWILD) || listen->family == AF_INET6; + int check_dst = !option_bool(OPT_NOWILD) || family == AF_INET6; /* packet buffer overwritten */ daemon->srv_save = NULL; @@ -1292,7 +1293,7 @@ void receive_query(struct listener *listen, time_t now) { auth_dns = listen->iface->dns_auth; - if (listen->family == AF_INET) + if (family == AF_INET) { dst_addr_4 = dst_addr.addr4 = listen->iface->addr.in.sin_addr; netmask = listen->iface->netmask; @@ -1322,9 +1323,9 @@ void receive_query(struct listener *listen, time_t now) information disclosure. */ memset(daemon->packet + n, 0, daemon->edns_pktsz - n); - source_addr.sa.sa_family = listen->family; + source_addr.sa.sa_family = family; - if (listen->family == AF_INET) + if (family == AF_INET) { /* Source-port == 0 is an error, we can't send back to that. http://www.ietf.org/mail-archive/web/dnsop/current/msg11441.html */ @@ -1344,7 +1345,7 @@ void receive_query(struct listener *listen, time_t now) { struct addrlist *addr; - if (listen->family == AF_INET6) + if (family == AF_INET6) { for (addr = daemon->interface_addrs; addr; addr = addr->next) if ((addr->flags & ADDRLIST_IPV6) && @@ -1382,7 +1383,7 @@ void receive_query(struct listener *listen, time_t now) return; #if defined(HAVE_LINUX_NETWORK) - if (listen->family == AF_INET) + if (family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) { @@ -1395,7 +1396,7 @@ void receive_query(struct listener *listen, time_t now) if_index = p.p->ipi_ifindex; } #elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) - if (listen->family == AF_INET) + if (family == AF_INET) { for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) { @@ -1420,7 +1421,7 @@ void receive_query(struct listener *listen, time_t now) } #endif - if (listen->family == AF_INET6) + if (family == AF_INET6) { for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) @@ -1441,16 +1442,16 @@ void receive_query(struct listener *listen, time_t now) if (!indextoname(listen->fd, if_index, ifr.ifr_name)) return; - if (!iface_check(listen->family, &dst_addr, ifr.ifr_name, &auth_dns)) + if (!iface_check(family, &dst_addr, ifr.ifr_name, &auth_dns)) { if (!option_bool(OPT_CLEVERBIND)) enumerate_interfaces(0); - if (!loopback_exception(listen->fd, listen->family, &dst_addr, ifr.ifr_name) && - !label_exception(if_index, listen->family, &dst_addr)) + if (!loopback_exception(listen->fd, family, &dst_addr, ifr.ifr_name) && + !label_exception(if_index, family, &dst_addr)) return; } - if (listen->family == AF_INET && option_bool(OPT_LOCALISE)) + if (family == AF_INET && option_bool(OPT_LOCALISE)) { struct irec *iface; @@ -1495,7 +1496,7 @@ void receive_query(struct listener *listen, time_t now) #endif char *types = querystr(auth_dns ? "auth" : "query", type); - if (listen->family == AF_INET) + if (family == AF_INET) log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, (union all_addr *)&source_addr.in.sin_addr, types); else diff --git a/src/network.c b/src/network.c index 881d823..8c4b3bb 100644 --- a/src/network.c +++ b/src/network.c @@ -388,10 +388,11 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, /* check whether the interface IP has been added already we call this routine multiple times. */ for (iface = daemon->interfaces; iface; iface = iface->next) - if (sockaddr_isequal(&iface->addr, addr)) + if (sockaddr_isequal(&iface->addr, addr) && iface->index == if_index) { iface->dad = !!(iface_flags & IFACE_TENTATIVE); iface->found = 1; /* for garbage collection */ + iface->netmask = netmask; return 1; } @@ -532,7 +533,82 @@ static int iface_allowed_v4(struct in_addr local, int if_index, char *label, return iface_allowed((struct iface_param *)vparam, if_index, label, &addr, netmask, prefix, 0); } - + +/* + * Clean old interfaces no longer found. + */ +static void clean_interfaces() +{ + struct irec *iface; + struct irec **up = &daemon->interfaces; + + for (iface = *up; iface; iface = *up) + { + if (!iface->found && !iface->done) + { + *up = iface->next; + free(iface->name); + free(iface); + } + else + { + up = &iface->next; + } + } +} + +/** Release listener if no other interface needs it. + * + * @return 1 if released, 0 if still required + */ +static int release_listener(struct listener *l) +{ + if (l->used > 1) + { + struct irec *iface; + for (iface = daemon->interfaces; iface; iface = iface->next) + if (iface->done && sockaddr_isequal(&l->addr, &iface->addr)) + { + if (iface->found) + { + /* update listener to point to active interface instead */ + if (!l->iface->found) + l->iface = iface; + } + else + { + l->used--; + iface->done = 0; + } + } + + /* Someone is still using this listener, skip its deletion */ + if (l->used > 0) + return 0; + } + + if (l->iface->done) + { + int port; + + port = prettyprint_addr(&l->iface->addr, daemon->addrbuff); + my_syslog(LOG_DEBUG, _("stopped listening on %s(#%d): %s port %d"), + l->iface->name, l->iface->index, daemon->addrbuff, port); + /* In case it ever returns */ + l->iface->done = 0; + } + + if (l->fd != -1) + close(l->fd); + if (l->tcpfd != -1) + close(l->tcpfd); + if (l->tftpfd != -1) + close(l->tftpfd); + + free(l); + return 1; +} + int enumerate_interfaces(int reset) { static struct addrlist *spare = NULL; @@ -630,6 +706,7 @@ int enumerate_interfaces(int reset) in OPT_CLEVERBIND mode, that at listener will just disappear after a call to enumerate_interfaces, this is checked OK on all calls. */ struct listener *l, *tmp, **up; + int freed = 0; for (up = &daemon->listeners, l = daemon->listeners; l; l = tmp) { @@ -637,25 +714,17 @@ int enumerate_interfaces(int reset) if (!l->iface || l->iface->found) up = &l->next; - else + else if (release_listener(l)) { - *up = l->next; - - /* In case it ever returns */ - l->iface->done = 0; - - if (l->fd != -1) - close(l->fd); - if (l->tcpfd != -1) - close(l->tcpfd); - if (l->tftpfd != -1) - close(l->tftpfd); - - free(l); + *up = tmp; + freed = 1; } } + + if (freed) + clean_interfaces(); } - + errno = errsave; spare = param.spare; @@ -893,10 +962,11 @@ static struct listener *create_listeners(union mysockaddr *addr, int do_tftp, in { l = safe_malloc(sizeof(struct listener)); l->next = NULL; - l->family = addr->sa.sa_family; l->fd = fd; l->tcpfd = tcpfd; - l->tftpfd = tftpfd; + l->tftpfd = tftpfd; + l->addr = *addr; + l->used = 1; l->iface = NULL; } @@ -935,20 +1005,43 @@ void create_wildcard_listeners(void) daemon->listeners = l; } +static struct listener *find_listener(union mysockaddr *addr) +{ + struct listener *l; + for (l = daemon->listeners; l; l = l->next) + if (sockaddr_isequal(&l->addr, addr)) + return l; + return NULL; +} + void create_bound_listeners(int dienow) { struct listener *new; struct irec *iface; struct iname *if_tmp; + struct listener *existing; for (iface = daemon->interfaces; iface; iface = iface->next) - if (!iface->done && !iface->dad && iface->found && - (new = create_listeners(&iface->addr, iface->tftp_ok, dienow))) + if (!iface->done && !iface->dad && iface->found) { - new->iface = iface; - new->next = daemon->listeners; - daemon->listeners = new; - iface->done = 1; + existing = find_listener(&iface->addr); + if (existing) + { + iface->done = 1; + existing->used++; /* increase usage counter */ + } + else if ((new = create_listeners(&iface->addr, iface->tftp_ok, dienow))) + { + int port; + + new->iface = iface; + new->next = daemon->listeners; + daemon->listeners = new; + iface->done = 1; + port = prettyprint_addr(&iface->addr, daemon->addrbuff); + my_syslog(LOG_DEBUG, _("listening on %s(#%d): %s port %d"), + iface->name, iface->index, daemon->addrbuff, port); + } } /* Check for --listen-address options that haven't been used because there's @@ -966,8 +1059,12 @@ void create_bound_listeners(int dienow) if (!if_tmp->used && (new = create_listeners(&if_tmp->addr, !!option_bool(OPT_TFTP), dienow))) { + int port; + new->next = daemon->listeners; daemon->listeners = new; + port = prettyprint_addr(&if_tmp->addr, daemon->addrbuff); + my_syslog(LOG_DEBUG, _("listening on %s port %d"), daemon->addrbuff, port); } } diff --git a/src/tftp.c b/src/tftp.c index 4c18577..fdd2855 100644 --- a/src/tftp.c +++ b/src/tftp.c @@ -61,8 +61,9 @@ void tftp_request(struct listener *listen, time_t now) char *prefix = daemon->tftp_prefix; struct tftp_prefix *pref; union all_addr addra; + int family = listen->addr.sa.sa_family; /* Can always get recvd interface for IPv6 */ - int check_dest = !option_bool(OPT_NOWILD) || listen->family == AF_INET6; + int check_dest = !option_bool(OPT_NOWILD) || family == AF_INET6; union { struct cmsghdr align; /* this ensures alignment */ char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; @@ -121,10 +122,10 @@ void tftp_request(struct listener *listen, time_t now) if (msg.msg_controllen < sizeof(struct cmsghdr)) return; - addr.sa.sa_family = listen->family; + addr.sa.sa_family = family; #if defined(HAVE_LINUX_NETWORK) - if (listen->family == AF_INET) + if (family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) { @@ -138,7 +139,7 @@ void tftp_request(struct listener *listen, time_t now) } #elif defined(HAVE_SOLARIS_NETWORK) - if (listen->family == AF_INET) + if (family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) { union { @@ -154,7 +155,7 @@ void tftp_request(struct listener *listen, time_t now) } #elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) - if (listen->family == AF_INET) + if (family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) { union { @@ -171,7 +172,7 @@ void tftp_request(struct listener *listen, time_t now) #endif - if (listen->family == AF_INET6) + if (family == AF_INET6) { for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) @@ -194,7 +195,7 @@ void tftp_request(struct listener *listen, time_t now) addra.addr4 = addr.in.sin_addr; - if (listen->family == AF_INET6) + if (family == AF_INET6) addra.addr6 = addr.in6.sin6_addr; if (daemon->tftp_interfaces) @@ -210,12 +211,12 @@ void tftp_request(struct listener *listen, time_t now) else { /* Do the same as DHCP */ - if (!iface_check(listen->family, &addra, name, NULL)) + if (!iface_check(family, &addra, name, NULL)) { if (!option_bool(OPT_CLEVERBIND)) enumerate_interfaces(0); - if (!loopback_exception(listen->tftpfd, listen->family, &addra, name) && - !label_exception(if_index, listen->family, &addra)) + if (!loopback_exception(listen->tftpfd, family, &addra, name) && + !label_exception(if_index, family, &addra)) return; } @@ -281,7 +282,7 @@ void tftp_request(struct listener *listen, time_t now) prefix = pref->prefix; } - if (listen->family == AF_INET) + if (family == AF_INET) { addr.in.sin_port = htons(port); #ifdef HAVE_SOCKADDR_SA_LEN @@ -304,7 +305,7 @@ void tftp_request(struct listener *listen, time_t now) if (option_bool(OPT_SINGLE_PORT)) transfer->sockfd = listen->tftpfd; - else if ((transfer->sockfd = socket(listen->family, SOCK_DGRAM, 0)) == -1) + else if ((transfer->sockfd = socket(family, SOCK_DGRAM, 0)) == -1) { free(transfer); return; @@ -337,7 +338,7 @@ void tftp_request(struct listener *listen, time_t now) { if (++port <= daemon->end_tftp_port) { - if (listen->family == AF_INET) + if (family == AF_INET) addr.in.sin_port = htons(port); else addr.in6.sin6_port = htons(port); @@ -375,7 +376,7 @@ void tftp_request(struct listener *listen, time_t now) if ((opt = next(&p, end)) && !option_bool(OPT_TFTP_NOBLOCK)) { /* 32 bytes for IP, UDP and TFTP headers, 52 bytes for IPv6 */ - int overhead = (listen->family == AF_INET) ? 32 : 52; + int overhead = (family == AF_INET) ? 32 : 52; transfer->blocksize = atoi(opt); if (transfer->blocksize < 1) transfer->blocksize = 1; -- 2.21.1