aboutsummaryrefslogtreecommitdiff
path: root/src/common/netif.cpp
blob: 08f034a412adf68c58cee69e4884110c1f89c54d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
// Copyright (c) 2024 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php.

#include <bitcoin-build-config.h> // IWYU pragma: keep

#include <common/netif.h>

#include <logging.h>
#include <netbase.h>
#include <util/check.h>
#include <util/sock.h>
#include <util/syserror.h>

#if defined(__linux__)
#include <linux/rtnetlink.h>
#elif defined(__FreeBSD__)
#include <osreldate.h>
#if __FreeBSD_version >= 1400000
// Workaround https://github.com/freebsd/freebsd-src/pull/1070.
#define typeof __typeof
#include <netlink/netlink.h>
#include <netlink/netlink_route.h>
#endif
#elif defined(WIN32)
#include <iphlpapi.h>
#elif defined(__APPLE__)
#include <net/route.h>
#include <sys/sysctl.h>
#endif

namespace {

// Linux and FreeBSD 14.0+. For FreeBSD 13.2 the code can be compiled but
// running it requires loading a special kernel module, otherwise socket(AF_NETLINK,...)
// will fail, so we skip that.
#if defined(__linux__) || (defined(__FreeBSD__) && __FreeBSD_version >= 1400000)

std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t family)
{
    // Create a netlink socket.
    auto sock{CreateSock(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)};
    if (!sock) {
        LogPrintLevel(BCLog::NET, BCLog::Level::Error, "socket(AF_NETLINK): %s\n", NetworkErrorString(errno));
        return std::nullopt;
    }

    // Send request.
    struct {
        nlmsghdr hdr; ///< Request header.
        rtmsg data; ///< Request data, a "route message".
        nlattr dst_hdr; ///< One attribute, conveying the route destination address.
        char dst_data[16]; ///< Route destination address. To query the default route we use 0.0.0.0/0 or [::]/0. For IPv4 the first 4 bytes are used.
    } request{};

    // Whether to use the first 4 or 16 bytes from request.dst_data.
    const size_t dst_data_len = family == AF_INET ? 4 : 16;

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

        // 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);
            } else if (family == AF_INET6 && sizeof(in6_addr) == RTA_PAYLOAD(rta_gateway)) {
                in6_addr gw;
                std::memcpy(&gw, RTA_DATA(rta_gateway), sizeof(gw));
                return CNetAddr(gw, scope_id);
            }
        }
    }

    return std::nullopt;
}

#elif defined(WIN32)

std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t family)
{
    NET_LUID interface_luid = {};
    SOCKADDR_INET destination_address = {};
    MIB_IPFORWARD_ROW2 best_route = {};
    SOCKADDR_INET best_source_address = {};
    DWORD best_if_idx = 0;
    DWORD status = 0;

    // Pass empty destination address of the requested type (:: or 0.0.0.0) to get interface of default route.
    destination_address.si_family = family;
    status = GetBestInterfaceEx((sockaddr*)&destination_address, &best_if_idx);
    if (status != NO_ERROR) {
        LogPrintLevel(BCLog::NET, BCLog::Level::Error, "Could not get best interface for default route: %s\n", NetworkErrorString(status));
        return std::nullopt;
    }

    // Get best route to default gateway.
    // Leave interface_luid at all-zeros to use interface index instead.
    status = GetBestRoute2(&interface_luid, best_if_idx, nullptr, &destination_address, 0, &best_route, &best_source_address);
    if (status != NO_ERROR) {
        LogPrintLevel(BCLog::NET, BCLog::Level::Error, "Could not get best route for default route for interface index %d: %s\n",
                best_if_idx, NetworkErrorString(status));
        return std::nullopt;
    }

    Assume(best_route.NextHop.si_family == family);
    if (family == AF_INET) {
        return CNetAddr(best_route.NextHop.Ipv4.sin_addr);
    } else if(family == AF_INET6) {
        return CNetAddr(best_route.NextHop.Ipv6.sin6_addr, best_route.InterfaceIndex);
    }
    return std::nullopt;
}

#elif defined(__APPLE__)

#define ROUNDUP32(a) \
    ((a) > 0 ? (1 + (((a) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t))

std::optional<CNetAddr> FromSockAddr(const struct sockaddr* addr)
{
    // Check valid length. Note that sa_len is not part of POSIX, and exists on MacOS and some BSDs only, so we can't
    // do this check in SetSockAddr.
    if (!(addr->sa_family == AF_INET && addr->sa_len == sizeof(struct sockaddr_in)) &&
        !(addr->sa_family == AF_INET6 && addr->sa_len == sizeof(struct sockaddr_in6))) {
        return std::nullopt;
    }

    // Fill in a CService from the sockaddr, then drop the port part.
    CService service;
    if (service.SetSockAddr(addr)) {
        return (CNetAddr)service;
    }
    return std::nullopt;
}

//! MacOS: Get default gateway from route table. See route(4) for the format.
std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t family)
{
    // net.route.0.inet[6].flags.gateway
    int mib[] = {CTL_NET, PF_ROUTE, 0, family, NET_RT_FLAGS, RTF_GATEWAY};
    // The size of the available data is determined by calling sysctl() with oldp=nullptr. See sysctl(3).
    size_t l = 0;
    if (sysctl(/*name=*/mib, /*namelen=*/sizeof(mib) / sizeof(int), /*oldp=*/nullptr, /*oldlenp=*/&l, /*newp=*/nullptr, /*newlen=*/0) < 0) {
        LogPrintLevel(BCLog::NET, BCLog::Level::Error, "Could not get sysctl length of routing table: %s\n", SysErrorString(errno));
        return std::nullopt;
    }
    std::vector<std::byte> buf(l);
    if (sysctl(/*name=*/mib, /*namelen=*/sizeof(mib) / sizeof(int), /*oldp=*/buf.data(), /*oldlenp=*/&l, /*newp=*/nullptr, /*newlen=*/0) < 0) {
        LogPrintLevel(BCLog::NET, BCLog::Level::Error, "Could not get sysctl data of routing table: %s\n", SysErrorString(errno));
        return std::nullopt;
    }
    // Iterate over messages (each message is a routing table entry).
    for (size_t msg_pos = 0; msg_pos < buf.size(); ) {
        if ((msg_pos + sizeof(rt_msghdr)) > buf.size()) return std::nullopt;
        const struct rt_msghdr* rt = (const struct rt_msghdr*)(buf.data() + msg_pos);
        const size_t next_msg_pos = msg_pos + rt->rtm_msglen;
        if (rt->rtm_msglen < sizeof(rt_msghdr) || next_msg_pos > buf.size()) return std::nullopt;
        // Iterate over addresses within message, get destination and gateway (if present).
        // Address data starts after header.
        size_t sa_pos = msg_pos + sizeof(struct rt_msghdr);
        std::optional<CNetAddr> dst, gateway;
        for (int i = 0; i < RTAX_MAX; i++) {
            if (rt->rtm_addrs & (1 << i)) {
                // 2 is just sa_len + sa_family, the theoretical minimum size of a socket address.
                if ((sa_pos + 2) > next_msg_pos) return std::nullopt;
                const struct sockaddr* sa = (const struct sockaddr*)(buf.data() + sa_pos);
                if ((sa_pos + sa->sa_len) > next_msg_pos) return std::nullopt;
                if (i == RTAX_DST) {
                    dst = FromSockAddr(sa);
                } else if (i == RTAX_GATEWAY) {
                    gateway = FromSockAddr(sa);
                }
                // Skip sockaddr entries for bit flags we're not interested in,
                // move cursor.
                sa_pos += ROUNDUP32(sa->sa_len);
            }
        }
        // Found default gateway?
        if (dst && gateway && dst->IsBindAny()) { // Route to 0.0.0.0 or :: ?
            return *gateway;
        }
        // Skip to next message.
        msg_pos = next_msg_pos;
    }
    return std::nullopt;
}

#else

// Dummy implementation.
std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t)
{
    return std::nullopt;
}

#endif

}

std::optional<CNetAddr> QueryDefaultGateway(Network network)
{
    Assume(network == NET_IPV4 || network == NET_IPV6);

    sa_family_t family;
    if (network == NET_IPV4) {
        family = AF_INET;
    } else if(network == NET_IPV6) {
        family = AF_INET6;
    } else {
        return std::nullopt;
    }

    std::optional<CNetAddr> ret = QueryDefaultGatewayImpl(family);

    // It's possible for the default gateway to be 0.0.0.0 or ::0 on at least Windows
    // for some routing strategies. If so, return as if no default gateway was found.
    if (ret && !ret->IsBindAny()) {
        return ret;
    } else {
        return std::nullopt;
    }
}

std::vector<CNetAddr> GetLocalAddresses()
{
    std::vector<CNetAddr> addresses;
#ifdef WIN32
    char pszHostName[256] = "";
    if (gethostname(pszHostName, sizeof(pszHostName)) != SOCKET_ERROR) {
        addresses = LookupHost(pszHostName, 0, true);
    }
#elif (HAVE_DECL_GETIFADDRS && HAVE_DECL_FREEIFADDRS)
    struct ifaddrs* myaddrs;
    if (getifaddrs(&myaddrs) == 0) {
        for (struct ifaddrs* ifa = myaddrs; ifa != nullptr; ifa = ifa->ifa_next)
        {
            if (ifa->ifa_addr == nullptr) continue;
            if ((ifa->ifa_flags & IFF_UP) == 0) continue;
            if ((ifa->ifa_flags & IFF_LOOPBACK) != 0) continue;
            if (ifa->ifa_addr->sa_family == AF_INET) {
                struct sockaddr_in* s4 = (struct sockaddr_in*)(ifa->ifa_addr);
                addresses.emplace_back(s4->sin_addr);
            } else if (ifa->ifa_addr->sa_family == AF_INET6) {
                struct sockaddr_in6* s6 = (struct sockaddr_in6*)(ifa->ifa_addr);
                addresses.emplace_back(s6->sin6_addr);
            }
        }
        freeifaddrs(myaddrs);
    }
#endif
    return addresses;
}