| 1 | /* |
| 2 | * Note: the code is extracted from SuperTuxKart: |
| 3 | * https://github.com/supertuxkart/stk-code |
| 4 | */ |
| 5 | |
| 6 | #include <cstdio> |
| 7 | |
| 8 | #include <stdlib.h> |
| 9 | #include <string.h> |
| 10 | |
| 11 | #include <sys/types.h> |
| 12 | #include <sys/socket.h> |
| 13 | #include <netdb.h> |
| 14 | |
| 15 | #include <assert.h> |
| 16 | |
| 17 | #include <enet/enet.h> |
| 18 | |
| 19 | #include <string> |
| 20 | #include <vector> |
| 21 | |
| 22 | ENetHost* m_transaction_host; |
| 23 | unsigned int m_stun_server_ip; |
| 24 | const uint32_t m_stun_magic_cookie = 0x2112A442; |
| 25 | uint8_t m_stun_tansaction_id[12]; |
| 26 | |
| 27 | // Discovered STUN endpoint |
| 28 | uint32_t m_ip; |
| 29 | uint16_t m_port; |
| 30 | |
| 31 | void addUInt16(std::vector<uint8_t>& m_buffer, const uint16_t value) |
| 32 | { |
| 33 | m_buffer.push_back((value >> 8) & 0xff); |
| 34 | m_buffer.push_back(value & 0xff); |
| 35 | } // addUInt16 |
| 36 | |
| 37 | // ------------------------------------------------------------------------ |
| 38 | /** Adds unsigned 32 bit integer. */ |
| 39 | void addUInt32(std::vector<uint8_t>& m_buffer, const uint32_t& value) |
| 40 | { |
| 41 | m_buffer.push_back((value >> 24) & 0xff); |
| 42 | m_buffer.push_back((value >> 16) & 0xff); |
| 43 | m_buffer.push_back((value >> 8) & 0xff); |
| 44 | m_buffer.push_back( value & 0xff); |
| 45 | } // addUInt32 |
| 46 | |
| 47 | template<typename T, size_t n> |
| 48 | T getFromBuffer(std::vector<uint8_t> m_buffer, int& m_current_offset) |
| 49 | { |
| 50 | int a = n; |
| 51 | T result = 0; |
| 52 | m_current_offset += n; |
| 53 | int offset = m_current_offset -1; |
| 54 | while (a--) |
| 55 | { |
| 56 | result <<= 8; // offset one byte |
| 57 | // add the data to result |
| 58 | result += m_buffer[offset - a]; |
| 59 | } |
| 60 | return result; |
| 61 | } |
| 62 | |
| 63 | // ---------------------------------------------------------------------------- |
| 64 | /** Creates a STUN request and sends it to a STUN server. |
| 65 | * See https://tools.ietf.org/html/rfc5389#section-6 |
| 66 | * for details on the message structure. |
| 67 | * The request is send through m_transaction_host, from which the answer |
| 68 | * will be retrieved by parseStunResponse() |
| 69 | */ |
| 70 | void createStunRequest() |
| 71 | { |
| 72 | // TODO: make STUN server configurable |
| 73 | const char* server_name = "stun1.voiceeclipse.net"; |
| 74 | printf("GetPublicAddress: Using STUN server %s\n", server_name); |
| 75 | |
| 76 | struct addrinfo hints, *res; |
| 77 | |
| 78 | memset(&hints, 0, sizeof hints); |
| 79 | hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version |
| 80 | hints.ai_socktype = SOCK_STREAM; |
| 81 | |
| 82 | // Resolve the stun server name so we can send it a STUN request |
| 83 | int status = getaddrinfo(server_name, NULL, &hints, &res); |
| 84 | if (status != 0) |
| 85 | { |
| 86 | printf("GetPublicAddress: Error in getaddrinfo: %s\n", |
| 87 | gai_strerror(status)); |
| 88 | return; |
| 89 | } |
| 90 | // documentation says it points to "one or more addrinfo structures" |
| 91 | assert(res != NULL); |
| 92 | struct sockaddr_in* current_interface = (struct sockaddr_in*)(res->ai_addr); |
| 93 | m_stun_server_ip = ntohl(current_interface->sin_addr.s_addr); |
| 94 | |
| 95 | // Create a new socket for the stun server. |
| 96 | m_transaction_host = enet_host_create(NULL, 1, 1, 0, 0); |
| 97 | if (m_transaction_host == NULL) { |
| 98 | printf("Failed to create enet host"); |
| 99 | return; |
| 100 | } |
| 101 | |
| 102 | // Assemble the message for the stun server |
| 103 | std::vector<uint8_t> m_buffer; |
| 104 | |
| 105 | // bytes 0-1: the type of the message |
| 106 | // bytes 2-3: message length added to header (attributes) |
| 107 | uint16_t message_type = 0x0001; // binding request |
| 108 | uint16_t message_length = 0x0000; |
| 109 | addUInt16(m_buffer, message_type); |
| 110 | addUInt16(m_buffer, message_length); |
| 111 | addUInt32(m_buffer, 0x2112A442); |
| 112 | // bytes 8-19: the transaction id |
| 113 | for (int i = 0; i < 12; i++) |
| 114 | { |
| 115 | uint8_t random_byte = rand() % 256; |
| 116 | m_buffer.push_back(random_byte); |
| 117 | m_stun_tansaction_id[i] = random_byte; |
| 118 | } |
| 119 | //m_buffer.push_back(0); -- this breaks STUN message |
| 120 | |
| 121 | static const int m_stun_server_port = 3478; |
| 122 | |
| 123 | // sendRawPacket |
| 124 | struct sockaddr_in to; |
| 125 | int to_len = sizeof(to); |
| 126 | memset(&to,0,to_len); |
| 127 | |
| 128 | to.sin_family = AF_INET; |
| 129 | to.sin_port = htons(m_stun_server_port); |
| 130 | to.sin_addr.s_addr = htonl(m_stun_server_ip); |
| 131 | |
| 132 | printf("GetPublicAddress: Sending STUN request to: %d.%d.%d.%d:%d\n", |
| 133 | ((m_stun_server_ip >> 24) & 0xff), ((m_stun_server_ip >> 16) & 0xff), |
| 134 | ((m_stun_server_ip >> 8) & 0xff), ((m_stun_server_ip >> 0) & 0xff), |
| 135 | m_stun_server_port); |
| 136 | |
| 137 | int send_result = sendto(m_transaction_host->socket, (char*)(m_buffer.data()), (int)m_buffer.size(), 0, |
| 138 | (sockaddr*)&to, to_len); |
| 139 | printf("GetPublicAddress: sendto result: %d\n", send_result); |
| 140 | |
| 141 | freeaddrinfo(res); |
| 142 | } // createStunRequest |
| 143 | |
| 144 | // ---------------------------------------------------------------------------- |
| 145 | /** |
| 146 | * Gets the response from the STUN server, checks it for its validity and |
| 147 | * then parses the answer into address and port |
| 148 | * \return "" if the address could be parsed or an error message |
| 149 | */ |
| 150 | std::string parseStunResponse() |
| 151 | { |
| 152 | // TransportAddress sender; |
| 153 | const int LEN = 2048; |
| 154 | char buffer[LEN]; |
| 155 | |
| 156 | |
| 157 | // receiveRawPacket |
| 158 | // int len = m_transaction_host->receiveRawPacket(buffer, LEN, &sender, 2000); |
| 159 | int max_tries = 2000; |
| 160 | |
| 161 | memset(buffer, 0, LEN); |
| 162 | |
| 163 | struct sockaddr_in addr; |
| 164 | socklen_t from_len = sizeof(addr); |
| 165 | |
| 166 | int err; |
| 167 | int len = recvfrom(m_transaction_host->socket, buffer, LEN, 0, |
| 168 | (struct sockaddr*)(&addr), &from_len); |
| 169 | |
| 170 | int count = 0; |
| 171 | // wait to receive the message because enet sockets are non-blocking |
| 172 | while(len < 0 && (count<max_tries || max_tries==-1) ) |
| 173 | { |
| 174 | count++; |
| 175 | usleep(1000); |
| 176 | len = recvfrom(m_transaction_host->socket, buffer, LEN, 0, |
| 177 | (struct sockaddr*)(&addr), &from_len); |
| 178 | } |
| 179 | if (len == -1) { |
| 180 | err = errno; |
| 181 | } |
| 182 | printf("GetPublicAddress: recvfrom result: %d\n", len); |
| 183 | if (len == -1) { |
| 184 | printf("GetPublicAddress: recvfrom error: %d\n", err); |
| 185 | } |
| 186 | |
| 187 | |
| 188 | // No message received |
| 189 | if (len < 0) |
| 190 | return "No message received"; |
| 191 | |
| 192 | uint32_t sender_ip = ntohl((uint32_t)(addr.sin_addr.s_addr)); |
| 193 | uint16_t sender_port = ntohs(addr.sin_port); |
| 194 | |
| 195 | |
| 196 | if(sender_ip != m_stun_server_ip) |
| 197 | { |
| 198 | printf("GetPublicAddress: Received stun response from different address: %d:%d (%d.%d.%d.%d:%d) %s\n", |
| 199 | addr.sin_addr.s_addr, addr.sin_port, |
| 200 | ((sender_ip >> 24) & 0xff), ((sender_ip >> 16) & 0xff), |
| 201 | ((sender_ip >> 8) & 0xff), ((sender_ip >> 0) & 0xff), |
| 202 | sender_port, buffer); |
| 203 | } |
| 204 | |
| 205 | if (len<0) |
| 206 | return "STUN response contains no data at all"; |
| 207 | |
| 208 | // Convert to network string. |
| 209 | // NetworkString datas((uint8_t*)buffer, len); |
| 210 | std::vector<uint8_t> m_buffer; |
| 211 | int m_current_offset; |
| 212 | |
| 213 | m_buffer.resize(len); |
| 214 | memcpy(m_buffer.data(), (uint8_t*)buffer, len); |
| 215 | |
| 216 | m_current_offset = 0; |
| 217 | // m_current_offset = 5; // ignore type and token -- this breaks STUN response processing |
| 218 | |
| 219 | // check that the stun response is a response, contains the magic cookie |
| 220 | // and the transaction ID |
| 221 | if (getFromBuffer<uint16_t, 2>(m_buffer, m_current_offset) != 0x0101) |
| 222 | return "STUN response has incorrect type"; |
| 223 | int message_size = getFromBuffer<uint16_t, 2>(m_buffer, m_current_offset); |
| 224 | if (getFromBuffer<uint32_t, 4>(m_buffer, m_current_offset) != m_stun_magic_cookie) |
| 225 | { |
| 226 | return "STUN response doesn't contain the magic cookie"; |
| 227 | } |
| 228 | |
| 229 | for (int i = 0; i < 12; i++) |
| 230 | { |
| 231 | if (m_buffer[m_current_offset++] != m_stun_tansaction_id[i]) |
| 232 | return "STUN response doesn't contain the transaction ID"; |
| 233 | } |
| 234 | |
| 235 | printf("GetPublicAddress: The STUN server responded with a valid answer\n"); |
| 236 | |
| 237 | // The stun message is valid, so we parse it now: |
| 238 | if (message_size == 0) |
| 239 | return "STUN response does not contain any information."; |
| 240 | if (message_size < 4) // cannot even read the size |
| 241 | return "STUN response is too short."; |
| 242 | |
| 243 | // Those are the port and the address to be detected |
| 244 | |
| 245 | int pos = 20; |
| 246 | while (true) |
| 247 | { |
| 248 | int type = getFromBuffer<uint16_t, 2>(m_buffer, m_current_offset); |
| 249 | int size = getFromBuffer<uint16_t, 2>(m_buffer, m_current_offset); |
| 250 | if (type == 0 || type == 1) |
| 251 | { |
| 252 | assert(size == 8); |
| 253 | m_current_offset++; // skip 1 byte |
| 254 | assert(m_buffer[m_current_offset++] == 0x01); // Family IPv4 only |
| 255 | m_port = getFromBuffer<uint16_t, 2>(m_buffer, m_current_offset); |
| 256 | m_ip = getFromBuffer<uint32_t, 4>(m_buffer, m_current_offset); |
| 257 | // finished parsing, we know our public transport address |
| 258 | printf("GetPublicAddress: The public address has been found: %d.%d.%d.%d:%d\n", |
| 259 | ((ip >> 24) & 0xff), ((ip >> 16) & 0xff), |
| 260 | ((ip >> 8) & 0xff), ((ip >> 0) & 0xff), |
| 261 | port); |
| 262 | break; |
| 263 | } // type = 0 or 1 |
| 264 | // datas.skip(4 + size); |
| 265 | m_current_offset += 4 + size; |
| 266 | assert(m_current_offset >=0 && |
| 267 | m_current_offset < (int)m_buffer.size()); |
| 268 | |
| 269 | message_size -= 4 + size; |
| 270 | if (message_size == 0) |
| 271 | return "STUN response is invalid."; |
| 272 | if (message_size < 4) // cannot even read the size |
| 273 | return "STUN response is invalid."; |
| 274 | } // while true |
| 275 | |
| 276 | return ""; |
| 277 | } // parseStunResponse |
| 278 | |
| 279 | JS::Value FindStunEndpoint(ScriptInterface& scriptInterface, int port) |
| 280 | { |
| 281 | createStunRequest(); |
| 282 | std::string parse_result = parseStunResponse(); |
| 283 | if (!parse_result.empty()) |
| 284 | printf("Parse error: %s\n", parse_result.c_str()); |
| 285 | |
| 286 | |
| 287 | JSContext* cx = scriptInterface.GetContext(); |
| 288 | JSAutoRequest rq(cx); |
| 289 | |
| 290 | JS::RootedValue stunEndpoint(cx); |
| 291 | scriptInterface.Eval("({})", &stunEndpoint); |
| 292 | scriptInterface.SetProperty(stunEndpoint, "ip", m_ip); |
| 293 | scriptInterface.SetProperty(stunEndpoint, "port", m_port); |
| 294 | return stunEndpoint; |
| 295 | } |
| 296 | |