简体   繁体   English

这个数据报套接字有什么问题?

[英]What's wrong with this datagram socket?

I have some code here which queries the Steam master servers to get a list of IPs of game servers: 我这里有一些代码可以查询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;
}

(Note the one second timeout .) (请注意一秒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! 任何输入将不胜感激!

Just a wild guess: maybe recvfrom is mangling the server argument, and the following sends have an incorrect address? 只是一个疯狂的猜测:也许recvfrom正在处理服务器参数,并且以下发送的地址有误? Try to pass a separate struct sockaddr. 尝试传递一个单独的struct sockaddr。

strace output could help. strace输出可能会有所帮助。

Since you posted the entire runnable code, I tried running it with strace as cdleonard suggested. 由于您发布了完整的可运行代码,因此我按照cdleonard的建议尝试使用strace运行它。 That immediately show that the gethostbyname call for for hl2master.steampowered.com returns one of two different addresses each time the program is run -- either 63.234.149.83 or 72.165.61.153 -- and when the first address is returned it works fine, whereas the second address fails with the timeouts. 这立即表明,每次运行程序时,对hl2master.steampowered.comgethostbyname调用hl2master.steampowered.com返回两个不同的地址之一63.234.149.8372.165.61.153当返回第一个地址时,它工作正常,而第二个地址因超时而失败。

So the issue seems to be that there's two address for the server on DNS but one of them doesn't really work. 因此,问题似乎在于DNS上有两个用于服务器的地址,但是其中一个并没有真正起作用。

I would suggest checking through the h_addr_list returned by gethostbyaddr and going through each address in turn in your loop, rather than just always sending to the first address. 我建议检查一下gethostbyaddr返回的h_addr_list并依次遍历循环中的每个地址,而不是总是发送到第一个地址。

The host hl2master.steampowered.com resolves to three IP addresses: 主机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$

Two of the three IP address are responding to queries, the third is not: 三个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$

Small note, I changed fprintf(stderr, "TIMEOUT\\n"); 小提示,我更改了fprintf(stderr, "TIMEOUT\\n"); to perror("recvfrom: TIMEOUT"); 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. 上面的编辑将尝试两次gethostbyame()返回的每个地址,然后再移至下一个返回的IP地址。

In case you are getting EAGAIN from your call to recvfrom() that is 'Resouce temporarily unavailable' you may try to re-call gethostbyname() , just in case the DNS might give you a different IP for that hostname you are passing (in a round-robin way). 如果您从对recvfrom()调用中获得EAGAIN ,即“暂时无法使用”,则可以尝试重新调用gethostbyname() ,以防DNS可能为您传递的主机名提供不同的IP(在循环方式)。

If this is the case and at least one of the addresses returned by the DNS is not reachable you will have exactly the behavior you are facing. 如果是这种情况,并且DNS返回的至少一个地址不可达,您将完全遇到您遇到的问题。

Just as a note: gethostbyname() may return static data, so it's a good idea to copy the result and not just reference it. 就像一个注释: gethostbyname()可能返回静态数据,因此复制结果而不是仅引用它是一个好主意。

Instead of gethostbyname() , which returns only one IP address, you could try with getaddrinfo() , which gives you all of them, and even with all supported address families. 您可以尝试使用getaddrinfo()来获取所有这些地址,甚至使用所有受支持的地址系列,而不是只返回一个IP地址的gethostbyname()

Here it is how it works: 它是这样工作的:

    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;
}

While the code still has the one or other flaw (till now, it never closes the sockets; buffer overflow vulnerability in master due to strcpy() instead of strncpy() ), it shows you how things work. 尽管代码仍然存在一个或另一个缺陷(到现在为止,它永远不会关闭套接字;由于strcpy()而不是strncpy() ), master缓冲区溢出漏洞),它向您展示了事情的工作原理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM