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

Code before:
    request.hdr.nlmsg_type = RTM_GETROUTE;
    request.hdr.nlmsg_flags = NLM_F_REQUEST;
#ifdef __linux__
    // Linux IPv4 / IPv6 - this must be present, otherwise no gateway is found
    // FreeBSD IPv4 - does not matter, the gateway is found with or without this
    // FreeBSD IPv6 - this must be absent, otherwise no gateway is found
    request.hdr.nlmsg_flags |= NLM_F_DUMP;
#endif
    request.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(rtmsg) + sizeof(nlattr) + dst_data_len);
    request.hdr.nlmsg_seq = 0; // Sequence number, used to match which reply is to which request. Irrelevant for us because we send just one request.
    request.data.rtm_family = family;
    request.data.rtm_dst_len = 0; // Prefix length.
#ifdef __FreeBSD__
    // 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];
    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);

        // 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));
            }
        }

Review comment (suggestion):
Is it guaranteed that the reponse to `NLM_F_DUMP` is always multipart? Or do we need to check `nlmsg_flags` for `NLM_F_MULTI`, and if not, break after the first packet?

This is not clear to me from the documentation:
- https://man7.org/linux/man-pages/man7/netlink.7.html
- https://man7.org/linux/man-pages/man7/rtnetlink.7.html



Code after:
    // 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).
            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));
                }