-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathSTUNExternalIP.c
More file actions
276 lines (216 loc) · 7.24 KB
/
STUNExternalIP.c
File metadata and controls
276 lines (216 loc) · 7.24 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
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
//
// STUNExternalIP.c
// STUNExternalIP
//
// Created by FireWolf on 2016-11-16.
// Revised by FireWolf on 2017-02-24.
// Revised by FireWolf on 2025-12-10.
//
// Copyright © 2016-2025 FireWolf. All rights reserved.
//
#include "STUNExternalIP.h"
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>
// MARK: === PRIVATE DATA STRUCTURE ===
/// RFC 5389 Section 6 STUN Message Structure
enum STUNMessageType
{
kSTUNMessageTypeBindingRequest = 0x0001,
kSTUNMessageTypeBindingResponse = 0x0101,
};
/// RFC 5389 Section 6 STUN Message Structure
static const uint32_t kSTUNMessageCookie = 0x2112A442;
/// RFC 5389 Section 6 STUN Message Structure
struct STUNMessageHeader
{
/// [BE] Message Type (Binding Request / Response)
uint16_t type;
/// [BE] Payload length of this message
uint16_t length;
/// [BE] Magic Cookie
uint32_t cookie;
/// [BE] Unique Transaction ID
uint32_t identifier[3];
};
/// RFC 5389 Section 15 STUN Attributes
enum STUNAttributeType
{
kSTUNAttributeTypeXORMappedAddress = 0x0020,
};
/// RFC 5389 Section 15 STUN Attributes
struct STUNAttribute
{
/// [BE] Attribute Type
uint16_t type;
/// [BE] Payload length of this attribute
uint16_t length;
/// The payload content
uint8_t value[0];
};
#define IPv4_ADDRESS_FAMILY 0x01;
#define IPv6_ADDRESS_FAMILY 0x02;
/// RFC 5389 Section 15.2 XOR-MAPPED-ADDRESS
struct STUNXORMappedIPv4Address
{
/// [BE] Reserved, Zeroed
uint8_t reserved;
/// [BE] The address family
uint8_t family;
/// [BE] The external port
uint16_t port;
/// [BE] The external IPv4 address
uint32_t address;
};
///
/// Resolve the address of a STUN server
///
/// @param address The address of a STUN server
/// @param result The IPv4 address of the STUN server of interest
/// @return `true` on success, `false` otherwise.
///
static bool resolveSTUNServerAddress(const char* address, struct in_addr* result)
{
// A list of resolved IPv4 addresses
struct addrinfo* results = NULL;
// Initialize the hints
const struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM };
// Guard: Resolve the server address
if (getaddrinfo(address, NULL, &hints, &results) != 0)
{
return false;
}
// Guard: Retrieve the result
if (results == NULL)
{
return false;
}
// Read the first socket address in the list
*result = ((struct sockaddr_in*) results->ai_addr)->sin_addr;
// Release the linked list
freeaddrinfo(results);
return true;
}
///
/// Get the external IPv4 address
///
/// @param server A STUN server
/// @param client A non-null STUN client that stores the IPv4 address and port number on return
/// @param timeout Specify the timeout in seconds waiting for the response message
/// @return `kReturnSuccess` on success;
/// `kReturnSocketError` if failed to create the socket;
/// `kReturnSocketError` if failed to set the reception timeout for the socket;
/// `kReturnSocketError` if failed to bind the socket;
/// `kReturnResolverError` if failed to resolve the address of the given STUN server;
/// `kReturnSendError` if failed to send the request message to the given STUN server;
/// `kReturnReceiveError` if failed to read the response message from the socket;
/// `kReturnReceiveError` if failed to parse the response message returned by the given STUN server.
///
int getPublicIPv4Address(struct STUNServer server, struct STUNClient* client, uint32_t timeout)
{
// Guard: Create a UDP socket
const int socketd = socket(AF_INET, SOCK_DGRAM, 0);
if (socketd < 0)
{
return kReturnSocketError;
}
// Guard: Set the reception timeout for the socket
const struct timeval tv = {timeout, 0};
if (setsockopt(socketd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval)) != 0)
{
close(socketd);
return kReturnSocketError;
}
// Initialize the local socket address
struct sockaddr_in localAddress =
{
.sin_family = AF_INET,
.sin_addr.s_addr = INADDR_ANY,
.sin_port = htons(0)
};
// Guard: Bind the socket
if (bind(socketd, (struct sockaddr*) &localAddress, sizeof(struct sockaddr_in)) < 0)
{
close(socketd);
return kReturnSocketError;
}
// Initialize the remote socket address
struct sockaddr_in remoteAddress =
{
.sin_family = AF_INET,
.sin_port = htons(server.port)
};
// Guard: Resolve the STUN server address
if (!resolveSTUNServerAddress(server.address, &remoteAddress.sin_addr))
{
close(socketd);
return kReturnResolverError;
}
// Construct a STUN binding request
srand((uint32_t) time(NULL));
const struct STUNMessageHeader request =
{
.type = htons(kSTUNMessageTypeBindingRequest),
.length = htons(0),
.cookie = htonl(kSTUNMessageCookie),
.identifier[0] = rand(),
.identifier[1] = rand(),
.identifier[2] = rand(),
};
// Guard: Send the request
if (sendto(socketd, &request, sizeof(struct STUNMessageHeader), 0, (struct sockaddr*) &remoteAddress, sizeof(struct sockaddr_in)) != sizeof(struct STUNMessageHeader))
{
close(socketd);
return kReturnSendError;
}
// Guard: Read the response
uint8_t buffer[512] = {0};
const ssize_t length = read(socketd, buffer, sizeof(buffer));
if (length < 0)
{
close(socketd);
return kReturnReceiveError;
}
// Guard: Validate the type in the response message header
const struct STUNMessageHeader* response = (struct STUNMessageHeader*) buffer;
if (response->type != htons(kSTUNMessageTypeBindingResponse))
{
close(socketd);
return kReturnReceiveError;
}
// Guard: Validate the identifier in the response message header
if (memcmp(request.identifier, response->identifier, sizeof(request.identifier)) != 0)
{
close(socketd);
return kReturnReceiveError;
}
// Parse the response message content
uint8_t* current = buffer + sizeof(struct STUNMessageHeader);
while (current < buffer + length)
{
// The response message contains a collection of attributes (TLVs)
const struct STUNAttribute* attribute = (struct STUNAttribute*) current;
current += sizeof(struct STUNAttribute) + ntohs(attribute->length);
// Guard: Look for the attribute that contains an XOR-mapped address
if (attribute->type != ntohs(kSTUNAttributeTypeXORMappedAddress))
{
continue;
}
// Found the attribute that contains an XOR-mapped address
const struct STUNXORMappedIPv4Address* content = (struct STUNXORMappedIPv4Address*) attribute->value;
client->address = ntohl(content->address) ^ kSTUNMessageCookie;
client->port = ntohs(content->port) ^ (kSTUNMessageCookie >> 16);
close(socketd);
return kReturnSuccess;
}
close(socketd);
return kReturnReceiveError;
}