Home | History | Annotate | Line # | Download | only in isc
      1 /*	$NetBSD: net.c,v 1.5 2026/04/08 00:16:15 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5  *
      6  * SPDX-License-Identifier: MPL-2.0
      7  *
      8  * This Source Code Form is subject to the terms of the Mozilla Public
      9  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11  *
     12  * See the COPYRIGHT file distributed with this work for additional
     13  * information regarding copyright ownership.
     14  */
     15 
     16 #include <stdbool.h>
     17 #include <sys/types.h>
     18 
     19 #if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__)
     20 #if defined(HAVE_SYS_PARAM_H)
     21 #include <sys/param.h>
     22 #endif /* if defined(HAVE_SYS_PARAM_H) */
     23 #include <sys/sysctl.h>
     24 #endif /* if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__) */
     25 #include <errno.h>
     26 #include <fcntl.h>
     27 #include <netdb.h>
     28 #include <sys/uio.h>
     29 #include <unistd.h>
     30 
     31 #include <isc/log.h>
     32 #include <isc/net.h>
     33 #include <isc/once.h>
     34 #include <isc/strerr.h>
     35 #include <isc/string.h>
     36 #include <isc/util.h>
     37 
     38 #ifndef socklen_t
     39 #define socklen_t unsigned int
     40 #endif /* ifndef socklen_t */
     41 
     42 /*%
     43  * Definitions about UDP port range specification.  This is a total mess of
     44  * portability variants: some use sysctl (but the sysctl names vary), some use
     45  * system-specific interfaces, some have the same interface for IPv4 and IPv6,
     46  * some separate them, etc...
     47  */
     48 
     49 /*%
     50  * The last resort defaults: use all non well known port space
     51  */
     52 #ifndef ISC_NET_PORTRANGELOW
     53 #define ISC_NET_PORTRANGELOW 1024
     54 #endif /* ISC_NET_PORTRANGELOW */
     55 #ifndef ISC_NET_PORTRANGEHIGH
     56 #define ISC_NET_PORTRANGEHIGH 65535
     57 #endif /* ISC_NET_PORTRANGEHIGH */
     58 
     59 #ifdef HAVE_SYSCTLBYNAME
     60 
     61 /*%
     62  * sysctl variants
     63  */
     64 #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__)
     65 #define USE_SYSCTL_PORTRANGE
     66 #define SYSCTL_V4PORTRANGE_LOW	"net.inet.ip.portrange.hifirst"
     67 #define SYSCTL_V4PORTRANGE_HIGH "net.inet.ip.portrange.hilast"
     68 #define SYSCTL_V6PORTRANGE_LOW	"net.inet.ip.portrange.hifirst"
     69 #define SYSCTL_V6PORTRANGE_HIGH "net.inet.ip.portrange.hilast"
     70 #endif /* if defined(__FreeBSD__) || defined(__APPLE__) || \
     71 	* defined(__DragonFly__) */
     72 
     73 #ifdef __NetBSD__
     74 #define USE_SYSCTL_PORTRANGE
     75 #define SYSCTL_V4PORTRANGE_LOW	"net.inet.ip.anonportmin"
     76 #define SYSCTL_V4PORTRANGE_HIGH "net.inet.ip.anonportmax"
     77 #define SYSCTL_V6PORTRANGE_LOW	"net.inet6.ip6.anonportmin"
     78 #define SYSCTL_V6PORTRANGE_HIGH "net.inet6.ip6.anonportmax"
     79 #endif /* ifdef __NetBSD__ */
     80 
     81 #else /* !HAVE_SYSCTLBYNAME */
     82 
     83 #ifdef __OpenBSD__
     84 #define USE_SYSCTL_PORTRANGE
     85 #define SYSCTL_V4PORTRANGE_LOW \
     86 	{ CTL_NET, PF_INET, IPPROTO_IP, IPCTL_IPPORT_HIFIRSTAUTO }
     87 #define SYSCTL_V4PORTRANGE_HIGH \
     88 	{ CTL_NET, PF_INET, IPPROTO_IP, IPCTL_IPPORT_HILASTAUTO }
     89 /* Same for IPv6 */
     90 #define SYSCTL_V6PORTRANGE_LOW	SYSCTL_V4PORTRANGE_LOW
     91 #define SYSCTL_V6PORTRANGE_HIGH SYSCTL_V4PORTRANGE_HIGH
     92 #endif /* ifdef __OpenBSD__ */
     93 
     94 #endif /* HAVE_SYSCTLBYNAME */
     95 
     96 static isc_once_t once_ipv6only = ISC_ONCE_INIT;
     97 #ifdef __notyet__
     98 static isc_once_t once_ipv6pktinfo = ISC_ONCE_INIT;
     99 #endif /* ifdef __notyet__ */
    100 
    101 #ifndef ISC_CMSG_IP_TOS
    102 #ifdef __APPLE__
    103 #define ISC_CMSG_IP_TOS 0 /* As of 10.8.2. */
    104 #else			  /* ! __APPLE__ */
    105 #define ISC_CMSG_IP_TOS 1
    106 #endif /* ! __APPLE__ */
    107 #endif /* ! ISC_CMSG_IP_TOS */
    108 
    109 static isc_once_t once = ISC_ONCE_INIT;
    110 
    111 static isc_result_t ipv4_result = ISC_R_NOTFOUND;
    112 static isc_result_t ipv6_result = ISC_R_NOTFOUND;
    113 static isc_result_t ipv6only_result = ISC_R_NOTFOUND;
    114 static isc_result_t ipv6pktinfo_result = ISC_R_NOTFOUND;
    115 
    116 static isc_result_t
    117 try_proto(int domain) {
    118 	int s;
    119 	isc_result_t result = ISC_R_SUCCESS;
    120 
    121 	s = socket(domain, SOCK_STREAM, 0);
    122 	if (s == -1) {
    123 		switch (errno) {
    124 #ifdef EAFNOSUPPORT
    125 		case EAFNOSUPPORT:
    126 #endif /* ifdef EAFNOSUPPORT */
    127 #ifdef EPFNOSUPPORT
    128 		case EPFNOSUPPORT:
    129 #endif /* ifdef EPFNOSUPPORT */
    130 #ifdef EPROTONOSUPPORT
    131 		case EPROTONOSUPPORT:
    132 #endif /* ifdef EPROTONOSUPPORT */
    133 #ifdef EINVAL
    134 		case EINVAL:
    135 #endif /* ifdef EINVAL */
    136 			return ISC_R_NOTFOUND;
    137 		default:
    138 			UNEXPECTED_SYSERROR(errno, "socket()");
    139 			return ISC_R_UNEXPECTED;
    140 		}
    141 	}
    142 
    143 	if (domain == PF_INET6) {
    144 		struct sockaddr_in6 sin6;
    145 		unsigned int len;
    146 
    147 		/*
    148 		 * Check to see if IPv6 is broken, as is common on Linux.
    149 		 */
    150 		len = sizeof(sin6);
    151 		if (getsockname(s, (struct sockaddr *)&sin6, (void *)&len) < 0)
    152 		{
    153 			isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
    154 				      ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
    155 				      "retrieving the address of an IPv6 "
    156 				      "socket from the kernel failed.");
    157 			isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
    158 				      ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
    159 				      "IPv6 is not supported.");
    160 			result = ISC_R_NOTFOUND;
    161 		} else {
    162 			if (len == sizeof(struct sockaddr_in6)) {
    163 				result = ISC_R_SUCCESS;
    164 			} else {
    165 				isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
    166 					      ISC_LOGMODULE_SOCKET,
    167 					      ISC_LOG_ERROR,
    168 					      "IPv6 structures in kernel and "
    169 					      "user space do not match.");
    170 				isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
    171 					      ISC_LOGMODULE_SOCKET,
    172 					      ISC_LOG_ERROR,
    173 					      "IPv6 is not supported.");
    174 				result = ISC_R_NOTFOUND;
    175 			}
    176 		}
    177 	}
    178 
    179 	(void)close(s);
    180 
    181 	return result;
    182 }
    183 
    184 static void
    185 initialize_action(void) {
    186 	ipv4_result = try_proto(PF_INET);
    187 	ipv6_result = try_proto(PF_INET6);
    188 }
    189 
    190 static void
    191 initialize(void) {
    192 	isc_once_do(&once, initialize_action);
    193 }
    194 
    195 isc_result_t
    196 isc_net_probeipv4(void) {
    197 	initialize();
    198 	return ipv4_result;
    199 }
    200 
    201 isc_result_t
    202 isc_net_probeipv6(void) {
    203 	initialize();
    204 	return ipv6_result;
    205 }
    206 
    207 static void
    208 try_ipv6only(void) {
    209 #ifdef IPV6_V6ONLY
    210 	int s, on;
    211 #endif /* ifdef IPV6_V6ONLY */
    212 	isc_result_t result;
    213 
    214 	result = isc_net_probeipv6();
    215 	if (result != ISC_R_SUCCESS) {
    216 		ipv6only_result = result;
    217 		return;
    218 	}
    219 
    220 #ifndef IPV6_V6ONLY
    221 	ipv6only_result = ISC_R_NOTFOUND;
    222 	return;
    223 #else  /* ifndef IPV6_V6ONLY */
    224 	/* check for TCP sockets */
    225 	s = socket(PF_INET6, SOCK_STREAM, 0);
    226 	if (s == -1) {
    227 		UNEXPECTED_SYSERROR(errno, "socket()");
    228 		ipv6only_result = ISC_R_UNEXPECTED;
    229 		return;
    230 	}
    231 
    232 	on = 1;
    233 	if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) {
    234 		ipv6only_result = ISC_R_NOTFOUND;
    235 		goto close;
    236 	}
    237 
    238 	close(s);
    239 
    240 	/* check for UDP sockets */
    241 	s = socket(PF_INET6, SOCK_DGRAM, 0);
    242 	if (s == -1) {
    243 		UNEXPECTED_SYSERROR(errno, "socket()");
    244 		ipv6only_result = ISC_R_UNEXPECTED;
    245 		return;
    246 	}
    247 
    248 	on = 1;
    249 	if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) {
    250 		ipv6only_result = ISC_R_NOTFOUND;
    251 		goto close;
    252 	}
    253 
    254 	ipv6only_result = ISC_R_SUCCESS;
    255 
    256 close:
    257 	close(s);
    258 	return;
    259 #endif /* IPV6_V6ONLY */
    260 }
    261 
    262 static void
    263 initialize_ipv6only(void) {
    264 	isc_once_do(&once_ipv6only, try_ipv6only);
    265 }
    266 
    267 #ifdef __notyet__
    268 static void
    269 try_ipv6pktinfo(void) {
    270 	int s, on;
    271 	isc_result_t result;
    272 	int optname;
    273 
    274 	result = isc_net_probeipv6();
    275 	if (result != ISC_R_SUCCESS) {
    276 		ipv6pktinfo_result = result;
    277 		return;
    278 	}
    279 
    280 	/* we only use this for UDP sockets */
    281 	s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
    282 	if (s == -1) {
    283 		UNEXPECTED_SYSERROR(errno, "socket()");
    284 		ipv6pktinfo_result = ISC_R_UNEXPECTED;
    285 		return;
    286 	}
    287 
    288 #ifdef IPV6_RECVPKTINFO
    289 	optname = IPV6_RECVPKTINFO;
    290 #else  /* ifdef IPV6_RECVPKTINFO */
    291 	optname = IPV6_PKTINFO;
    292 #endif /* ifdef IPV6_RECVPKTINFO */
    293 	on = 1;
    294 	if (setsockopt(s, IPPROTO_IPV6, optname, &on, sizeof(on)) < 0) {
    295 		ipv6pktinfo_result = ISC_R_NOTFOUND;
    296 		goto close;
    297 	}
    298 
    299 	ipv6pktinfo_result = ISC_R_SUCCESS;
    300 
    301 close:
    302 	close(s);
    303 	return;
    304 }
    305 
    306 static void
    307 initialize_ipv6pktinfo(void) {
    308 	isc_once_do(&once_ipv6pktinfo, try_ipv6pktinfo);
    309 }
    310 #endif /* ifdef __notyet__ */
    311 
    312 isc_result_t
    313 isc_net_probe_ipv6only(void) {
    314 	initialize_ipv6only();
    315 	return ipv6only_result;
    316 }
    317 
    318 isc_result_t
    319 isc_net_probe_ipv6pktinfo(void) {
    320 /*
    321  * XXXWPK if pktinfo were supported then we could listen on :: for ipv6 and get
    322  * the information about the destination address from pktinfo structure passed
    323  * in recvmsg but this method is not portable and libuv doesn't support it - so
    324  * we need to listen on all interfaces.
    325  * We should verify that this doesn't impact performance (we already do it for
    326  * ipv4) and either remove all the ipv6pktinfo detection code from above
    327  * or think of fixing libuv.
    328  */
    329 #ifdef __notyet__
    330 	initialize_ipv6pktinfo();
    331 #endif /* ifdef __notyet__ */
    332 	return ipv6pktinfo_result;
    333 }
    334 
    335 #if defined(USE_SYSCTL_PORTRANGE)
    336 #if defined(HAVE_SYSCTLBYNAME)
    337 static isc_result_t
    338 getportrange_sysctl(int af, in_port_t *low, in_port_t *high) {
    339 	int port_low, port_high;
    340 	size_t portlen;
    341 	const char *sysctlname_lowport, *sysctlname_hiport;
    342 
    343 	if (af == AF_INET) {
    344 		sysctlname_lowport = SYSCTL_V4PORTRANGE_LOW;
    345 		sysctlname_hiport = SYSCTL_V4PORTRANGE_HIGH;
    346 	} else {
    347 		sysctlname_lowport = SYSCTL_V6PORTRANGE_LOW;
    348 		sysctlname_hiport = SYSCTL_V6PORTRANGE_HIGH;
    349 	}
    350 	portlen = sizeof(port_low);
    351 	if (sysctlbyname(sysctlname_lowport, &port_low, &portlen, NULL, 0) < 0)
    352 	{
    353 		return ISC_R_FAILURE;
    354 	}
    355 	portlen = sizeof(port_high);
    356 	if (sysctlbyname(sysctlname_hiport, &port_high, &portlen, NULL, 0) < 0)
    357 	{
    358 		return ISC_R_FAILURE;
    359 	}
    360 	if ((port_low & ~0xffff) != 0 || (port_high & ~0xffff) != 0) {
    361 		return ISC_R_RANGE;
    362 	}
    363 
    364 	*low = (in_port_t)port_low;
    365 	*high = (in_port_t)port_high;
    366 
    367 	return ISC_R_SUCCESS;
    368 }
    369 #else  /* !HAVE_SYSCTLBYNAME */
    370 static isc_result_t
    371 getportrange_sysctl(int af, in_port_t *low, in_port_t *high) {
    372 	int mib_lo4[4] = SYSCTL_V4PORTRANGE_LOW;
    373 	int mib_hi4[4] = SYSCTL_V4PORTRANGE_HIGH;
    374 	int mib_lo6[4] = SYSCTL_V6PORTRANGE_LOW;
    375 	int mib_hi6[4] = SYSCTL_V6PORTRANGE_HIGH;
    376 	int *mib_lo, *mib_hi, miblen;
    377 	int port_low, port_high;
    378 	size_t portlen;
    379 
    380 	if (af == AF_INET) {
    381 		mib_lo = mib_lo4;
    382 		mib_hi = mib_hi4;
    383 		miblen = sizeof(mib_lo4) / sizeof(mib_lo4[0]);
    384 	} else {
    385 		mib_lo = mib_lo6;
    386 		mib_hi = mib_hi6;
    387 		miblen = sizeof(mib_lo6) / sizeof(mib_lo6[0]);
    388 	}
    389 
    390 	portlen = sizeof(port_low);
    391 	if (sysctl(mib_lo, miblen, &port_low, &portlen, NULL, 0) < 0) {
    392 		return ISC_R_FAILURE;
    393 	}
    394 
    395 	portlen = sizeof(port_high);
    396 	if (sysctl(mib_hi, miblen, &port_high, &portlen, NULL, 0) < 0) {
    397 		return ISC_R_FAILURE;
    398 	}
    399 
    400 	if ((port_low & ~0xffff) != 0 || (port_high & ~0xffff) != 0) {
    401 		return ISC_R_RANGE;
    402 	}
    403 
    404 	*low = (in_port_t)port_low;
    405 	*high = (in_port_t)port_high;
    406 
    407 	return ISC_R_SUCCESS;
    408 }
    409 #endif /* HAVE_SYSCTLBYNAME */
    410 #endif /* USE_SYSCTL_PORTRANGE */
    411 
    412 void
    413 isc_net_getportrange(int af, in_port_t *low, in_port_t *high) {
    414 	int result = ISC_R_FAILURE;
    415 #if !defined(USE_SYSCTL_PORTRANGE) && defined(__linux__)
    416 	FILE *fp;
    417 #endif /* if !defined(USE_SYSCTL_PORTRANGE) && defined(__linux__) */
    418 
    419 	REQUIRE(low != NULL && high != NULL);
    420 
    421 #if defined(USE_SYSCTL_PORTRANGE)
    422 	result = getportrange_sysctl(af, low, high);
    423 #elif defined(__linux__)
    424 
    425 	UNUSED(af);
    426 
    427 	/*
    428 	 * Linux local ports are address family agnostic.
    429 	 */
    430 	fp = fopen("/proc/sys/net/ipv4/ip_local_port_range", "r");
    431 	if (fp != NULL) {
    432 		int n;
    433 		unsigned int l, h;
    434 
    435 		n = fscanf(fp, "%u %u", &l, &h);
    436 		if (n == 2 && (l & ~0xffff) == 0 && (h & ~0xffff) == 0) {
    437 			*low = l;
    438 			*high = h;
    439 			result = ISC_R_SUCCESS;
    440 		}
    441 		fclose(fp);
    442 	}
    443 #else  /* if defined(USE_SYSCTL_PORTRANGE) */
    444 	UNUSED(af);
    445 #endif /* if defined(USE_SYSCTL_PORTRANGE) */
    446 
    447 	if (result != ISC_R_SUCCESS) {
    448 		*low = ISC_NET_PORTRANGELOW;
    449 		*high = ISC_NET_PORTRANGEHIGH;
    450 	}
    451 }
    452 
    453 void
    454 isc_net_disableipv4(void) {
    455 	initialize();
    456 	if (ipv4_result == ISC_R_SUCCESS) {
    457 		ipv4_result = ISC_R_DISABLED;
    458 	}
    459 }
    460 
    461 void
    462 isc_net_disableipv6(void) {
    463 	initialize();
    464 	if (ipv6_result == ISC_R_SUCCESS) {
    465 		ipv6_result = ISC_R_DISABLED;
    466 	}
    467 }
    468 
    469 void
    470 isc_net_enableipv4(void) {
    471 	initialize();
    472 	if (ipv4_result == ISC_R_DISABLED) {
    473 		ipv4_result = ISC_R_SUCCESS;
    474 	}
    475 }
    476 
    477 void
    478 isc_net_enableipv6(void) {
    479 	initialize();
    480 	if (ipv6_result == ISC_R_DISABLED) {
    481 		ipv6_result = ISC_R_SUCCESS;
    482 	}
    483 }
    484