-
-
Save jyaif/e0db3a680443730c05ca36be26f22c93 to your computer and use it in GitHub Desktop.
// How to compile: | |
// clang++ -std=c++11 stun.cpp | |
#include <arpa/inet.h> | |
#include <netdb.h> | |
#include <stdio.h> | |
#include <sys/socket.h> | |
#include <unistd.h> | |
#include <array> | |
#include <cstring> | |
#include <string> | |
constexpr uint16_t kBindingRequest = 0x0001; | |
constexpr uint16_t kBindingResponse = 0x0101; | |
constexpr uint16_t kXorMappedAddress = 0x0020; | |
constexpr uint32_t kMagicCookie = 0x2112A442; | |
// https://datatracker.ietf.org/doc/html/rfc5389#section-6 | |
struct __attribute__((packed)) StunRequest { | |
StunRequest() { | |
for (int i = 0; i < transaction_id_.size(); i++) { | |
transaction_id_[i] = rand() % 256; | |
} | |
} | |
const int16_t stun_message_type_ = htons(kBindingRequest); | |
const int16_t message_length_ = htons(0x0000); | |
const int32_t magic_cookie_ = htonl(kMagicCookie); | |
std::array<uint8_t, 12> transaction_id_; | |
}; | |
// https://datatracker.ietf.org/doc/html/rfc5389#section-7 | |
struct __attribute__((packed)) StunResponse { | |
int16_t stun_message_type_; | |
int16_t message_length_; | |
int32_t magic_cookie_; | |
std::array<uint8_t, 12> transaction_id_; | |
std::array<uint8_t, 1000> attributes_; | |
}; | |
struct IPAndPort { | |
std::string ip_; | |
uint16_t port_ = 0; | |
}; | |
// Converts a host name into an IP address. | |
std::string HostnameToIP(std::string const& hostname) { | |
char ip[100]; | |
struct addrinfo hints, *servinfo, *p; | |
struct sockaddr_in* h; | |
int rv; | |
memset(&hints, 0, sizeof(hints)); | |
hints.ai_family = AF_INET; | |
hints.ai_socktype = SOCK_STREAM; | |
if ((rv = getaddrinfo(hostname.c_str(), "http", &hints, &servinfo)) != 0) { | |
return ""; | |
} | |
for (p = servinfo; p != NULL; p = p->ai_next) { | |
h = (struct sockaddr_in*)p->ai_addr; | |
strcpy(ip, inet_ntoa(h->sin_addr)); | |
} | |
freeaddrinfo(servinfo); | |
return std::string(ip); | |
} | |
// Returns true on success | |
bool PerformStunRequest(int socket_fd, | |
std::string const& stun_server_ip, | |
short stun_server_port, | |
short local_port, | |
IPAndPort& ip_and_port) { | |
struct sockaddr_in servaddr; | |
struct sockaddr_in localaddr; | |
StunRequest stun_request; | |
StunResponse stun_response; | |
bzero(&servaddr, sizeof(servaddr)); | |
servaddr.sin_family = AF_INET; | |
inet_pton(AF_INET, stun_server_ip.c_str(), &servaddr.sin_addr); | |
servaddr.sin_port = htons(stun_server_port); | |
bzero(&localaddr, sizeof(localaddr)); | |
localaddr.sin_family = AF_INET; | |
localaddr.sin_port = htons(local_port); | |
int err = bind(socket_fd, (struct sockaddr*)&localaddr, sizeof(localaddr)); | |
if (err < 0) { | |
printf("bind error\n"); | |
return false; | |
} | |
printf("Sending STUN request to %s:%d\n", stun_server_ip.c_str(), | |
stun_server_port); | |
err = sendto(socket_fd, &stun_request, sizeof(stun_request), 0, | |
(struct sockaddr*)&servaddr, sizeof(servaddr)); | |
if (err < 0) { | |
printf("sendto error\n"); | |
return false; | |
} | |
err = recvfrom(socket_fd, &stun_response, sizeof(stun_response), 0, NULL, 0); | |
if (err < 0) { | |
printf("recvfrom error\n"); | |
return false; | |
} | |
if (stun_response.magic_cookie_ != htonl(kMagicCookie)) { | |
printf("magic cookie of response does not match request\n"); | |
return false; | |
} | |
if (stun_response.transaction_id_ != stun_request.transaction_id_) { | |
printf("incorrect transaction id\n"); | |
return false; | |
} | |
if (stun_response.stun_message_type_ != htons(kBindingResponse)) { | |
printf("incorrect message type\n"); | |
return false; | |
} | |
auto const& attributes = stun_response.attributes_; | |
int16_t attributes_length = | |
std::min<int16_t>(htons(stun_response.message_length_), | |
stun_response.attributes_.size()); | |
int i = 0; | |
while (i < attributes_length) { | |
auto attribute_type = htons(*(int16_t*)(&attributes[i])); | |
auto attribute_length = htons(*(int16_t*)(&attributes[i + 2])); | |
if (attribute_type == kXorMappedAddress) { | |
uint16_t port = ntohs(*(uint16_t*)(&attributes[i + 6])); | |
port ^= (kMagicCookie >> 16); | |
std::string ip = std::to_string(attributes[i + 8] ^ ((kMagicCookie & 0xff000000) >> 24)) + "." + | |
std::to_string(attributes[i + 9] ^ ((kMagicCookie & 0x00ff0000) >> 16)) + "." + | |
std::to_string(attributes[i + 10] ^ ((kMagicCookie & 0x0000ff00) >> 8)) + "." + | |
std::to_string(attributes[i + 11] ^ ((kMagicCookie & 0x000000ff) >> 0)) + "."; | |
ip_and_port.ip_ = ip; | |
ip_and_port.port_ = port; | |
return true; | |
} | |
i += (4 + attribute_length); | |
} | |
return false; | |
} | |
int main(int argc, char* argv[]) { | |
const char* stun_server_host_name = "stun2.l.google.com"; | |
std::string ip = HostnameToIP(stun_server_host_name); | |
printf("%s resolved to %s\n", stun_server_host_name, ip.c_str()); | |
IPAndPort ip_and_port; | |
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0); | |
bool stun_request_succesfull = | |
PerformStunRequest(socket_fd, ip, 19305, 9910, ip_and_port); | |
close(socket_fd); | |
if (stun_request_succesfull) { | |
printf("local ip:port = %s:%d\n", ip_and_port.ip_.c_str(), | |
ip_and_port.port_); | |
} else { | |
printf("Failed to perform stun request"); | |
} | |
} |
I have a question, where do you get the attribute types from?
I read the site about it, but I can't find where the signature of the types are.
For example, the constexpr uint16_t kXorMappedAddress = 0x0020;
But very nice work!
See section 18.2:
Comprehension-required range (0x0000-0x7FFF):
0x0000: (Reserved)
0x0001: MAPPED-ADDRESS
0x0002: (Reserved; was RESPONSE-ADDRESS)
0x0003: (Reserved; was CHANGE-ADDRESS)
0x0004: (Reserved; was SOURCE-ADDRESS)
0x0005: (Reserved; was CHANGED-ADDRESS)
0x0006: USERNAME
0x0007: (Reserved; was PASSWORD)
0x0008: MESSAGE-INTEGRITY
0x0009: ERROR-CODE
0x000A: UNKNOWN-ATTRIBUTES
0x000B: (Reserved; was REFLECTED-FROM)
0x0014: REALM
0x0015: NONCE
0x0020: XOR-MAPPED-ADDRESS
See section 18.2:
Comprehension-required range (0x0000-0x7FFF): 0x0000: (Reserved) 0x0001: MAPPED-ADDRESS 0x0002: (Reserved; was RESPONSE-ADDRESS) 0x0003: (Reserved; was CHANGE-ADDRESS) 0x0004: (Reserved; was SOURCE-ADDRESS) 0x0005: (Reserved; was CHANGED-ADDRESS) 0x0006: USERNAME 0x0007: (Reserved; was PASSWORD) 0x0008: MESSAGE-INTEGRITY 0x0009: ERROR-CODE 0x000A: UNKNOWN-ATTRIBUTES 0x000B: (Reserved; was REFLECTED-FROM) 0x0014: REALM 0x0015: NONCE 0x0020: XOR-MAPPED-ADDRESS
Oh i didnt saw that thank you for your fast reply. I thought it was under STUN Attributes.
Fixed the bug and addressed your clean up suggestion. Thank you!