Так ли страшен Symmetric NAT

в 10:20, , рубрики: Сетевые технологии

Задача прямого соединения машин, находящихся за NAT'ом стара как мир и я думаю, что многие слышали про UDP Hole Punching. Когда я только начинал интересоваться вопросом, я утвердился во мнении, что symmetric nat пробить невозможно и пытаться даже не стоит. Однако совсем недавно мне попалась статья в которой утверждалось, что симметричный нат — это не приговор.

Давайте разберемся.

Типы NAT

Традиционно во многих статьях в Интернете все NATы делят на четыре типа:

  • Full-cone NAT;
  • Address-restricted cone NAT;
  • Port-restricted cone NAT;
  • Symmetric NAT

На самом деле это неверно. Точнее не совсем верно. У любого NAT’a есть две основных характеристики:

1) фильтр входящих пакетов;
2) правило маппинга портов.

Первая характеристика как раз описана в большинстве статей и означает, какие входящие пакеты передавать машине за NAT’ом: все (no filter – Full cone), с конкретного адреса (address-restricted) или с конкретного адреса и порта (port-restricted).

Вторая характеристика же присуще только симметричному НАТ’у, так как первые три типа пытаются сделать отражение один в один. Например, если клиент посылает пакет с внутреннего адреса 192.168.10.24:62145, то от роутера пакет пойдет с адреса 1.2.3.4:62145. Причем вне зависимости от адреса получателя.

Symmetric NAT

А теперь детальнее про симметричный NAT. Сразу оговорюсь, что фильтры входящих пакетов тоже могут быть любые (no filter, address-restricted или port-restricted). И единственное отличие этого типа NAT’a от предыдущих как раз в выборе исходящего порта на роутере, он почти наверняка будет отличаться от исходного порта на клиенте. Вернувшись к предыдущему примеру отражение может быть таким: 192.168.10.24:62145 -> 1.2.3.4:1274.

Выбирается тот самый порт случайно (ну или не случайно, а по очереди, но это не важно, так как повлиять на его выбор извне мы не можем). Но есть определенные правила, они похожи на фильтр входящих пакетов:

  • Порт может оставаться всегда одним и тем же, в не зависимости от получателя (cone);
  • Порт может оставаться одним и тем же для конкретного адреса получателя (address);
  • Порт может оставаться одним и тем же лишь для конкретного адреса и порта получателя (port);

При этом есть еще и правила для выбора следующего порта:
Это может быть какая-то дельта (+1/-1 или +10/-10) или вообще каждый раз случайно.
Кроме того видел один NAT у которого каждый последующий порт отстоял от предыдущего на случайное число, но всегда кратное 4096.

Вместо заключения

Итак, понятного, что зная правило распределения портов и дельту можно угадать, с какого порта пойдет исходящий пакет, соответственно пробить тот самый симметричный NAT. Разумеется, в случае выбора порта совсем случайно, этот фокус не пройдет.

Ну что же мы подобрались к сути и цели статьи. Ответу на вопрос

«Можно ли определить правило распределения портов и дельту, находясь за NAT’ом?»

Поможет нам в этом STUN, конечно. Наша задача сделать четыре запроса к разным адресам и портами используя один сокет (один локальный порт) и оценить результаты:
Мы сможем понять каким образом распределяются исходящие порты (адрес или порт) и попробовать рассчитать ту самую дельту.

И тут я призываю читатели мне помочь со статистикой. На просторах Интернета был найден простенький stun клиент, немножко допилен кувалдой и вот что получилось:

Исходник

//(c) joric^proxium, 2010, public domain
                   

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <memory.h>


#ifndef __NET_H__
#define __NET_H__

#ifdef _WIN32
#include <malloc.h>
#include <winsock2.h>
#include <windows.h>
//#include "md5.h"
//#include "hmac.h"
#define THREAD DWORD
#else
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <openssl/md5.h>
#include <openssl/hmac.h>
#include <pthread.h>
#define SOCKET int
#define THREAD pthread_t
#define closesocket close
#define Sleep(t) usleep(t*1000)
#endif

#define ADDRESS struct sockaddr_in

#ifndef __HEX_H__
#define __HEX_H__

int hex_encode(char *dest, char *src, int len)
{
	char *table = "0123456789abcdef";
	int j;
	for (j = 0; j < len; j++)
	{
		dest[j * 2] = table[((src[j] >> 4) & 0xF)];
		dest[j * 2 + 1] = table[(src[j]) & 0x0F];
	}
	dest[len * 2] = 0;
	return len * 2;
}

void hex_decode(char *dest, char *src, int len)
{
	unsigned char v = 0;
	char *d = dest;
	char *p = src;
	int res;

	while ((res = sscanf(p, "%02x", &v)) > 0)
	{
		*d++ = v;
		p += res * 2;
	}
}

char *hex_string(char *buf, int len)
{
	static char msg[256];
	hex_encode(msg, buf, len);
	return msg;
}

#endif //__HEX_H__

typedef struct
{
	char *buf;
	int ofs;
	int size;
	int len;
} PACKET;

int packet_init(PACKET * m, char *buf, int buf_size)
{
	m->buf = buf;
	m->size = buf_size;
	m->len = 0;
	m->ofs = 0;
	return 0;
}

int packet_write(PACKET * m, int v)
{
	if (m && m->ofs < m->size - 1)
	{
		m->buf[m->ofs++] = v & 0xff;

		if (m->len < m->ofs)
			m->len++;

		return 1;
	}
	return 0;
}

unsigned long long packet_read(PACKET * m)
{
	return (m && (m->ofs < m->size - 1)) ? m->buf[m->ofs++] & 0xff : 0;
}

#define w8(m, v) packet_write(m, v & 0xff)
#define w16(m, v) w8(m, v >> 8) + w8(m, v)
#define w32(m, v) w16(m, v >> 16) + w16(m, v)
#define w64(m, v) w32(m, v >> 32) + w32(m, v)
#define wBuf(m, buf, len) { int k = 0, i = 0; for (i = 0; i < len; i++) k += w8(m, buf[i]); }
#define r8(m) packet_read(m)
#define r16(m) (((r8(m) << 8) | r8(m)) & 0xffff)
#define r32(m) ((r16(m) << 16) | r16(m))
#define r64(m) ((r32(m) << 32) | r32(m))
#define rBuf(m, buf, len) { int i= 0; for (i = 0; i < len; i++) buf[i] = r8(m); }

int round_int(int offset, int align)
{
	return offset + ((align - (offset % align)) % align);
}

void random_data(char *buf, int a, int b, int len)
{
	int i;
	for (i = 0; i < len; i++)
		buf[i] = a + rand() % (b - a);
}

typedef struct
{
	ADDRESS *addr;
	SOCKET sock;
	THREAD thread;
	char *host;
	int port;		//local port
	int started;
} LINK;

char *net_to_string(ADDRESS * addr)
{
	static char msg[64];
	unsigned char *ip = (unsigned char *) &addr->sin_addr.s_addr;
	sprintf(msg, "%d.%d.%d.%d:%d", ip[0], ip[1], ip[2], ip[3], ntohs(addr->sin_port));
	return msg;
}

void net_make_address(ADDRESS * addr, unsigned int ip, int port)
{
	memset(addr, 0, sizeof(ADDRESS));
	addr->sin_family = AF_INET;
	addr->sin_port = htons(port);
	addr->sin_addr.s_addr = ip;
}

int net_resolve_address(ADDRESS * addr, char *host, int port)
{
	net_make_address(addr, 0, port);
	struct hostent *hp = (struct hostent *) gethostbyname(host);
	if (hp)
		*(int *) &addr->sin_addr.s_addr = *(int *) hp->h_addr_list[0];
return 0;
}

int net_is_local_address(ADDRESS * addr)
{
	SOCKET sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	ADDRESS loc_addr;
	net_make_address(&loc_addr, addr->sin_addr.s_addr, 0);	//ephemeral

	printf("Checking NAT: trying to bind to %sn", net_to_string(&loc_addr));

	if (bind(sock, (struct sockaddr *) &loc_addr, sizeof(ADDRESS)) == 0)
	{
		printf("success, no NATn");
		closesocket(sock);
		return 1;
	}
	printf("failed, NAT presentn");
	return 0;
}

extern void net_recv(char *buf, int len);

static void *net_thread(void *param)
{
	int buf_size = 8192;
	char buf[8192];
	int len = 0;

	LINK *link = (LINK *) param;

	printf("resolving host %sn", link->host);

	if (link->host)
		net_resolve_address(link->addr, link->host, link->port);

    printf("resolved to %sn", net_to_string(link->addr));

	if ((link->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
		goto cleanup;


	{
		link->port = 0;	//ephemeral
		ADDRESS loc_addr;
		net_make_address(&loc_addr, INADDR_ANY, link->port);  

		if (bind(link->sock, (struct sockaddr *) &loc_addr, sizeof(ADDRESS)) < 0)
			goto cleanup;

		//trying to figure out local port

		int addr_size = sizeof(ADDRESS);
		getsockname(link->sock, (struct sockaddr *) &loc_addr, &addr_size);

		link->port = loc_addr.sin_port;

        printf("sitting on: %sn", net_to_string(&loc_addr));
	}


	link->started = 1;

	while (link != NULL)
	{
		ADDRESS addr;
		int addr_size = sizeof(ADDRESS);

		len = recvfrom(link->sock, buf, buf_size, 0, (struct sockaddr *) &addr, &addr_size);

		if (len >= 0)
		{
			printf("received %d bytes from %sn", len, net_to_string(&addr));
			net_recv(buf, len);
		}

		Sleep(1);
	}

cleanup:
	closesocket(link->sock);

	return 0;
}

int net_init()
{
#ifdef _WIN32
	WSADATA wsa;
	return WSAStartup(MAKEWORD(2, 2), &wsa) == 0;
#endif
	return 0;
}

int net_open_link(LINK * link)
{
#ifdef _WIN32
	CreateThread((LPSECURITY_ATTRIBUTES) NULL, 0, (LPTHREAD_START_ROUTINE) net_thread, link, 0, &link->thread);
#else
	pthread_create(&link->thread, NULL, net_thread, link);
#endif
	return 0;
}

int net_send(LINK * link, char *buf, int len)
{
	printf("sending %d bytes to %sn", len, net_to_string(link->addr));

	sendto(link->sock, buf, len, 0, (struct sockaddr *) link->addr, sizeof(struct sockaddr));
return 0;
}

#endif //__NET_H__

#ifndef __STUN_H__
#define __STUN_H__



typedef struct
{
	ADDRESS base_address;
	ADDRESS mapped_address;
	ADDRESS changed_address;
	LINK *link;
	int state;
	int received;
	int test;
	int nat;
	int finished;
	int send_time;
	int results[8];
	int timeout;
	int mappedports[4];
	int presport;
	int fpresport;
	
} STUN;

#define TN(id) {static char buf[16]; sprintf(buf, "0x%04x", id); return buf; }
#define T(id) if (type == id) return #id+5; else

enum
{
	STUN_HEADER_SIZE = 20,
	STUN_MAGIC_COOKIE = 0x2112A442,
	STUN_BINDING_METHOD = 1,
	STUN_SHARED_SECRET_METHOD = 2,
	STUN_ALLOCATE_METHOD = 3,
	STUN_REFRESH_METHOD = 4,
	STUN_SEND_METHOD = 6,
	STUN_DATA_METHOD = 7,
	STUN_CHANNEL_BIND_METHOD = 9,
	STUN_REQUEST_CLASS = 0,
	STUN_INDICATION_CLASS = 1,
	STUN_SUCCESS_CLASS = 2,
	STUN_ERROR_CLASS = 3,
};

enum stun_vars
{
	STUN_MAPPED_ADDRESS = 0x0001,
	STUN_RESPONSE_ADDRESS = 0x0002,
	STUN_CHANGE_REQUEST = 0x0003,
	STUN_SOURCE_ADDRESS = 0x0004,
	STUN_CHANGED_ADDRESS = 0x0005,
	STUN_USERNAME = 0x0006,
	STUN_PASSWORD = 0x0007,
	STUN_MESSAGE_INTEGRITY = 0x0008,
	STUN_ERROR_CODE = 0x0009,
	STUN_UNKNOWN_ATTRIBUTES = 0x000A,
	STUN_REFLECTED_FROM = 0x000B,
	STUN_CHANNEL_NUMBER = 0x000C,
	STUN_LIFETIME = 0x000D,
	STUN_BANDWIDTH = 0x0010,
	STUN_PEER_ADDRESS = 0x0012,
	STUN_DATA = 0x0013,
	STUN_REALM = 0x0014,
	STUN_NONCE = 0x0015,
	STUN_RELAYED_ADDRESS = 0x0016,
	STUN_REQUESTED_ADDRESS_TYPE = 0x0017,
	STUN_REQUESTED_PROPS = 0x0018,
	STUN_REQUESTED_TRANSPORT = 0x0019,
	STUN_XOR_MAPPED_ADDRESS = 0x8020,
	STUN_TIMER_VAL = 0x0021,
	STUN_RESERVATION_TOKEN = 0x0022,
	STUN_XOR_REFLECTED_FROM = 0x0023,
	STUN_PRIORITY = 0x0024,
	STUN_USE_CANDIDATE = 0x0025,
	STUN_ICMP = 0x0030,
	STUN_END_MANDATORY_ATTR,
	STUN_START_EXTENDED_ATTR = 0x8021,
	STUN_SOFTWARE = 0x8022,
	STUN_ALTERNATE_SERVER = 0x8023,
	STUN_REFRESH_INTERVAL = 0x8024,
	STUN_FINGERPRINT = 0x8028,
	STUN_ICE_CONTROLLED = 0x8029,
	STUN_ICE_CONTROLLING = 0x802A,
};

#define TN(id) {static char buf[16]; sprintf(buf, "0x%04x", id); return buf; }
#define T(id) if (type == id) return #id+5; else

char *ATTR_NAME(int type)
{
	T(STUN_MAPPED_ADDRESS);
	T(STUN_RESPONSE_ADDRESS);
	T(STUN_CHANGE_REQUEST);
	T(STUN_SOURCE_ADDRESS);
	T(STUN_CHANGED_ADDRESS);
	T(STUN_USERNAME);
	T(STUN_PASSWORD);
	T(STUN_MESSAGE_INTEGRITY);
	T(STUN_ERROR_CODE);
	T(STUN_UNKNOWN_ATTRIBUTES);
	T(STUN_REFLECTED_FROM);
	T(STUN_CHANNEL_NUMBER);
	T(STUN_LIFETIME);
	T(STUN_BANDWIDTH);
	T(STUN_PEER_ADDRESS);
	T(STUN_DATA);
	T(STUN_REALM);
	T(STUN_NONCE);
	T(STUN_RELAYED_ADDRESS);
	T(STUN_REQUESTED_ADDRESS_TYPE);
	T(STUN_REQUESTED_PROPS);
	T(STUN_REQUESTED_TRANSPORT);
	T(STUN_XOR_MAPPED_ADDRESS);
	T(STUN_TIMER_VAL);
	T(STUN_RESERVATION_TOKEN);
	T(STUN_XOR_REFLECTED_FROM);
	T(STUN_PRIORITY);
	T(STUN_USE_CANDIDATE);
	T(STUN_ICMP);
	T(STUN_END_MANDATORY_ATTR);
	T(STUN_START_EXTENDED_ATTR);
	T(STUN_SOFTWARE);
	T(STUN_ALTERNATE_SERVER);
	T(STUN_REFRESH_INTERVAL);
	T(STUN_FINGERPRINT);
	T(STUN_ICE_CONTROLLED);
	T(STUN_ICE_CONTROLLING);
	TN(type);
};

void stun_write_header(PACKET * m, int type)
{
	char tsx_id[12];
	random_data(tsx_id, 0, 0xff, 12);
	w16(m, type);
	w16(m, 0);
	w32(m, STUN_MAGIC_COOKIE);
	wBuf(m, tsx_id, 12);
}

void stun_write_footer(PACKET * m)
{
	m->ofs = 2;
	w16(m, m->len - STUN_HEADER_SIZE);
}

int stun_xor_address(ADDRESS * addr)
{
	int i;
	int x = htonl(STUN_MAGIC_COOKIE);
	char *p = (char *) &x;
	int msb = ((char *) &x)[0] << 8 | ((char *) &x)[1];
	addr->sin_port ^= htons(msb);
	char *ip = (char *) &addr->sin_addr.s_addr;
	for (i = 0; i < 4; i++)
		ip[i] ^= p[i];
return 0;
}

int stun_parse_address(PACKET * m, ADDRESS * addr)
{
	addr->sin_family = r16(m) == 1 ? 2 : 1;
	addr->sin_port = htons(r16(m));
	char *p = (char *) &addr->sin_addr.s_addr;
	rBuf(m, p, 4);
return 0;
}

int stun_parse(STUN * stun, PACKET * m)
{
	m->ofs = 0;
	int type = r16(m);
	int length = r16(m);
	int magic = r32(m);
	char tsx_id[12];

	if (magic != STUN_MAGIC_COOKIE)
		return 0;

	rBuf(m, tsx_id, 12);

	int msg = type & ~0x110;
	int code = type & 0x110;

	printf(" Message: %d (%d)n", msg, code);
	printf("  hdr: length=%d, magic=0x%x, tsx_id=%s", length, magic, hex_string(tsx_id, 12));
	printf("n");
	printf("  Attributes:n");

	int offset = m->ofs;

	while ((offset - STUN_HEADER_SIZE) < length)
	{
		int attr = r16(m);
		int len = r16(m);

		//printf(" 0x%04x length=%d, ", attr, len);
		printf("  %s length=%d, ", ATTR_NAME(attr), len);

		switch (attr)
		{
			case STUN_MAPPED_ADDRESS:
			case STUN_RESPONSE_ADDRESS:
			case STUN_SOURCE_ADDRESS:
			case STUN_CHANGED_ADDRESS:
			case STUN_XOR_MAPPED_ADDRESS:
			{
				ADDRESS addr;

				stun_parse_address(m, &addr);

				if (attr == STUN_XOR_MAPPED_ADDRESS)
					stun_xor_address(&addr);

				printf(net_to_string(&addr));

				if (attr == STUN_MAPPED_ADDRESS)
					memcpy(&stun->mapped_address, &addr, sizeof(ADDRESS));

				if (attr == STUN_CHANGED_ADDRESS)
					memcpy(&stun->changed_address, &addr, sizeof(ADDRESS));

				break;
			}

			case STUN_SOFTWARE:
				printf(m->buf + m->ofs);
				break;

			default:
				printf(hex_string(m->buf + m->ofs, len));
				break;
		}

		printf("n");
		len = round_int(len, 4);
		offset += len + 4;
		m->ofs = offset;

		stun->received = 1;
	}

	return 1;
}

void stun_write_attr(PACKET * m, int attr, char *buf, int len)
{
	int pad = round_int(len, 4) - len;
	w16(m, attr);
	w16(m, len);
	wBuf(m, buf, len);
	m->ofs += pad;
}

#define stun_write_str(m,attr,str) stun_write_attr(m, attr, str, strlen(str));
#define stun_write_uint(m,attr,value) w16(m, attr); w16(m, 4); w32(m, value);

void stun_recv(STUN * stun, char *buf, int len)
{
	PACKET m;
	packet_init(&m, buf, len);
	stun_parse(stun, &m);
}

int stun_send_message(STUN * stun, int type)
{
	LINK *link = stun->link;
	PACKET mp;
	PACKET *m = &mp;

	char buf[1024];
	int buf_size = 1024;


	packet_init(m, buf, buf_size);

	switch (type)
	{
		case 1:	// Test I 
			link->addr = &stun->base_address;
			stun_write_header(m, STUN_BINDING_METHOD);
			stun_write_footer(m);
			break;

		case 2:	// Test II
			link->addr = &stun->base_address;
			stun_write_header(m, STUN_BINDING_METHOD);
			stun_write_uint(m, STUN_CHANGE_REQUEST, 4 + 2);	//change addr & port
			stun_write_footer(m);
			break;

		case 3:	// Test I(2)
			link->addr = &stun->changed_address;
			stun_write_header(m, STUN_BINDING_METHOD);
			stun_write_footer(m);
			break;

		case 4:	// Test III
			link->addr = &stun->base_address;
			stun_write_header(m, STUN_BINDING_METHOD);
			stun_write_uint(m, STUN_CHANGE_REQUEST, 2);	//change port
			stun_write_footer(m);
			break;

		case 5:
			net_resolve_address(link->addr,"216.93.246.14",3478);
			stun_write_header(m, STUN_BINDING_METHOD);
			stun_write_footer(m);
			break;

		case 6:	
			net_resolve_address(link->addr,"216.93.246.14",3479);
			stun_write_header(m, STUN_BINDING_METHOD);
			stun_write_footer(m);
			break;

		case 7:	
			net_resolve_address(link->addr,"216.93.246.15",3478);
			stun_write_header(m, STUN_BINDING_METHOD);
			stun_write_footer(m);
			break;


		case 8:
			net_resolve_address(link->addr,"216.93.246.15",3479);
			stun_write_header(m, STUN_BINDING_METHOD);
			stun_write_footer(m);
			break;


		default:
			break;
	}

	if (m->len)
	{
		net_send(link, m->buf, m->len);
		stun_parse(stun, m);
	}

	return m->len;
}

int stun_start(LINK * link, STUN * stun)
{
	stun->state = 0;
	stun->finished = 0;
	link->addr = &stun->base_address;
	link->started = 0;
	stun->test = 1;
	stun->link = link;
	net_open_link(stun->link);
return 0;
}

int stun_update(STUN * stun)
{
	int state = stun->state;

	switch (stun->state)
	{
		case 0:	//starting
			if (stun->link->started)
			{
				int i;
				stun->timeout = 4;
				for (i = 0; i < 4; i++)
					stun->results[i] = 0;
				state = 1;
			}
			break;

		case 1:	//send message
			printf("nTEST %dn", stun->test);
			stun_send_message(stun, stun->test);
			stun->received = 0;
			stun->send_time = time(0);
			state = 2;
			break;

		case 2:	//wait result
			if (time(0) - stun->send_time > stun->timeout)
				state = 3;
			if (stun->received)
				state = 4;
			break;

		case 3:
			printf("[timeout]n");

			if (stun->test == 1)
				stun->finished = 1;

			if (stun->test == 2 && !stun->nat)
				stun->finished = 1;

			stun->results[stun->test - 1] = 0;
			stun->test++;
			state = 1;
			break;

		case 4:
			printf("[received %d]n", stun->test);
			stun->timeout = time(0) - stun->send_time + 1;

			if (stun->test == 1)
				stun->nat = !net_is_local_address(&stun->mapped_address);

			if (stun->test != 1 && stun->test<5)
				stun->presport = (stun->mapped_address.sin_port == stun->link->port);
			
			if (stun->test == 1) // it's also important!
				stun->fpresport = (stun->mapped_address.sin_port == stun->link->port);
			if(stun->test > 4)
			{
				stun->mappedports[stun->test-5] = ntohs(stun->mapped_address.sin_port);
			}

			stun->results[stun->test - 1] = 1;
			stun->test++;
			state = 1;
			break;
	}

	if (stun->test == 5 && state == 1){

	closesocket(stun->link->sock);
	stun->link->port=3478;
	net_open_link(stun->link);
	stun->timeout = 4;
	Sleep(1000); 
	
	}
	if (stun->test == 9)
		stun->finished = 1;

	stun->state = state;

	return !stun->finished;
}

#endif //__STUN_H__

STUN m_stun;
LINK m_link;

void net_recv(char *buf, int len)
{
	stun_recv(&m_stun, buf, len);
}

int main(int argc, char **argv)
{
	int port = 3478;
	char *host = "stun.counterpath.net";

	if (argc > 1)
		host = argv[1];

	net_init();

	LINK *link = &m_link;
	STUN *stun = &m_stun;

	link->host = host;
	link->port = port;

	stun_start(link, stun);

	while (stun_update(stun))
		Sleep(5);


	//make a mask for tests, i.e. 1,0,1,1 == 0x1011
	int i, mask = 0;
	for (i = 0; i < 4; i++)
		mask |= stun->results[i] << (3 - i) * 4;

	char *type;
	if(stun->results[3]==1)
		type = "Address restricted NAT";
	else
		type = "Port restricted NAT";



	switch (mask)
	{        
        case 0x0000:
            type = "UDP Blocked";
            break;
		case 0x1000:
			type = "UDP Firewall";
			break;
		case 0x1100:
			type = "Open Internet";
			break;
    	case 0x1011:
			type = "Full Cone NAT";
			break;
	}
	printf("nnResultsn-------------------------------n");
	printf("tests: %04xn", mask);
	printf("NAT present: %dn", stun->nat);
	printf("first preserved port: %dn", stun->fpresport);
	printf("preserves port: %dn", stun->presport);
	if(stun->nat)
	printf("type: %sn", type);

	printf("mapped ports: ");
	for(i = 0; i<4; i++)
	printf("%d ",stun->mappedports[i]);

	printf("n");



}

!

Пользователи Линукса прекрасно знают как это скомпилировать.

Вот так

gcc -lpthread -o stun stun.c

Под винду отлично компилится студией, вот тут бинарник, если студии под рукой нет.

Да простит меня stun.counterpath.net за хабра эффект :)

Вот мои результаты, но у меня не симметричный НАТ и не интересно:

Results
tests: 1010
NAT present: 1
first preserved port: 1
preserves port: 0
type: Port restricted NAT
mapped ports: 55907 55907 55907 55907

Всем спасибо за помощь!

Автор: den_admin

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js