Skip to content

Commit 2562fe1

Browse files
committed
Merge bitcoin/bitcoin#32159: net, pcp: handle multi-part responses and filter for default route while querying default gateway
88db09b net: handle multi-part netlink responses (willcl-ark) 42e99ad net: skip non-route netlink responses (willcl-ark) 57ce645 net: filter for default routes in netlink responses (willcl-ark) Pull request description: ...for default route in pcp pinholing. Currently we only make a single recv call, which trucates results from large routing tables, or in the case the kernel may split the message into multiple responses (which may happen with `NLM_F_DUMP`). We also do not filter on the default route. For IPv6, this led to selecting the first route with an `RTA_GATEWAY` attribute, often a non-default route instead of the actual default. This caused PCP port mapping failures because the wrong gateway was used. Fix both issues by adding multi-part handling of responses and filter for the default route. Limit responses to ~ 1MB to prevent any router-based DoS. ACKs for top commit: achow101: ACK 88db09b davidgumberg: Code Review re-ACK 88db09b Sjors: re-utACK 88db09b Tree-SHA512: ea5948edebfad5896a487a61737aa5af99f529fad3cf3da68dced456266948238a7143383847e79a7bb90134e023eb173c25116d8eb80ff57fa4c4a0377ca1ed
2 parents ed2ff3c + 88db09b commit 2562fe1

File tree

1 file changed

+62
-31
lines changed

1 file changed

+62
-31
lines changed

src/common/netif.cpp

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ std::optional<CNetAddr> FromSockAddr(const struct sockaddr* addr, std::optional<
6565
// will fail, so we skip that.
6666
#if defined(__linux__) || (defined(__FreeBSD__) && __FreeBSD_version >= 1400000)
6767

68+
// Good for responses containing ~ 10,000-15,000 routes.
69+
static constexpr ssize_t NETLINK_MAX_RESPONSE_SIZE{1'048'576};
70+
6871
std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t family)
6972
{
7073
// Create a netlink socket.
@@ -113,40 +116,68 @@ std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t family)
113116

114117
// Receive response.
115118
char response[4096];
116-
int64_t recv_result;
117-
do {
118-
recv_result = sock->Recv(response, sizeof(response), 0);
119-
} while (recv_result < 0 && (errno == EINTR || errno == EAGAIN));
120-
if (recv_result < 0) {
121-
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "recv() from netlink socket: %s\n", NetworkErrorString(errno));
122-
return std::nullopt;
123-
}
119+
ssize_t total_bytes_read{0};
120+
bool done{false};
121+
while (!done) {
122+
int64_t recv_result;
123+
do {
124+
recv_result = sock->Recv(response, sizeof(response), 0);
125+
} while (recv_result < 0 && (errno == EINTR || errno == EAGAIN));
126+
if (recv_result < 0) {
127+
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "recv() from netlink socket: %s\n", NetworkErrorString(errno));
128+
return std::nullopt;
129+
}
124130

125-
for (nlmsghdr* hdr = (nlmsghdr*)response; NLMSG_OK(hdr, recv_result); hdr = NLMSG_NEXT(hdr, recv_result)) {
126-
rtmsg* r = (rtmsg*)NLMSG_DATA(hdr);
127-
int remaining_len = RTM_PAYLOAD(hdr);
128-
129-
// Iterate over the attributes.
130-
rtattr *rta_gateway = nullptr;
131-
int scope_id = 0;
132-
for (rtattr* attr = RTM_RTA(r); RTA_OK(attr, remaining_len); attr = RTA_NEXT(attr, remaining_len)) {
133-
if (attr->rta_type == RTA_GATEWAY) {
134-
rta_gateway = attr;
135-
} else if (attr->rta_type == RTA_OIF && sizeof(int) == RTA_PAYLOAD(attr)) {
136-
std::memcpy(&scope_id, RTA_DATA(attr), sizeof(scope_id));
137-
}
131+
total_bytes_read += recv_result;
132+
if (total_bytes_read > NETLINK_MAX_RESPONSE_SIZE) {
133+
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "Netlink response exceeded size limit (%zu bytes, family=%d)\n", NETLINK_MAX_RESPONSE_SIZE, family);
134+
return std::nullopt;
138135
}
139136

140-
// Found gateway?
141-
if (rta_gateway != nullptr) {
142-
if (family == AF_INET && sizeof(in_addr) == RTA_PAYLOAD(rta_gateway)) {
143-
in_addr gw;
144-
std::memcpy(&gw, RTA_DATA(rta_gateway), sizeof(gw));
145-
return CNetAddr(gw);
146-
} else if (family == AF_INET6 && sizeof(in6_addr) == RTA_PAYLOAD(rta_gateway)) {
147-
in6_addr gw;
148-
std::memcpy(&gw, RTA_DATA(rta_gateway), sizeof(gw));
149-
return CNetAddr(gw, scope_id);
137+
for (nlmsghdr* hdr = (nlmsghdr*)response; NLMSG_OK(hdr, recv_result); hdr = NLMSG_NEXT(hdr, recv_result)) {
138+
if (!(hdr->nlmsg_flags & NLM_F_MULTI)) {
139+
done = true;
140+
}
141+
142+
if (hdr->nlmsg_type == NLMSG_DONE) {
143+
done = true;
144+
break;
145+
}
146+
147+
rtmsg* r = (rtmsg*)NLMSG_DATA(hdr);
148+
int remaining_len = RTM_PAYLOAD(hdr);
149+
150+
if (hdr->nlmsg_type != RTM_NEWROUTE) {
151+
continue; // Skip non-route messages
152+
}
153+
154+
// Only consider default routes (destination prefix length of 0).
155+
if (r->rtm_dst_len != 0) {
156+
continue;
157+
}
158+
159+
// Iterate over the attributes.
160+
rtattr* rta_gateway = nullptr;
161+
int scope_id = 0;
162+
for (rtattr* attr = RTM_RTA(r); RTA_OK(attr, remaining_len); attr = RTA_NEXT(attr, remaining_len)) {
163+
if (attr->rta_type == RTA_GATEWAY) {
164+
rta_gateway = attr;
165+
} else if (attr->rta_type == RTA_OIF && sizeof(int) == RTA_PAYLOAD(attr)) {
166+
std::memcpy(&scope_id, RTA_DATA(attr), sizeof(scope_id));
167+
}
168+
}
169+
170+
// Found gateway?
171+
if (rta_gateway != nullptr) {
172+
if (family == AF_INET && sizeof(in_addr) == RTA_PAYLOAD(rta_gateway)) {
173+
in_addr gw;
174+
std::memcpy(&gw, RTA_DATA(rta_gateway), sizeof(gw));
175+
return CNetAddr(gw);
176+
} else if (family == AF_INET6 && sizeof(in6_addr) == RTA_PAYLOAD(rta_gateway)) {
177+
in6_addr gw;
178+
std::memcpy(&gw, RTA_DATA(rta_gateway), sizeof(gw));
179+
return CNetAddr(gw, scope_id);
180+
}
150181
}
151182
}
152183
}

0 commit comments

Comments
 (0)