aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/netliba/v6/udp_socket.cpp
blob: fd85ef4d007fd2e52f96516e8cd86520e59b495d (plain) (blame)
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
#include "stdafx.h"
#include "udp_socket.h"
#include "block_chain.h"
#include "udp_address.h"

#include <util/datetime/cputimer.h>
#include <util/system/spinlock.h>
#include <util/random/random.h>

#include <library/cpp/netliba/socket/socket.h>

#include <errno.h>

//#define SIMULATE_NETWORK_FAILURES
// there is no explicit bit in the packet header for last packet of transfer
// last packet is just smaller then maximum size

namespace NNetliba {
    static bool LocalHostFound;
    enum {
        IPv4 = 0,
        IPv6 = 1
    };

    struct TIPv6Addr {
        ui64 Network, Interface;

        TIPv6Addr() {
            Zero(*this);
        }
        TIPv6Addr(ui64 n, ui64 i)
            : Network(n)
            , Interface(i)
        {
        }
    };
    inline bool operator==(const TIPv6Addr& a, const TIPv6Addr& b) {
        return a.Interface == b.Interface && a.Network == b.Network;
    }

    static ui32 LocalHostIP[2];
    static TVector<ui32> LocalHostIPList[2];
    static TVector<TIPv6Addr> LocalHostIPv6List;

    // Struct sockaddr_in6 does not have ui64-array representation
    // so we add it here. This avoids "strict aliasing" warnings
    typedef union {
        in6_addr Addr;
        ui64 Addr64[2];
    } TIPv6AddrUnion;

    static ui32 GetIPv6SuffixCrc(const sockaddr_in6& addr) {
        TIPv6AddrUnion a;
        a.Addr = addr.sin6_addr;
        ui64 suffix = a.Addr64[1];
        return (suffix & 0xffffffffll) + (suffix >> 32);
    }

    bool InitLocalIPList() {
        // Do not use TMutex here: it has a non-trivial destructor which will be called before
        // destruction of current thread, if its TThread declared as global/static variable.
        static TAdaptiveLock cs;
        TGuard lock(cs);

        if (LocalHostFound)
            return true;

        TVector<TUdpAddress> addrs;
        if (!GetLocalAddresses(&addrs))
            return false;
        for (int i = 0; i < addrs.ysize(); ++i) {
            const TUdpAddress& addr = addrs[i];
            if (addr.IsIPv4()) {
                LocalHostIPList[IPv4].push_back(addr.GetIPv4());
                LocalHostIP[IPv4] = addr.GetIPv4();
            } else {
                sockaddr_in6 addr6;
                GetWinsockAddr(&addr6, addr);

                LocalHostIPList[IPv6].push_back(GetIPv6SuffixCrc(addr6));
                LocalHostIP[IPv6] = GetIPv6SuffixCrc(addr6);
                LocalHostIPv6List.push_back(TIPv6Addr(addr.Network, addr.Interface));
            }
        }
        LocalHostFound = true;
        return true;
    }

    template <class T, class TElem>
    inline bool IsInSet(const T& c, const TElem& e) {
        return Find(c.begin(), c.end(), e) != c.end();
    }

    bool IsLocalIPv4(ui32 ip) {
        return IsInSet(LocalHostIPList[IPv4], ip);
    }
    bool IsLocalIPv6(ui64 network, ui64 iface) {
        return IsInSet(LocalHostIPv6List, TIPv6Addr(network, iface));
    }

    //////////////////////////////////////////////////////////////////////////
    void TNetSocket::Open(int port) {
        TIntrusivePtr<NNetlibaSocket::ISocket> theSocket = NNetlibaSocket::CreateSocket();
        theSocket->Open(port);
        Open(theSocket);
    }

    void TNetSocket::Open(const TIntrusivePtr<NNetlibaSocket::ISocket>& socket) {
        s = socket;
        if (IsValid()) {
            PortCrc = s->GetSelfAddress().sin6_port;
        }
    }

    void TNetSocket::Close() {
        if (IsValid()) {
            s->Close();
        }
    }

    void TNetSocket::SendSelfFakePacket() const {
        s->CancelWait();
    }

    inline ui32 CalcAddressCrc(const sockaddr_in6& addr) {
        Y_ASSERT(addr.sin6_family == AF_INET6);
        const ui64* addr64 = (const ui64*)addr.sin6_addr.s6_addr;
        const ui32* addr32 = (const ui32*)addr.sin6_addr.s6_addr;
        if (addr64[0] == 0 && addr32[2] == 0xffff0000ll) {
            // ipv4
            return addr32[3];
        } else {
            // ipv6
            return GetIPv6SuffixCrc(addr);
        }
    }

    TNetSocket::ESendError TNetSocket::SendTo(const char* buf, int size, const sockaddr_in6& toAddress, const EFragFlag frag) const {
        Y_ASSERT(size >= UDP_LOW_LEVEL_HEADER_SIZE);
        ui32 crc = CalcChecksum(buf + UDP_LOW_LEVEL_HEADER_SIZE, size - UDP_LOW_LEVEL_HEADER_SIZE);
        ui32 ipCrc = CalcAddressCrc(toAddress);
        ui32 portCrc = toAddress.sin6_port;
        *(ui32*)buf = crc + ipCrc + portCrc;
#ifdef SIMULATE_NETWORK_FAILURES
        if ((RandomNumber<size_t>() % 3) == 0)
            return true; // packet lost
        if ((RandomNumber<size_t>() % 3) == 0)
            (char&)(buf[RandomNumber<size_t>() % size]) += RandomNumber<size_t>(); // packet broken
#endif

        char tosBuffer[NNetlibaSocket::TOS_BUFFER_SIZE];
        void* t = NNetlibaSocket::CreateTos(Tos, tosBuffer);
        const NNetlibaSocket::TIoVec iov = NNetlibaSocket::CreateIoVec((char*)buf, size);
        NNetlibaSocket::TMsgHdr hdr = NNetlibaSocket::CreateSendMsgHdr(toAddress, iov, t);

        const int rv = s->SendMsg(&hdr, 0, frag);
        if (rv < 0) {
            if (errno == EHOSTUNREACH || errno == ENETUNREACH) {
                return SEND_NO_ROUTE_TO_HOST;
            } else {
                return SEND_BUFFER_OVERFLOW;
            }
        }
        Y_ASSERT(rv == size);
        return SEND_OK;
    }

    inline bool CrcMatches(ui32 pktCrc, ui32 crc, const sockaddr_in6& addr) {
        Y_ASSERT(LocalHostFound);
        Y_ASSERT(addr.sin6_family == AF_INET6);
        // determine our ip address family based on the sender address
        // address family can not change in network, so sender address type determines type of our address used
        const ui64* addr64 = (const ui64*)addr.sin6_addr.s6_addr;
        const ui32* addr32 = (const ui32*)addr.sin6_addr.s6_addr;
        yint ipType;
        if (addr64[0] == 0 && addr32[2] == 0xffff0000ll) {
            // ipv4
            ipType = IPv4;
        } else {
            // ipv6
            ipType = IPv6;
        }
        if (crc + LocalHostIP[ipType] == pktCrc) {
            return true;
        }
        // crc failed
        // check if packet was sent to different IP address
        for (int idx = 0; idx < LocalHostIPList[ipType].ysize(); ++idx) {
            ui32 otherIP = LocalHostIPList[ipType][idx];
            if (crc + otherIP == pktCrc) {
                LocalHostIP[ipType] = otherIP;
                return true;
            }
        }
        // crc is really failed, discard packet
        return false;
    }

    bool TNetSocket::RecvFrom(char* buf, int* size, sockaddr_in6* fromAddress) const {
        for (;;) {
            int rv;
            if (s->IsRecvMsgSupported()) {
                const NNetlibaSocket::TIoVec v = NNetlibaSocket::CreateIoVec(buf, *size);
                NNetlibaSocket::TMsgHdr hdr = NNetlibaSocket::CreateRecvMsgHdr(fromAddress, v);
                rv = s->RecvMsg(&hdr, 0);

            } else {
                sockaddr_in6 dummy;
                TAutoPtr<NNetlibaSocket::TUdpRecvPacket> pkt = s->Recv(fromAddress, &dummy, -1);
                rv = !!pkt ? pkt->DataSize - pkt->DataStart : -1;
                if (rv > 0) {
                    memcpy(buf, pkt->Data.get() + pkt->DataStart, rv);
                }
            }

            if (rv < 0)
                return false;
            // ignore empty packets
            if (rv == 0)
                continue;
            // skip small packets
            if (rv < UDP_LOW_LEVEL_HEADER_SIZE)
                continue;
            *size = rv;
            ui32 pktCrc = *(ui32*)buf;
            ui32 crc = CalcChecksum(buf + UDP_LOW_LEVEL_HEADER_SIZE, rv - UDP_LOW_LEVEL_HEADER_SIZE);
            if (!CrcMatches(pktCrc, crc + PortCrc, *fromAddress)) {
                // crc is really failed, discard packet
                continue;
            }
            return true;
        }
    }

    void TNetSocket::Wait(float timeoutSec) const {
        s->Wait(timeoutSec);
    }

    void TNetSocket::SetTOS(int n) const {
        Tos = n;
    }

    bool TNetSocket::Connect(const sockaddr_in6& addr) {
        // "connect" - meaningless operation
        // needed since port unreachable is routed only to "connected" udp sockets in ingenious FreeBSD
        if (s->Connect((sockaddr*)&addr, sizeof(addr)) < 0) {
            if (errno == EHOSTUNREACH || errno == ENETUNREACH) {
                return false;
            } else {
                Y_ASSERT(0);
            }
        }
        return true;
    }

    void TNetSocket::SendEmptyPacket() {
        NNetlibaSocket::TIoVec v;
        Zero(v);

        // darwin ignores packets with msg_iovlen == 0, also windows implementation uses sendto of first iovec.
        NNetlibaSocket::TMsgHdr hdr;
        Zero(hdr);
        hdr.msg_iov = &v;
        hdr.msg_iovlen = 1;

        s->SendMsg(&hdr, 0, FF_ALLOW_FRAG); // sends empty packet to connected address
    }

    bool TNetSocket::IsHostUnreachable() {
#ifdef _win_
        char buf[10000];
        sockaddr_in6 fromAddress;

        const NNetlibaSocket::TIoVec v = NNetlibaSocket::CreateIoVec(buf, Y_ARRAY_SIZE(buf));
        NNetlibaSocket::TMsgHdr hdr = NNetlibaSocket::CreateRecvMsgHdr(&fromAddress, v);

        const ssize_t rv = s->RecvMsg(&hdr, 0);
        if (rv < 0) {
            int err = WSAGetLastError();
            if (err == WSAECONNRESET)
                return true;
        }
#else
        int err = 0;
        socklen_t bufSize = sizeof(err);
        s->GetSockOpt(SOL_SOCKET, SO_ERROR, (char*)&err, &bufSize);
        if (err == ECONNREFUSED)
            return true;
#endif
        return false;
    }
}