Repository: bitcoin/bitcoin
File: src/common/netif.cpp (C++)

Code before:
    request.data.rtm_flags = RTM_F_PREFIX;
#endif
    request.dst_hdr.nla_type = RTA_DST;
    request.dst_hdr.nla_len = sizeof(nlattr) + dst_data_len;

    if (sock->Send(&request, request.hdr.nlmsg_len, 0) != static_cast<ssize_t>(request.hdr.nlmsg_len)) {
        LogPrintLevel(BCLog::NET, BCLog::Level::Error, "send() to netlink socket: %s\n", NetworkErrorString(errno));
        return std::nullopt;
    }

    // Receive response.
    char response[4096];
    int64_t recv_result;
    do {
        recv_result = sock->Recv(response, sizeof(response), 0);
    } while (recv_result < 0 && (errno == EINTR || errno == EAGAIN));
    if (recv_result < 0) {
        LogPrintLevel(BCLog::NET, BCLog::Level::Error, "recv() from netlink socket: %s\n", NetworkErrorString(errno));
        return std::nullopt;
    }

    for (nlmsghdr* hdr = (nlmsghdr*)response; NLMSG_OK(hdr, recv_result); hdr = NLMSG_NEXT(hdr, recv_result)) {
        rtmsg* r = (rtmsg*)NLMSG_DATA(hdr);
        int remaining_len = RTM_PAYLOAD(hdr);

        if (hdr->nlmsg_type != RTM_NEWROUTE) {
            continue; // Skip non-route messages
        }

        // Only consider default routes (destination prefix length of 0).
        if (r->rtm_dst_len != 0) {
            continue;
        }

        // Iterate over the attributes.
        rtattr *rta_gateway = nullptr;
        int scope_id = 0;
        for (rtattr* attr = RTM_RTA(r); RTA_OK(attr, remaining_len); attr = RTA_NEXT(attr, remaining_len)) {
            if (attr->rta_type == RTA_GATEWAY) {
                rta_gateway = attr;
            } else if (attr->rta_type == RTA_OIF && sizeof(int) == RTA_PAYLOAD(attr)) {
                std::memcpy(&scope_id, RTA_DATA(attr), sizeof(scope_id));
            }
        }

        // Found gateway?
        if (rta_gateway != nullptr) {
            if (family == AF_INET && sizeof(in_addr) == RTA_PAYLOAD(rta_gateway)) {
                in_addr gw;
                std::memcpy(&gw, RTA_DATA(rta_gateway), sizeof(gw));
                return CNetAddr(gw);

Review comment (performance):
In https://github.com/bitcoin/bitcoin/pull/32159/commits/42e99ad77396e4e9b02d67daf46349e215e99a0f (_net: skip non-route netlink responses_)

The `man 7 rtnetlink` page (https://man7.org/linux/man-pages/man7/rtnetlink.7.html) does not really offer much insight here, but as far as I can tell, the convention comes from the fact that the rtnetlink interface is also used for monitoring changes to the kernel's routing table, and because the userspace application is trying to maintain an idea of the kernel routing table's state, the interfaces are symmetrical, a userspace application sends an `RTM_NEWROUTE` when it wants to insert a new route, and likewise whenever the kernel inserts a new route, it sends out an `RTM_NEWROUTE` message over open `NETLINK_ROUTE` sockets, so the reply to an `RTM_GETROUTE` follows this convention and replies with `RTM_NEWROUTE`, and I imagine the same is true for `GETADDR` and `GETLINK`

Looking through the linux kernel's implementation for evidence of this:

e.g. [`inet_rtm_getroute`](https://github.com/torvalds/linux/blob/39f90c1967215375f7d87b81d14b0f3ed6b40c29/net/ipv4/route.c#L3281) is the kernel's [callback](https://github.com/torvalds/linux/blob/39f90c1967215375f7d87b81d14b0f3ed6b40c29/net/ipv4/route.c#L3708-L3711) that handles `RTM_GETROUTE` messages for IPv4 routes, and here is a simplified + annotated view of the [relevant section](https://github.com/torvalds/linux/blob/39f90c1967215375f7d87b81d14b0f3ed6b40c29/net/ipv4/route.c#L3392-L3441) of that function:

```c
static int inet_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
			     struct netlink_ext_ack *extack)
{
	// [....]

	// [ This is never true for us. ]
	if (rtm->rtm_flags & RTM_F_FIB_MATCH) {
		// [ fib_dump_info populates sk_buff* skb, which holds packet data,
		//   and places the `event` arg in the nlmsg hdr->nlmsg_type
		//   https://github.com/torvalds/linux/blob/39f90c1967215375f7d87b81d14b0f3ed6b40c29/net/ipv4/fib_semantics.c#L1732
		// ]
		fib_dump_info(skb, NETLINK_CB(in_skb).portid,
				    nlh->nlmsg_seq,/*event=*/RTM_NEWROUTE, &fri, 0);
	} else {
		// [ This case is relevant for us, rt_fill_info always creates an RTM_NEWROUTE message.]
		//   https://github.com/torvalds/linux/blob/39f90c1967215375f7d87b81d14b0f3ed6b40c29/net/ipv4/route.c#L2951
		rt_fill_info(net, dst, src, rt, table_id, res.dscp, &fl4,
				   skb, NETLINK_CB(in_skb).portid,
				   nlh->nlmsg_seq, 0);
	}

	// [ Broadcast the `skb` on the socket. ]
	rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
}

// [ https://github.com/torvalds/linux/blob/39f90c1967215375f7d87b81d14b0f3ed6b40c29/net/ipv4/route.c#L2939-L2951 ]
static int rt_fill_info(
    // [...]
)
{
	nlh = nlmsg_put(skb, portid, seq, RTM_NEWROUTE, sizeof(*r), flags);
}
```

Code after:
    // Linux IPv4 / IPv6 this must be absent, otherwise no gateway is found
    // FreeBSD IPv4 - does not matter, the gateway is found with or without this
    // FreeBSD IPv6 - this must be present, otherwise no gateway is found
    request.data.rtm_flags = RTM_F_PREFIX;
#endif
    request.dst_hdr.nla_type = RTA_DST;
    request.dst_hdr.nla_len = sizeof(nlattr) + dst_data_len;

    if (sock->Send(&request, request.hdr.nlmsg_len, 0) != static_cast<ssize_t>(request.hdr.nlmsg_len)) {
        LogPrintLevel(BCLog::NET, BCLog::Level::Error, "send() to netlink socket: %s\n", NetworkErrorString(errno));
        return std::nullopt;
    }

    // Receive response.
    char response[4096];
    ssize_t total_bytes_read{0};
    bool done{false};
    while (!done) {
        int64_t recv_result;
        do {
            recv_result = sock->Recv(response, sizeof(response), 0);
        } while (recv_result < 0 && (errno == EINTR || errno == EAGAIN));
        if (recv_result < 0) {
            LogPrintLevel(BCLog::NET, BCLog::Level::Error, "recv() from netlink socket: %s\n", NetworkErrorString(errno));
            return std::nullopt;
        }

        total_bytes_read += recv_result;
        if (total_bytes_read > NETLINK_MAX_RESPONSE_SIZE) {
            LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "Netlink response exceeded size limit (%zu bytes, family=%d)\n", NETLINK_MAX_RESPONSE_SIZE, family);
            return std::nullopt;
        }

        for (nlmsghdr* hdr = (nlmsghdr*)response; NLMSG_OK(hdr, recv_result); hdr = NLMSG_NEXT(hdr, recv_result)) {
            if (!(hdr->nlmsg_flags & NLM_F_MULTI)) {
                done = true;
            }

            if (hdr->nlmsg_type == NLMSG_DONE) {
                done = true;
                break;
            }

            rtmsg* r = (rtmsg*)NLMSG_DATA(hdr);
            int remaining_len = RTM_PAYLOAD(hdr);

            if (hdr->nlmsg_type != RTM_NEWROUTE) {
                continue; // Skip non-route messages
            }

            // Only consider default routes (destination prefix length of 0).