-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathipv4_relay_fallback.cpp
More file actions
175 lines (157 loc) · 6.61 KB
/
Copy pathipv4_relay_fallback.cpp
File metadata and controls
175 lines (157 loc) · 6.61 KB
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
#include "ipv4_relay_fallback.h"
#include <chrono>
#include <cstdint>
#include <vector>
#include "log.h"
#include "nat_protocol.h"
namespace {
constexpr int kRelayReceiveTimeoutMs = 200;
bool Send(socket_t sock, const UdpEndpoint& endpoint, const std::string& data) {
return sendto(sock, data.data(), static_cast<int>(data.size()), 0,
reinterpret_cast<const sockaddr*>(&endpoint.addr), endpoint.addr_len)
== static_cast<int>(data.size());
}
UdpEndpoint FromSockaddr(const sockaddr_storage& address, socket_len_t len) {
UdpEndpoint endpoint{};
endpoint.addr = address;
endpoint.addr_len = len;
endpoint.family = address.ss_family;
return endpoint;
}
bool ParsePort(const std::string& text, uint16_t* port) {
try {
size_t consumed = 0;
const unsigned long parsed = std::stoul(text, &consumed);
if (consumed != text.size() || parsed == 0 || parsed > 65535) return false;
*port = static_cast<uint16_t>(parsed);
return true;
} catch (...) {
return false;
}
}
UdpEndpoint WithPort(const UdpEndpoint& endpoint, uint16_t port) {
UdpEndpoint result = endpoint;
reinterpret_cast<sockaddr_in*>(&result.addr)->sin_port = htons(port);
return result;
}
std::string RelayServerError(const std::vector<std::string>& fields) {
if (fields.empty()) return "IPv4 relay was rejected by the rendezvous server";
const std::string& code = fields[0];
if (code == "ipv4-relay-disabled") {
return "IPv4 relay is disabled on the rendezvous server";
}
if (code == "ipv4-relay-port-exhausted") {
return "No IPv4 relay UDP port is available on the rendezvous server";
}
if (code == "ipv4-relay-resource-unavailable") {
return "The rendezvous server cannot create an IPv4 relay session";
}
if (code == "peer-not-found") {
return "The selected peer is no longer available for IPv4 relay";
}
if (code == "peer-busy") {
return "The selected peer is paired with another client";
}
if (code == "unauthorized") {
return "The rendezvous server rejected IPv4 relay authentication";
}
return "IPv4 relay was rejected by the rendezvous server: " + code;
}
} // namespace
bool DiscoverAndConnectIpv4Relay(
socket_t* sock, const Config& config,
const UdpEndpoint& rendezvousServer, const std::atomic<bool>& running,
const std::string& expectedPeerId, UdpEndpoint* peer, std::string* error) {
if (rendezvousServer.family != AF_INET || expectedPeerId.empty()) {
*error = "IPv4 relay requires a matched peer and an IPv4 rendezvous server";
return false;
}
if (*sock == kInvalidSocket) {
*sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (*sock == kInvalidSocket) {
*error = "Cannot create IPv4 relay UDP socket. err="
+ std::to_string(GetSocketError());
return false;
}
}
SetSocketRecvTimeoutMs(*sock, kRelayReceiveTimeoutMs);
// Keep cleanup on the IPv4 rendezvous path even if a failed IPv6 attempt
// previously left an AF_INET6 candidate in *peer.
*peer = rendezvousServer;
const std::string join = MakeControlMessage("RELAY_JOIN",
{config.room_id, config.peer_id, expectedPeerId, config.auth_token});
const auto deadline = std::chrono::steady_clock::now()
+ std::chrono::seconds(config.punch_timeout);
auto nextJoin = std::chrono::steady_clock::time_point{};
auto nextHello = std::chrono::steady_clock::time_point{};
UdpEndpoint relayEndpoint{};
std::string sessionId;
std::string accessKey;
bool haveOffer = false;
std::vector<uint8_t> buffer(2048);
while (running.load() && std::chrono::steady_clock::now() < deadline) {
const auto now = std::chrono::steady_clock::now();
if (now >= nextJoin) {
Send(*sock, rendezvousServer, join);
nextJoin = now + std::chrono::milliseconds(haveOffer ? 1000 : 500);
}
if (haveOffer && now >= nextHello) {
Send(*sock, relayEndpoint, MakeControlMessage("RELAY_HELLO",
{sessionId, config.peer_id, accessKey}));
nextHello = now + std::chrono::milliseconds(500);
}
sockaddr_storage sourceAddress{};
socket_len_t sourceLen = static_cast<socket_len_t>(sizeof(sourceAddress));
const int received = recvfrom(*sock,
reinterpret_cast<char*>(buffer.data()),
static_cast<int>(buffer.size()), 0,
reinterpret_cast<sockaddr*>(&sourceAddress), &sourceLen);
if (received < 0) {
if (!running.load()) break;
const int receiveError = GetSocketError();
if (IsRecvTimeout(receiveError)
|| IsUdpDestinationUnreachable(receiveError)) continue;
*error = "IPv4 relay receive failed. err="
+ std::to_string(receiveError);
return false;
}
const UdpEndpoint source = FromSockaddr(sourceAddress, sourceLen);
std::string type;
std::vector<std::string> fields;
if (!ParseControlMessage(buffer.data(), static_cast<size_t>(received),
&type, &fields)) continue;
if (SameUdpEndpoint(source, rendezvousServer)) {
if (type == "ERROR") {
*error = RelayServerError(fields);
return false;
}
if (type != "RELAY_OFFER" || fields.size() != 4
|| fields[1] != expectedPeerId
|| !IsSafeControlField(fields[2])
|| !IsSafeControlField(fields[3])) continue;
uint16_t relayPort = 0;
if (!ParsePort(fields[0], &relayPort)) continue;
relayEndpoint = WithPort(rendezvousServer, relayPort);
sessionId = fields[2];
accessKey = fields[3];
if (!haveOffer) {
Log(LogLevel::Info, "IPv4 relay offered for peer "
+ expectedPeerId + " at "
+ FormatUdpEndpoint(relayEndpoint));
}
haveOffer = true;
nextHello = std::chrono::steady_clock::time_point{};
continue;
}
if (!haveOffer || !SameUdpEndpoint(source, relayEndpoint)
|| type != "RELAY_READY" || fields.size() != 2
|| fields[0] != sessionId || fields[1] != expectedPeerId) continue;
*peer = relayEndpoint;
Log(LogLevel::Info, "IPv4 relay fallback confirmed with "
+ FormatUdpEndpoint(*peer));
return true;
}
*error = running.load() ? "IPv4 relay fallback timed out"
: "IPv4 relay fallback stopped";
return false;
}