[posix] let -I choose the utun number on macOS (#13742)

A macOS utun has no free name; the kernel assigns utunN with the lowest
free N, so the Thread interface's name changed between restarts and
nothing could refer to it in advance: not ot-ctl, not a firewall rule,
not a service file. The requested interface name was ignored.

Honour a requested name utunN: ask the kernel for that unit (sc_unit is
the number plus one) and die with EBUSY if it exists. The digits must be
the whole rest of the name, written as the kernel names the interface
(no leading zero), and fit, or the daemon dies with
OT_EXIT_INVALID_ARGUMENTS rather than silently getting another utun. The
default name, which main.c passes when -I is not given, and an empty
name keep the kernel's choice as before; any other name cannot be
honoured on macOS and ends the daemon with OT_EXIT_INVALID_ARGUMENTS too.

check-posix-pty requests a utun name on macOS and asserts, on both
platforms, that the daemon created the interface it was asked for; the
CLI leg on macOS also asks for utunabc and expects exit code 2.
This commit is contained in:
Clara
2026-11-02 14:12:28 -08:00
committed by GitHub
parent 0dbe05b0a1
commit 57368b5dcd
2 changed files with 57 additions and 8 deletions
+13 -3
View File
@@ -251,8 +251,12 @@ do_check()
timeout_run 10 test -c "${SOCAT_PTY}"
echo 'SOCAT_PTY' "${SOCAT_PTY}"
# Cover setting a valid network interface name.
VALID_NETIF_NAME="wan$(date +%H%M%S)"
# Cover setting a valid network interface name. On macOS only a utun number can be chosen.
if [[ ${OSTYPE} == darwin* ]]; then
VALID_NETIF_NAME="utun$((200 + 10#$(date +%S)))"
else
VALID_NETIF_NAME="wan$(date +%H%M%S)"
fi
readonly VALID_NETIF_NAME
RADIO_URL="spinel+hdlc+uart://${SOCAT_PTY}?region=US&max-power-table-mbm=1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600"
@@ -263,8 +267,8 @@ do_check()
sudo -E "$PWD/build/posix/src/posix/ot-daemon" -d5 -v -I "${VALID_NETIF_NAME}" "${RADIO_URL}" >"${OT_DAEMON_OUTPUT}" 2>&1 &
OT_DAEMON_JOB=$!
sleep 3
# macOS cannot explicitly set network interface name
NETIF_NAME=$(grep -o 'Thread interface: .\+' "${OT_DAEMON_OUTPUT}" | cut -d: -f2 | tr -d ' \r\n')
[[ ${NETIF_NAME} == "${VALID_NETIF_NAME}" ]] || die "ot-daemon created ${NETIF_NAME}, not the requested ${VALID_NETIF_NAME}"
OT_CTL_PATH="$PWD/build/posix/src/posix/ot-ctl"
if [[ ${OT_DAEMON_ALLOW_ALL} == 1 ]] || [[ -n ${OT_ISOLATED_REEXEC:-} ]]; then
OT_CTL=("${OT_CTL_PATH}")
@@ -316,6 +320,12 @@ do_check()
readonly INVALID_NETIF_NAME
sudo "${OT_CLI}" -I "${INVALID_NETIF_NAME}" -n "${RADIO_URL}" || test $? = 2
if [[ ${OSTYPE} == darwin* ]]; then
# A utun name needs a number. The check runs when the interface is created, after a dry run ends,
# so no -n here; the CLI exits with 2 (OT_EXIT_INVALID_ARGUMENTS) before it would block.
sudo "${OT_CLI}" -I utunabc "${RADIO_URL}" || test $? = 2
fi
OT_CLI_CMD="$PWD/build/posix/src/posix/ot-cli ${RADIO_URL}"
fi
+44 -5
View File
@@ -3389,10 +3389,41 @@ static void platformConfigureTunDevice(otPlatformConfig *aPlatformConfig)
#if defined(__APPLE__) && (OPENTHREAD_POSIX_CONFIG_MACOS_TUN_OPTION == OT_POSIX_CONFIG_MACOS_UTUN)
static void platformConfigureTunDevice(otPlatformConfig *aPlatformConfig)
{
(void)aPlatformConfig;
int err = 0;
constexpr char kUtunPrefix[] = "utun";
int err = 0;
struct sockaddr_ctl addr;
struct ctl_info info;
uint32_t unit = 0; // 0: the kernel picks the lowest free utun
const char *name = aPlatformConfig->mInterfaceName;
// A requested name utunN asks for that unit, which gives the interface a stable name across restarts; the
// digits must be the whole rest of the name, written as the kernel names the interface (no leading zero).
// The default name, which main.c passes when -I is not given, leaves the choice to the kernel, the only way
// to a free utun; any other name cannot be honoured on macOS and is refused.
if ((name != nullptr) && (strncmp(name, kUtunPrefix, sizeof(kUtunPrefix) - 1) == 0))
{
const char *digits = name + sizeof(kUtunPrefix) - 1;
char *end;
unsigned long number;
errno = 0;
number = strtoul(digits, &end, 10);
if ((*digits < '0') || (*digits > '9') || ((*digits == '0') && (digits[1] != '\0')) || (*end != '\0') ||
(errno != 0) || (number >= UINT32_MAX) || (strlen(name) >= IFNAMSIZ))
{
LogCrit("Invalid utun interface name: %s", name);
DieNow(OT_EXIT_INVALID_ARGUMENTS);
}
unit = static_cast<uint32_t>(number) + 1; // sc_unit is the utun number plus one
}
else if ((name != nullptr) && (name[0] != '\0') &&
(strcmp(name, OPENTHREAD_POSIX_CONFIG_THREAD_NETIF_DEFAULT_NAME) != 0))
{
LogCrit("Interface name %s cannot be chosen on macOS: use utunN for a given utun, or none", name);
DieNow(OT_EXIT_INVALID_ARGUMENTS);
}
sTunFd = ot::Posix::SocketWithCloseExec(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL, ot::Posix::kSocketNonBlock);
VerifyOrDie(sTunFd >= 0, OT_EXIT_ERROR_ERRNO);
@@ -3407,9 +3438,17 @@ static void platformConfigureTunDevice(otPlatformConfig *aPlatformConfig)
addr.sc_family = AF_SYSTEM;
addr.ss_sysaddr = AF_SYS_CONTROL;
addr.sc_unit = 0;
err = connect(sTunFd, (struct sockaddr *)&addr, sizeof(addr));
VerifyOrDie(err == 0, OT_EXIT_ERROR_ERRNO);
addr.sc_unit = unit;
err = connect(sTunFd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr));
if (err != 0)
{
int error = errno; // the log call may change it, and the exit code reports it
// EBUSY: the requested utun exists already.
LogCrit("Failed to create %s: %s", (unit != 0) ? name : "a utun", strerror(error));
errno = error;
DieNow(OT_EXIT_ERROR_ERRNO);
}
socklen_t devNameLen;
devNameLen = (socklen_t)sizeof(gNetifName);