| /* $Id: query.c 114 2007-02-08 16:42:22Z lennart $ */ |
| |
| /*** |
| This file is part of nss-mdns. |
| |
| nss-mdns is free software; you can redistribute it and/or modify it |
| under the terms of the GNU Lesser General Public License as |
| published by the Free Software Foundation; either version 2 of the |
| License, or (at your option) any later version. |
| |
| nss-mdns is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with nss-mdns; if not, write to the Free Software |
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
| USA. |
| ***/ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <inttypes.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <assert.h> |
| #include <sys/time.h> |
| #include <net/if.h> |
| #include <sys/ioctl.h> |
| #include <stdlib.h> |
| |
| #include "dns.h" |
| #include "util.h" |
| #include "query.h" |
| |
| static const usec_t retry_ms[] = { 500000, 1000000, 0 }; |
| |
| static uint16_t get_random_id(void) { |
| uint16_t id = 0; |
| int ok = 0, fd; |
| |
| if ((fd = open("/dev/urandom", O_RDONLY)) >= 0) { |
| ok = read(fd, &id, sizeof(id)) == 2; |
| close(fd); |
| } |
| |
| if (!ok) |
| ok = random() & 0xFFFF; |
| |
| return id; |
| } |
| |
| static void mdns_mcast_group(struct sockaddr_in *ret_sa) { |
| assert(ret_sa); |
| |
| memset(ret_sa, 0, sizeof(struct sockaddr_in)); |
| |
| ret_sa->sin_family = AF_INET; |
| ret_sa->sin_port = htons(5353); |
| ret_sa->sin_addr.s_addr = inet_addr("224.0.0.251"); |
| } |
| |
| int mdns_open_socket(void) { |
| struct sockaddr_in sa; |
| int fd = -1, ttl, yes; |
| |
| if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) |
| goto fail; |
| |
| ttl = 255; |
| if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) |
| goto fail; |
| |
| sa.sin_family = AF_INET; |
| sa.sin_port = 0; |
| sa.sin_addr.s_addr = INADDR_ANY; |
| |
| if (bind(fd, (struct sockaddr*) &sa, sizeof(sa)) < 0) |
| goto fail; |
| |
| #ifdef IP_PKTINFO |
| yes = 1; |
| if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0) |
| goto fail; |
| #else |
| |
| #ifdef IP_RECVINTERFACE |
| yes = 1; |
| if (setsockopt (fd, IPPROTO_IP, IP_RECVINTERFACE, &yes, sizeof(yes)) < 0) |
| goto fail; |
| #elif defined(IP_RECVIF) |
| yes = 1; |
| if (setsockopt (fd, IPPROTO_IP, IP_RECVIF, &yes, sizeof(yes)) < 0) |
| goto fail; |
| #endif |
| |
| #ifdef IP_RECVDSTADDR |
| yes = 1; |
| if (setsockopt (fd, IPPROTO_IP, IP_RECVDSTADDR, &yes, sizeof(yes)) < 0) |
| goto fail; |
| #endif |
| |
| #endif /* IP_PKTINFO */ |
| |
| #ifdef IP_RECVTTL |
| yes = 1; |
| if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(yes)) < 0) |
| goto fail; |
| #endif |
| |
| if (set_cloexec(fd) < 0) |
| goto fail; |
| |
| if (set_nonblock(fd) < 0) |
| goto fail; |
| |
| return fd; |
| |
| fail: |
| if (fd >= 0) |
| close(fd); |
| |
| return -1; |
| } |
| |
| static int send_dns_packet(int fd, struct dns_packet *p) { |
| struct sockaddr_in sa; |
| struct msghdr msg; |
| struct iovec io; |
| #ifdef IP_PKTINFO |
| struct cmsghdr *cmsg; |
| uint8_t cmsg_data[CMSG_SPACE(sizeof(struct in_pktinfo))]; |
| struct in_pktinfo *pkti; |
| #elif defined(IP_SENDSRCADDR) |
| struct cmsghdr *cmsg; |
| uint8_t cmsg_data[CMSG_SPACE(sizeof(struct in_addr))]; |
| struct in_addr *addr; |
| #endif |
| int i, n; |
| struct ifreq ifreq[32]; |
| struct ifconf ifconf; |
| int n_sent = 0; |
| int last_index = -1; |
| |
| assert(fd >= 0 && p); |
| assert(dns_packet_check_valid(p) >= 0); |
| |
| mdns_mcast_group(&sa); |
| |
| memset(&io, 0, sizeof(io)); |
| io.iov_base = p->data; |
| io.iov_len = p->size; |
| |
| memset(&msg, 0, sizeof(msg)); |
| msg.msg_name = &sa; |
| msg.msg_namelen = sizeof(sa); |
| msg.msg_iov = &io; |
| msg.msg_iovlen = 1; |
| msg.msg_flags = 0; |
| |
| #ifdef IP_PKTINFO |
| memset(cmsg_data, 0, sizeof(cmsg_data)); |
| msg.msg_control = cmsg_data; |
| msg.msg_controllen = sizeof(cmsg_data); |
| |
| cmsg = CMSG_FIRSTHDR(&msg); |
| cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); |
| cmsg->cmsg_level = IPPROTO_IP; |
| cmsg->cmsg_type = IP_PKTINFO; |
| |
| pkti = (struct in_pktinfo*) CMSG_DATA(cmsg); |
| |
| msg.msg_controllen = cmsg->cmsg_len; |
| #elif defined(IP_SENDSRCADDR) |
| memset(cmsg_data, 0, sizeof(cmsg_data)); |
| msg.msg_control = cmsg_data; |
| msg.msg_controllen = sizeof(cmsg_data); |
| |
| cmsg = CMSG_FIRSTHDR(&msg); |
| cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); |
| cmsg->cmsg_level = IPPROTO_IP; |
| cmsg->cmsg_type = IP_SENDSRCADDR; |
| |
| addr = (struct in_addr *)CMSG_DATA(cmsg); |
| |
| msg.msg_controllen = cmsg->cmsg_len; |
| #elif defined(__GNUC__) |
| #warning "FIXME: We need some code to set the outgoing interface/local address here if IP_PKTINFO/IP_SENDSRCADDR is not available" |
| #endif |
| |
| ifconf.ifc_req = ifreq; |
| ifconf.ifc_len = sizeof(ifreq); |
| |
| if (ioctl(fd, SIOCGIFCONF, &ifconf) < 0) |
| return -1; |
| |
| for (i = 0, n = ifconf.ifc_len/sizeof(struct ifreq); i < n; i++) { |
| struct sockaddr_in *ifsa; |
| u_int32_t s_addr; |
| |
| /* Check if this is the loopback device or any other invalid interface */ |
| ifsa = (struct sockaddr_in*) &ifreq[i].ifr_addr; |
| s_addr = htonl(ifsa->sin_addr.s_addr); |
| if (ifsa->sin_family != AF_INET || |
| s_addr == INADDR_LOOPBACK || |
| s_addr == INADDR_ANY || |
| s_addr == INADDR_BROADCAST) |
| continue; |
| |
| if (ioctl(fd, SIOCGIFFLAGS, &ifreq[i]) < 0) |
| continue; /* Since SIOCGIFCONF and this call is not |
| * issued in a transaction, we ignore errors |
| * here, since the interface may have vanished |
| * since that call */ |
| |
| /* Check whether this network interface supports multicasts and is up and running */ |
| if (!(ifreq[i].ifr_flags & IFF_MULTICAST) || |
| !(ifreq[i].ifr_flags & IFF_UP) || |
| !(ifreq[i].ifr_flags & IFF_RUNNING)) |
| continue; |
| |
| if (ioctl(fd, SIOCGIFINDEX, &ifreq[i]) < 0) |
| continue; /* See above why we ignore this error */ |
| |
| #ifdef IP_PKTINFO |
| /* Only send the the packet once per interface. We assume that |
| * multiple addresses assigned to the same interface follow |
| * immediately one after the other.*/ |
| if (last_index == ifreq[i].ifr_ifindex) |
| continue; |
| |
| last_index = pkti->ipi_ifindex = ifreq[i].ifr_ifindex; |
| #elif defined(IP_SENDSRCADDR) |
| addr->s_addr = ifsa->sin_addr.s_addr; |
| #elif defined(__GNUC__) |
| #warning "FIXME: We need some code to set the outgoing interface/local address here if IP_PKTINFO/IP_SENDSRCADDR is not available" |
| #endif |
| |
| for (;;) { |
| |
| if (sendmsg(fd, &msg, MSG_DONTROUTE) >= 0) |
| break; |
| |
| if (errno != EAGAIN) |
| return -1; |
| |
| if (wait_for_write(fd, NULL) < 0) |
| return -1; |
| } |
| |
| n_sent++; |
| } |
| |
| return n_sent; |
| } |
| |
| static int recv_dns_packet(int fd, struct dns_packet **ret_packet, uint8_t *ret_ttl, struct timeval *end) { |
| struct dns_packet *p= NULL; |
| struct msghdr msg; |
| struct iovec io; |
| int ret = -1; |
| uint8_t aux[64]; |
| assert(fd >= 0); |
| |
| if (!(p = dns_packet_new())) |
| return -1; /* OOM */ |
| |
| io.iov_base = p->data; |
| io.iov_len = sizeof(p->data); |
| |
| memset(&msg, 0, sizeof(msg)); |
| msg.msg_name = NULL; |
| msg.msg_namelen = 0; |
| msg.msg_iov = &io; |
| msg.msg_iovlen = 1; |
| msg.msg_control = aux; |
| msg.msg_controllen = sizeof(aux); |
| msg.msg_flags = 0; |
| |
| for (;;) { |
| ssize_t l; |
| int r; |
| |
| if ((l = recvmsg(fd, &msg, 0)) >= 0) { |
| struct cmsghdr *cmsg; |
| *ret_ttl = 0; |
| |
| for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg,cmsg)) { |
| #ifdef SOL_IP |
| if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_TTL) |
| #else |
| if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TTL) |
| #endif |
| { |
| *ret_ttl = (uint8_t) (*(uint32_t*) CMSG_DATA(cmsg)); |
| break; |
| } |
| } |
| |
| if (!cmsg) |
| goto fail; |
| |
| p->size = (size_t) l; |
| |
| *ret_packet = p; |
| return 0; |
| } |
| |
| if (errno != EAGAIN) |
| goto fail; |
| |
| if ((r = wait_for_read(fd, end)) < 0) |
| goto fail; |
| else if (r > 0) { /* timeout */ |
| ret = 1; |
| goto fail; |
| } |
| } |
| |
| fail: |
| if (p) |
| dns_packet_free(p); |
| |
| return ret; |
| } |
| |
| static int send_name_query(int fd, const char *name, uint16_t id, int query_ipv4, int query_ipv6) { |
| int ret = -1; |
| struct dns_packet *p = NULL; |
| uint8_t *prev_name = NULL; |
| int qdcount = 0; |
| |
| assert(fd >= 0 && name && (query_ipv4 || query_ipv6)); |
| |
| if (!(p = dns_packet_new())) |
| goto finish; /* OOM */ |
| |
| dns_packet_set_field(p, DNS_FIELD_ID, id); |
| dns_packet_set_field(p, DNS_FIELD_FLAGS, DNS_FLAGS(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); |
| |
| #ifndef NSS_IP6_ONLY |
| if (query_ipv4) { |
| if (!(prev_name = dns_packet_append_name(p, name))) |
| goto finish; /* Bad name */ |
| |
| dns_packet_append_uint16(p, DNS_TYPE_A); |
| dns_packet_append_uint16(p, DNS_CLASS_IN); |
| qdcount++; |
| } |
| #endif |
| |
| #ifndef NSS_IP4_ONLY |
| if (query_ipv6) { |
| if (!dns_packet_append_name_compressed(p, name, prev_name)) |
| goto finish; /* Bad name */ |
| |
| dns_packet_append_uint16(p, DNS_TYPE_AAAA); |
| dns_packet_append_uint16(p, DNS_CLASS_IN); |
| qdcount++; |
| } |
| #endif |
| |
| dns_packet_set_field(p, DNS_FIELD_QDCOUNT, qdcount); |
| |
| ret = send_dns_packet(fd, p); |
| |
| finish: |
| if (p) |
| dns_packet_free(p); |
| |
| return ret; |
| } |
| |
| static int domain_cmp(const char *a, const char *b) { |
| size_t al, bl; |
| |
| al = strlen(a); |
| bl = strlen(b); |
| |
| if (al > 0 && a[al-1] == '.') |
| al --; |
| |
| if (bl > 0 && b[bl-1] == '.') |
| bl --; |
| |
| if (al != bl) |
| return al > bl ? 1 : (al < bl ? -1 : 0); |
| |
| return strncasecmp(a, b, al); |
| } |
| |
| static int skip_questions(struct dns_packet *p) { |
| unsigned i; |
| assert(p); |
| |
| for (i = dns_packet_get_field(p, DNS_FIELD_QDCOUNT); i > 0; i--) { |
| char pname[256]; |
| uint16_t type, class; |
| |
| if (dns_packet_consume_name(p, pname, sizeof(pname)) < 0 || |
| dns_packet_consume_uint16(p, &type) < 0 || |
| dns_packet_consume_uint16(p, &class) < 0) |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int process_name_response(int fd, const char *name, usec_t timeout, uint16_t id, void (*ipv4_func)(const ipv4_address_t *ipv4, void *userdata), void (*ipv6_func)(const ipv6_address_t *ipv6, void *userdata), void *userdata) { |
| struct dns_packet *p = NULL; |
| int done = 0; |
| struct timeval end; |
| |
| assert(fd >= 0 && name && (ipv4_func || ipv6_func)); |
| |
| gettimeofday(&end, NULL); |
| timeval_add(&end, timeout); |
| |
| while (!done) { |
| uint8_t ttl; |
| int r; |
| |
| if ((r = recv_dns_packet(fd, &p, &ttl, &end)) < 0) |
| return -1; |
| else if (r > 0) /* timeout */ |
| return 1; |
| |
| /* Ignore packets with RFC != 255 */ |
| if (/* ttl == 255 && */ |
| dns_packet_check_valid_response(p) >= 0 && |
| dns_packet_get_field(p, DNS_FIELD_ID) == id) { |
| |
| unsigned i; |
| |
| if (skip_questions(p) >= 0) |
| |
| for (i = dns_packet_get_field(p, DNS_FIELD_ANCOUNT); i > 0; i--) { |
| char pname[256]; |
| uint16_t type, class; |
| uint32_t rr_ttl; |
| uint16_t rdlength; |
| |
| if (dns_packet_consume_name(p, pname, sizeof(pname)) < 0 || |
| dns_packet_consume_uint16(p, &type) < 0 || |
| dns_packet_consume_uint16(p, &class) < 0 || |
| dns_packet_consume_uint32(p, &rr_ttl) < 0 || |
| dns_packet_consume_uint16(p, &rdlength) < 0) { |
| break; |
| } |
| |
| /* Remove mDNS cache flush bit */ |
| class &= ~0x8000; |
| |
| #ifndef NSS_IPV6_ONLY |
| if (ipv4_func && |
| type == DNS_TYPE_A && |
| class == DNS_CLASS_IN && |
| !domain_cmp(name, pname) && |
| rdlength == sizeof(ipv4_address_t)) { |
| |
| ipv4_address_t ipv4; |
| |
| if (dns_packet_consume_bytes(p, &ipv4, sizeof(ipv4)) < 0) |
| break; |
| |
| ipv4_func(&ipv4, userdata); |
| done = 1; |
| |
| } |
| #endif |
| #if ! defined(NSS_IPV6_ONLY) && ! defined(NSS_IPV4_ONLY) |
| /* else */ |
| #endif |
| #ifndef NSS_IPV4_ONLY |
| if (ipv6_func && |
| type == DNS_TYPE_AAAA && |
| class == DNS_CLASS_IN && |
| !domain_cmp(name, pname) && |
| rdlength == sizeof(ipv6_address_t)) { |
| |
| ipv6_address_t ipv6; |
| |
| if (dns_packet_consume_bytes(p, &ipv6, sizeof(ipv6_address_t)) < 0) |
| break; |
| |
| ipv6_func(&ipv6, userdata); |
| done = 1; |
| } |
| #endif |
| else { |
| |
| /* Step over */ |
| |
| if (dns_packet_consume_seek(p, rdlength) < 0) |
| break; |
| } |
| } |
| } |
| |
| |
| if (p) |
| dns_packet_free(p); |
| |
| } |
| |
| return 0; |
| } |
| |
| int mdns_query_name(int fd, const char *name, void (*ipv4_func)(const ipv4_address_t *ipv4, void *userdata), void (*ipv6_func)(const ipv6_address_t *ipv6, void *userdata), void *userdata) { |
| const usec_t *timeout = retry_ms; |
| uint16_t id; |
| |
| assert(fd >= 0 && name && (ipv4_func || ipv6_func)); |
| |
| id = get_random_id(); |
| |
| while (*timeout > 0) { |
| int n; |
| |
| if (send_name_query(fd, name, id, !!ipv4_func, !!ipv6_func) < 0) |
| return -1; |
| |
| if ((n = process_name_response(fd, name, *timeout, id, ipv4_func, ipv6_func, userdata)) < 0) |
| return -1; |
| |
| if (n == 0) |
| return 0; |
| |
| /* Timeout */ |
| |
| timeout++; |
| } |
| |
| return -1; |
| } |
| |
| static int send_reverse_query(int fd, const char *name, uint16_t id) { |
| int ret = -1; |
| struct dns_packet *p = NULL; |
| |
| assert(fd >= 0 && name); |
| |
| if (!(p = dns_packet_new())) |
| goto finish; |
| |
| dns_packet_set_field(p, DNS_FIELD_ID, id); |
| dns_packet_set_field(p, DNS_FIELD_FLAGS, DNS_FLAGS(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); |
| |
| if (!dns_packet_append_name(p, name)) |
| goto finish; |
| |
| dns_packet_append_uint16(p, DNS_TYPE_PTR); |
| dns_packet_append_uint16(p, DNS_CLASS_IN); |
| |
| dns_packet_set_field(p, DNS_FIELD_QDCOUNT, 1); |
| |
| ret = send_dns_packet(fd, p); |
| |
| finish: |
| if (p) |
| dns_packet_free(p); |
| |
| return ret; |
| } |
| |
| static int process_reverse_response(int fd, const char *name, usec_t timeout, uint16_t id, void (*name_func)(const char *name, void *userdata), void *userdata) { |
| struct dns_packet *p = NULL; |
| int done = 0; |
| struct timeval end; |
| |
| assert(fd >= 0 && name && name_func); |
| |
| gettimeofday(&end, NULL); |
| timeval_add(&end, timeout); |
| |
| while (!done) { |
| uint8_t ttl; |
| int r; |
| |
| if ((r = recv_dns_packet(fd, &p, &ttl, &end)) < 0) |
| return -1; |
| else if (r > 0) /* timeout */ |
| return 1; |
| |
| /* Ignore packets with RFC != 255 */ |
| if (/* ttl == 255 && */ |
| dns_packet_check_valid_response(p) >= 0 && |
| dns_packet_get_field(p, DNS_FIELD_ID) == id) { |
| |
| unsigned i; |
| |
| if (skip_questions(p) >= 0) { |
| |
| for (i = dns_packet_get_field(p, DNS_FIELD_ANCOUNT); i > 0; i--) { |
| char pname[256]; |
| uint16_t type, class; |
| uint32_t rr_ttl; |
| uint16_t rdlength; |
| |
| if (dns_packet_consume_name(p, pname, sizeof(pname)) < 0 || |
| dns_packet_consume_uint16(p, &type) < 0 || |
| dns_packet_consume_uint16(p, &class) < 0 || |
| dns_packet_consume_uint32(p, &rr_ttl) < 0 || |
| dns_packet_consume_uint16(p, &rdlength) < 0) { |
| break; |
| } |
| |
| /* Remove mDNS cache flush bit */ |
| class &= ~0x8000; |
| |
| if (type == DNS_TYPE_PTR && |
| class == DNS_CLASS_IN && |
| !domain_cmp(name, pname)) { |
| |
| char rname[256]; |
| |
| if (dns_packet_consume_name(p, rname, sizeof(rname)) < 0) |
| break; |
| |
| name_func(rname, userdata); |
| done = 1; |
| |
| } else { |
| |
| /* Step over */ |
| |
| if (dns_packet_consume_seek(p, rdlength) < 0) |
| break; |
| } |
| } |
| } |
| } |
| |
| if (p) |
| dns_packet_free(p); |
| } |
| |
| return 0; |
| } |
| |
| static int query_reverse(int fd, const char *name, void (*name_func)(const char *name, void *userdata), void *userdata) { |
| const usec_t *timeout = retry_ms; |
| uint16_t id; |
| |
| assert(fd >= 0 && name && name_func); |
| |
| id = get_random_id(); |
| |
| while (*timeout > 0) { |
| int n; |
| |
| if (send_reverse_query(fd, name, id) <= 0) /* error or no interface to send data on */ |
| return -1; |
| |
| if ((n = process_reverse_response(fd, name, *timeout, id, name_func, userdata)) < 0) |
| return -1; |
| |
| if (n == 0) |
| return 0; |
| |
| /* Timeout */ |
| |
| timeout++; |
| } |
| |
| return -1; |
| } |
| |
| #ifndef NSS_IPV6_ONLY |
| int mdns_query_ipv4(int fd, const ipv4_address_t *ipv4, void (*name_func)(const char *name, void *userdata), void *userdata) { |
| char name[256]; |
| uint32_t a; |
| assert(fd >= 0 && ipv4 && name_func); |
| |
| a = ntohl(ipv4->address); |
| snprintf(name, sizeof(name), "%u.%u.%u.%u.in-addr.arpa", a & 0xFF, (a >> 8) & 0xFF, (a >> 16) & 0xFF, a >> 24); |
| |
| return query_reverse(fd, name, name_func, userdata); |
| } |
| #endif |
| |
| #ifndef NSS_IPV4_ONLY |
| int mdns_query_ipv6(int fd, const ipv6_address_t *ipv6, void (*name_func)(const char *name, void *userdata), void *userdata) { |
| char name[256]; |
| assert(fd >= 0 && ipv6 && name_func); |
| |
| snprintf(name, sizeof(name), "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.ip6.arpa", |
| ipv6->address[15] & 0xF, ipv6->address[15] >> 4, |
| ipv6->address[14] & 0xF, ipv6->address[14] >> 4, |
| ipv6->address[13] & 0xF, ipv6->address[13] >> 4, |
| ipv6->address[12] & 0xF, ipv6->address[12] >> 4, |
| ipv6->address[11] & 0xF, ipv6->address[11] >> 4, |
| ipv6->address[10] & 0xF, ipv6->address[10] >> 4, |
| ipv6->address[9] & 0xF, ipv6->address[9] >> 4, |
| ipv6->address[8] & 0xF, ipv6->address[8] >> 4, |
| ipv6->address[7] & 0xF, ipv6->address[7] >> 4, |
| ipv6->address[6] & 0xF, ipv6->address[6] >> 4, |
| ipv6->address[5] & 0xF, ipv6->address[5] >> 4, |
| ipv6->address[4] & 0xF, ipv6->address[4] >> 4, |
| ipv6->address[3] & 0xF, ipv6->address[3] >> 4, |
| ipv6->address[2] & 0xF, ipv6->address[2] >> 4, |
| ipv6->address[1] & 0xF, ipv6->address[1] >> 4, |
| ipv6->address[0] & 0xF, ipv6->address[0] >> 4); |
| |
| return query_reverse(fd, name, name_func, userdata); |
| } |
| #endif |