Chapter Orientation
Chapters 63 through 69 solved "how data flows out": addresses, ports, buffers, DNS, TCP, UDP, proxies. This chapter supplies the other half of the picture — seeing yourself: which NICs this machine has, which addresses hang on each, what the loopback interface is called, what the machine's preferred address is. This information is indispensable in four scenarios: startup logs printing the network environment, admin diagnostic pages showing node info, choosing a bind address on multi-NIC machines, and the scope identifier IPv6 link-local addresses must carry. Their shapes on native APIs are famously fragmented — Windows' adapter GUIDs, POSIX's if_nametoindex, per-platform naming schemes — and XRT chokes them down into a small module with zero engine dependencies: no Sockets, threads, or DNS introduced, freely combinable with any transport layer.
71.1引入
Suppose you are writing a distributed service's startup flow. Operations requires every startup log to carry "hostname + local address + hardware identifier" so a node can be located instantly among thousands of log lines; the admin endpoint /health shows the same set; on a multi-NIC machine the config file gives an interface name eth0 and you must look up its index for IPv6 multicast; and there is the classic error — fe80::1 won't connect, because a link-local address is complete only written as fe80::1%3 (3 being the interface index).
Individually these needs are easy; together they are a cross-platform swamp: a Windows interface's "name" is a GUID, its display name "Ethernet"; Linux's canonical name is eth0; the loopback interface is named entirely differently on each side. Hardware addresses come from GetAdaptersAddresses on Windows and ioctl on POSIX, and the length is not necessarily 6 bytes. Doing the chokepoint yourself costs one branch per platform, while XRT's interface module has already done it: one call yields a consistent snapshot structure, names and indices convert both ways, and the local trio (address/hardware/hostname) comes in three convenience tiers each.
71.2概念
The interface snapshot: one allocation, all views borrowed
xrtNetInterfaces produces a consistent snapshot of the system's current interfaces and addresses: one XRT allocation holds the interface array, the address array, names, and hardware addresses; afterwards every field is a borrowed view.
The snapshot's value is consistency: interfaces added or removed during enumeration cannot misalign entries; the price is that it is a momentary slice, not a change subscription. Per-interface metadata: IPv4Index/IPv6Index (Windows maintains them separately; POSIX usually identical), Flags bit flags, Mtu, Name (canonical) and DisplayName (display) views, HardwareAddress raw bytes, and the Addresses array — each address carries PrefixLength (0..32 for IPv4, 0..128 for IPv6; XNET_INTERFACE_PREFIX_UNKNOWN when the platform can't tell).
Two usage disciplines: the order of interfaces and addresses is OS-decided — never treat order as identity; views die immediately after freeing, so all reads must precede Free.
Flags: the stable cross-platform intersection
Flags exposes only the six flags every platform can observe reliably: UP, RUNNING, LOOPBACK, BROADCAST, POINT_TO_POINT, MULTICAST. A flag with no reliable source on some platform stays unset — unset does not mean the capability is absent, only that the platform can't see it. Judging "can join a multicast group" reads MULTICAST; "is loopback" reads LOOPBACK — each a single bit-and.
Names and indices: two-way conversion
Interfaces have two identity systems. The index is the kernel world's key (IPv6 scope and multicast membership management use it); the name is the human world's key (config files write it):
xrtNetInterfaceIndex(名称, 族)returns the index; index zero is not a valid result — a miss reportsXERR_NOT_FOUND.xrtNetInterfaceName(索引, 族, 缓冲, 容量)outputs the canonical name in reverse, returning the needed length; a null buffer measures first, and an insufficient buffer truncates safely, zero-terminates, and returns the full needed length — the same two-part convention as Chapter 63's text APIs.
Naming differences are absorbed by the module: on Windows the canonical name comes from the system adapter name (GUID-like) and the display name is the interface alias; on POSIX both are if_name. Index accepts both canonical and display names, and a UNSPEC family query prefers the IPv6 index, falling back to IPv4.
The local-info trio: three tiers
The diagnostic set "hostname, local address, hardware address" has three usage tiers, and the API splits accordingly:
| Tier | Address | Hardware | Hostname | Fits |
|---|---|---|---|---|
| Struct/bytes out-param | xrtNetLocalAddress | xrtNetLocalHardware | xrtNetHostName | Continuing with binary forms (connections, comparisons) |
| Two-part text | xrtNetLocalAddressText | xrtNetLocalHardwareText | — (HostName is already text) | Fixed buffers, allocation-free contexts |
| String convenience | xrtNetLocalAddressString | xrtNetLocalHardwareString | xrtNetHostNameString | Startup logs and diagnostic pages, printed in one go |
xrtNetLocalAddress's choice is a deterministic preference query: it prefers in turn UP, RUNNING, non-loopback, non-link-local interfaces, with UNSPEC preferring IPv6 on ties. It suits startup logs and node reports, but is not the public egress address and does not represent the default route — for precise choices, walk xrtNetInterfaces and decide yourself. Hardware-address text is uppercase separator-free compact HEX (a 6-byte MAC is 12 characters); for colon-separated and other display formats, read the raw bytes and format yourself. The String tier returns strings freed with xrtFree, where null means unavailable (query semantics — no thread error is set).
An unexpected bonus: IPv6 scope parsing
With this module enabled, Chapter 63's xrtNetAddrParse and xrtNetAddrParseEndpoint accept both numeric and interface-name scopes: fe80::1%3 and fe80::1%eth0 both parse, and so does [fe80::1%eth0]:8080. The parse result stores only the stable numeric interface index; textual output (xrtNetAddrText) keeps its canonical system-query-free form — names change with system configuration, while the index is the persistently comparable identity.
71.3示例
First complete program: enumerating interfaces and address prefixes
The following program comes from examples/network/interface/main.c, walking every local interface and printing name, dual-stack indices, MTU, and each address's prefix — a one-shot panorama for multi-NIC inspection:
#include <stdio.h> #include <xrt.h> */ /* 列出本机接口以及可用于绑定、Scope 和多播的地址元数据。 */ int main(void) { xnetinterfacelist List; size_t i; if ( !xrtNetInterfaces(&List) ) { return 1; } for ( i = 0; i < List.Count; i++ ) { const xnetinterface* pInterface = &List.Items[i]; size_t j; printf("%.*s (%.*s) index4=%u index6=%u mtu=%u\n", (int)pInterface->Name.Size, pInterface->Name.Data, (int)pInterface->DisplayName.Size, pInterface->DisplayName.Data, (unsigned int)pInterface->IPv4Index, (unsigned int)pInterface->IPv6Index, (unsigned int)pInterface->Mtu); for ( j = 0; j < pInterface->AddressCount; j++ ) { const xnetinterfaceaddress* pAddress = &pInterface->Addresses[j]; char sAddress[96]; if ( xrtNetAddrText( &pAddress->Address, sAddress, sizeof(sAddress) ) == XRT_NPOS ) { xrtNetInterfacesFree(&List); return 1; } printf(" %s/%u\n", sAddress, (unsigned int)pAddress->PrefixLength); } } xrtNetInterfacesFree(&List); return 0; }
What just happened. Three points. (1) xrtNetInterfaces enumerates and snapshots in one call — List.Count and List.Items are the whole interface set; no handles to close one by one. (2) Interface names and display names are both xstrview, printed with %.*s and Size; addresses reuse Chapter 63's xrtNetAddrText for text, with XRT_NPOS signaling an insufficient buffer. (3) PrefixLength appends straight after the address — 127.0.0.1/8, 192.168.1.10/24 CIDR-style lines are the snapshot's real prefixes. Output varies by machine, so the example header's expected output is a description, not fixed text.
Second complete program: name/index round-trip and the local trio
The second program comes from examples/network/interface_tour/main.c, chaining the name → index → name round-trip and the two-part queries of local address, hardware, and hostname into one checkup:
*/ #include <stdio.h> #include <string.h> #include <xrt.h> int main(void) { char sName[64]; char sText[64]; uint8 Hardware[16]; xnetaddr Address; uint32 iIndex; size_t iSize = 0; size_t iNeed = 0; int iResult = 1; /* ---- InterfaceIndex / InterfaceName:回环往返 ---- */ #if defined(_WIN32) || defined(_WIN64) iIndex = xrtNetInterfaceIndex("loopback4", XNET_FAMILY_IPV4); #elif defined(__APPLE__) iIndex = xrtNetInterfaceIndex("lo0", XNET_FAMILY_IPV4); #else iIndex = xrtNetInterfaceIndex("lo", XNET_FAMILY_IPV4); #endif if ( iIndex == 0u ) { /* Windows 命名不同:回环显示名 "Loopback Pseudo-Interface 1"。 */ iIndex = xrtNetInterfaceIndex( "Loopback Pseudo-Interface 1", XNET_FAMILY_IPV4); } if ( iIndex == 0u ) { goto Cleanup; /* 找不到回环:环境异常 */ } /* InterfaceName 两段式:查询 → 写入 → 回环索引反查一致。 */ iNeed = xrtNetInterfaceName(iIndex, XNET_FAMILY_IPV4, NULL, 0u); if ( (iNeed == 0u) || (iNeed >= sizeof(sName)) ) { goto Cleanup; } iSize = xrtNetInterfaceName(iIndex, XNET_FAMILY_IPV4, sName, sizeof(sName)); if ( (iSize == 0u) || (iSize != iNeed) ) { goto Cleanup; } /* 名字反查索引一致(roundtrip)。 */ if ( xrtNetInterfaceIndex(sName, XNET_FAMILY_IPV4) != iIndex ) { /* 显示名与规范名可能不同:跳过反查但记录。 */ printf("iface: (display-name index differs) "); } printf("iface: loopback index!=0 name=[%s] ok\n", sName); /* ---- LocalAddress / Text ---- */ if ( !xrtNetLocalAddress(&Address, XNET_FAMILY_IPV4) || (Address.Family != XNET_FAMILY_IPV4) || (Address.Port != 0u) ) { goto Cleanup; } iNeed = xrtNetLocalAddressText(XNET_FAMILY_IPV4, NULL, 0u); if ( (iNeed == 0u) || (iNeed >= sizeof(sText)) ) { goto Cleanup; } iSize = xrtNetLocalAddressText(XNET_FAMILY_IPV4, sText, sizeof(sText)); if ( (iSize == 0u) || (iSize != iNeed) || (strchr(sText, '.') == NULL) ) { goto Cleanup; /* IPv4 文本必含点 */ } printf("iface: local addr=[%s]", sText); /* ---- LocalHardware / Text:MAC 至少 6 字节。 ---- */ iNeed = xrtNetLocalHardware(NULL, 0u); if ( (iNeed < 6u) || (iNeed > sizeof(Hardware)) ) { goto Cleanup; } iSize = xrtNetLocalHardware(Hardware, sizeof(Hardware)); if ( iSize != iNeed ) { goto Cleanup; } iNeed = xrtNetLocalHardwareText(NULL, 0u); if ( (iNeed < 12u) || (iNeed >= sizeof(sText)) ) { goto Cleanup; /* 6 字节 → 12 个 HEX 字符 */ } iSize = xrtNetLocalHardwareText(sText, sizeof(sText)); if ( (iSize != iNeed) ) { goto Cleanup; } printf(" hw=%s", sText); /* ---- HostName:非空且可写入。 ---- */ iNeed = xrtNetHostName(NULL, 0u); if ( (iNeed == 0u) || (iNeed >= sizeof(sName)) ) { goto Cleanup; } iSize = xrtNetHostName(sName, sizeof(sName)); if ( (iSize == 0u) || (iSize != iNeed) ) { goto Cleanup; } printf(" host=[%s] ok\n", sName); iResult = 0; Cleanup: return iResult; }
What just happened. (1) The loopback interface first tries the POSIX name loopback4, falling back to the Windows display name Loopback Pseudo-Interface 1 — the standard way of "one fallback absorbing cross-platform naming differences at the call site". (2) InterfaceName's two-part form: measure the needed length with NULL/0 first, then write and cross-check the two returns; with the canonical name in hand, reverse-lookup the index as a round-trip check (platforms where display and canonical names differ may skip the reverse lookup). (3) The local trio verified in turn: LocalAddress struct out-param with family and zero-port checks, LocalAddressText output necessarily containing a dot (an IPv4 text trait), LocalHardware raw bytes at least 6, LocalHardwareText exactly twice the byte count in HEX characters, HostName non-empty and writable. Any step failing goes through the unified Cleanup returning non-zero — writing the "checkup" as a fail-fast chain reads far better than scattered ifs.
Third complete program: the diagnostic-page overview (String convenience tier)
The third program comes from examples/network/local_info/main.c, gathering the startup-log trio in one go via the String tier, with placeholder text when the hardware address is missing:
#include <stdio.h> #include <xrt.h> */ /* 一次读取适合启动日志和诊断页展示的本机网络信息。 */ int main(void) { str sAddress = xrtNetLocalAddressString(XNET_FAMILY_UNSPEC); str sHost = xrtNetHostNameString(); str sHardware = xrtNetLocalHardwareString(); if ( (sAddress == NULL) || (sHost == NULL) ) { xrtFree(sAddress); xrtFree(sHost); xrtFree(sHardware); return 1; } printf("host = %s\n", sHost); printf("address = %s\n", sAddress); printf("hardware = %s\n", sHardware != NULL ? sHardware : "unavailable"); xrtFree(sHardware); xrtFree(sHost); xrtFree(sAddress); return 0; }
What just happened. The String tier's contract shows here: it returns str (freed with xrtFree), and NULL means unavailable without setting a thread error — so a null check is the entire error handling. A missing address or hostname counts as an environmental anomaly returning 1, while the hardware address may legitimately be absent in a pure-loopback environment (containers, NIC-less VMs), printing unavailable and carrying on. The three strings are freed together at the end — even a mid-way failure frees them all, since xrtFree(NULL) is a no-op needing no per-string null checks.
71.4契约
- Snapshot ownership:
Interfacesowns all storage in one allocation; field views only borrow;InterfacesFreereturns it and zeroes the struct, callable repeatedly on an empty list; failure leaves the output untouched. - No order guarantee: interface and address order is OS-decided and must not serve as stable identity; identity is the index (numeric) and the canonical name.
- Name/index: index zero is not a valid result;
Indexaccepts canonical and display names,UNSPECpreferring the IPv6 index;Namefollows the text APIs' two-part convention (measure → write; insufficient truncates safely and returns the full needed length). - Convenience-tier boundary:
LocalAddressis a deterministic preference query (UP/RUNNING/non-loopback/non-link-local, UNSPEC preferring IPv6) — not the public egress, not the default route, no substitute for listening and source-address policy; for precise choices, walkInterfaces. - Text tier: hardware-address text is uppercase compact HEX; the String tier returns strings freed with
xrtFree,NULLmeaning unavailable, no thread error set (query semantics). - Errors: failures are reported via
xrtGetError()—XERR_NOT_FOUND(interface/address/hardware absent),XERR_IO(system enumeration failure),XERR_RANGE+XNET_ERROR_BUFFER(insufficient buffer), domainxrt.net. - Dependencies and trimming:
XRT_MODULE_NET_INTERFACEdepends only on the network address foundation — no Sockets, Engine, threads, DNS, or containers;XRT_MODULE_NET_INTERFACE_TEXTis a separate thin layer adding HEX encoding.
Common Mistakes
Symptoms: the service is unreachable in multi-NIC/NAT environments — the "local address" in the logs belongs to some intranet interface, not the address peers can reach; or binding to it leaves the service inaccessible outside the container.
Cause: xrtNetLocalAddress is a preference pick (preferring online, running, non-loopback, non-link-local unicast addresses); it knows nothing of the routing table, let alone the public egress behind NAT. Treating it as the "externally reachable address" or the sole bind address mistakes diagnostic information for a routing decision.
xnetaddr Addr; xrtNetLocalAddress(&Addr, XNET_FAMILY_UNSPEC); /* directly registering Addr as "the address peers can connect to me on" */ register_service(xrtNetAddrText(&Addr, ...));
for multi-NIC binding, walk the snapshot and decide yourself */ xnetinterfacelist List; if ( xrtNetInterfaces(&List) ) { for ( size_t i = 0; i < List.Count; i++ ) { const xnetinterface* pIf = &List.Items[i]; if ( (pIf->Flags & XNET_INTERFACE_UP) == 0 ) { continue; } /* pick by business rules: family, prefix, multicast capability... */ } xrtNetInterfacesFree(&List); }
Symptoms: occasional crashes or garbage bytes, often in code that "caches interface info".
Cause: Items[i].Name.Data, Addresses[j], HardwareAddress.Data all point into the snapshot's internal storage; after xrtNetInterfacesFree they all dangle. The snapshot is a value-semantics momentary slice, not a long-held object.
xnetinterfacelist List; xrtNetInterfaces(&List); const xstrview Name = List.Items[0].Name; /* borrowed view */ const xnetinterfaceaddress* Addrs = List.Items[0].Addresses; xrtNetInterfacesFree(&List); /* storage returned */ printf("%.*s\n", (int)Name.Size, Name.Data); /* dangling read */
xnetinterfacelist List; if ( xrtNetInterfaces(&List) ) { for ( size_t i = 0; i < List.Count; i++ ) { const xnetinterface* pIf = &List.Items[i]; printf("%.*s\n", (int)pIf->Name.Size, pIf->Name.Data); /* copy information you must keep into owned storage right now */ } xrtNetInterfacesFree(&List); }
Symptoms: truncated buffers or wrong lengths on platforms with 8-byte EUI-64 addresses or certain virtual interfaces.
Cause: MAC-48's 6 bytes are only the "typical value"; the module contract explicitly says LocalHardware returns the needed byte count and never hard-codes the length; the HardwareAddress view's Size is likewise system-decided.
uint8 Mac[6]; if ( xrtNetLocalHardware(Mac, sizeof(Mac)) == 0 ) { /* assumes 6 bytes: longer addresses silently truncated */ }
size_t iNeed = xrtNetLocalHardware(NULL, 0u); /* measure first */ if ( (iNeed == 0u) || (iNeed > 16u) ) { return 1; } uint8 Hardware[16]; size_t iSize = xrtNetLocalHardware(Hardware, iNeed); /* only iSize == iNeed is a complete address; the HEX text length is 2*iSize */
Programming Exercises
Enumerate the snapshot, find the interface whose Flags include XNET_INTERFACE_LOOPBACK, and print its name, dual-stack indices, and all addresses. Acceptance: run once each on Windows and Linux (WSL works too), both correctly printing loopback info; the program exits normally after freeing the snapshot.
Write a function uint32 pick_multicast_iface(void): walk the snapshot and pick the interface that is "up, multicast-capable, non-loopback, with the largest MTU", returning its IPv6 index, or 0 if none. Hint: test Flags for both XNET_INTERFACE_UP and XNET_INTERFACE_MULTICAST, exclude loopback with XNET_INTERFACE_LOOPBACK; don't forget InterfacesFree before returning.
Implement print_diagnostics(void): print in one go the hostname (HostName two-part), local address (LocalAddressText two-part, family UNSPEC), hardware address (LocalHardware raw bytes self-formatted as colon-separated uppercase HEX, like 00:E0:4C:7D:7E:66), and the total interface and address counts (from the snapshot). Acceptance: zero leaks across all output (cross-check with Chapter 6's allocation stats); a missing hardware address prints hardware: unavailable instead of failing; the two returns of every two-part call cross-checked one by one.
Summary
| Topic | Quick reference |
|---|---|
| Snapshot | Interfaces owns everything in one allocation; views all borrowed; InterfacesFree zeroes and is repeatable; order is not identity |
| Interface metadata | dual-stack indices / Flags / Mtu / Name+DisplayName / HardwareAddress / Addresses+PrefixLength |
| Flags | UP RUNNING LOOPBACK BROADCAST POINT_TO_POINT MULTICAST; unset ≠ capability absent |
| Name↔index | InterfaceIndex (name → index, 0 = failure) / InterfaceName (index → name, two-part); UNSPEC prefers IPv6 |
| Local trio | address (preference query, not the public egress) / hardware (byte count varies) / hostname (local label, not a DNS name) |
| Three tiers | out-param (continue in binary) → Text two-part (allocation-free) → String (logs in one go, xrtFree + null check) |
| Hardware text | uppercase compact HEX (6 bytes = 12 chars); format display forms yourself |
| IPv6 scope | with the module enabled, both fe80::1%3 and fe80::1%eth0 parse; the stable numeric index is stored |
| Errors | NOT_FOUND (interface/address/hardware) / IO (enumeration) / RANGE+BUFFER (insufficient), domain xrt.net |
| Trimming | XRT_MODULE_NET_INTERFACE has zero engine dependencies; the TEXT layer separately adds HEX |
📖 API references in this chapter:net_interface (16 entries)net (247 entries)
Click for full signatures, parameter constraints, return values and error notes