Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

Parser pakietów [TCP/IP, WinSock]

Ostatnio zmodyfikowano 2017-01-17 14:10
Autor Wiadomość
Rashmistrz
Temat założony przez niniejszego użytkownika
Parser pakietów [TCP/IP, WinSock]
» 2017-01-17 04:05:05
Ostatnio tworzę program do sprawdzania statusu serwera
pewnej gry z użyciem nieoficjalnej dokumentacji protokołu.

  • Jak podejść do "prasowania" pakietów? ;)
    (Szczególnie, że niektóre pola mają zmienną długość.)
  • W jaki sposób zorganizować system buforów
    do wysyłania i odbierania pakietów?
  • Jak ten system buforów powinien działać?
  • Co zrobić w sytuacji, jeśli pakiet wypełnił bufor,
    że nie cały pakiet się "pobrał" lub jak uniknąć takiej sytuacji?
  • Jak ograniczyć kopiowanie pamięci do minimum?
    (Na przykład bezpośrednie działanie na
    buforze podanym do recv i send?)

  • + Opcjonalnie: jak zrobić funkcje zrzutu pakietów?
    (Tak żebym mógł odtworzyć całą sytuację
    po stronie klienta bez obecności serwera,
    nie tracąc przy tym ich kolejności i czasów.)

Napisany już przeze mnie kod źródłowy: (tutejszy parser mi zniekształca kod)
C/C++
// -----------------------------
#include <cstring>
#include <iostream>
#include <winsock2.h>
// -----------------------------
//  0xDEAD00 = 14593280

int main( int argc, char * argv[] ) {
   
    // host
    char * host = "mc-pl.net";
    unsigned short port = 25565;
   
    // WinSockAPI startup
    WSADATA wsaData;
    SOCKADDR_IN saddr;
    SOCKET sock;
   
    WSAStartup( MAKEWORD( 2, 2 ), & wsaData );
    sock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
   
    // resolve hostname
    LPHOSTENT hostEntry = gethostbyname( host );
    if( !hostEntry ) {
        unsigned int addr = inet_addr( host );
        hostEntry = gethostbyaddr(( char * ) & addr, 4, AF_INET );
        if( !hostEntry ) return 0xDEAD01;
       
    } /* can't resolve hostname */
   
   
    saddr.sin_addr.S_un.S_addr
    = *(( int * ) * hostEntry->h_addr_list );
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons( port );
   
    // establishing connection
    if( connect( sock,( sockaddr * ) & saddr, sizeof( sockaddr ) ) == SOCKET_ERROR )
         return 0xDEAD02; /* connection is not established */
   
    // constructing "handshake" packet
    char handshake[] = { 0x14, 0x00, 0xD2, 0x01, 0x0D,
        '1', '5', '1', '.',
        '8', '0', '.',
        '1', '0', '8', '.',
        '1', '5',
        0x63, 0xDD, 0x01 };
    char & hs_lenght = handshake[ 0 ];
   
    // constructing "request" packet
    char request[] = { 0x01, 0x00 };
    char & rq_lenght = request[ 0 ];
   
    // server status request
    char buffer[ 0x10000 ];
    short bytes_sent = 0;
    short bytes_readen = 0;
   
    /* assuming succesful sending and reciving */
    bytes_sent += send( sock, handshake, hs_lenght + 1, 0 );
    bytes_sent += send( sock, request, rq_lenght + 1, 0 );
    bytes_readen += recv( sock, buffer, 0x10000, 0 );
   
    // ping pong
    char ping[ 10 ] = { 0x09, 0x01, /* any 8 bytes */ };
    char pong[ 10 ]; /* pong should be same as ping */
   
    send( sock, ping, 10, 0 );
    recv( sock, pong, 10, 0 );
   
    // analising and displaying results
    char * JSON =( char * ) memchr( buffer, '{', 12 );
    /* 12 = (2 * varint + PacketID) */
   
    if( !memcmp( ping, pong, 10 ) )
    std::cout << JSON << '\n' << '\n'
    << "\tping pong is:\tOK!\n"
    << "\tbytes sent:\t" << bytes_sent << '\n'
         << "\tbytes readen:\t" << bytes_readen << '\n';
    else return 0xDEAD00; /* ping pong failed */
   
    // EOC - end of coding ;P (today's)
    closesocket( sock );
    WSACleanup();
    return 0x0;
}

Ogólne informacje co jest wysyłane i otrzymywane:

Ogólny format pakietów ... : (bez kompresji)

Nazwa pola Typ pola Notki
Długość pakietu VarInt (To pole nie liczy siebie samego (?) )
ID pakietu VarInt nadaje znaczenie następującym po nim danych
Dane pakietu Byte Array znaczenie zależy od ID i trybu/stanu serwera

... i pola poszczególnych z nich:

Handshake (wysyła klient, ID: 0x00)
Nazwa pola Typ pola Notki
Wersja protokołu VarInt serwer powinien akceptować dowolną
Adres Serwera String hostname lub IP
Port Serwera Unsigned Short standardowy to 25565
Tryb/Stan serwera VarInt Następny oczekiwany stan serwera ( 1 do pobrania statusu )

Request (wysyła klient, ID: 0x00) // nie posiada pól

Response (wysyła serwer, ID: 0x00)
Nazwa pola Typ pola Notki
JSON Response String nie kończy się zerem/NULem
(poprzedza go VarInt mówiący o długości pola)

Ping (wysyła klient, ID:0x00)
Nazwa pola Typ pola Notki
Payload Long (64 bity) Dowolna liczba. Najlepiej aktualny Timestamp

Pong (wysyła serwer, ID:0x00) // odpowiedź na Ping
Nazwa pola Typ pola Notki
Payload Long (64 bity) Dokładnie to samo co wysłał klient.

Użyte kodowanie VarInt i VarLong:

Kodowanie jest podobne do tego z Protocol Buffer,
ale jest zupełnie inne od użytego tutaj.

Najbardziej znaczący bit informuje czy następny bajt również należy do naszej liczby.
W ten sposób otrzymujemy grupki po 7dem bitów w kolejności little endian.
Przy maksymalnym użyciu bajtów (VarInt ma 5, a VarLong 10) przycinane są
najbardziej znaczące bity z najbardziej znaczącej grupki tak
by uzyskać 32 bity przy VarInt'cie i 64 bity przy VarLong'y.

Przykładowe VarInt: // z VarLong'ami jest tak samo.
Wartość Hex
0 0x00
1 0x01
2 0x02
127 0x7f
128 0x80 0x01
255 0xff 0x01
2147483647 0xff 0xff 0xff 0xff 0x07
-1 0xff 0xff 0xff 0xff 0x0f
-2147483648 0x80 0x80 0x80 0x80 0x08


Przykładowe hostname serwerów do testów:

mc-pl.net play.hivemc.com  gommehd.net  eu.shotbow.net

Przebieg komunikacji z serwerem:

Hex-dump pakietów jakie powinny zostać wysłane i odebrane od/do
"minecraftpolska.net" [151.80.108.15], aby otrzymać status serwera:

Handshake ( client --> serwer ):
00000000  14 00 d2 01 0d 31 35 31  2e 38 30 2e 31 30 38 2e .....151 .80.108.
00000010  31 35 63 dd 01                                   15c..

Request ( client --> serwer ):
00000015  01 00


Response ( client <-- serwer ):
// środek pakietu można zignorować, najważniejsze jest jego otrzymanie
00000000  c3 6c 00 c0 6c 7b 22 76  65 72 73 69 6f 6e 22 3a .l..l{"v ersion":
// -----  -- -- -- -- -- -- -- --  -- -- -- -- -- -- -- -- -----------------
0000363C  69 73 74 22 3a 5b 5d 7d  7d                      ist":[]} }

Ping ( client --> serwer ):
00000017  09 01 xx xx xx xx xx xx  xx xx                   ........ ..


Pong ( client <-- serwer ):
00003645  09 01 xx xx xx xx xx xx  xx xx                   ........ ..


Tu połączenie jest zamykane.
________________________________________________________

PRE-EDIT: Nie zapominać o linkowaniu ws2_32.dll
z folderu systemowego: "C:\Windows\System32\ws2_32.dll"
P-156527
mokrowski
» 2017-01-17 10:43:07
1. Mi sprawdzały się unia struktur. W zależności od rodzaju pakietu strukturą w unii możesz go "rozebrać" przez dostęp do pola.
2. Oddzielny bufor na nadawane pakiety i oddzielny na przyjmowane. Jeśli będziesz obsługiwał pakiety w wielu wątkach to zdwojone bufory. Jeden jest parsowany a inny wypełniany (ale czy tu naprawdę to potrzebne?).
3. Co do działania patrz punkt 2.
4. Poprawność odebrania pakietu sprawdzić można przez sumę kontrolną, kod CRC, jakieś parzystości/nieparzystości. Zależy czego oczekujesz jeśli chodzi o kontrolę. Natomiast kompletność pakietu obsłużysz przez timeout'y. Na odebranie pakietu masz określony czas i tyle. Po jego upłynięciu uznajesz że pakiet jest niekompletny jeśli nie został odebrany w całości.
5. Co do kopiowania hmm... w klasach trzymać referencje czy wskaźniki i ... przestawiać je na pola bufora :-) Tylko trzeba pamiętać że po jego wyczyszczeniu lub przekazaniu do wypełniania, referencje/wskaźniki w klasie będą nieaktualne :-)

Z samym WinSock nie miałem do czynienia. Z tego co jednak widzę, "pachnie" BSD Sockets :-)
P-156533
j23
» 2017-01-17 12:54:53
Struktury średnio się tutaj nadadzą, bo poszczególne wartości są zmiennej długości (VarInt). Inaczej niż sekwencyjne czytanie kolejnych pól/wartości pakietu tego nie zrobisz (może jakiś strumień były dobry).
P-156534
mokrowski
» 2017-01-17 14:03:15
@j23 E tam.. Aż z ciekawości zerknąłem do implementacji Google Protobuf bo może coś wymyślili innego. Jednak struktury i unie.
https://github.com/protobuf-c​/protobuf-c/blob/master​/protobuf-c/protobuf-c.h okolice 393 i dalej
Podobnie z deskryptorami. Jeśli tylko pojawiają się bufory o zmiennej długości to ew. wskaźnik..
P-156538
j23
» 2017-01-17 14:10:01
@mokrowski, opierałem się na tym, co podał OP. I nie chodzi mi o to, że w ogóle nie da się zastosować struktur, ale przy tego typu układzie danych, gdzie nie ma stałych wielkości pól, użycie struktur może bardziej skomplikować niż ułatwić zadanie.
P-156540
« 1 »
  Strona 1 z 1