1

ここに、 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;
}

(1秒に注意してくださいtimeout。)

通信の奇妙な振る舞い以外は問題ありません。初めて動作するか、何度も何度タイムアウトし続け、成功しないようです。

修正する方法は、単にもう一度実行することで、機能する可能性がありますが、勝手に機能しない理由がわかりません。

任意の入力をいただければ幸いです!

4

5 に答える 5

3

ホストhl2master.steampowered.comは、次の3つのIPアドレスに解決されます。

syzdek@blackenhawk$ dig +short hl2master.steampowered.com
63.234.149.83
63.234.149.90
72.165.61.153
syzdek@blackenhawk$

3つのIPアドレスのうち2つはクエリに応答していますが、3つ目は応答していません。

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()によって返された各アドレスを2回試行してから、次に返されるIPアドレスに移動します。

于 2011-10-20T16:38:32.967 に答える
3

ただの大げさな推測:おそらくrecvfromがサーバー引数を操作していて、次の送信のアドレスが正しくありませんか?別のstructsockaddrを渡してみてください。

strace出力が役立つ可能性があります。

于 2011-10-20T16:06:09.747 に答える
3

実行可能なコード全体を投稿したので、cdleonardが提案したようにstraceを使用して実行してみました。これは、プログラムが実行されるたびにgethostbynameforの呼び出しが2つの異なるアドレスのいずれか(または)を返すことをすぐに示しています。最初のアドレスが返されると正常に機能しますが、2番目のアドレスはタイムアウトで失敗します。hl2master.steampowered.com63.234.149.8372.165.61.153

したがって、問題はDNS上のサーバーに2つのアドレスがあるようですが、そのうちの1つは実際には機能しません。

常に最初のアドレスに送信するのではなく、gethostbyaddrによって返されるh_addr_listを確認し、ループ内で各アドレスを順番に確認することをお勧めします。

于 2011-10-20T16:25:02.910 に答える
1

EAGAIN「リソースが一時的に利用できません」という呼び出しから取得している場合は、DNSが渡したホスト名に対して別のIPを(ラウンドロビン方式で)提供する可能性がある場合に備えて、recvfrom()再呼び出しを試みることができます。gethostbyname()

これが事実であり、DNSによって返されるアドレスの少なくとも1つに到達できない場合、あなたはまさにあなたが直面している振る舞いをするでしょう。

注:gethostbyname()静的データを返す可能性があるため、結果を参照するだけでなく、結果をコピーすることをお勧めします。

于 2011-10-20T16:24:18.393 に答える
0

gethostbyname()1つのIPアドレスのみを返すの代わりに、すべてのIPアドレスgetaddrinfo()を提供する、およびサポートされているすべてのアドレスファミリを使用して試すことができます。

仕組みは次のとおりです。

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

コードにはまだ1つまたは他の欠陥がありますが(今までは、ソケットを閉じることはありません。代わりにバッファオーバーフローの脆弱master性があります)、動作方法を示しています。strcpy()strncpy()

于 2011-10-20T21:13:16.323 に答える