| /* |
| * Copyright (c) 2011-2020, 2023 Apple Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "mDNSEmbeddedAPI.h" |
| #include "mDNSMacOSX.h" |
| |
| #include <sys/types.h> |
| #include <sys/time.h> |
| #include <sys/event.h> |
| #include <netinet/tcp.h> |
| |
| #include "mdns_strict.h" |
| |
| extern mDNS mDNSStorage; |
| |
| #define ValidSocket(s) ((s) >= 0) |
| |
| // Global to store the 4 DNS Proxy Listeners (UDPv4/6, TCPv4/6) |
| static int dp_listener[4]; |
| |
| #define NUM_PROXY_TCP_CONNS 100 |
| |
| typedef struct |
| { |
| TCPSocket sock; |
| DNSMessage *reply; |
| mDNSu16 replyLen; |
| mDNSu32 nread; |
| } ProxyTCPInfo_t; |
| |
| // returns -1 for failures including the other end closing the socket |
| // returns 0 if successful in reading data, but still not read the data fully |
| // returns 1 if successful in reading all the data |
| mDNSlocal int ProxyTCPRead(ProxyTCPInfo_t *tcpInfo) |
| { |
| long n; |
| mDNSBool closed; |
| |
| if (tcpInfo->nread < 2) // First read the two-byte length preceeding the DNS message |
| { |
| mDNSu8 *lenptr = (mDNSu8 *)&tcpInfo->replyLen; |
| n = mDNSPlatformReadTCP(&tcpInfo->sock, lenptr + tcpInfo->nread, 2 - tcpInfo->nread, &closed); |
| if (n < 0 || closed) |
| { |
| LogMsg("ProxyTCPRead: attempt to read message length failed"); |
| return -1; |
| } |
| |
| tcpInfo->nread += (unsigned long)n; |
| if (tcpInfo->nread < 2) |
| { |
| LogMsg("ProxyTCPRead: nread %d, n %d", tcpInfo->nread, n); |
| return 0; |
| } |
| |
| tcpInfo->replyLen = (mDNSu16)((mDNSu16)lenptr[0] << 8 | lenptr[1]); |
| if (tcpInfo->replyLen < sizeof(DNSMessageHeader)) |
| { |
| LogMsg("ProxyTCPRead: Message length too short (%d bytes)", tcpInfo->replyLen); |
| return -1; |
| } |
| |
| tcpInfo->reply = (DNSMessage *) mallocL("ProxyTCPInfo", tcpInfo->replyLen); |
| if (!tcpInfo->reply) |
| { |
| LogMsg("ProxyTCPRead: Memory failure"); |
| return -1; |
| } |
| } |
| |
| n = mDNSPlatformReadTCP(&tcpInfo->sock, ((char *)tcpInfo->reply) + (tcpInfo->nread - 2), tcpInfo->replyLen - (tcpInfo->nread - 2), &closed); |
| |
| if (n < 0 || closed) |
| { |
| LogMsg("ProxyTCPRead: read failure n %d, closed %d", n, closed); |
| return -1; |
| } |
| tcpInfo->nread += (unsigned long)n; |
| if ((tcpInfo->nread - 2) != tcpInfo->replyLen) |
| return 0; |
| else |
| return 1; |
| } |
| |
| mDNSlocal void ProxyTCPSocketCallBack(int s1, short filter, void *context, __unused mDNSBool encounteredEOF) |
| { |
| int ret; |
| struct sockaddr_storage from; |
| struct sockaddr_storage to; |
| mDNSAddr senderAddr, destAddr; |
| mDNSIPPort senderPort; |
| ProxyTCPInfo_t *ti = (ProxyTCPInfo_t *)context; |
| TCPSocket *sock = &ti->sock; |
| struct tcp_info tcp_if; |
| socklen_t size = sizeof(tcp_if); |
| int32_t intf_id = 0; |
| |
| (void) filter; |
| |
| ret = ProxyTCPRead(ti); |
| if (ret == -1) |
| { |
| mDNSPlatformDisposeProxyContext(ti); |
| return; |
| } |
| else if (!ret) |
| { |
| debugf("ProxyTCPReceive: Not yet read completely Actual length %d, Read length %d", ti->replyLen, ti->nread); |
| return; |
| } |
| // We read all the data and hence not interested in read events anymore |
| KQueueSet(s1, EV_DELETE, EVFILT_READ, &sock->kqEntry); |
| |
| mDNSPlatformMemZero(&to, sizeof(to)); |
| mDNSPlatformMemZero(&from, sizeof(from)); |
| socklen_t len = sizeof(to); |
| ret = getsockname(s1, (struct sockaddr*) &to, &len); |
| if (ret < 0) |
| { |
| LogMsg("ProxyTCPReceive: getsockname(fd=%d) errno %d", s1, errno); |
| mDNSPlatformDisposeProxyContext(ti); |
| return; |
| } |
| ret = getpeername(s1, (struct sockaddr*) &from, &len); |
| if (ret < 0) |
| { |
| LogMsg("ProxyTCPReceive: getpeername(fd=%d) errno %d", s1, errno); |
| mDNSPlatformDisposeProxyContext(ti); |
| return; |
| } |
| if (getsockopt(s1, IPPROTO_TCP, TCP_INFO, &tcp_if, &size) != 0) |
| { |
| LogMsg("ProxyTCPReceive: getsockopt for TCP_INFO failed (fd=%d) errno %d", s1, errno); |
| return; |
| } |
| intf_id = tcp_if.tcpi_last_outif; |
| |
| if (from.ss_family == AF_INET) |
| { |
| struct sockaddr_in *s = (struct sockaddr_in*)&from; |
| |
| senderAddr.type = mDNSAddrType_IPv4; |
| senderAddr.ip.v4.NotAnInteger = s->sin_addr.s_addr; |
| senderPort.NotAnInteger = s->sin_port; |
| |
| s = (struct sockaddr_in *)&to; |
| destAddr.type = mDNSAddrType_IPv4; |
| destAddr.ip.v4.NotAnInteger = s->sin_addr.s_addr; |
| |
| LogInfo("ProxyTCPReceive received IPv4 packet(len %d) from %#-15a to %#-15a on skt %d %s ifindex %d", |
| ti->replyLen, &senderAddr, &destAddr, s1, NULL, intf_id); |
| } |
| else if (from.ss_family == AF_INET6) |
| { |
| struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&from; |
| senderAddr.type = mDNSAddrType_IPv6; |
| senderAddr.ip.v6 = *(mDNSv6Addr*)&sin6->sin6_addr; |
| senderPort.NotAnInteger = sin6->sin6_port; |
| |
| sin6 = (struct sockaddr_in6 *)&to; |
| destAddr.type = mDNSAddrType_IPv6; |
| destAddr.ip.v6 = *(mDNSv6Addr*)&sin6->sin6_addr; |
| |
| LogInfo("ProxyTCPReceive received IPv6 packet(len %d) from %#-15a to %#-15a on skt %d %s ifindex %d", |
| ti->replyLen, &senderAddr, &destAddr, s1, NULL, intf_id); |
| } |
| else |
| { |
| LogMsg("ProxyTCPReceive from is unknown address family %d", from.ss_family); |
| mDNSPlatformDisposeProxyContext(ti); |
| return; |
| } |
| |
| // We pass sock for the TCPSocket and the "ti" for context as that's what we want to free at the end. |
| // In the UDP case, there is just a single socket and nothing to free. Hence, the context (last argument) |
| // would be NULL. |
| ti->sock.m->p->TCPProxyCallback(sock, ti->reply, (mDNSu8 *)ti->reply + ti->replyLen, &senderAddr, senderPort, &destAddr, |
| UnicastDNSPort, (mDNSInterfaceID)(uintptr_t)intf_id, ti); |
| } |
| |
| mDNSlocal void ProxyTCPAccept(int s1, short filter, void *context, __unused mDNSBool encounteredEOF) |
| { |
| int newfd; |
| struct sockaddr_storage ss; |
| socklen_t sslen = sizeof(ss); |
| const int on = 1; |
| TCPSocket *listenSock = (TCPSocket *)context; |
| |
| (void) filter; |
| |
| while ((newfd = accept(s1, (struct sockaddr *)&ss, &sslen)) != -1) |
| { |
| int err; |
| |
| // Even though we just need a single KQueueEntry, for simplicity we re-use |
| // the KQSocketSet |
| ProxyTCPInfo_t * const ti = (ProxyTCPInfo_t *)callocL("ProxyTCPContext", sizeof(*ti)); |
| if (!ti) |
| { |
| LogMsg("ProxyTCPAccept: cannot allocate TCPSocket"); |
| close(newfd); |
| return; |
| } |
| TCPSocket * const sock = &ti->sock; |
| sock->fd = -1; |
| sock->m = listenSock->m; |
| |
| fcntl(newfd, F_SETFL, fcntl(newfd, F_GETFL, 0) | O_NONBLOCK); // set non-blocking |
| if (ss.ss_family == AF_INET) |
| { |
| // Receive interface identifiers |
| err = setsockopt(newfd, IPPROTO_IP, IP_RECVIF, &on, sizeof(on)); |
| if (err) |
| { |
| LogMsg("ProxyTCPAccept: IP_RECVIF %d errno %d (%s)", newfd, errno, strerror(errno)); |
| mDNSPlatformDisposeProxyContext(ti); |
| close(newfd); |
| return; |
| } |
| } |
| else |
| { |
| // We want to receive destination addresses and receive interface identifiers |
| err = setsockopt(newfd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)); |
| if (err) |
| { |
| LogMsg("ProxyTCPAccept: IP_RECVPKTINFO %d errno %d (%s)", newfd, errno, strerror(errno)); |
| mDNSPlatformDisposeProxyContext(ti); |
| close(newfd); |
| return; |
| } |
| } |
| // mDNSPlatformReadTCP/WriteTCP (unlike the UDP counterpart) does not provide the destination address |
| // from which we can infer the destination address family. Hence we need to remember that here. |
| // Instead of remembering the address family, we remember the right fd. |
| sock->fd = newfd; |
| sock->kqEntry.KQcallback = ProxyTCPSocketCallBack; |
| sock->kqEntry.KQcontext = ti; |
| sock->kqEntry.KQtask = "TCP Proxy packet reception"; |
| #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM |
| sock->kqEntry.readSource = mDNSNULL; |
| sock->kqEntry.writeSource = mDNSNULL; |
| sock->kqEntry.fdClosed = mDNSfalse; |
| #endif |
| sock->connected = mDNStrue; |
| sock->m = listenSock->m; |
| KQueueSet(newfd, EV_ADD, EVFILT_READ, &sock->kqEntry); |
| } |
| } |
| |
| mDNSlocal mStatus SetupUDPProxySocket(int skt, KQSocketSet *cp, u_short sa_family, mDNSBool useBackgroundTrafficClass) |
| { |
| int *s = (sa_family == AF_INET) ? &cp->sktv4 : &cp->sktv6; |
| KQueueEntry *k = (sa_family == AF_INET) ? &cp->kqsv4 : &cp->kqsv6; |
| const int on = 1; |
| mStatus err = mStatus_NoError; |
| |
| cp->m = &mDNSStorage; |
| cp->closeFlag = mDNSNULL; |
| |
| // set default traffic class |
| // setTrafficClass(skt, mDNSfalse); |
| (void) useBackgroundTrafficClass; |
| |
| if (sa_family == AF_INET) |
| { |
| err = setsockopt(skt, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)); |
| if (err < 0) |
| { |
| LogMsg("SetupUDPProxySocket: IP_RECVDSTADDR %d errno %d (%s)", skt, errno, strerror(errno)); |
| return err; |
| } |
| |
| // We want to receive interface identifiers |
| err = setsockopt(skt, IPPROTO_IP, IP_RECVIF, &on, sizeof(on)); |
| if (err < 0) |
| { |
| LogMsg("SetupUDPProxySocket: IP_RECVIF %d errno %d (%s)", skt, errno, strerror(errno)); |
| return err; |
| } |
| } |
| else if (sa_family == AF_INET6) |
| { |
| // We want to receive destination addresses and receive interface identifiers |
| err = setsockopt(skt, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)); |
| if (err < 0) |
| { |
| LogMsg("SetupUDPProxySocket: IPV6_RECVPKTINFO %d errno %d (%s)", skt, errno, strerror(errno)); |
| return err; |
| } |
| |
| // We want to receive packet hop count value so we can check it |
| err = setsockopt(skt, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)); |
| if (err < 0) |
| { |
| LogMsg("SetupUDPProxySocket: IPV6_RECVHOPLIMIT %d errno %d (%s)", skt, errno, strerror(errno)); |
| return err; |
| } |
| } |
| else |
| { |
| LogMsg("SetupUDPProxySocket: wrong family %d", sa_family); |
| return -1; |
| } |
| |
| if (fcntl(skt, F_SETFL, fcntl(skt, F_GETFL, 0) | O_NONBLOCK) < 0) |
| { |
| LogMsg("SetupUDPProxySocket: fnctl failed %d", errno); |
| return -1; |
| } |
| |
| *s = skt; |
| //k->KQcallback = ProxyUDPSocketCallBack; |
| k->KQcallback = myKQSocketCallBack; |
| k->KQcontext = cp; |
| k->KQtask = "UDP Proxy packet reception"; |
| #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM |
| k->readSource = mDNSNULL; |
| k->writeSource = mDNSNULL; |
| k->fdClosed = mDNSfalse; |
| #endif |
| |
| KQueueSet(*s, EV_ADD, EVFILT_READ, k); |
| |
| return(err); |
| } |
| |
| mDNSlocal mStatus SetupTCPProxySocket(int skt, TCPSocket *sock, u_short sa_family, mDNSBool useBackgroundTrafficClass) |
| { |
| mStatus err; |
| mDNS *m = &mDNSStorage; |
| |
| // for TCP sockets, the traffic class is set once and not changed |
| // setTrafficClass(skt, useBackgroundTrafficClass); |
| (void) useBackgroundTrafficClass; |
| (void) sa_family; |
| |
| // All the socket setup has already been done |
| err = listen(skt, NUM_PROXY_TCP_CONNS); |
| if (err) |
| { |
| LogMsg("SetupTCPProxySocket: listen %d errno %d (%s)", skt, errno, strerror(errno)); |
| return err; |
| } |
| fcntl(skt, F_SETFL, fcntl(skt, F_GETFL, 0) | O_NONBLOCK); // set non-blocking |
| |
| sock->fd = skt; |
| sock->kqEntry.KQcallback = ProxyTCPAccept; |
| sock->kqEntry.KQcontext = sock; |
| sock->kqEntry.KQtask = "TCP Accept"; |
| #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM |
| sock->kqEntry.readSource = mDNSNULL; |
| sock->kqEntry.writeSource = mDNSNULL; |
| sock->kqEntry.fdClosed = mDNSfalse; |
| #endif |
| sock->m = m; |
| KQueueSet(skt, EV_ADD, EVFILT_READ, &sock->kqEntry); |
| return mStatus_NoError; |
| } |
| |
| mDNSlocal void BindDPSocket(int fd, int sa_family, int type) |
| { |
| int err; |
| const int on = 1; |
| |
| if (type == SOCK_STREAM) |
| { |
| err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); |
| if (err != 0) |
| { |
| const int setsockopt_errno = errno; |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, |
| "BindDPSocket: setsockopt SO_REUSEADDR failed for " PUB_S " %d errno %d (" PUB_S ")", |
| (sa_family == AF_INET) ? "IPv4" : "IPv6", fd, setsockopt_errno, strerror(setsockopt_errno)); |
| return; |
| } |
| } |
| if (sa_family == AF_INET) |
| { |
| struct sockaddr_in addr; |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.sin_family = AF_INET; |
| addr.sin_port = htons(53); |
| |
| err = bind(fd, (struct sockaddr*) &addr, sizeof(addr)); |
| if (err) |
| { |
| LogMsg("BindDPSocket: bind %d errno %d (%s)", fd, errno, strerror(errno)); |
| return; |
| } |
| } |
| else |
| { |
| struct sockaddr_in6 addr6; |
| |
| // We want to receive only IPv6 packets. Without this option we get IPv4 packets too, |
| // with mapped addresses of the form 0:0:0:0:0:FFFF:xxxx:xxxx, where xxxx:xxxx is the IPv4 address |
| err = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); |
| if (err < 0) |
| { |
| LogMsg("DPFBindSocket: setsockopt IPV6_V6ONLY %d errno %d (%s)", fd, errno, strerror(errno)); |
| return; |
| } |
| memset(&addr6, 0, sizeof(addr6)); |
| addr6.sin6_family = AF_INET6; |
| addr6.sin6_port = htons(53); |
| |
| err = bind(fd, (struct sockaddr*) &addr6, sizeof(addr6)); |
| if (err) |
| { |
| LogMsg("BindDPSocket: bind6 %d errno %d (%s)", fd, errno, strerror(errno)); |
| return; |
| } |
| } |
| } |
| |
| // Setup DNS Proxy Skts in main kevent loop and set the skt options |
| mDNSlocal void SetupDNSProxySkts(int fd[4]) |
| { |
| mDNS *const m = &mDNSStorage; |
| int i; |
| mStatus err; |
| KQSocketSet *udpSS; |
| TCPSocket *v4, *v6; |
| |
| udpSS = &m->p->UDPProxy.ss; |
| udpSS->port = UnicastDNSPort; |
| v4 = &m->p->TCPProxyV4; |
| v6 = &m->p->TCPProxyV6; |
| v4->m = m; |
| v4->port = UnicastDNSPort; |
| v6->m = m; |
| v6->port = UnicastDNSPort; |
| |
| LogMsg("SetupDNSProxySkts: %d, %d, %d, %d", fd[0], fd[1], fd[2], fd[3]); |
| |
| // myKQSocketCallBack checks for proxy and calls the m->p->ProxyCallback instead of mDNSCoreReceive |
| udpSS->proxy = mDNStrue; |
| err = SetupUDPProxySocket(fd[0], udpSS, AF_INET, mDNSfalse); |
| if (err) |
| LogMsg("SetupDNSProxySkts: ERROR!! UDPv4 Socket"); |
| |
| err = SetupUDPProxySocket(fd[1], udpSS, AF_INET6, mDNSfalse); |
| if (err) |
| LogMsg("SetupDNSProxySkts: ERROR!! UDPv6 Socket"); |
| |
| err = SetupTCPProxySocket(fd[2], v4, AF_INET, mDNSfalse); |
| if (err) |
| LogMsg("SetupDNSProxySkts: ERROR!! TCPv4 Socket"); |
| |
| err = SetupTCPProxySocket(fd[3], v6, AF_INET6, mDNSfalse); |
| if (err) |
| LogMsg("SetupDNSProxySkts: ERROR!! TCPv6 Socket"); |
| |
| for (i = 0; i < 4; i++) |
| dp_listener[i] = fd[i]; |
| } |
| |
| // Create and bind the DNS Proxy Skts for use |
| mDNSexport void mDNSPlatformInitDNSProxySkts(ProxyCallback UDPCallback, ProxyCallback TCPCallback) |
| { |
| int dpskt[4]; |
| |
| dpskt[0] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); |
| dpskt[1] = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); |
| dpskt[2] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); |
| dpskt[3] = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); |
| |
| // Close all DNS Proxy skts in case any of them are invalid |
| if (!ValidSocket(dpskt[0]) || !ValidSocket(dpskt[1]) || |
| !ValidSocket(dpskt[2]) || !ValidSocket(dpskt[3])) |
| { |
| if (ValidSocket(dpskt[0])) |
| close(dpskt[0]); |
| if (ValidSocket(dpskt[1])) |
| close(dpskt[1]); |
| if (ValidSocket(dpskt[2])) |
| close(dpskt[2]); |
| if (ValidSocket(dpskt[3])) |
| close(dpskt[3]); |
| } |
| |
| BindDPSocket(dpskt[0], AF_INET, SOCK_DGRAM); |
| BindDPSocket(dpskt[1], AF_INET6, SOCK_DGRAM); |
| BindDPSocket(dpskt[2], AF_INET, SOCK_STREAM); |
| BindDPSocket(dpskt[3], AF_INET6, SOCK_STREAM); |
| |
| LogInfo("mDNSPlatformInitDNSProxySkts: Opened Listener Sockets for DNS Proxy : %d, %d, %d, %d", |
| dpskt[0], dpskt[1], dpskt[2], dpskt[3]); |
| |
| mDNSStorage.p->UDPProxyCallback = UDPCallback; |
| mDNSStorage.p->TCPProxyCallback = TCPCallback; |
| |
| SetupDNSProxySkts(dpskt); |
| } |
| |
| mDNSexport void mDNSPlatformCloseDNSProxySkts(mDNS *const m) |
| { |
| (void) m; |
| int i; |
| for (i = 0; i < 4; i++) |
| close(dp_listener[i]); |
| LogInfo("mDNSPlatformCloseDNSProxySkts: Closing DNS Proxy Listener Sockets"); |
| } |
| |
| mDNSexport void mDNSPlatformDisposeProxyContext(void *context) |
| { |
| ProxyTCPInfo_t *ti; |
| TCPSocket *sock; |
| |
| if (!context) |
| return; |
| |
| ti = (ProxyTCPInfo_t *)context; |
| sock = &ti->sock; |
| |
| if (sock->fd >= 0) |
| { |
| mDNSPlatformCloseFD(&sock->kqEntry, sock->fd); |
| sock->fd = -1; |
| } |
| |
| if (ti->reply) |
| freeL("ProxyTCPInfoLen", ti->reply); |
| freeL("ProxyTCPContext", ti); |
| } |
| |
| // Local Variables: |
| // mode: C |
| // tab-width: 4 |
| // c-file-style: "bsd" |
| // c-basic-offset: 4 |
| // fill-column: 108 |
| // indent-tabs-mode: nil |
| // End: |