blob: fb750ce59ea2e237cbe8afac997cf25fda16dc28 [file] [log] [blame]
/* posix.c
*
* Copyright (c) 2018-2021 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
*
* https://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.
*
* utility functions common to all posix implementations (e.g., MacOS, Linux).
*/
#define _GNU_SOURCE
#include <netinet/in.h>
#include <net/if.h>
#ifndef LINUX
#include <netinet/in_var.h>
#include <net/if_dl.h>
#endif
#include <sys/ioctl.h>
#include <errno.h>
#include <ifaddrs.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <arpa/inet.h>
#include "dns_sd.h"
#include "srp.h"
#include "dns-msg.h"
#include "ioloop.h"
interface_address_state_t *interface_addresses;
bool
ioloop_map_interface_addresses(const char *ifname, void *context, interface_callback_t callback)
{
return ioloop_map_interface_addresses_here(&interface_addresses, ifname, context, callback);
}
bool
ioloop_map_interface_addresses_here_(interface_address_state_t **here, const char *ifname, void *context,
interface_callback_t callback, const char *file, int line)
{
struct ifaddrs *ifaddrs, *ifp;
interface_address_state_t *kept_ifaddrs = NULL, **ki_end = &kept_ifaddrs;
interface_address_state_t *new_ifaddrs = NULL, **ni_end = &new_ifaddrs;
interface_address_state_t **ip, *nif;
if (getifaddrs(&ifaddrs) < 0) {
ERROR("getifaddrs failed: " PUB_S_SRP, strerror(errno));
return false;
}
for (ifp = ifaddrs; ifp; ifp = ifp->ifa_next) {
bool remove = false;
bool keep = true;
// It is impossible to have an interface without interface name.
if (ifp->ifa_name == NULL) {
continue;
}
if (ifname != NULL && strcmp(ifname, ifp->ifa_name)) {
continue;
}
#ifndef LINUX
// Check for temporary addresses, etc.
if (ifp->ifa_addr != NULL && ifp->ifa_addr->sa_family == AF_INET6) {
struct in6_ifreq ifreq;
int sock;
size_t len;
len = strlen(ifp->ifa_name);
if (len >= sizeof(ifreq.ifr_name)) {
len = sizeof(ifreq.ifr_name) - 1;
}
memcpy(ifreq.ifr_name, ifp->ifa_name, len);
ifreq.ifr_name[len] = 0;
if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
ERROR("socket(AF_INET6, SOCK_DGRAM): " PUB_S_SRP, strerror(errno));
continue;
}
memcpy(&ifreq.ifr_addr, ifp->ifa_addr, sizeof ifreq.ifr_addr);
if (ioctl(sock, SIOCGIFAFLAG_IN6, &ifreq) < 0) {
ERROR("ioctl(SIOCGIFAFLAG_IN6): " PUB_S_SRP, strerror(errno));
close(sock);
continue;
}
int flags = ifreq.ifr_ifru.ifru_flags6;
if (flags & (IN6_IFF_ANYCAST | IN6_IFF_TENTATIVE | IN6_IFF_DETACHED | IN6_IFF_TEMPORARY)) {
keep = false;
}
if (flags & IN6_IFF_DEPRECATED) {
remove = true;
}
close(sock);
}
#ifdef DEBUG_AF_LINK
if (ifp->ifa_addr != NULL && ifp->ifa_addr->sa_family != AF_INET && ifp->ifa_addr->sa_family != AF_INET6) {
struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifp->ifa_addr;
const uint8_t *addr = (uint8_t *)LLADDR(sdl);
INFO("%.*s index %d alen %d dlen %d SDL: %02x:%02x:%02x:%02x:%02x:%02x",
sdl->sdl_nlen, sdl->sdl_data, sdl->sdl_index, sdl->sdl_alen, sdl->sdl_slen,
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
}
#endif // DEBUG_AF_LINK
#endif // LINUX
// Is this an interface address we can use?
if (keep && ifp->ifa_addr != NULL && (
#ifndef LINUX
ifp->ifa_addr->sa_family == AF_LINK ||
#endif
((ifp->ifa_addr->sa_family == AF_INET6 || ifp->ifa_addr->sa_family == AF_INET) && ifp->ifa_netmask != NULL)) &&
(ifp->ifa_flags & IFF_UP))
{
keep = false;
for (ip = here; *ip != NULL; ) {
interface_address_state_t *ia = *ip;
// Same interface and address?
if (!remove && !strcmp(ia->name, ifp->ifa_name) &&
ifp->ifa_addr->sa_family == ia->addr.sa.sa_family &&
(
#ifndef LINUX
ifp->ifa_addr->sa_family == AF_LINK ||
#endif
(((ifp->ifa_addr->sa_family == AF_INET &&
((struct sockaddr_in *)ifp->ifa_addr)->sin_addr.s_addr == ia->addr.sin.sin_addr.s_addr) ||
(ifp->ifa_addr->sa_family == AF_INET6 &&
!memcmp(&((struct sockaddr_in6 *)ifp->ifa_addr)->sin6_addr,
&ia->addr.sin6.sin6_addr, sizeof ia->addr.sin6.sin6_addr))) &&
((ifp->ifa_netmask->sa_family == AF_INET &&
((struct sockaddr_in *)ifp->ifa_netmask)->sin_addr.s_addr == ia->mask.sin.sin_addr.s_addr) ||
(ifp->ifa_netmask->sa_family == AF_INET6 &&
!memcmp(&((struct sockaddr_in6 *)ifp->ifa_netmask)->sin6_addr,
&ia->mask.sin6.sin6_addr, sizeof ia->mask.sin6.sin6_addr))))))
{
*ip = ia->next;
*ki_end = ia;
ki_end = &ia->next;
ia->next = NULL;
keep = true;
break;
} else {
ip = &ia->next;
}
}
// If keep is false, this is a new interface/address.
if (!keep) {
size_t len = strlen(ifp->ifa_name);
#ifdef MALLOC_DEBUG_LOGGING
nif = debug_calloc(1, len + 1 + sizeof(*nif), file, line);
#else
(void)file;
(void)line;
nif = calloc(1, len + 1 + sizeof(*nif));
#endif
// We don't have a way to fix nif being null; what this means is that we don't detect a new
// interface address.
if (nif != NULL) {
nif->name = (char *)(nif + 1);
memcpy(nif->name, ifp->ifa_name, len);
nif->name[len] = 0;
if (ifp->ifa_addr->sa_family == AF_INET) {
nif->addr.sin = *((struct sockaddr_in *)ifp->ifa_addr);
nif->mask.sin = *((struct sockaddr_in *)ifp->ifa_netmask);
IPv4_ADDR_GEN_SRP(&nif->addr.sin.sin_addr.s_addr, __new_interface_ipv4_addr);
INFO("new IPv4 interface address added - ifname: " PUB_S_SRP
", addr: " PRI_IPv4_ADDR_SRP, nif->name,
IPv4_ADDR_PARAM_SRP(&nif->addr.sin.sin_addr.s_addr, __new_interface_ipv4_addr));
} else if (ifp->ifa_addr->sa_family == AF_INET6) {
nif->addr.sin6 = *((struct sockaddr_in6 *)ifp->ifa_addr);
nif->mask.sin6 = *((struct sockaddr_in6 *)ifp->ifa_netmask);
SEGMENTED_IPv6_ADDR_GEN_SRP(nif->addr.sin6.sin6_addr.s6_addr, __new_interface_ipv6_addr);
INFO("new IPv6 interface address added - ifname: " PUB_S_SRP
", addr: " PRI_SEGMENTED_IPv6_ADDR_SRP, nif->name,
SEGMENTED_IPv6_ADDR_PARAM_SRP(nif->addr.sin6.sin6_addr.s6_addr,
__new_interface_ipv6_addr));
} else {
#ifndef LINUX
struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifp->ifa_addr;
memset(&nif->mask, 0, sizeof(nif->mask));
if (sdl->sdl_alen == 6) {
nif->addr.ether_addr.len = 6;
memcpy(nif->addr.ether_addr.addr, LLADDR(sdl), 6);
} else {
nif->addr.ether_addr.len = 0;
}
nif->addr.ether_addr.index = sdl->sdl_index;
nif->addr.ether_addr.family = AF_LINK;
#endif // LINUX
}
nif->flags = ifp->ifa_flags;
*ni_end = nif;
ni_end = &nif->next;
}
}
}
}
#ifndef LINUX
// Get rid of any link-layer addresses for which there is no other address on that interface
// This is clunky, but we can't assume that the AF_LINK address will come after some other
// address, so there's no more efficient way to do this that I can think of.
for (ip = &new_ifaddrs; *ip; ) {
if ((*ip)->addr.sa.sa_family == AF_LINK) {
bool drop = true;
for (nif = new_ifaddrs; nif; nif = nif->next) {
if (nif != *ip && !strcmp(nif->name, (*ip)->name)) {
drop = false;
break;
}
}
if (drop) {
nif = *ip;
*ip = nif->next;
free(nif);
} else {
ip = &(*ip)->next;
}
} else {
ip = &(*ip)->next;
}
}
#endif // LINUX
#ifdef TOO_MUCH_INFO
char infobuf[1000];
int i;
for (i = 0; i < 3; i++) {
char *infop = infobuf;
int len, lim = sizeof infobuf;
const char *title;
switch(i) {
case 0:
title = "deleted";
nif = *here;
break;
case 1:
title = " kept";
nif = kept_ifaddrs;
break;
case 2:
title = " new";
nif = new_ifaddrs;
break;
default:
abort();
}
for (; nif; nif = nif->next) {
snprintf(infop, lim, " %p (", nif);
len = (int)strlen(infop);
lim -= len;
infop += len;
inet_ntop(AF_INET6, &nif->addr.sin6.sin6_addr, infop, lim);
len = (int)strlen(infop);
lim -= len;
infop += len;
if (lim > 1) {
*infop++ = ')';
lim--;
}
}
*infop = 0;
INFO(PUB_S_SRP ":" PUB_S_SRP, title, infobuf);
}
#endif
// Report and free deleted interface addresses...
for (ip = here; *ip; ) {
nif = *ip;
*ip = nif->next;
if (callback != NULL) {
callback(context, nif->name, &nif->addr, &nif->mask, nif->flags, interface_address_deleted);
}
free(nif);
}
// Report added interface addresses...
for (nif = new_ifaddrs; nif; nif = nif->next) {
if (callback != NULL) {
callback(context, nif->name, &nif->addr, &nif->mask, nif->flags, interface_address_added);
}
}
// Report unchanged interface addresses...
for (nif = kept_ifaddrs; nif; nif = nif->next) {
if (callback != NULL) {
callback(context, nif->name, &nif->addr, &nif->mask, nif->flags, interface_address_unchanged);
}
}
// Restore kept interface addresses and append new addresses to the list.
*here = kept_ifaddrs;
for (ip = here; *ip; ip = &(*ip)->next)
;
*ip = new_ifaddrs;
freeifaddrs(ifaddrs);
return true;
}
ssize_t
ioloop_recvmsg(int sock, uint8_t *buffer, size_t buffer_length, int *ifindex, int *hop_limit, addr_t *source,
addr_t *destination)
{
ssize_t rv;
struct msghdr msg;
struct iovec bufp;
char cmsgbuf[128];
struct cmsghdr *cmh;
bufp.iov_base = buffer;
bufp.iov_len = buffer_length;
msg.msg_iov = &bufp;
msg.msg_iovlen = 1;
msg.msg_name = source;
msg.msg_namelen = sizeof(*source);
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
rv = recvmsg(sock, &msg, 0);
if (rv < 0) {
return rv;
}
// For UDP, we use the interface index as part of the validation strategy, so go get
// the interface index.
for (cmh = CMSG_FIRSTHDR(&msg); cmh; cmh = CMSG_NXTHDR(&msg, cmh)) {
if (cmh->cmsg_level == IPPROTO_IPV6 && cmh->cmsg_type == IPV6_PKTINFO &&
cmh->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo)))
{
struct in6_pktinfo pktinfo;
memcpy(&pktinfo, CMSG_DATA(cmh), sizeof pktinfo);
*ifindex = (int)pktinfo.ipi6_ifindex;
/* Get the destination address, for use when replying. */
destination->sin6.sin6_family = AF_INET6;
destination->sin6.sin6_port = 0;
destination->sin6.sin6_addr = pktinfo.ipi6_addr;
#ifndef NOT_HAVE_SA_LEN
destination->sin6.sin6_len = sizeof(destination->sin6);
#endif
} else if (cmh->cmsg_level == IPPROTO_IP && cmh->cmsg_type == IP_PKTINFO &&
cmh->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
struct in_pktinfo pktinfo;
memcpy(&pktinfo, CMSG_DATA(cmh), sizeof pktinfo);
*ifindex = (int)pktinfo.ipi_ifindex;
destination->sin.sin_family = AF_INET;
destination->sin.sin_port = 0;
destination->sin.sin_addr = pktinfo.ipi_addr;
#ifndef NOT_HAVE_SA_LEN
destination->sin.sin_len = sizeof(destination->sin);
#endif
} else if (cmh->cmsg_level == IPPROTO_IPV6 && cmh->cmsg_type == IPV6_HOPLIMIT &&
cmh->cmsg_len == CMSG_LEN(sizeof(int))) {
*hop_limit = *(int *)CMSG_DATA(cmh);
}
}
return rv;
}
message_t *
ioloop_message_create_(size_t message_size, const char *file, int line)
{
message_t *message;
// Never should have a message shorter than this.
if (message_size < DNS_HEADER_SIZE || message_size > UINT16_MAX) {
return NULL;
}
message = (message_t *)malloc(message_size + (sizeof(message_t)) - (sizeof(dns_wire_t)));
if (message) {
memset(message, 0, (sizeof(message_t)) - (sizeof(dns_wire_t)));
RETAIN(message);
message->length = (uint16_t)message_size;
}
return message;
}
#ifdef DEBUG_FD_LEAKS
int
get_num_fds(void)
{
DIR *dirfd = opendir("/dev/fd");
int num = 0;
if (dirfd == NULL) {
return -1;
}
while (readdir(dirfd) != NULL) {
num++;
}
closedir(dirfd);
return num;
}
#endif // DEBUG_VERBOSE
#ifdef MALLOC_DEBUG_LOGGING
#undef malloc
#undef calloc
#undef strdup
#undef free
void *
debug_malloc(size_t len, const char *file, int line)
{
void *ret = malloc(len);
INFO("%p: malloc(%zu) at " PUB_S_SRP ":%d", ret, len, file, line);
return ret;
}
void *
debug_calloc(size_t count, size_t len, const char *file, int line)
{
void *ret = calloc(count, len);
INFO("%p: calloc(%zu, %zu) at " PUB_S_SRP ":%d", ret, count, len, file, line);
return ret;
}
char *
debug_strdup(const char *s, const char *file, int line)
{
char *ret = strdup(s);
INFO("%p: strdup(%p) at " PUB_S_SRP ":%d", ret, s, file, line);
return ret;
}
void
debug_free(void *p, const char *file, int line)
{
INFO("%p: free() at " PUB_S_SRP ":%d", p, file, line);
free(p);
}
#endif
// Local Variables:
// mode: C
// tab-width: 4
// c-file-style: "bsd"
// c-basic-offset: 4
// fill-column: 108
// indent-tabs-mode: nil
// End: