Skip to content

Commit 4c53178

Browse files
committed
net: handle multi-part netlink responses
Handle multi-part netlink responses to prevent truncated results from large routing tables. Previously, we only made a single recv call, which led to incomplete results when the kernel split the message into multiple responses (which happens frequently with NLM_F_DUMP). Also guard against a potential hanging issue where the code would indefinitely wait for NLMSG_DONE for non-multi-part responses by detecting the NLM_F_MULTI flag and only continue waiting when necessary.
1 parent 42e99ad commit 4c53178

File tree

1 file changed

+68
-37
lines changed

1 file changed

+68
-37
lines changed

src/common/netif.cpp

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ namespace {
3636
// will fail, so we skip that.
3737
#if defined(__linux__) || (defined(__FreeBSD__) && __FreeBSD_version >= 1400000)
3838

39+
// Good for responses containing ~ 10,000-15,000 routes.
40+
static constexpr ssize_t NETLINK_MAX_RESPONSE_SIZE{1'048'576};
41+
3942
std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t family)
4043
{
4144
// Create a netlink socket.
@@ -84,50 +87,78 @@ std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t family)
8487

8588
// Receive response.
8689
char response[4096];
87-
int64_t recv_result;
88-
do {
89-
recv_result = sock->Recv(response, sizeof(response), 0);
90-
} while (recv_result < 0 && (errno == EINTR || errno == EAGAIN));
91-
if (recv_result < 0) {
92-
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "recv() from netlink socket: %s\n", NetworkErrorString(errno));
93-
return std::nullopt;
94-
}
95-
96-
for (nlmsghdr* hdr = (nlmsghdr*)response; NLMSG_OK(hdr, recv_result); hdr = NLMSG_NEXT(hdr, recv_result)) {
97-
rtmsg* r = (rtmsg*)NLMSG_DATA(hdr);
98-
int remaining_len = RTM_PAYLOAD(hdr);
99-
100-
if (hdr->nlmsg_type != RTM_NEWROUTE) {
101-
continue; // Skip non-route messages
90+
ssize_t total_bytes_read{0};
91+
bool done{false};
92+
bool multi_part{false};
93+
while (!done) {
94+
int64_t recv_result;
95+
do {
96+
recv_result = sock->Recv(response, sizeof(response), 0);
97+
} while (recv_result < 0 && (errno == EINTR || errno == EAGAIN));
98+
if (recv_result < 0) {
99+
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "recv() from netlink socket: %s\n", NetworkErrorString(errno));
100+
return std::nullopt;
102101
}
103102

104-
// Only consider default routes (destination prefix length of 0).
105-
if (r->rtm_dst_len != 0) {
106-
continue;
103+
total_bytes_read += recv_result;
104+
if (total_bytes_read > NETLINK_MAX_RESPONSE_SIZE) {
105+
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "Netlink response exceeded size limit (%zu bytes, family=%d)\n", NETLINK_MAX_RESPONSE_SIZE, family);
106+
return std::nullopt;
107107
}
108108

109-
// Iterate over the attributes.
110-
rtattr *rta_gateway = nullptr;
111-
int scope_id = 0;
112-
for (rtattr* attr = RTM_RTA(r); RTA_OK(attr, remaining_len); attr = RTA_NEXT(attr, remaining_len)) {
113-
if (attr->rta_type == RTA_GATEWAY) {
114-
rta_gateway = attr;
115-
} else if (attr->rta_type == RTA_OIF && sizeof(int) == RTA_PAYLOAD(attr)) {
116-
std::memcpy(&scope_id, RTA_DATA(attr), sizeof(scope_id));
109+
bool processed_one{false};
110+
for (nlmsghdr* hdr = (nlmsghdr*)response; NLMSG_OK(hdr, recv_result); hdr = NLMSG_NEXT(hdr, recv_result)) {
111+
rtmsg* r = (rtmsg*)NLMSG_DATA(hdr);
112+
int remaining_len = RTM_PAYLOAD(hdr);
113+
114+
processed_one = true;
115+
if (hdr->nlmsg_flags & NLM_F_MULTI) {
116+
multi_part = true;
117117
}
118-
}
119118

120-
// Found gateway?
121-
if (rta_gateway != nullptr) {
122-
if (family == AF_INET && sizeof(in_addr) == RTA_PAYLOAD(rta_gateway)) {
123-
in_addr gw;
124-
std::memcpy(&gw, RTA_DATA(rta_gateway), sizeof(gw));
125-
return CNetAddr(gw);
126-
} else if (family == AF_INET6 && sizeof(in6_addr) == RTA_PAYLOAD(rta_gateway)) {
127-
in6_addr gw;
128-
std::memcpy(&gw, RTA_DATA(rta_gateway), sizeof(gw));
129-
return CNetAddr(gw, scope_id);
119+
if (hdr->nlmsg_type == NLMSG_DONE) {
120+
done = true;
121+
break;
130122
}
123+
124+
if (hdr->nlmsg_type != RTM_NEWROUTE) {
125+
continue; // Skip non-route messages
126+
}
127+
128+
// Only consider default routes (destination prefix length of 0).
129+
if (r->rtm_dst_len != 0) {
130+
continue;
131+
}
132+
133+
// Iterate over the attributes.
134+
rtattr* rta_gateway = nullptr;
135+
int scope_id = 0;
136+
for (rtattr* attr = RTM_RTA(r); RTA_OK(attr, remaining_len); attr = RTA_NEXT(attr, remaining_len)) {
137+
if (attr->rta_type == RTA_GATEWAY) {
138+
rta_gateway = attr;
139+
} else if (attr->rta_type == RTA_OIF && sizeof(int) == RTA_PAYLOAD(attr)) {
140+
std::memcpy(&scope_id, RTA_DATA(attr), sizeof(scope_id));
141+
}
142+
}
143+
144+
// Found gateway?
145+
if (rta_gateway != nullptr) {
146+
if (family == AF_INET && sizeof(in_addr) == RTA_PAYLOAD(rta_gateway)) {
147+
in_addr gw;
148+
std::memcpy(&gw, RTA_DATA(rta_gateway), sizeof(gw));
149+
return CNetAddr(gw);
150+
} else if (family == AF_INET6 && sizeof(in6_addr) == RTA_PAYLOAD(rta_gateway)) {
151+
in6_addr gw;
152+
std::memcpy(&gw, RTA_DATA(rta_gateway), sizeof(gw));
153+
return CNetAddr(gw, scope_id);
154+
}
155+
}
156+
}
157+
158+
// If we processed at least one message and multi flag not set, or if
159+
// we received no valid messages, then we're done.
160+
if ((processed_one && !multi_part) || !processed_one) {
161+
done = true;
131162
}
132163
}
133164

0 commit comments

Comments
 (0)