Last active
August 23, 2024 13:14
-
-
Save jyaif/e0db3a680443730c05ca36be26f22c93 to your computer and use it in GitHub Desktop.
A simple stun client in C++ (posix only)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Oh i didnt saw that thank you for your fast reply. I thought it was under STUN Attributes.