Commit 8d9ac409 authored by Jason Fong's avatar Jason Fong
Browse files

first commit

parents
CC = gcc
CFLAGS = -g -Wall
all: nat_traversal
nat_traversal: main.o nat_traversal.o nat_type.o
$(CC) $(CFLAGS) -o nat_traversal main.o nat_traversal.o nat_type.o
main.o: main.c
$(CC) $(CFLAGS) -c main.c
nat_traversal.o: nat_traversal.c
$(CC) $(CFLAGS) -c nat_traversal.c
nat_type.o: nat_type.c
$(CC) $(CFLAGS) -c nat_type.c
clean:
$(RM) nat_traversal *.o *~
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "nat_traversal.h"
#define DEFAULT_SERVER_PORT 9988
#define MSG_BUF_SIZE 512
// use some public stun servers to detect port allocation rule
static char *stun_servers[] = {
"stun.ideasip.com",
"stun.ekiga.net",
"203.183.172.196"
};
int main(int argc, char** argv)
{
char* stun_server = stun_servers[0];
char* local_host = "0.0.0.0";
uint16_t stun_port = DEFAULT_STUN_SERVER_PORT;
uint16_t local_port = DEFAULT_LOCAL_PORT;
char* punch_server = NULL;
uint32_t peer_id = 0;
static char usage[] = "usage: [-h] [-H STUN_HOST] [-P STUN_PORT] [-s punch server] [-d id] [-i SOURCE_IP] [-p SOURCE_PORT]\n";
int opt;
while ((opt = getopt (argc, argv, "H:h:P:p:s:d:i")) != -1)
{
switch (opt)
{
case 'h':
printf("%s", usage);
break;
case 'H':
stun_server = optarg;
break;
case 'P':
stun_port = atoi(optarg);
break;
case 's':
punch_server = optarg;
break;
case 'p':
local_port = atoi(optarg);
break;
case 'd':
peer_id = atoi(optarg);
break;
case 'i':
local_host = optarg;
break;
case '?':
default:
printf("invalid option: %c\n", opt);
printf("%s", usage);
return -1;
}
}
char ext_ip[16] = {0};
uint16_t ext_port = 0;
nat_type type = detect_nat_type(stun_server, stun_port, local_host, local_port, ext_ip, &ext_port);
// TODO log
printf("NAT type: %s\n", get_nat_desc(type));
if (ext_port) {
printf("external address: %s:%d\n", ext_ip, ext_port);
} else {
return -1;
}
if (!punch_server) {
printf("please specify punch server\n");
return -1;
}
struct peer_info self;
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(punch_server);
server_addr.sin_port = htons(DEFAULT_SERVER_PORT);
client c;
// test
c.type = SymmetricNAT;
if (init(self, server_addr, &c)) {
printf("init failed\n");
return -1;
}
if (peer_id) {
if (connect_to_peer(&c, peer_id) < 0) {
printf("failed to connect\n");
return -1;
}
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <pthread.h>
#include "nat_traversal.h"
#define MAX_PORT 65535
#define MIN_PORT 1025
#define NUM_OF_PORTS 700
#define MSG_BUF_SIZE 512
// file scope variables
static int nums[MAX_PORT - MIN_PORT];
static int send_to_punch_server(client* c, int flags) {
int n = send(c->sfd, c->msg_buf, c->msg_buf - c->buf, flags);
c->msg_buf= c->buf;
return n;
}
static int get_peer_info(client* cli, uint32_t peer_id, struct peer_info *peer) {
cli->msg_buf = encode16(cli->msg_buf, GetPeerInfo);
cli->msg_buf = encode32(cli->msg_buf, peer_id);
if (-1 == send_to_punch_server(cli, 0)) {
return -1;
}
int bytes = recv(cli->sfd, (void*)peer, sizeof(struct peer_info), 0);
if (bytes < 20) {
return -1;
}
peer->port = ntohs(peer->port);
peer->type = ntohs(peer->type);
printf("peer %d info: %s:%d, nat type: %s\n", peer_id, peer->ip, peer->port, get_nat_desc(peer->type));
return 0;
}
static int send_dummy_udp_packet(int fd, struct sockaddr_in addr) {
char dummy = 'c';
struct timeval tv = {5, 0};
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
return sendto(fd, &dummy, 1, 0, (struct sockaddr *)&addr, sizeof(addr));
}
static int punch_hole(struct sockaddr_in peer_addr) {
int hole = socket(AF_INET, SOCK_DGRAM, 0);
if (hole != -1) {
// no need to choose local port for now, let OS do that
/*struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
local_addr.sin_port = htons(DEFAULT_LOCAL_PORT + 1);
if (bind(hole, (struct sockaddr *)&local_addr, sizeof(local_addr))) {
if (errno == EADDRINUSE) {
printf("addr in use, try another port\n");
return -1;
}
} */
/* TODO we can use traceroute to get the number of hops to the peer
* to make sure this packet woudn't reach the peer but get through the NAT of itself
*/
int ttl = 5;
setsockopt(hole, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
// send short ttl packets to avoid triggering flooding protection of NAT in front of peer
if (send_dummy_udp_packet(hole, peer_addr) < 0) {
return -1;
}
}
return hole;
}
static int wait_for_peer(int* socks, int sock_num, struct timeval *timeout) {
fd_set fds;
int max_fd = 0;
FD_ZERO(&fds);
int i;
for (i = 0; i < sock_num; ++i) {
FD_SET(socks[i], &fds);
if (socks[i] > max_fd) {
max_fd = socks[i];
}
}
int ret = select(max_fd + 1, &fds, NULL, NULL, timeout);
int index = -1;
if (ret > 0) {
for (i = 0; i < sock_num; ++i) {
if (FD_ISSET(socks[i], &fds)) {
index = i;
break;
}
}
} else {
// timeout or error
}
// one of the fds is ready, close others
if (index != -1) {
for (i = 0; i < sock_num; ++i) {
if (index != i) {
close(socks[i]);
}
}
return socks[index];
}
return -1;
}
static void shuffle(int *num, int len) {
srand(time(NULL));
// Fisher-Yates shuffle algorithm
int i, r, temp;
for (i = len - 1; i > 0; i--) {
r = rand() % i;
temp = num[i];
num[i] = num[r];
num[r] = temp;
}
}
static int connect_to_symmetric_nat(client* c, uint32_t peer_id, struct peer_info peer) {
// TODO choose port prediction strategy
/*
* according to birthday paradox, probability that port randomly chosen from [1024, 65535]
* will collide with another one chosen by the same way is
* p(n) = 1-(64511!/(64511^n*64511!))
* where '!' is the factorial operator, n is the number of ports chosen.
* P(100)=0.073898
* P(200)=0.265667
* P(300)=0.501578
* P(400)=0.710488
* P(500)=0.856122
* P(600)=0.938839
* but symmetric NAT has port sensitive filter for incoming packet
* which makes the probalility decline dramatically.
* Moreover, symmetric NATs don't really allocate ports randomly.
*/
struct sockaddr_in peer_addr;
peer_addr.sin_family = AF_INET;
peer_addr.sin_addr.s_addr = inet_addr(peer.ip);
int *holes = malloc(NUM_OF_PORTS * sizeof(int));
shuffle(nums, MAX_PORT - MIN_PORT + 1);
int i = 0;
for (; i < NUM_OF_PORTS;) {
uint16_t port = nums[i];
if (port != peer.port) { // exclude the known one
peer_addr.sin_port = htons(port);
if ((holes[i] = punch_hole(peer_addr)) < 0) {
// NAT in front of us wound't tolerate too many ports used by one application
printf("NAT flooding protection triggered, try %d times\n", i);
break;
}
++i;
} else {
nums[i] = nums[1000];
continue;
}
}
// hole punched, notify peer
c->msg_buf = encode16(c->msg_buf, NotifyPeer);
c->msg_buf = encode32(c->msg_buf, peer_id);
send_to_punch_server(c, 0);
struct timeval timeout={10, 0};
int fd = wait_for_peer(holes, i, &timeout);
if (fd > 0) {
on_connected(fd);
} else {
int j = 0;
for (; j < i; ++j) {
close(holes[j]);
}
}
return 0;
}
// run in another thread
static void* server_notify_handler(void* data) {
int server_sock = *(int*)data;
struct peer_info peer;
// wait for notification
printf("waiting for notification...\n");
if (recv(server_sock, &peer, sizeof peer, 0) <= 0) {
return NULL;
}
peer.port = ntohs(peer.port);
peer.type = ntohs(peer.type);
printf("recv command, ready to connect to %s:%d\n", peer.ip, peer.port);
struct sockaddr_in peer_addr;
peer_addr.sin_family = AF_INET;
peer_addr.sin_addr.s_addr = inet_addr(peer.ip);
int sock_array[NUM_OF_PORTS];
int i = 0;
shuffle(nums, MAX_PORT - MIN_PORT + 1);
// send probe packets, check if connected with peer, if yes, stop probing
for (; i < NUM_OF_PORTS;) {
if (nums[i] == peer.port) {
nums[i] = nums[1000]; // TODO
continue;
}
if ((sock_array[i] = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
printf("failed to create socket, send %d probe packets\n", i);
break;
}
peer_addr.sin_port = htons(nums[i]);
// let OS choose available ports
if (send_dummy_udp_packet(sock_array[i], peer_addr) < 0) {
printf("may trigger flooding protection\n");
break;
}
/* add socket to FD_SET and check if connected
* set both fields of the timeval structure to zero
* to make select() return immediately
*/
struct timeval tv = {0, 0};
int fd = wait_for_peer(sock_array, ++i, &tv);
if (fd > 0) {
// connected
on_connected(fd);
// TODO
return NULL;
}
}
struct timeval tv = {10, 0};
int fd = wait_for_peer(sock_array, i, &tv);
if (fd > 0) {
on_connected(fd);
} else {
int j = 0;
for (j = 0; j < i; ++j) {
close(sock_array[j]);
}
}
// TODO wait for next notification
return NULL;
}
int init(struct peer_info self, struct sockaddr_in punch_server, client* c) {
int i, temp;
for (i = 0, temp = MIN_PORT; temp <= MAX_PORT; i++, temp++) {
nums[i] = temp;
}
int server_sock = socket(AF_INET, SOCK_STREAM, 0);
if (connect(server_sock, (struct sockaddr *)&punch_server, sizeof(punch_server)) < 0) {
printf("failed to connect to punch server\n");
return -1;
}
c->sfd = server_sock;
c->msg_buf = c->buf;
c->msg_buf = encode16(c->msg_buf, Enroll);
c->msg_buf = encode(c->msg_buf, self.ip, 16);
c->msg_buf = encode16(c->msg_buf, self.port);
c->msg_buf = encode16(c->msg_buf, self.type);
if (-1 == send_to_punch_server(c, 0)) {
printf("failed to enroll\n");
return -1;
}
// wait for message from punch server in another thread
pthread_t thread_id;
pthread_create(&thread_id, NULL, server_notify_handler, (void*)&server_sock);
pthread_join(thread_id, NULL);
return 0;
}
void on_connected(int sock) {
char buf[MSG_BUF_SIZE] = {0};
struct sockaddr_in remote_addr;
socklen_t fromlen = sizeof remote_addr;
recvfrom(sock, buf, MSG_BUF_SIZE, 0, (struct sockaddr *)&remote_addr, &fromlen);
printf("connected with peer from %s:%d\n", inet_ntoa(remote_addr.sin_addr), ntohs(remote_addr.sin_port));
printf("recv %s\n", buf);
// restore the ttl
int ttl = 64;
setsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
sendto(sock, "hello, peer", strlen("hello, peer"), 0, (struct sockaddr *)&remote_addr, sizeof(remote_addr));
}
int connect_to_peer(client* cli, uint32_t peer_id) {
struct peer_info peer;
if (get_peer_info(cli, peer_id, &peer) < 0) {
printf("peer %d offline\n", peer_id);
return -1;
}
// choose less restricted peer as initiator
switch(peer.type) {
case OpenInternet:
// todo
break;
case FullCone:
break;
case RestricNAT:
// todo
break;
case RestricPortNAT:
// todo
break;
case SymmetricNAT:
if (cli->type == SymmetricNAT) {
connect_to_symmetric_nat(cli, peer_id, peer);
}
else {
// todo
}
break;
default:
printf("unknown nat type\n");
return -1;
// log
}
return 0;
}
#include <stdint.h>
#include "nat_type.h"
typedef struct client client;
struct client {
int sfd;
uint32_t id;
char buf[128];
char* msg_buf;
nat_type type;
char ext_ip[16];
uint16_t ext_port;
};
struct peer_info {
char ip[16];
uint16_t port;
uint16_t type;
};
enum msg_type {
Enroll = 0x01,
GetPeerInfo = 0x02,
NotifyPeer = 0x03,
};
// public functions
int init(struct peer_info self, struct sockaddr_in punch_server, client* c);
int connect_to_peer(client* cli, uint32_t peer_id);
void on_connected(int sock);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include "nat_type.h"
static const char* nat_types[] = {
"blocked",
"open internet",
"full cone",
"restricted NAT",
"port-restricted cone",
"symmetric NAT",
"error"
};
char* encode16(char* buf, uint16_t data)
{
uint16_t ndata = htons(data);
memcpy(buf, (void*)(&ndata), sizeof(uint16_t));
return buf + sizeof(uint16_t);
}
char* encode32(char* buf, uint32_t data)
{
uint32_t ndata = htonl(data);
memcpy(buf, (void*)(&ndata), sizeof(uint32_t));
return buf + sizeof(uint32_t);
}
char* encodeAtrUInt32(char* ptr, uint16_t type, uint32_t value)
{
ptr = encode16(ptr, type);
ptr = encode16(ptr, 4);
ptr = encode32(ptr, value);
return ptr;
}
char* encode(char* buf, const char* data, unsigned int length)
{
memcpy(buf, data, length);
return buf + length;
}
static int stun_parse_atr_addr( char* body, unsigned int hdrLen, StunAtrAddress* result )
{
if (hdrLen != 8 /* ipv4 size */ && hdrLen != 20 /* ipv6 size */ ) {
return -1;
}
body++; // Skip pad
result->family = *body++;
uint16_t nport;
memcpy(&nport, body, 2);
body+=2;
result->port = ntohs(nport);
if (result->family == IPv4Family) {
uint32_t naddr;
memcpy(&naddr, body, sizeof(uint32_t)); body+=sizeof(uint32_t);
result->addr.ipv4 = ntohl(naddr);
// Note: addr.ipv4 is stored in host byte order
return 0;
} else if (result->family == IPv6Family) {
printf("ipv6 is not implemented yet");
}
return -1;
}
static void gen_random_string(char *s, const int len) {
static const char alphanum[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
int i = 0;
for (; i < len; ++i) {
s[i] = alphanum[rand() % (sizeof(alphanum) - 1)];
}
s[len] = 0;
}
static int send_bind_request(int sock, const char* remote_host, uint16_t remote_port, uint32_t change_ip, uint32_t change_port, StunAtrAddress* addr_array) {
char* buf = malloc(MAX_STUN_MESSAGE_LENGTH);
char* ptr = buf;
StunHeader h;
h.msgType = BindRequest;
gen_random_string((char*)&h.magicCookieAndTid, 16);
ptr = encode16(ptr, h.msgType);
char* lengthp = ptr;
ptr = encode16(ptr, 0);
ptr = encode(ptr, (const char*)&h.id, sizeof(h.id));
if (change_ip || change_port) {
ptr = encodeAtrUInt32(ptr, ChangeRequest, change_ip | change_port);
// length of stun body
encode16(lengthp, ptr - buf - sizeof(StunHeader));
}
struct hostent *server = gethostbyname(remote_host);
if (server == NULL) {
fprintf(stderr, "no such host, %s\n", remote_host);
free(buf);
return -1;
}
struct sockaddr_in remote_addr;
remote_addr.sin_family = AF_INET;
memcpy(&remote_addr.sin_addr.s_addr, server->h_addr_list[0], server->h_length);
remote_addr.sin_port = htons(remote_port);
if (-1 == sendto(sock, buf, ptr - buf, 0, (struct sockaddr *)&remote_addr, sizeof(remote_addr))) {
free(buf);
return -1;
}
socklen_t fromlen = sizeof remote_addr;
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
if (recvfrom(sock, buf, 512, 0, (struct sockaddr *)&remote_addr, &fromlen) <= 0) {
free(buf);
return -1;
}
StunHeader reply_header;
memcpy(&reply_header, buf, sizeof(StunHeader));
uint16_t msg_type = ntohs(reply_header.msgType);
if (msg_type == BindResponse) {
char* body = buf + sizeof(StunHeader);
uint16_t size = ntohs(reply_header.msgLength);
StunAtrHdr* attr;
unsigned int attrLen;
unsigned int attrLenPad;
int atrType;
while (size > 0) {
attr = (StunAtrHdr*)(body);
attrLen = ntohs(attr->length);
// attrLen may not be on 4 byte boundary, in which case we need to pad to 4 bytes when advancing to next attribute
attrLenPad = attrLen % 4 == 0 ? 0 : 4 - (attrLen % 4);
atrType = ntohs(attr->type);
if ( attrLen + attrLenPad + 4 > size ) {
free(buf);
return -1;
}
body += 4; // skip the length and type in attribute header
size -= 4;
switch (atrType) {
case MappedAddress:
if (stun_parse_atr_addr(body, attrLen, addr_array)) {
free(buf);
return -1;
}
break;
case ChangedAddress:
if (stun_parse_atr_addr( body, attrLen, addr_array + 1)) {
free(buf);
return -1;
}
break;
case SourceAddress:
if (stun_parse_atr_addr( body, attrLen, addr_array + 2)) {
free(buf);
return -1;
}
break;
default:
// ignore
break;
}
body += attrLen + attrLenPad;
size -= attrLen + attrLenPad;
}
}
free(buf);
return 0;
}
const char* get_nat_desc(nat_type type) {
return nat_types[type];
}
nat_type detect_nat_type(const char* stun_host, uint16_t stun_port, const char* local_host, uint16_t local_port, char* ext_ip, uint16_t* ext_port) {
uint32_t mapped_ip = 0;
uint16_t mapped_port = 0;
int s;
if((s = socket(AF_INET, SOCK_DGRAM, 0)) <= 0) {
return Error;
}
nat_type nat_type;
int reuse_addr = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_addr, sizeof(reuse_addr));
struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = inet_addr(local_host);
local_addr.sin_port = htons(local_port);
if (bind(s, (struct sockaddr *)&local_addr, sizeof(local_addr))) {
if (errno == EADDRINUSE) {
printf("addr in use, try another port\n");
nat_type = Error;
goto cleanup_sock;
}
}
// 0 for mapped addr, 1 for changed addr
StunAtrAddress bind_result[2];
memset(bind_result, 0, sizeof(StunAtrAddress) * 2);
if (send_bind_request(s, stun_host, stun_port, 0, 0, bind_result)) {
nat_type = Blocked;
goto cleanup_sock;
}
mapped_ip = bind_result[0].addr.ipv4; // in host byte order
mapped_port = bind_result[0].port;
uint32_t changed_ip = bind_result[1].addr.ipv4;
uint16_t changed_port = bind_result[1].port;
struct in_addr mapped_addr;
mapped_addr.s_addr = htonl(mapped_ip);
/*
* it's complicated to get the RECEIVER address of UDP packet,
* For Linux, use the setsockopt() called IP_PKTINFO
* which will get you a parameter over recvmsg() called
* IP_PKTINFO, which carries a struct in_pktinfo,
* which has a 4 byte IP address hiding in its ipi_addr field.
*/
/* TODO use getifaddrs() to get interface address,
* then compare it with mapped address to determine
* if it's open internet
*/
if (!strcmp(local_host, inet_ntoa(mapped_addr))) {
nat_type = OpenInternet;
goto cleanup_sock;
} else {
if (changed_ip != 0 && changed_port != 0) {
if (send_bind_request(s, stun_host, stun_port, ChangeIpFlag, ChangePortFlag, bind_result)) {
struct in_addr addr = {changed_ip};
char* alt_host = inet_ntoa(addr);
memset(bind_result, 0, sizeof(StunAtrAddress) * 2);
if (send_bind_request(s, alt_host, changed_port, 0, 0, bind_result)) {
printf("failed to send request to alterative server\n");
nat_type = Error;
goto cleanup_sock;
}
if (mapped_ip != bind_result[0].addr.ipv4 || mapped_port != bind_result[0].port) {
nat_type = SymmetricNAT;
goto cleanup_sock;
}
if (send_bind_request(s, alt_host, changed_port, 0, ChangePortFlag, bind_result)) {
nat_type = RestricPortNAT;
goto cleanup_sock;
}
nat_type = RestricNAT;
goto cleanup_sock;
}
else {
nat_type = FullCone;
goto cleanup_sock;
}
} else {
printf("no alterative server, can't detect nat type\n");
nat_type = Error;
goto cleanup_sock;
}
}
cleanup_sock:
close(s);
struct in_addr ext_addr;
ext_addr.s_addr = htonl(mapped_ip);
strcpy(ext_ip, inet_ntoa(ext_addr));
*ext_port = mapped_port;
return nat_type;
}
#include <stdint.h>
typedef enum {
Blocked,
OpenInternet,
FullCone,
RestricNAT,
RestricPortNAT,
SymmetricNAT,
Error,
} nat_type;
#define DEFAULT_STUN_SERVER_PORT 3478
#define DEFAULT_LOCAL_PORT 34780
#define MAX_STUN_MESSAGE_LENGTH 512
// const static constants cannot be used in case label
#define MappedAddress 0x0001
#define SourceAddress 0x0004
#define ChangedAddress 0x0005
// define stun constants
const static uint8_t IPv4Family = 0x01;
const static uint8_t IPv6Family = 0x02;
const static uint32_t ChangeIpFlag = 0x04;
const static uint32_t ChangePortFlag = 0x02;
const static uint16_t BindRequest = 0x0001;
const static uint16_t BindResponse = 0x0101;
const static uint16_t ResponseAddress = 0x0002;
const static uint16_t ChangeRequest = 0x0003; /* removed from rfc 5389.*/
const static uint16_t MessageIntegrity = 0x0008;
const static uint16_t ErrorCode = 0x0009;
const static uint16_t UnknownAttribute = 0x000A;
const static uint16_t XorMappedAddress = 0x0020;
typedef struct { uint32_t longpart[4]; } UInt128;
typedef struct { uint32_t longpart[3]; } UInt96;
typedef struct
{
uint32_t magicCookie; // rfc 5389
UInt96 tid;
} Id;
typedef struct
{
uint16_t msgType;
uint16_t msgLength; // length of stun body
union
{
UInt128 magicCookieAndTid;
Id id;
};
} StunHeader;
typedef struct
{
uint16_t type;
uint16_t length;
} StunAtrHdr;
typedef struct
{
uint8_t family;
uint16_t port;
union
{
uint32_t ipv4; // in host byte order
UInt128 ipv6; // in network byte order
} addr;
} StunAtrAddress;
char* encode16(char* buf, uint16_t data);
char* encode32(char* buf, uint32_t data);
char* encode(char* buf, const char* data, unsigned int length);
nat_type detect_nat_type(const char* stun_host, uint16_t stun_port, const char* local_host, uint16_t local_port, char* ext_ip, uint16_t* ext_port);
const char* get_nat_desc(nat_type type);
package main
import (
"encoding/binary"
"fmt"
"net"
"sync"
)
type nat_info struct {
Ip [16]byte
Port uint16
Nat_type uint16
}
const (
Enroll = 1
GetPeerInfo = 2
NotifyPeer = 3
)
var seq uint32 = 1
var peers map[uint32]nat_info
var peers_conn map[uint32]net.Conn
var m sync.Mutex
func main() {
peers = make(map[uint32]nat_info)
peers_conn = make(map[uint32]net.Conn)
l, _ := net.Listen("tcp", ":9988")
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
continue
}
go handleConn(conn)
}
}
// 2 bytes for message type
func handleConn(c net.Conn) {
defer c.Close()
var peerID uint32 = 0
for {
// read message type first
data := make([]byte, 2)
_, err := c.Read(data)
if err != nil {
m.Lock()
fmt.Printf("error: %v, peer %d disconnected\n", err, peerID)
delete(peers, peerID)
delete(peers_conn, peerID)
m.Unlock()
return
}
switch binary.BigEndian.Uint16(data[:]) {
case Enroll:
var peer nat_info
err = binary.Read(c, binary.BigEndian, &peer)
if err != nil {
return
}
fmt.Println("peer enrolled, addr: ", string(peer.Ip[:]), peer.Port, peer.Nat_type)
m.Lock()
seq++
peerID = seq
peers[peerID] = peer
peers_conn[peerID] = c
fmt.Println("new peer, id : ", peerID)
m.Unlock()
case GetPeerInfo:
var peer_id uint32
binary.Read(c, binary.BigEndian, &peer_id)
if val, ok := peers[peer_id]; ok {
binary.Write(c, binary.BigEndian, val)
} else {
var offline uint8 = 0
binary.Write(c, binary.BigEndian, offline)
fmt.Printf("%d offline\n", peer_id)
}
case NotifyPeer:
var peer_id uint32
binary.Read(c, binary.BigEndian, &peer_id)
fmt.Println("notify to peer", peer_id)
if val, ok := peers_conn[peer_id]; ok {
if err = binary.Write(val, binary.BigEndian, peers[peerID]); err != nil {
// unable to notify peer
fmt.Println("offline")
}
} else {
fmt.Println("offline")
}
default:
fmt.Println("illegal message")
}
}
return
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment