/* GS natneg client 0.1.2 by Luigi Auriemma e-mail: aluigi@autistici.org web: aluigi.org INTRODUCTION ============ Gamespy natneg is a way used by Gamespy to allow the people behind router and NAT (so those who can't receive connections from internet) to create public servers and at the same time allows clients to query and join these servers. The function in this code is very easy to use and can be implemented in a trasparent way in any program for adding the client-side support to the Gamespy natneg. HOW TO USE ========== Exists only one function to use and it's gsnatneg() which returns 0 if success or a negative value in case of errors. The following are the arguments of the function: int sd the UDP socket you have created in your client, this parameter is required since the master server will create a direct UDP connection between the port used by that socket and the server u8 *gamename the Gamespy name of the game, it's needed since the Gamespy master server needs to locate the IP of the server you want to contact in its big database, the full list of Gamespy gamenames is available here: http://aluigi.org/papers/gslist.cfg u8 *host the hostname or the IP address in text format of the server to contact (like "1.2.3.4" or "host.example.com") in_addr_t ip as above but it's directly the IP in the inet_addr format, is required to choose between the first and this argument u16 port the port of the server to contact *peer a sockaddr_in structure which will be filled with the correct port of the server, because due to the nat negotiation the port with which will be made the communication differs from the one of displayed in the server list. if this parameter is NULL it will not be used EXAMPLES ======== int sd; sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if(gsnatneg(sd, "halor", "1.2.3.4", 0, 2302) < 0) exit(1); // or if(gsnatneg(sd, "halor", NULL, inet_addr("1.2.3.4"), 2302) < 0) exit(1); // if you want you can also terminate and free the gsnatneg resources using: gsnatneg(-1, NULL, NULL, 0, 0); LICENSE ======= Copyright 2008 Luigi Auriemma This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA http://www.gnu.org/licenses/gpl.txt */ #include #include #include #include #include #include #ifdef WIN32 #include #define close closesocket #define sleep Sleep #define ONESEC 1000 #define in_addr_t uint32_t #else #include #include #include #include #include #include #define ONESEC 1 #define stricmp strcasecmp #endif typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; #define BUFFSZ 0xffff #define GSNATNEGPORT 27901 #define GSMSXPORT 28910 #define MAXRETRY 2 #define GOTOQUITX(X) { ret_err = X; goto quit; } int gsnatneg_make_tcp(void); int gsnatneg_make_udp(void); in_addr_t gsnatneg_get_sock_ip_port(int sd, u16 *port, struct sockaddr_in *ret_peer); int gsnatneg_putrr(u8 *data, int len); int gsnatneg_putxx(u8 *data, u32 num, int bits); int gsnatneg_putss(u8 *buff, u8 *data); int gsnatneg_putmm(u8 *buff, u8 *data, int len); int gsnatneg_send(int sd, in_addr_t ip, u16 port, u8 *buff, int len); int gsnatneg_sendto(int sd, in_addr_t ip, u16 port, u8 *buff, int len); int gsnatneg_recv(int sd, u8 *buff); int gsnatneg_tcprecv(int sd, u8 *buff, int len); int gsnatneg_recvfrom(int sd, struct sockaddr_in *peer, u8 *buff, int size); in_addr_t gsnatneg_gsresolv(u8 *gamename, int num); int gsnatneg_timeout(int sock, int secs); in_addr_t gsnatneg_resolv(u8 *host); static int gsnatneg_verbose = 0; int gsnatneg(int sd, u8 *gamename, u8 *host, in_addr_t ip, u16 port, struct sockaddr_in *ret_peer) { static const u8 natnegx[6] = "\xfd\xfc\x1e\x66\x6a\xb2"; struct sockaddr_in peer; static in_addr_t gsmsx = INADDR_NONE, gsnatneg1 = INADDR_NONE, gsnatneg2 = INADDR_NONE, myip = INADDR_NONE; // not needed in_addr_t xip = INADDR_NONE; static u32 seed = 0; static int su = -1, st = -1; int len, retry = MAXRETRY, // in case of packets lost and timed out connections ret_err = -1; u16 myport = 0, xport = 0; static u8 gamenamex[128] = "", *buff = NULL; u8 *p; if((sd <= 0) || !gamename) { // free resources if(buff) { free(buff); buff = NULL; } GOTOQUITX(-1) } if(host) { ip = gsnatneg_resolv(host); if(ip == INADDR_NONE) GOTOQUITX(-2) } else { if(ip == INADDR_NONE) GOTOQUITX(-3) } if(!gamenamex[0] || stricmp(gamename, gamenamex)) { gsmsx = gsnatneg_gsresolv(gamename, 0); // gamename.ms?.gamespy.com if(gsmsx == INADDR_NONE) GOTOQUITX(-4) gsnatneg1 = gsnatneg_gsresolv(gamename, 1); // gamename.natneg1.gamespy.com if(gsnatneg1 == INADDR_NONE) GOTOQUITX(-5) gsnatneg2 = gsnatneg_gsresolv(gamename, 2); // gamename.natneg2.gamespy.com if(gsnatneg2 == INADDR_NONE) GOTOQUITX(-6) strncpy(gamenamex, gamename, sizeof(gamenamex)); gamenamex[sizeof(gamenamex) - 1] = 0; redo_step1: if(st > 0) close(st); st = gsnatneg_make_tcp(); // connect to gamename.ms?.gamespy.com if(st < 0) GOTOQUITX(-7) if(gsnatneg_send(st, gsmsx, GSMSXPORT, NULL, 0) < 0) GOTOQUITX(-8) if(su <= 0) { su = gsnatneg_make_udp(); if(su < 0) GOTOQUITX(-9) } if(!buff) { buff = malloc(BUFFSZ); if(!buff) GOTOQUITX(-10) } p = buff; p += gsnatneg_putxx(p, 0, 8); p += gsnatneg_putxx(p, 1, 8); p += gsnatneg_putxx(p, 3, 8); p += gsnatneg_putxx(p, 0, 32); p += gsnatneg_putss(p, gamename); p += gsnatneg_putss(p, gamename); p += gsnatneg_putrr(p, 8); p += gsnatneg_putss(p, ""); // parameters filter *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 2; if(gsnatneg_send(st, 0, 0, buff, p - buff) < 0) GOTOQUITX(-11) if(!gsnatneg_timeout(st, 5)) { if(recv(st, (void *)buff, BUFFSZ, 0) <= 0) GOTOQUITX(-12) } } seed = time(NULL); // needed p = buff; p += gsnatneg_putxx(p, 2, 8); p += gsnatneg_putxx(p, ip, 32); p += gsnatneg_putxx(p, htons(port), 16); p += gsnatneg_putmm(p, (u8*)natnegx,sizeof(natnegx)); p += gsnatneg_putxx(p, seed, 32); if(gsnatneg_send(st, 0, 0, buff, p - buff) < 0) { if(--retry) goto redo_step1; // in case we were using an old st connection GOTOQUITX(-13) } myip = gsnatneg_get_sock_ip_port(st, NULL, NULL); // probably not needed but it's for compatibility with the protocol retry = MAXRETRY; redo_step2: p = buff; p += gsnatneg_putmm(p, (u8*)natnegx,sizeof(natnegx)); p += gsnatneg_putxx(p, 2, 8); // natneg version p += gsnatneg_putxx(p, 0, 8); // step, buff[12] p += gsnatneg_putxx(p, seed, 32); // id for tracking the reply p += gsnatneg_putxx(p, 0, 8); // natneg number, buff[12] p += gsnatneg_putxx(p, 0, 8); // ??? p += gsnatneg_putxx(p, 1, 8); // ??? p += gsnatneg_putxx(p, myip, 32); // client's IP p += gsnatneg_putxx(p, 0, 16); // client's port, buff + 19 p += gsnatneg_putss(p, gamename); buff[12] = 0; if(gsnatneg_sendto(sd, gsnatneg1, GSNATNEGPORT, buff, p - buff) < 0) GOTOQUITX(-14) buff[12] = 1; if(gsnatneg_sendto(su, gsnatneg1, GSNATNEGPORT, buff, p - buff) < 0) GOTOQUITX(-15) buff[12] = 2; gsnatneg_get_sock_ip_port(sd, &myport, NULL); // retrieve the port assigned to the socket used in the first packet gsnatneg_putxx(buff + 19, htons(myport), 16); if(gsnatneg_sendto(su, gsnatneg2, GSNATNEGPORT, buff, p - buff) < 0) GOTOQUITX(-16) for(;;) { len = gsnatneg_recvfrom(su, &peer, buff, BUFFSZ); if(len < 0) { if(--retry) goto redo_step2; GOTOQUITX(-17) } if((len >= 18) && (buff[7] == 5)) {// && (peer.sin_addr.s_addr == gsnatneg1)) { xip = *(in_addr_t *)(buff + 12); xport = ntohs(*(u16 *)(buff + 16)); if(gsnatneg_verbose) printf(" %s:%hu\n", inet_ntoa(*(struct in_addr *)&xip), xport); break; if((xip == ip) && (xport == port)) break; // not needed } } for(;;) { // wait the packet from the game server len = gsnatneg_recvfrom(sd, &peer, buff, BUFFSZ); if(len < 0) break; if((peer.sin_addr.s_addr == ip) && (peer.sin_port == htons(port))) break; if((peer.sin_addr.s_addr == xip) && (peer.sin_port == htons(xport))) { ip = xip; port = xport; break; } } for(retry = 0; retry < MAXRETRY; retry++) { // be sure that our reply arrives to the game server (max 1 second lost) p = buff; p += gsnatneg_putmm(p, (u8 *)natnegx, sizeof(natnegx)); p += gsnatneg_putxx(p, 2, 8); p += gsnatneg_putxx(p, 7, 8); p += gsnatneg_putxx(p, seed, 32); p += gsnatneg_putxx(p, ip, 32); p += gsnatneg_putxx(p, htons(port), 16); p += gsnatneg_putxx(p, 1, 8); p += gsnatneg_putxx(p, 1, 8); if(gsnatneg_sendto(sd, ip, port, buff, p - buff) < 0) GOTOQUITX(-18) if(gsnatneg_timeout(sd, 1) < 0) break; len = gsnatneg_recvfrom(sd, &peer, buff, BUFFSZ); if(len < 0) break; } if(ret_peer) { ret_peer->sin_addr.s_addr = ip; ret_peer->sin_port = htons(port); ret_peer->sin_family = AF_INET; } return(0); quit: if(su > 0) { close(su); su = -1; } if(st > 0) { close(st); st = -1; } return(ret_err); } int gsnatneg_make_tcp(void) { struct linger ling = {1,1}; int sd; sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(sd < 0) return(-1); setsockopt(sd, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling)); return(sd); } int gsnatneg_make_udp(void) { struct linger ling = {1,1}; int sd; sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if(sd < 0) return(-1); setsockopt(sd, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling)); return(sd); } in_addr_t gsnatneg_get_sock_ip_port(int sd, u16 *port, struct sockaddr_in *ret_peer) { struct sockaddr_in peer; int psz; psz = sizeof(struct sockaddr_in); if(getsockname(sd, (struct sockaddr *)&peer, &psz) < 0) return(INADDR_NONE); if(port) *port = ntohs(peer.sin_port); if(ret_peer) memcpy(ret_peer, &peer, sizeof(struct sockaddr_in)); return(peer.sin_addr.s_addr); } int gsnatneg_putrr(u8 *data, int len) { u32 seed; int i; seed = time(NULL); for(i = 0; i < len; i++) { seed = (seed * 0x343FD) + 0x269EC3; data[i] = (((seed >> 16) & 0x7fff) % 93) + 33; } data[i++] = 0; return(i); } int gsnatneg_putxx(u8 *data, u32 num, int bits) { int i, bytes; bytes = bits >> 3; for(i = 0; i < bytes; i++) { data[i] = (num >> (i << 3)) & 0xff; } return(bytes); } int gsnatneg_putss(u8 *buff, u8 *data) { int len; len = strlen(data) + 1; memcpy(buff, data, len); return(len); } int gsnatneg_putmm(u8 *buff, u8 *data, int len) { memcpy(buff, data, len); return(len); } int gsnatneg_send(int sd, in_addr_t ip, u16 port, u8 *buff, int len) { struct sockaddr_in peer; u8 tmp[2]; if(!buff) { peer.sin_addr.s_addr = ip; peer.sin_port = htons(port); peer.sin_family = AF_INET; if(gsnatneg_verbose) printf("- TCP connection to %s\n", inet_ntoa(peer.sin_addr)); if(connect(sd, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)) < 0) return(-1); return(0); } peer.sin_addr.s_addr = gsnatneg_get_sock_ip_port(sd, NULL, NULL); if(gsnatneg_verbose) printf("- TCP send %d bytes to %s\n", len, inet_ntoa(peer.sin_addr)); /* fast sending solution, but requires more memory static u8 *tmp = NULL; if(!tmp) { tmp = malloc(2 + 0xffff); if(!tmp) return(-1); } len += 2; gsnatneg_putxx(tmp, htons(len), 16); memcpy(tmp + 2, buff, len - 2); if(send(sd, tmp, len, 0) != len) return(-1); */ gsnatneg_putxx(tmp, htons(len + 2), 16); if(send(sd, tmp, 2, 0) != 2) return(-1); if(send(sd, buff, len, 0) != len) return(-1); return(0); } int gsnatneg_sendto(int sd, in_addr_t ip, u16 port, u8 *buff, int len) { struct sockaddr_in peer; if(ip == INADDR_NONE) return(0); // or -1? peer.sin_addr.s_addr = ip; peer.sin_port = htons(port); peer.sin_family = AF_INET; if(!buff) { connect(sd, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)); return(0); } if(gsnatneg_verbose) printf("- UDP send %d bytes to %s:%hu\n", len, inet_ntoa(peer.sin_addr), ntohs(peer.sin_port)); if(sendto(sd, buff, len, 0, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)) != len) return(-1); return(0); } int gsnatneg_recv(int sd, u8 *buff) { int len; u8 tmp[2]; if(gsnatneg_tcprecv(sd, tmp, 2) < 0) return(-1); len = (tmp[0] << 8) | tmp[1]; len -= 2; if(len < 2) return(-1); if(gsnatneg_tcprecv(sd, buff, len) < 0) return(-1); return(len); } int gsnatneg_tcprecv(int sd, u8 *buff, int len) { int t; while(len) { if(gsnatneg_timeout(sd, 3) < 0) return(-1); t = recv(sd, buff, len, 0); if(t <= 0) return(-1); buff += t; len -= t; } return(0); } int gsnatneg_recvfrom(int sd, struct sockaddr_in *peer, u8 *buff, int size) { int len, psz; if(gsnatneg_timeout(sd, 2) < 0) return(-1); if(peer) { psz = sizeof(struct sockaddr_in); len = recvfrom(sd, buff, BUFFSZ, 0, (struct sockaddr *)peer, &psz); } else { len = recvfrom(sd, buff, BUFFSZ, 0, NULL, NULL); } if(len < 0) return(-1); if(gsnatneg_verbose) printf("- recv %d bytes from %s\n", len, peer ? inet_ntoa(peer->sin_addr) : "unknown"); return(len); } in_addr_t gsnatneg_gsresolv(u8 *gamename, int num) { in_addr_t ip; u32 i, c, server_num; int len; u8 tmp[256]; server_num = 0; for(i = 0; gamename[i]; i++) { c = tolower(gamename[i]); server_num = c - (server_num * 0x63306ce7); } server_num %= 20; if(num) { // natneg len = snprintf(tmp, sizeof(tmp), "%s.natneg%d.gamespy.com", gamename, num); } else { // ms? len = snprintf(tmp, sizeof(tmp), "%s.ms%d.gamespy.com", gamename, server_num); } if((len < 0) || (len >= sizeof(tmp))) return(INADDR_NONE); if(gsnatneg_verbose) printf("- resolv %s", tmp); ip = gsnatneg_resolv(tmp); if(gsnatneg_verbose) printf(" --> %s\n", inet_ntoa(*(struct in_addr *)&ip)); return(ip); } int gsnatneg_timeout(int sock, int secs) { struct timeval tout; fd_set fd_read; tout.tv_sec = secs; tout.tv_usec = 0; FD_ZERO(&fd_read); FD_SET(sock, &fd_read); if(select(sock + 1, &fd_read, NULL, NULL, &tout) <= 0) return(-1); return(0); } in_addr_t gsnatneg_resolv(u8 *host) { struct hostent *hp; in_addr_t host_ip; host_ip = inet_addr(host); if(host_ip == INADDR_NONE) { hp = gethostbyname(host); if(!hp) return(INADDR_NONE); host_ip = *(in_addr_t *)hp->h_addr; } return(host_ip); }