[posix] correlate netlink ACKs and handle kernel response errors (#13605)

When the POSIX platform issues netlink requests (RTM_NEWADDR,
RTM_DELADDR, RTM_NEWROUTE, RTM_DELROUTE, RTM_NEWADDRLABEL,
RTM_DELADDRLABEL), it requests an ACK from the kernel (NLM_F_ACK)
with a monotonic sequence number. Previously, HandleNetlinkResponse()
merely logged errors upon receiving NLMSG_ERROR without correlating
the response to the specific request that failed, treating benign
idempotent conditions as errors and leaving the platform unable to
retry transient failures or alert on address configuration errors.

This commit adds netlink request correlation and error handling:
- Introduces PendingNetlinkRequest tracking to correlate incoming
  netlink responses (NLMSG_ERROR / ACKs) by sequence number.
- Recognizes idempotent errors (such as EEXIST for address or route
  additions, and EADDRNOTAVAIL/ENOENT/ESRCH for deletions) and treats
  them as successful operations rather than logging warnings.
- Retries transient failures (EBUSY, EAGAIN) with updated sequence
  numbers up to a maximum retry limit.
- Logs specific alerts when the host interface permanently fails to
  apply or remove an IPv6 address.
This commit is contained in:
Jonathan Hui
2026-09-21 09:22:18 -07:00
committed by GitHub
parent 55b4f37042
commit e04ff19275
+219 -8
View File
@@ -559,6 +559,119 @@ exit:
return error;
}
struct PendingNetlinkRequest
{
static constexpr size_t kMaxPayloadLen = 512;
PendingNetlinkRequest *mNext;
bool mInUse;
uint32_t mSeq;
uint16_t mType;
uint8_t mRetryCount;
otIp6Address mAddress;
uint8_t mPrefixLength;
uint16_t mPayloadLen;
alignas(struct nlmsghdr) uint8_t mPayload[kMaxPayloadLen];
};
static constexpr size_t kMaxPendingNetlinkRequests = 16;
static constexpr uint8_t kMaxNetlinkRequestRetries = 3;
static PendingNetlinkRequest sPendingNetlinkRequests[kMaxPendingNetlinkRequests];
static PendingNetlinkRequest *sPendingNetlinkRequestsHead = nullptr;
static void PushPendingNetlinkRequest(PendingNetlinkRequest &aRequest)
{
aRequest.mInUse = true;
aRequest.mNext = sPendingNetlinkRequestsHead;
sPendingNetlinkRequestsHead = &aRequest;
}
static void ClearPendingNetlinkRequests(void)
{
sPendingNetlinkRequestsHead = nullptr;
for (PendingNetlinkRequest &entry : sPendingNetlinkRequests)
{
entry.mInUse = false;
entry.mNext = nullptr;
}
}
static void RecordPendingNetlinkRequest(uint32_t aSeq,
uint16_t aType,
const void *aPayload,
size_t aPayloadLen,
const otIp6Address *aAddress = nullptr,
uint8_t aPrefixLength = 0)
{
PendingNetlinkRequest *entry = nullptr;
for (PendingNetlinkRequest &candidate : sPendingNetlinkRequests)
{
if (!candidate.mInUse)
{
entry = &candidate;
break;
}
}
if (entry == nullptr)
{
PendingNetlinkRequest **prev = &sPendingNetlinkRequestsHead;
while ((*prev)->mNext != nullptr)
{
prev = &(*prev)->mNext;
}
entry = *prev;
*prev = nullptr;
LogWarn("Pending netlink request queue full, evicting request#%u", entry->mSeq);
}
memset(entry, 0, sizeof(*entry));
entry->mSeq = aSeq;
entry->mType = aType;
entry->mRetryCount = 0;
entry->mPrefixLength = aPrefixLength;
if (aAddress != nullptr)
{
entry->mAddress = *aAddress;
}
if (aPayloadLen <= sizeof(entry->mPayload))
{
entry->mPayloadLen = static_cast<uint16_t>(aPayloadLen);
memcpy(entry->mPayload, aPayload, aPayloadLen);
}
else
{
LogWarn("Netlink request payload too large to record (%zu bytes)", aPayloadLen);
}
PushPendingNetlinkRequest(*entry);
}
static PendingNetlinkRequest *RemovePendingNetlinkRequest(uint32_t aSeq)
{
PendingNetlinkRequest *removed = nullptr;
for (PendingNetlinkRequest **prev = &sPendingNetlinkRequestsHead; *prev != nullptr; prev = &(*prev)->mNext)
{
if ((*prev)->mSeq == aSeq)
{
removed = *prev;
*prev = removed->mNext;
removed->mNext = nullptr;
removed->mInUse = false;
break;
}
}
return removed;
}
#if OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE
static bool IsOmrAddress(otInstance *aInstance, const otIp6AddressInfo &aAddressInfo)
{
@@ -610,10 +723,12 @@ static void SendDeleteAddress(const PendingRemoveAddress &aAddress)
AddRtAttr(&req.nh, sizeof(req), IFA_LOCAL, &aAddress.mAddress, sizeof(aAddress.mAddress));
if (send(sNetlinkFd, &req, req.nh.nlmsg_len, 0) != -1)
if (SendNetlinkMessage(&req, req.nh.nlmsg_len) == OT_ERROR_NONE)
{
LogInfo("Sent request#%u to remove %s/%u", sNetlinkSequence, Ip6AddressString(&aAddress.mAddress).AsCString(),
aAddress.mPrefixLength);
RecordPendingNetlinkRequest(sNetlinkSequence, req.nh.nlmsg_type, &req, req.nh.nlmsg_len, &aAddress.mAddress,
aAddress.mPrefixLength);
}
else
{
@@ -748,6 +863,8 @@ static void UpdateUnicastLinux(otInstance *aInstance, const otIp6AddressInfo &aA
{
LogInfo("Sent request#%u to add/replace %s/%u", sNetlinkSequence,
Ip6AddressString(aAddressInfo.mAddress).AsCString(), aAddressInfo.mPrefixLength);
RecordPendingNetlinkRequest(sNetlinkSequence, req.nh.nlmsg_type, &req, req.nh.nlmsg_len, aAddressInfo.mAddress,
aAddressInfo.mPrefixLength);
}
else
{
@@ -948,6 +1065,7 @@ template <size_t N> otError AddRoute(const uint8_t (&aAddress)[N], uint8_t aPref
else
{
LogInfo("Sent request#%u to add route %s/%u", sNetlinkSequence, addrStrBuf, aPrefixLen);
RecordPendingNetlinkRequest(sNetlinkSequence, req.header.nlmsg_type, &req, req.header.nlmsg_len);
}
exit:
return error;
@@ -1003,6 +1121,7 @@ template <size_t N> otError DeleteRoute(const uint8_t (&aAddress)[N], uint8_t aP
else
{
LogInfo("Sent request#%u to delete route %s/%u", sNetlinkSequence, addrStrBuf, aPrefixLen);
RecordPendingNetlinkRequest(sNetlinkSequence, req.header.nlmsg_type, &req, req.header.nlmsg_len);
}
exit:
@@ -1055,6 +1174,7 @@ static void AddAddressLabel(const uint8_t *aAddress, uint8_t aPrefixLen, uint32_
{
LogInfo("Sent request#%u to add address label %s/%u with label %u", sNetlinkSequence, addrStrBuf, aPrefixLen,
aLabel);
RecordPendingNetlinkRequest(sNetlinkSequence, req.header.nlmsg_type, &req, req.header.nlmsg_len);
}
exit:
return;
@@ -1103,6 +1223,7 @@ static void DeleteAddressLabel(const uint8_t *aAddress, uint8_t aPrefixLen)
else
{
LogInfo("Sent request#%u to delete address label %s/%u", sNetlinkSequence, addrStrBuf, aPrefixLen);
RecordPendingNetlinkRequest(sNetlinkSequence, req.header.nlmsg_type, &req, req.header.nlmsg_len);
}
exit:
return;
@@ -2206,13 +2327,17 @@ exit:
// | ** optionally (2) extended ACK attrs |
// ----------------------------------------------
//
static void HandleNetlinkResponse(struct nlmsghdr *msg)
static void HandleNetlinkResponse(otInstance *aInstance, struct nlmsghdr *msg)
{
OT_UNUSED_VARIABLE(aInstance);
const struct nlmsgerr *err;
const char *errorMsg;
size_t rtaLength;
size_t requestPayloadLength = 0;
uint32_t requestSeq = 0;
uint32_t requestSeq = msg->nlmsg_seq;
PendingNetlinkRequest *pending = nullptr;
int errCode = 0;
if (msg->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr)))
{
@@ -2220,8 +2345,8 @@ static void HandleNetlinkResponse(struct nlmsghdr *msg)
ExitNow();
}
err = reinterpret_cast<const nlmsgerr *>(NLMSG_DATA(msg));
requestSeq = err->msg.nlmsg_seq;
err = reinterpret_cast<const nlmsgerr *>(NLMSG_DATA(msg));
pending = RemovePendingNetlinkRequest(requestSeq);
if (err->error == 0)
{
@@ -2231,7 +2356,8 @@ static void HandleNetlinkResponse(struct nlmsghdr *msg)
// For rtnetlink, `abs(err->error)` maps to values of `errno`.
// But this is not a requirement in RFC 3549.
errorMsg = strerror(abs(err->error));
errCode = abs(err->error);
errorMsg = strerror(errCode);
// The payload of the request is omitted if NLM_F_CAPPED is set
if (!(msg->nlmsg_flags & NLM_F_CAPPED))
@@ -2259,7 +2385,91 @@ static void HandleNetlinkResponse(struct nlmsghdr *msg)
}
}
LogWarn("Failed to process request#%u: %s", requestSeq, errorMsg);
if (pending != nullptr)
{
bool isIdempotent = false;
switch (pending->mType)
{
case RTM_NEWADDR:
isIdempotent = (errCode == EEXIST);
break;
case RTM_DELADDR:
isIdempotent = (errCode == EADDRNOTAVAIL || errCode == ENOENT);
break;
case RTM_NEWROUTE:
isIdempotent = (errCode == EEXIST);
break;
case RTM_DELROUTE:
isIdempotent = (errCode == ESRCH || errCode == ENOENT);
break;
case RTM_NEWADDRLABEL:
isIdempotent = (errCode == EEXIST);
break;
case RTM_DELADDRLABEL:
isIdempotent = (errCode == ENOENT || errCode == ESRCH);
break;
default:
break;
}
if (isIdempotent)
{
LogInfo("Netlink request#%u (type %u) completed with idempotent code %d (%s), treating as success",
requestSeq, pending->mType, errCode, errorMsg);
ExitNow();
}
if ((errCode == EBUSY || errCode == EAGAIN) && pending->mRetryCount < kMaxNetlinkRequestRetries &&
pending->mPayloadLen >= sizeof(struct nlmsghdr))
{
uint32_t oldSeq = pending->mSeq;
struct nlmsghdr *reqHdr = reinterpret_cast<struct nlmsghdr *>(pending->mPayload);
otError error;
pending->mRetryCount++;
pending->mSeq = ++sNetlinkSequence;
reqHdr->nlmsg_seq = pending->mSeq;
LogInfo("Retrying netlink request#%u as #%u (attempt %u/%u) after transient error %d: %s", oldSeq,
pending->mSeq, pending->mRetryCount, kMaxNetlinkRequestRetries, errCode, errorMsg);
error = SendNetlinkMessage(pending->mPayload, pending->mPayloadLen);
if (error == OT_ERROR_NONE)
{
PushPendingNetlinkRequest(*pending);
}
else
{
LogWarn("Failed to re-send netlink request#%u: %s", pending->mSeq, otThreadErrorToString(error));
}
ExitNow();
}
LogWarn("Failed to process request#%u (type %u): %s (errno %d)", requestSeq, pending->mType, errorMsg, errCode);
if (pending->mType == RTM_NEWADDR)
{
LogWarn("Host netif failed to configure IPv6 address %s/%u",
Ip6AddressString(&pending->mAddress).AsCString(), pending->mPrefixLength);
}
else if (pending->mType == RTM_DELADDR)
{
LogWarn("Host netif failed to remove IPv6 address %s/%u", Ip6AddressString(&pending->mAddress).AsCString(),
pending->mPrefixLength);
}
}
else
{
LogWarn("Failed to process request#%u: %s", requestSeq, errorMsg);
}
exit:
return;
@@ -2345,7 +2555,7 @@ static void processNetlinkEvent(otInstance *aInstance)
#else
case NLMSG_ERROR:
HandleNetlinkResponse(msg);
HandleNetlinkResponse(aInstance, msg);
break;
#endif
@@ -2814,6 +3024,7 @@ void platformNetifDeinit(void)
#ifdef __linux__
sPendingNetlinkTxQueue.Clear();
ClearPendingNetlinkRequests();
#endif
#if OPENTHREAD_POSIX_USE_MLD_MONITOR