2011-10-20 7 views
1

私はゲームサーバーのIPアドレスのリストを取得するにはSteamマスターサーバーに照会し、ここでいくつかのコードがあります:それはいくつかの奇妙な行動以外の罰金ですこのデータグラムソケットには何が問題なのですか?

#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に注意してください。)

をコミュニケーションと一緒に。 が最初にまたはのいずれかの連続したタイムアウトになり、再びになると思われます。

修正する方法は、単にもう一度実行するだけですが、うまくいく可能性がありますが、それがうまく動作しない理由はわかりません。

すべての入力をいただければ幸いです!

答えて

3

ホストhl2master.steampowered.com解決3へのIPアドレス:3つのIPアドレスの

[email protected]$ dig +short hl2master.steampowered.com 
63.234.149.83 
63.234.149.90 
72.165.61.153 
[email protected]$ 

2つのクエリに応答している、第三はありません:

[email protected]$ ./a.out -s 63.234.149.83 |head -2 
66.189.187.173:27012 
216.6.229.173:27015 
[email protected]$ ./a.out -s 63.234.149.90 |head -2 
66.189.187.173:27012 
216.6.229.173:27015 
[email protected]$ ./a.out -s 72.165.61.153 
recvfrom: TIMEOUT: Resource temporarily unavailable 
recvfrom: TIMEOUT: Resource temporarily unavailable 
^C 
[email protected]$ 

小メモ、コードを試しているうちに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 */ 

上記の編集は次の返されたIPアドレスに移動する前に2回)gethostbyame(によって返された各アドレスを試みます。

3

おそらくrecvfromがサーバー引数をマングリングしていますが、次の送信者が間違ったアドレスを持っていますか?別のstruct sockaddrを渡してみてください。

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

+0

はい、それは奇妙です。 'server'のメンバのすべての代入を' while'ループの先頭に移動すると、各反復が再割り当てされるので、タイムアウトが少なくとも1回は発生したように見えます。 'recvfrom'がバッファ以外の何かを変更するのはなぜですか? – Doddy

+0

@panicサーバがサーバポート以外のポートからの応答を送信している可能性があります(UDPでは一般的ではありません)。その場合、あなたはそのポートを拾ってそれに送ろうとします。 – nos

+1

gethostbyname呼び出しを他の人が示唆しているように移動したために、おそらく動作しますか? – cdleonard

1

あなたが(DNSはあなたが渡しているそのホスト名のためにあなたに別のIPを与える可能性があるだけの場合には、再呼び出しgethostbyname()しようとする場合がありますが、「Resouce一時的に利用できない」ですrecvfrom()までお電話からEAGAINを取得している場合にはラウンドロビン方式)。

この場合、DNSによって返されたアドレスの少なくとも1つに到達できない場合は、直面している動作とまったく同じです。

メモと同様に:gethostbyname()は静的なデータを返すことがあるので、結果をコピーして参照するのではなく、コピーすることをお勧めします。

3

runnableコード全体を投稿したので、私はstraceでcdleonardを実行してみました。これは、がhl2master.steampowered.comを呼び出すと、プログラムが実行されるたびに2つの異なるアドレスの1つ、つまり63.234.149.83または72.165.61.153が返され、最初のアドレスが返されると正常に動作し、2番目のアドレスはタイムアウトに失敗します。

したがって、DNSには2つのアドレスがありますが、そのうちの1つは実際には機能しません。

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

+1

ええ、私が提案したように、h_addr_listをループすることは、単にgethostbynameを呼び出すよりも効率的です。 – alk

0

IPアドレスが1つのみ返されるgethostbyname()の代わりに、getaddrinfo()を試してみてください。getaddrinfo()はすべてのアドレスファミリをサポートします。コードはまだ1つのまたは他の欠陥を持っている

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()に今まで、それはソケットを閉じたことはありません):ここではどのように動作

はそれがありますどのように動作するかを示します。

関連する問題