我这里有一些代码可以查询Steam主服务器以获取游戏服务器的IP列表:
#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;
}
(注意一秒timeout
。)
除了沟通的一些奇怪的行为之外,这很好。似乎要么第一次工作或再次连续超时,再次永远不会成功。
修复的方法是简单地再次运行它可能会起作用,但我不明白它随意不起作用的原因。
任何意见都会受到赞赏!
答案 0 :(得分:3)
只是一个疯狂的猜测:也许recvfrom会破坏服务器参数,以下发送的地址不正确?尝试传递一个单独的struct sockaddr。
strace输出可能有所帮助。
答案 1 :(得分:3)
由于您发布了整个可运行代码,我尝试使用strace运行它作为cdleonard建议。这会立即显示gethostbyname
对hl2master.steampowered.com
的调用会在每次运行程序时返回两个不同地址中的一个 - 63.234.149.83
或72.165.61.153
- 以及第一个地址返回它工作正常,而第二个地址失败并带有超时。
所以问题似乎是DNS上的服务器有两个地址,但其中一个地址不起作用。
我建议检查gethostbyaddr返回的h_addr_list并在循环中依次遍历每个地址,而不是总是发送到第一个地址。
答案 2 :(得分:3)
主机 hl2master.steampowered.com 解析为三个IP地址:
syzdek@blackenhawk$ dig +short hl2master.steampowered.com
63.234.149.83
63.234.149.90
72.165.61.153
syzdek@blackenhawk$
三个IP地址中有两个响应查询,第三个不响应:
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$
小注意事项,我在尝试代码的过程中将fprintf(stderr, "TIMEOUT\n");
更改为perror("recvfrom: 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 */
上述编辑将尝试gethostbyame()返回的每个地址两次,然后再转到下一个返回的IP地址。
答案 3 :(得分:1)
如果您从EAGAIN
拨打recvfrom()
即“暂时无法使用”,您可以尝试重新拨打gethostbyname()
,以防DNS可能会给您您传递的主机名的不同IP(以循环方式)。
如果是这种情况,并且至少有一个DNS返回的地址无法访问,那么您将完全具有您所面临的行为。
正如注释:gethostbyname()
可能会返回静态数据,因此最好复制结果,而不仅仅是引用它。
答案 4 :(得分:0)
而不是仅返回一个IP地址的gethostbyname()
,您可以尝试使用getaddrinfo()
,它可以为您提供所有这些IP地址,甚至可以使用所有支持的地址系列。
这是它的工作原理:
int sockFD;
struct hostent* hostInfo;
struct addrinfo hints = {
.ai_socktype = SOCK_DGRAM,
.ai_protocol = IPPROTO_UDP /* MHO redundant*/
};
struct addrinfo * ai_chain, *ai;
int gai_ret = getaddrinfo(master, NULL, &hints, &ai_chain);
if (gai_ret != 0) {
fprintf(stderr, "getaddrinfo: %s", gai_strerror(gai_ret));
exit(EXIT_FAILURE);
}
for (ai = ai_chain; ai; ai = ai->ai_next) {
printf("try %s\n", ai->ai_canonname ? ai->ai_canonname : "");
sockFD = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sockFD == -1)
{
perror("socket");
continue;
}
if ((setsockopt(sockFD, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout))) != 0)
{
perror("setsockopt 1");
continue;
}
if ((setsockopt(sockFD, SOL_SOCKET, SO_SNDTIMEO, &timeout,
sizeof(timeout)))
!= 0)
{
perror("setsockopt 2");
continue;
}
if ((sendto(sockFD, query, 15, 0, ai->ai_addr, ai->ai_addrlen)) == -1)
{
perror("sendto");
continue;
}
struct sockaddr_in6 server;
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");
continue;
}
else
{
perror("recvfrom");
continue;
}
}
else
break;
}
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;
}
虽然代码仍然存在一个或其他缺陷(到现在为止,它永远不会关闭套接字;由于master
而不是strcpy()
导致strncpy()
中的缓冲区溢出漏洞),它显示你是如何运作的。