diff --git a/CHANGELOG.md b/CHANGELOG.md index 4684c8a24..67aaf818c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,19 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.4.1](https://github.com/rdkcentral/tr69hostif/compare/1.4.0...1.4.1) + +- RDKEMW-15041: Add RFC Handlers for meminsight RFC [`#426`](https://github.com/rdkcentral/tr69hostif/pull/426) +- Merge tag '1.4.0' into develop [`fe37481`](https://github.com/rdkcentral/tr69hostif/commit/fe374814ab61d33d3dcd23581eef526c9f467a3d) + #### [1.4.0](https://github.com/rdkcentral/tr69hostif/compare/1.3.9...1.4.0) +> 3 April 2026 + - Added Workflow for the JSON parse logic [`#444`](https://github.com/rdkcentral/tr69hostif/pull/444) - RDKEMW-10029 : Syncing of Gerrit commits that are required for security components [`#440`](https://github.com/rdkcentral/tr69hostif/pull/440) - Rebase with Develop [`#439`](https://github.com/rdkcentral/tr69hostif/pull/439) +- tr69hostif 1.4.0 release changelog updates [`1c76955`](https://github.com/rdkcentral/tr69hostif/commit/1c76955b93fa406a3c35e66ffe5b01e41bbfe6ba) - Merge tag '1.3.9' into develop [`172808d`](https://github.com/rdkcentral/tr69hostif/commit/172808d7d26343a6a7142dae366749e71f846b7c) - RDKEMW-10029: Remove duplicate RedRecovery parameter [`20db7b3`](https://github.com/rdkcentral/tr69hostif/commit/20db7b3f884dd200b6d67db41d139999d80ab567) diff --git a/docs/architecture/partner-defaults-workflow.md b/docs/architecture/partner-defaults-workflow.md new file mode 100644 index 000000000..90d057767 --- /dev/null +++ b/docs/architecture/partner-defaults-workflow.md @@ -0,0 +1,374 @@ +# Partner Defaults Workflow + +## Overview + +`tr69hostif` resolves partner-specific bootstrap defaults through `XBSStore`, which loads JSON defaults from `partners_defaults.json`, merges any device-specific additions from `partners_defaults_device.json`, overlays persisted bootstrap overrides, and reloads when PartnerId becomes available later in boot. + +This workflow exists because the daemon may start before AuthService has written the runtime PartnerId. In that early-boot window, the code intentionally uses a reduced `default_boot` section. Once the actual PartnerId is discovered, the store reloads and switches to either the matching partner section or the generic `default` section. + +## Architecture + +### Component Diagram + +```mermaid +flowchart TB + START[tr69hostif startup] --> BS[XBSStore::getInstance] + BS --> INI[/opt/secure/RFC/bootstrap ini or tr181store cache/] + BS --> JSON[/etc/partners_defaults.json/] + BS --> JSONDEV[/etc/partners_defaults_device.json/] + BS --> PID[PartnerId lookup] + PID --> AUTH[/opt/www/authService/partnerId3.dat/] + PID --> BSI[/opt/secure/RFC/bootstrap.ini/] + + PID --> DECIDE{PartnerId available?} + DECIDE -->|No| BOOT[Use default_boot] + DECIDE -->|Yes, matching section| PARTNER[Use partner section] + DECIDE -->|Yes, no match| DEF[Use default] + + BOOT --> MERGE[Merge device-specific defaults] + PARTNER --> MERGE + DEF --> MERGE + + MERGE --> STORE[In-memory bootstrap map] + STORE --> GET[GET Device.* bootstrap params] + STORE --> SET[Persisted overrides and journal] + + WATCH[PartnerId watcher thread] --> RELOAD[Reload on PartnerId change] + RELOAD --> PID +``` + +## Key Components + +### `XBSStore` + +`XBSStore` owns bootstrap default resolution, in-memory storage, persisted override handling, and the PartnerId monitoring thread. The startup path is implemented in `XBSStore::getInstance()`, `init()`, `loadBSPropertiesIntoCache()`, and `loadFromJson()`. + +### `partners_defaults.json` + +This file contains the generic and per-partner bootstrap defaults. The current layout includes at least these top-level sections: + +- `default_boot` for early-boot fallback values +- `default` for generic steady-state defaults when a resolved partner block is unavailable +- one or more partner-specific sections such as `community` + +### `partners_defaults_device.json` + +If present, this file overlays device-specific values on top of the selected partner configuration. Existing keys are replaced; missing keys are appended. + +### PartnerId sources + +The store resolves PartnerId in this order: + +1. `/opt/www/authService/partnerId3.dat` +2. `/opt/secure/RFC/bootstrap.ini` + +If neither source yields a value during startup, the code falls back to `default_boot`. + +## Workflow Phases + +### 1. Startup Cache Load + +At startup, `XBSStore::getInstance()` constructs the singleton, loads any persisted bootstrap values from disk, then calls `loadFromJson()` to apply firmware defaults. + +Persisted values are read before JSON defaults so the store can preserve runtime overrides and remove only stale firmware-default entries during a firmware update. + +### 2. PartnerId Resolution + +`loadFromJson()` calls `hostIf_DeviceInfo::get_PartnerId_From_Script()` to resolve the current PartnerId. + +Possible outcomes: + +1. PartnerId is available and matches a JSON section: use that section. +2. PartnerId is available but no matching section exists: fall back to `default`. +3. PartnerId is not available yet: fall back to `default_boot`. + +This is the key distinction between `default_boot` and `default`: + +- `default_boot` is a temporary early-boot profile used only when PartnerId is not yet known. +- `default` is the generic steady-state fallback used after PartnerId resolution when the partner block is missing. + +### 3. Device-Specific Overlay + +After selecting the base configuration, `getPartnerDeviceConfig()` optionally reads `partners_defaults_device.json` and merges those entries into the chosen partner object. + +Overlay rules: + +1. If a key already exists in the selected base object, the device-specific file replaces it. +2. If a key does not exist, the device-specific file adds it. +3. If the device-specific file does not exist, startup continues without error. + +### 4. Store Population + +The merged JSON object is iterated and each key-value pair is written into the in-memory bootstrap map through `setRawValue(..., HOSTIF_SRC_DEFAULT)`. + +During this phase, the code also: + +1. marks initial update state when the persistent bootstrap file does not yet exist +2. removes obsolete firmware-default entries that disappeared from the new JSON but were not overridden by RFC or WebPA +3. updates journal state through `XBSStoreJournal` + +### 5. Runtime Reload When PartnerId Appears + +After singleton creation, `XBSStore` starts a detached watcher thread that monitors `/opt/www/authService/partnerId3.dat` with `inotify`. + +When the file is created or modified: + +1. the thread re-reads PartnerId +2. compares it to the stored PartnerId value +3. updates the PartnerId bootstrap entry if it changed +4. calls `loadFromJson()` again to rebuild defaults using the resolved partner section + +This is how the daemon transitions from `default_boot` to the partner-specific or `default` steady-state configuration. + +## Sequence Diagram + +```mermaid +sequenceDiagram + participant Main as tr69hostif startup + participant BS as XBSStore + participant PID as PartnerId lookup + participant JSON as partners_defaults.json + participant DEV as partners_defaults_device.json + participant Watch as PartnerId watcher + + Main->>BS: getInstance() + BS->>BS: load cached bootstrap overrides + BS->>PID: get_PartnerId_From_Script() + + alt PartnerId unavailable + PID-->>BS: empty + BS->>JSON: load default_boot + else PartnerId section exists + PID-->>BS: partner name + BS->>JSON: load partner section + else PartnerId missing in JSON + PID-->>BS: partner name + BS->>JSON: load default + end + + BS->>DEV: merge device-specific overrides + BS->>BS: populate in-memory map + BS-->>Main: bootstrap values ready + + Main->>Watch: start detached monitor thread + Watch->>PID: wait for partnerId3.dat update + PID-->>Watch: new PartnerId + Watch->>BS: loadFromJson() + BS->>JSON: reload partner or default section +``` + +## Threading Model + +The partner-defaults workflow uses two execution contexts: + +| Context | Purpose | Notes | +|---------|---------|-------| +| Startup thread | Initial bootstrap load | Runs during singleton initialization | +| Detached PartnerId watcher thread | Watches for `partnerId3.dat` creation or modification | Calls `loadFromJson()` again when PartnerId changes | + +Synchronization notes: + +1. `XBSStore` uses a recursive mutex around store access and reload operations. +2. The watcher thread updates the in-memory store only after detecting a changed PartnerId. +3. `default_boot` is intentionally temporary and may be replaced later in the same process lifetime. + +## Memory And Persistence Model + +### Ownership + +1. JSON objects parsed with `cJSON` are temporary and released after reload completes. +2. Effective bootstrap values are copied into the in-memory dictionary. +3. Persisted runtime overrides remain on disk and survive daemon restart. + +### Persistence Layers + +Effective value precedence for bootstrap-backed parameters is: + +1. persisted RFC or WebPA override +2. device-specific overlay from `partners_defaults_device.json` when present +3. selected partner default from `partners_defaults.json` + +Operationally, the JSON files provide firmware defaults, while runtime changes are kept in the bootstrap store and journal under `/opt/secure/RFC/`. + +### How `bootstrap.ini` Is Created And Updated + +The bootstrap store file is owned by `tr69hostif` itself. The file path is obtained from `/etc/rfc.properties` through the `BS_STORE_FILENAME` property, and in the current environment that path resolves to `/opt/secure/RFC/bootstrap.ini`. + +The creation and update flow is: + +1. `XBSStore::init()` loads the configured bootstrap-store filename. +2. `loadBSPropertiesIntoCache()` attempts to read the existing file into the in-memory dictionary. +3. If the file does not yet exist, startup continues and `loadFromJson()` marks the bootstrap load as an initial update. +4. During the initial update, each selected JSON default is written through `setRawValue()`, which creates the `/opt/secure/RFC` directory if needed and appends `key=value` entries into `bootstrap.ini`. +5. After initial creation, later updates rewrite the full file from the in-memory dictionary so the persistent store remains synchronized with the active bootstrap cache. + +This means the firmware JSON files are the source of default values, but `bootstrap.ini` is the persistent runtime copy managed by `XBSStore`. + +### PartnerId Read Dependency On `bootstrap.ini` + +When AuthService has not yet created `/opt/www/authService/partnerId3.dat`, PartnerId lookup falls back to `/opt/secure/RFC/bootstrap.ini`. + +That fallback matters in two ways: + +1. it allows a previously persisted PartnerId to survive reboot +2. if no PartnerId is present in either location, the system remains in the `default_boot` path until a later reload occurs + +## Error Handling And Fallbacks + +| Condition | Behavior | +|-----------|----------| +| `partnerId3.dat` missing at startup | use `default_boot` | +| PartnerId resolved but no matching JSON section | use `default` | +| `partners_defaults_device.json` missing | continue without device-specific overlay | +| malformed JSON in partner defaults file | `loadFromJson()` fails and logs an error | +| malformed JSON in device-specific defaults file | device-specific merge fails and logs an error | + +One deliberate behavior is that the firmware initial management notification is skipped when the store is still using `default_boot`. That notification is sent only once the active configuration is no longer the boot-time fallback. + +## Scenario Guide + +### Scenario 1: First Boot With No PartnerId Available Yet + +In this case: + +1. `/opt/www/authService/partnerId3.dat` does not exist yet +2. `/opt/secure/RFC/bootstrap.ini` either does not exist yet or does not contain a PartnerId +3. `loadFromJson()` falls back to `default_boot` + +Expected behavior: + +- `XBSStore` populates the cache from the `default_boot` section +- `bootstrap.ini` is created if this is the first persistent bootstrap load +- only the reduced early-boot parameter set is available + +This is the intended startup-safe behavior, not an error condition by itself. + +### Scenario 2: Parameter Exists In JSON But Has An Empty Default Value + +Some `default_boot` parameters intentionally use empty strings as placeholders. + +For a GET request, `XBSStore::getValue()` checks whether the resolved value length is greater than zero. If the stored value is an empty string, the code treats the request the same way it treats a missing value. + +Expected behavior: + +- the parameter may exist in the selected JSON section +- the stored value may still be empty +- the GET path returns an internal-error-style result because `getValue()` requires a non-empty string to treat the lookup as successful + +This behavior most commonly appears during the `default_boot` stage for parameters such as early NTP or URL placeholders. + +### Scenario 3: Parameter Missing From `default_boot` But Present In `default` + +If the system is still using `default_boot`, only keys present in that section are loaded into the bootstrap cache. + +Expected behavior: + +- parameters missing from `default_boot` are not available yet +- the same parameter may become available later after PartnerId resolution reloads the store into a partner-specific section or `default` + +This explains why a parameter can appear unavailable early in boot and available later without any manual repair step. + +### Scenario 4: PartnerId Resolves Later And Store Reloads + +Once the watcher thread detects creation or modification of `partnerId3.dat`, it re-reads PartnerId and compares it with the currently stored PartnerId value. + +If the value changed: + +1. the stored PartnerId entry is updated +2. `loadFromJson()` runs again +3. the active bootstrap configuration moves from `default_boot` to either the matching partner section or `default` + +Expected behavior: + +- more steady-state parameters become available +- placeholder empty defaults may be replaced by actual partner defaults +- firmware-initial notification is allowed once the active configuration is no longer `default_boot` + +### Scenario 5: Unknown Partner In `partners_defaults.json` + +If PartnerId is resolved successfully but the base defaults file does not contain a matching partner block, `XBSStore` falls back to the `default` section. + +Expected behavior: + +- the daemon stays operational +- the bootstrap store uses generic steady-state defaults +- no partner-specific entries from the missing section are applied + +This is a base-defaults fallback, not a bootstrap-store corruption case. + +### Scenario 6: Unknown Partner In `partners_defaults_device.json` + +The device-specific overlay file is processed separately from the base partner-defaults file. + +If the resolved PartnerId is absent only in `partners_defaults_device.json`: + +- base partner selection may still succeed normally from `partners_defaults.json` +- the device-specific overlay path falls back to `default` inside the device-specific file +- generic device-specific overrides are applied instead of partner-specific device overrides + +This scenario means the overlay file is incomplete for that partner. It does not necessarily mean the main partner-defaults file is wrong. + +### Scenario 7: Persisted Overrides Present + +If RFC or WebPA has previously overridden bootstrap-backed values, those persisted values remain active even when firmware defaults are reloaded. + +Expected behavior: + +- the runtime override remains the effective value +- firmware defaults are still refreshed in the journal as reference values +- a firmware update does not silently replace the higher-precedence override + +This is why runtime behavior may differ from the raw value currently visible in `partners_defaults.json`. + +## Troubleshooting Without Logs + +When investigating partner-default behavior, validate the following in order: + +1. `/etc/rfc.properties` points `BS_STORE_FILENAME` to the expected bootstrap file. +2. `/etc/partners_defaults.json` contains the expected `default_boot`, `default`, and partner-specific sections. +3. `/etc/partners_defaults_device.json` contains the expected partner section if device-specific overrides are required. +4. `/opt/secure/RFC/bootstrap.ini` exists and contains the persisted bootstrap state expected for that device. +5. `/opt/www/authService/partnerId3.dat` exists when the device is expected to have completed PartnerId discovery. + +If a parameter appears unavailable, determine which of these cases applies first: + +1. the system is still in `default_boot` +2. the parameter is present but intentionally empty +3. the parameter is absent from the currently selected section +4. PartnerId resolved to a section that does not exist and the system fell back to `default` +5. the device-specific overlay is missing the active partner section + +## Operational Notes + +### Why `default_boot` exists + +Early boot may not have AuthService output yet, but some parameters still need safe values so dependent services can start. The `default_boot` section provides that minimum set. + +### Why `default` is separate + +Once PartnerId is known, falling back to `default` means the device has entered its steady-state configuration path, even if there is no explicit partner section for that ID. + +### Typical Parameters In Each Section + +In the current repository version: + +- `default_boot` contains a reduced set of NTP, Xconf, WebPA, and locale-related keys. +- `default` contains the broader partner bootstrap and feature baseline, including multiple NTP servers and several RFC feature flags. + +## Testing + +Relevant unit-test coverage exists for the bootstrap-store behavior in `src/hostif/profiles/DeviceInfo/gtest/gtest_main.cpp`, including: + +1. reading bootstrap values before PartnerId becomes available +2. reading bootstrap values after PartnerId is resolved +3. device-specific merge behavior through `getPartnerDeviceConfig()` +4. missing device-specific file handling + +The current tests validate the reload path and merge helpers, but they do not fully document every production JSON section. When partner-default content changes, update both the JSON fixtures and the documentation. + +## See Also + +- [System Overview](overview.md) +- [Data Flow](data-flow.md) +- [JSON Usage](json-usage.md) +- [DeviceInfo Profile](../../src/hostif/profiles/DeviceInfo/docs/README.md) \ No newline at end of file diff --git a/src/hostif/parodusClient/pal/libpd.cpp b/src/hostif/parodusClient/pal/libpd.cpp index d626ca593..4b918d0d3 100644 --- a/src/hostif/parodusClient/pal/libpd.cpp +++ b/src/hostif/parodusClient/pal/libpd.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -66,7 +67,7 @@ static long timeValDiff(struct timespec *starttime, struct timespec *finishtime) libpd_instance_t libparodus_instance = NULL; char parodus_url[URL_SIZE] = {'\0'}; char client_url[URL_SIZE] = {'\0'}; -bool exit_parodus_recv = false; +std::atomic_bool exit_parodus_recv(false); pthread_cond_t parodus_cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t parodus_lock = PTHREAD_MUTEX_INITIALIZER; /*----------------------------------------------------------------------------*/ @@ -84,8 +85,10 @@ void libpd_set_notifyConfigFile(const char* configFile) void stop_parodus_recv_wait() { - exit_parodus_recv = true; + pthread_mutex_lock(&parodus_lock); + exit_parodus_recv.store(true); pthread_cond_signal(&parodus_cond); + pthread_mutex_unlock(&parodus_lock); } /** * Initialize libpd and Load Data model, Invoke connection to parodus @@ -143,7 +146,7 @@ static void parodus_receive_wait() RDK_LOG(RDK_LOG_DEBUG,LOG_PARODUS_IF,"Entering parodus_receive_wait.. \n"); - while (!exit_parodus_recv) + while (!exit_parodus_recv.load()) { rtn = libparodus_receive (libparodus_instance, &wrp_msg, 2000); if (rtn == 1) @@ -155,16 +158,19 @@ static void parodus_receive_wait() clock_gettime(CLOCK_MONOTONIC, &currTime); currTime.tv_sec += 5; pthread_mutex_lock(&parodus_lock); - int wait_ret = pthread_cond_timedwait(&parodus_cond, &parodus_lock,&currTime); - if(wait_ret == ETIMEDOUT) + if (!exit_parodus_recv.load()) { - RDK_LOG(RDK_LOG_DEBUG,LOG_PARODUS_IF,"parodus_receive_wait(): wait for key acquisition timed out"); - } - else if(wait_ret != 0) - { - RDK_LOG(RDK_LOG_ERROR,LOG_PARODUS_IF,"parodus_receive_wait(): pthread_cond_timedwait failed with error %d", wait_ret); + int wait_ret = pthread_cond_timedwait(&parodus_cond, &parodus_lock,&currTime); + if(wait_ret == ETIMEDOUT) + { + RDK_LOG(RDK_LOG_DEBUG,LOG_PARODUS_IF,"parodus_receive_wait(): wait for key acquisition timed out"); + } + else if(wait_ret != 0) + { + RDK_LOG(RDK_LOG_ERROR,LOG_PARODUS_IF,"parodus_receive_wait(): pthread_cond_timedwait failed with error %d", wait_ret); + } } - RDK_LOG(RDK_LOG_INFO,LOG_PARODUS_IF,"[%s:%d] Unlocking mutex... \n", __FUNCTION__, __LINE__); + RDK_LOG(RDK_LOG_INFO,LOG_PARODUS_IF,"[%s:%d] Unlocking mutex... \n", __FUNCTION__, __LINE__); pthread_mutex_unlock(&parodus_lock); continue; } diff --git a/src/hostif/parodusClient/pal/webpa_attribute.cpp b/src/hostif/parodusClient/pal/webpa_attribute.cpp index 73fa2681c..edaa9f934 100644 --- a/src/hostif/parodusClient/pal/webpa_attribute.cpp +++ b/src/hostif/parodusClient/pal/webpa_attribute.cpp @@ -121,6 +121,11 @@ static WAL_STATUS getParamAttributes(const char *pParameterName, AttrVal ***attr unsigned int i = 0; HOSTIF_MsgData_t Param = {0}; + if ((pParameterName == NULL) || (attr == NULL) || (TotalParams == NULL)) + { + return WAL_ERR_INVALID_PARAM; + } + memset(&Param, '\0', sizeof(HOSTIF_MsgData_t)); // Check if pParameterName is in the list of notification parameters and check if the parameter is one among them diff --git a/src/hostif/profiles/DeviceInfo/Device_DeviceInfo.cpp b/src/hostif/profiles/DeviceInfo/Device_DeviceInfo.cpp index 5dca0a297..dbc08854d 100644 --- a/src/hostif/profiles/DeviceInfo/Device_DeviceInfo.cpp +++ b/src/hostif/profiles/DeviceInfo/Device_DeviceInfo.cpp @@ -3119,7 +3119,7 @@ int hostIf_DeviceInfo::findLocalPortAvailable() { struct sockaddr_in address = {0,0,0}; int sockfd = -1, status; - int port = MIN_PORT_RANGE; + uint16_t port = MIN_PORT_RANGE; while (port <= MAX_PORT_RANGE) { address.sin_family = AF_INET; diff --git a/src/hostif/profiles/DeviceInfo/XrdkCentralComBSStore.cpp b/src/hostif/profiles/DeviceInfo/XrdkCentralComBSStore.cpp index 6e09464af..9b8f8050c 100755 --- a/src/hostif/profiles/DeviceInfo/XrdkCentralComBSStore.cpp +++ b/src/hostif/profiles/DeviceInfo/XrdkCentralComBSStore.cpp @@ -119,7 +119,7 @@ bool createBspCompleteFiles() void XBSStore::getAuthServicePartnerID() { - const std::string partnerIdPath = "/opt/www/authService/partnerId3.dat"; + const std::string filePath = "/opt/www/authService/partnerId3.dat"; // Initialize inotify int inotifyFd = inotify_init(); @@ -129,7 +129,6 @@ void XBSStore::getAuthServicePartnerID() } // Extracting the parent directories dynamically - std::string filePath(partnerIdPath); std::string authServiceDir = getParentDirectory(filePath); // "/opt/www/authService" std::string wwwDir = getParentDirectory(authServiceDir); // "/opt/www" std::string parentDir = getParentDirectory(wwwDir); // "/opt" diff --git a/src/hostif/profiles/Ethernet/Device_Ethernet_Interface.cpp b/src/hostif/profiles/Ethernet/Device_Ethernet_Interface.cpp index ca3e07461..6b393f7ee 100644 --- a/src/hostif/profiles/Ethernet/Device_Ethernet_Interface.cpp +++ b/src/hostif/profiles/Ethernet/Device_Ethernet_Interface.cpp @@ -200,7 +200,7 @@ static int getEthernetInterfaceName (unsigned int ethInterfaceNum, char* name) unsigned int count = 0; for (struct if_nameindex* ifnp = ifname; ifnp->if_index != 0; ifnp++) { - if ((strncmp (ifnp->if_name, "eth", 3) == 0) && (++count == ethInterfaceNum)) + if ((ifnp->if_name != NULL) && (strncmp (ifnp->if_name, "eth", 3) == 0) && (++count == ethInterfaceNum)) { rc=strcpy_s (name, BUFF_LENGTH_64,ifnp->if_name); ERR_CHK(rc); diff --git a/src/hostif/profiles/IP/Device_IP.cpp b/src/hostif/profiles/IP/Device_IP.cpp index 21c12ab4c..16029733e 100644 --- a/src/hostif/profiles/IP/Device_IP.cpp +++ b/src/hostif/profiles/IP/Device_IP.cpp @@ -320,6 +320,9 @@ char* hostIf_IP::getVirtualInterfaceName (struct if_nameindex *phy_if_list, unsi char *p, *v; for (struct ifaddrs *ifa_node = ifa; ifa_node; ifa_node = ifa_node->ifa_next) { + if ((ifa_node->ifa_name == NULL) || (ifa_node->ifa_addr == NULL)) + continue; + if (ifa_node->ifa_addr->sa_family == AF_INET) // virtual interfaces are IPv4-specific, so use IPv4 address family to hunt for them. { for (struct if_nameindex *phy_if = phy_if_list; phy_if->if_index != 0; phy_if++)