Access to MAC addresses of local network interfaces in macOS 27

Hi all,

we are building a custom controller for ATDECC, which is a layer 2 protocol standardized by IEEE in 1722.1. Our controller can work on multiple network interfaces at the same time . It uses the interface's MAC address to identify, on which interface a certain AVB / ATDECC device was discovered. It then sends replies for this device only to this interface.

This controller worked fine up to and including macOS 26, but when running the same code on macOS 27, we cannot get the MAC addresses for the local interfaces anymore, but we receive 02:00:00:00:00:00 for each of them. This seems to indicate that the MAC address was redacted (looks like the same MAC address, that is being returned since iOS 11 due to privacy reason).

Is this a bug or is macOS going to redact the MAC addresses also in the final release? If MAC addresses are being redacted, would it help to request access to the new entitlement called com.apple.developer.networking.topology-observation?

I attached a little code snippet, that returns actual MAC addresses on macOS 26, but redacted ones on macOS 27.

Build with clang++ -std=c++23 -o ifprobe ifprobe.cpp and then run it with ./ifprobe.

Looks like the file content doesn't display correctly. Here it is again.

// clang++ -std=c++23 -o ifprobe ifprobe.cpp && ./ifprobe
#include <ifaddrs.h>
#include <net/bpf.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <sys/ioctl.h>
#include <sys/sockio.h>
#include <sys/sysctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>

static int open_bpf()
{
    for ( int i = 0; i < 256; ++i )
    {
        std::string p = "/dev/bpf" + std::to_string( i );
        int fd = open( p.c_str(), O_RDWR );
        if ( fd != -1 ) return fd;
        if ( errno != EBUSY ) break;
    }
    return -1;
}

static const char* functional_type_name( unsigned t )
{
    switch ( t )
    {
        case IFRTYPE_FUNCTIONAL_LOOPBACK:   return "LOOPBACK";
        case IFRTYPE_FUNCTIONAL_WIRED:      return "WIRED";
        case IFRTYPE_FUNCTIONAL_WIFI_INFRA: return "WIFI_INFRA";
        case IFRTYPE_FUNCTIONAL_WIFI_AWDL:  return "WIFI_AWDL";
        case IFRTYPE_FUNCTIONAL_CELLULAR:   return "CELLULAR";
        default:                            return "UNKNOWN";
    }
}

static size_t sockaddr_aligned_size(const sockaddr* address)
{
    if (address->sa_len == 0)
        return sizeof(long);

    return (address->sa_len + sizeof(long) - 1) &
           ~(sizeof(long) - 1);
}

static void print_sysctl_mac(const char* name)
{
    const unsigned int index = if_nametoindex(name);
    if (index == 0)
    {
        printf(" sysctl_mac=<unknown interface>");
        return;
    }

    int mib[] = {
        CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST,
        static_cast<int>(index)
    };

    size_t length = 0;
    if (sysctl(mib, 6, nullptr, &length, nullptr, 0) != 0)
    {
        printf(" sysctl_mac=<size query failed errno=%d>", errno);
        return;
    }

    std::vector<unsigned char> buffer(length);
    if (sysctl(mib, 6, buffer.data(), &length, nullptr, 0) != 0)
    {
        printf(" sysctl_mac=<read failed errno=%d>", errno);
        return;
    }

    auto* message = reinterpret_cast<const if_msghdr*>(buffer.data());
    const auto* address = reinterpret_cast<const sockaddr*>(
        message + 1);

    for (int address_index = 0;
         address_index < RTAX_MAX;
         ++address_index)
    {
        if ((message->ifm_addrs & (1 << address_index)) == 0)
            continue;

        if (address_index == RTAX_IFP &&
            address->sa_family == AF_LINK)
        {
            auto* link_address =
                reinterpret_cast<const sockaddr_dl*>(address);

            if (link_address->sdl_alen == 6)
            {
                const auto* mac =
                    reinterpret_cast<const unsigned char*>(
                        LLADDR(link_address));

                printf(" sysctl_mac=%02x:%02x:%02x:%02x:%02x:%02x",
                       mac[0], mac[1], mac[2],
                       mac[3], mac[4], mac[5]);
                return;
            }
        }

        address = reinterpret_cast<const sockaddr*>(
            reinterpret_cast<const unsigned char*>(address) +
            sockaddr_aligned_size(address));
    }

    printf(" sysctl_mac=<unavailable>");
}

int main()
{
    ifaddrs* ifap = nullptr;
    if ( getifaddrs( &ifap ) != 0 ) return 1;

    int probe = socket( AF_INET, SOCK_DGRAM, 0 );

    for ( ifaddrs* p = ifap; p; p = p->ifa_next )
    {
        if ( !p->ifa_addr || p->ifa_addr->sa_family != AF_LINK ) continue;

        auto* dl = reinterpret_cast< sockaddr_dl* >( p->ifa_addr );

        unsigned ftype = 0;
        ifreq fr{};
        strncpy( fr.ifr_name, p->ifa_name, IFNAMSIZ - 1 );
        if ( ioctl( probe, SIOCGIFFUNCTIONALTYPE, &fr ) == 0 )
            ftype = fr.ifr_ifru.ifru_functional_type;

        // BIOCGDLT is the decisive check: DLT_EN10MB means our 14-byte Ethernet header is correct framing.
        int dlt = -1;
        int fd = open_bpf();
        if ( fd >= 0 )
        {
            ifreq br{};
            strncpy( br.ifr_name, p->ifa_name, IFNAMSIZ - 1 );
            if ( ioctl( fd, BIOCSETIF, &br ) == 0 ) ioctl( fd, BIOCGDLT, &dlt );
            close( fd );
        }

        printf( "%-10s maclen=%-2u ftype=%-11s dlt=%-3d up=%d running=%d ",
                p->ifa_name, dl->sdl_alen, functional_type_name( ftype ), dlt,
                ( p->ifa_flags & IFF_UP ) != 0, ( p->ifa_flags & IFF_RUNNING ) != 0 );
        print_sysctl_mac(p->ifa_name);
        printf("\n");
    }

    close( probe );
    freeifaddrs( ifap );
}
Access to MAC addresses of local network interfaces in macOS 27
 
 
Q