I have some code here which queries the Steam master servers to get a list of IPs of game servers:
#define _BSD_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
struct timeval timeout = {1, 0};
char master[256];
char reply[1500];
uint16_t port;
uint8_t query[] = {0x31, 0x03, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e,
0x30, 0x3a, 0x30, 0x00, 0x00};
uint8_t replyHeader[] = {0xff, 0xff, 0xff, 0xff, 0x66, 0x0a};
int gotResponse = 0;
int bytesRead = 0;
int verbosity = 0;
int main(int argc, char** argv)
{
strcpy(master, "hl2master.steampowered.com");
port = 27011;
int opt;
while ((opt = getopt(argc, argv, "s:p:v")) != -1)
{
switch (opt)
{
case 's':
strcpy(master, optarg);
break;
case 'p':
port = atoi(optarg);
break;
case 'v':
verbosity++;
break;
}
}
int sockFD;
struct sockaddr_in server;
struct hostent* hostInfo;
sockFD = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockFD == -1)
{
perror("socket");
exit(EXIT_FAILURE);
}
if ((setsockopt(sockFD, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)))
!= 0)
{
perror("setsockopt");
exit(EXIT_FAILURE);
}
if ((setsockopt(sockFD, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)))
!= 0)
{
perror("setsockopt");
exit(EXIT_FAILURE);
}
server.sin_family = AF_INET;
server.sin_port = htons(port);
hostInfo = gethostbyname(master);
if (hostInfo == NULL)
{
fprintf(stderr, "Unknown host %s\n", master);
exit(EXIT_FAILURE);
}
server.sin_addr = *(struct in_addr*) hostInfo->h_addr_list[0];
while (gotResponse == 0)
{
if ((sendto(sockFD, query, 15, 0, (struct sockaddr*) &server,
sizeof(server))) == -1)
{
perror("sendto");
exit(EXIT_FAILURE);
}
socklen_t serverSize = sizeof(server);
if ((bytesRead = recvfrom(sockFD, reply, 1500, 0,
(struct sockaddr*) &server, &serverSize)) == -1)
{
if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
{
fprintf(stderr, "TIMEOUT\n");
}
else
{
perror("recvfrom");
exit(EXIT_FAILURE);
}
}
else
gotResponse = 1;
}
if ((close(sockFD)) == -1)
{
perror("close");
exit(EXIT_FAILURE);
}
if ((strncmp(reply, replyHeader, 6)) != 0)
{
fprintf(stderr, "Bad reply from master server\n");
exit(EXIT_FAILURE);
}
uint32_t i = 6;
while (i < bytesRead)
{
if (verbosity > 0)
fprintf(stderr, "%u <= %d\n", i, bytesRead);
uint8_t ip[4] = {reply[i], reply[i + 1], reply[i + 2], reply[i + 3]};
printf("%hu.%hu.%hu.%hu:", ip[0], ip[1], ip[2], ip[3]);
uint16_t thisPort = reply[i + 4] + (reply[i + 5] << 8);
printf("%hu\n", ntohs(thisPort));
i += 6;
}
return EXIT_SUCCESS;
}
(Note the one second timeout
.)
It is fine other than some odd behaviour with the communication. It seems to either work first time or continually timeout again, and again, and never succeed.
The way to fix is to simply run it again and it may work, but I don't understand the reason for it to arbitrarily not work.
Any input would be appreciated!
The host hl2master.steampowered.com resolves to three IP addresses:
syzdek@blackenhawk$ dig +short hl2master.steampowered.com
63.234.149.83
63.234.149.90
72.165.61.153
syzdek@blackenhawk$
Two of the three IP address are responding to queries, the third is not:
syzdek@blackenhawk$ ./a.out -s 63.234.149.83 |head -2
66.189.187.173:27012
216.6.229.173:27015
syzdek@blackenhawk$ ./a.out -s 63.234.149.90 |head -2
66.189.187.173:27012
216.6.229.173:27015
syzdek@blackenhawk$ ./a.out -s 72.165.61.153
recvfrom: TIMEOUT: Resource temporarily unavailable
recvfrom: TIMEOUT: Resource temporarily unavailable
^C
syzdek@blackenhawk$
Small note, I changed fprintf(stderr, "TIMEOUT\n");
to perror("recvfrom: TIMEOUT");
in the course of trying your code.
Maybe try a using a different server after the timeout:
int retryCount = 0;
while (gotResponse == 0)
{
// verify that next address exists
if (hostInfo->h_addr_list[retryCount/2] == NULL)
{
fprintf(stderr, "All servers are not responding.");
exit(EXIT_FAILURE);
};
// Attempt each address twice before moving to next IP address
server.sin_addr = *(struct in_addr*) hostInfo->h_addr_list[retryCount/2];
retryCount++;
if ((sendto(sockFD, query, 15, 0, (struct sockaddr*) &server,
sizeof(server))) == -1)
{
perror("sendto");
exit(EXIT_FAILURE);
}
/ * rest of code */
The above edits will try each address returned by gethostbyame() twice before moving to the next returned IP address.