Skip to content

Commit

Permalink
WIP UDP TUNNEL
Browse files Browse the repository at this point in the history
  • Loading branch information
adenovan committed Jul 10, 2017
1 parent 6f51d52 commit 03a9c31
Show file tree
Hide file tree
Showing 4 changed files with 456 additions and 7 deletions.
5 changes: 2 additions & 3 deletions Core/HLE/proAdhoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1353,7 +1353,7 @@ int getLocalIp(sockaddr_in * SocketAddress){
struct hostent *pHost = 0;
pHost = ::gethostbyname(szHostName);
if(pHost) {
memcpy(&SocketAddress->sin_addr, pHost->h_addr_list[0], pHost->h_length);
memcpy(&SocketAddress->sin_addr, &localip, sizeof(uint32_t));
return 0;
}
return -1;
Expand Down Expand Up @@ -1707,5 +1707,4 @@ const char* getMatchingOpcodeStr(int code) {
buf = "UNKNOWN";
}
return buf;
}

}
302 changes: 302 additions & 0 deletions Core/HLE/proAdhocServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2037,3 +2037,305 @@ int server_loop(int server)
// Return Success
return 0;
}

bool tunnelRunning = false;
bool tcpTunnelRunning = false;
bool udpTunnelRunning = false;
int utunnelsocket = 0;
tcpGamePortWrapper * db_tcp_tunnel = NULL;
udpGamePortWrapper * db_udp_tunnel = NULL;
uint32_t db_tcp_tunnel_count = 0;
uint32_t db_udp_tunnel_count = 0;
std::thread tcpTunnelThread;
std::thread udpTunnelThread;

int tcpTunnel(int port) {
int result = 0;
INFO_LOG(SCENET, "Tcp Tunnel: Begin of Tcp Tunnel Thread");

// Create Listening Socket
int tsocket = (int)socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if (tsocket != -1)
{
// Enable Address Reuse
enable_address_reuse(tsocket);

// Make Socket Nonblocking
change_blocking_mode(tsocket, 1);

struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);

// Bind Local Address to Socket
int bindresult = bind(tsocket, (struct sockaddr *)&addr, sizeof(addr));

// Bound Local Address to Socket
if (bindresult != -1)
{
// Switch Socket into Listening Mode
listen(tsocket, TCP_TUNNEL_BACKLOG);

// Notify User
INFO_LOG(SCENET, "TCP Tunnel : Tunnel Port Listening on TCP Port %u", port);
}
else ERROR_LOG(SCENET, "TCP tunnel: Bind returned %i (Socket error %d)", bindresult, errno);

}

if (tsocket != 1) {
//enter tunnel loop
result = tcpTunnelLoop(tsocket);
}

// Notify User
INFO_LOG(SCENET, "Tcp Tunnel: Tunnel Port shutdown complete");

tcpTunnelRunning = false;

INFO_LOG(SCENET, "Tcp Tunnel: End of Tunnel Tcp Thread");

// Return Result
return result;

}

void storeTcpGameSocket(int stream, uint32_t ip,uint16_t port) {
// Check IP Duplication
tcpGamePortWrapper * g = db_tcp_tunnel;
while (g != NULL && g->ip != ip && g->port != port) g = g->next;

if (g != NULL) { // IP Already existed
uint8_t * ip4 = (uint8_t *)&g->ip;
INFO_LOG(SCENET, "Tcp Tunnel : Already Exist IP %u.%u.%u.%u\n", ip4[0], ip4[1], ip4[2], ip4[3]);
}

// Unique IP Address
else //if(u == NULL)
{
// Allocate Gameport Memory
tcpGamePortWrapper * gameport = (tcpGamePortWrapper *)malloc(sizeof(tcpGamePortWrapper));

if (gameport != NULL)
{
// Clear Memory
memset(gameport, 0, sizeof(tcpGamePortWrapper));

// Save Socket
gameport->stream = stream;

// Save IP
gameport->ip = ip;

// Link into tunnel List
gameport->next = db_tcp_tunnel;
if (db_tcp_tunnel != NULL) db_tcp_tunnel->prev = gameport;
db_tcp_tunnel = gameport;


// LOG successful tunnel
uint8_t * ipa = (uint8_t *)&gameport->ip;
INFO_LOG(SCENET, "Tcp Tunnel : New Connection from %u.%u.%u.%u:%u", ipa[0], ipa[1], ipa[2], ipa[3],port);

// increate socket Counter
db_tcp_tunnel_count++;

// Exit Function
return;
}
}

//already exist or malloc failed;
closesocket(stream);
}

int tcpTunnelLoop(int tcptunnel) {

tcpTunnelRunning = true;
while (tcpTunnelRunning) {
// store connection block
{
int newStream = 0;
do
{
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
memset(&addr, 0, sizeof(addr));

//accept new incoming connection
newStream = accept(tcptunnel, (struct sockaddr *)&addr, &addrlen);
if (newStream != -1)
{
// Switch Socket into Non-Blocking Mode
change_blocking_mode(newStream, 1);
}

// Login User (Stream)
if (newStream != -1) {
u32_le sip = addr.sin_addr.s_addr;
uint16_t sport = addr.sin_port;
storeTcpGameSocket(newStream, sip, sport);
}
} while (newStream != -1);
}

tcpGamePortWrapper * gport = db_tcp_tunnel;
while (gport != NULL) {
tcpGamePortWrapper * next = gport->next;


gport = next;
}

// Prevent needless CPU Overload (1ms Sleep)
sleep_ms(1);

// Don't do anything if it's paused, otherwise the log will be flooded
while (tcpTunnelRunning && Core_IsStepping()) sleep_ms(1);
}
closesocket(tcptunnel);
return 0;
}

void sendUdpPacket(const char * data,uint16_t len, uint32_t ip,uint16_t port) {
sockaddr_in target;
target.sin_family = AF_INET;
target.sin_addr.s_addr = ip;
target.sin_port = htons(port);

int sent = sendto(utunnelsocket, data, len, 0, (sockaddr *)&target, sizeof(target));

int error = errno;
if (sent == SOCKET_ERROR) {
uint8_t *dip = (uint8_t *)&target.sin_addr.s_addr;
ERROR_LOG(SCENET, "Socket Error (%i) on Forward Packet TO:%u.%u.%u.%u:%u (size=%i)", error,dip[0],dip[1],dip[2],dip[3], ntohs(target.sin_port), sizeof(data));
}

if (sent == len) {
uint8_t *dip = (uint8_t *)&target.sin_addr.s_addr;
INFO_LOG(SCENET, "Forward Packet TO:%u.%u.%u.%u:%u (sent=%i,size=%i)", dip[0], dip[1], dip[2], dip[3], ntohs(target.sin_port),sent,len);
}
}

void storeUdpGameSocket(uint32_t ip, uint16_t port, char * buff, int packetlen) {
// Check IP Duplication
udpGamePortWrapper * g = db_udp_tunnel;
while (g != NULL && g->ip != ip && g->port != port) g = g->next;

if (g != NULL) {
size_t length = packetlen * sizeof(uint8_t);

This comment has been minimized.

Copy link
@anr2me

anr2me Jul 11, 2017

isn't this suppose to calculate sizeof(udpTunnelData) also?

This comment has been minimized.

Copy link
@adenovan

adenovan Jul 11, 2017

Author Owner

yeah its must calculate the sizeof(udpTunnelData) also, its often caused heap coruption when do memory operation.
its hard to allocate the memory because the size of data is dynamic. i push a new commit its ok when do a copy of data from buffer on windows but its crashed on android. no ideas why

This comment has been minimized.

Copy link
@anr2me

anr2me Jul 12, 2017

if it's crashed on android but worked on windows (but leading to heap corruption) it's because you're trying to access area beyond what you allocate, the "length" must not big enough to cover all the data you tried to access.

udpTunnelData have a header on front and followed by data at the end right?
but what you allocate is only for the packet data from socket isn't?

This comment has been minimized.

Copy link
@adenovan

adenovan Jul 12, 2017

Author Owner

yes udpTunnelData have a header and the game data is at the end.

that received size is equal to udpTunnelData header + game data. i wrap the data on the pdpsend and forward it to tunnel. when do logging the received size is equal to the udpTunnelData header + game data len. so i just using it.

its is good if we allocate the packet to the buffersize length? can it cause a heap problem ? look like the heap is ok when allocate it like this.
udpTunnelData * packet = (udpTunnelData *) malloc(8192);

when doing memcpy(&packet, buff, length) its always corrupt the heap.
it is a problem when do memcpy on the packet when the packet size is lower than the buffer size?
example packet malloc = 219, buffsize = 8192, copy = memcpy(219 ,buff length is 8192, copying 219 length);

//this cast caused crash how to workaround?
udpTunnelData * packet = (udpTunnelData *) malloc(length);
if (packet != NULL) {
memcpy(&packet, buff, length);
uint8_t * sip = (uint8_t *)&packet->sourceIP;
uint8_t * dip = (uint8_t *)&packet->destIP;
INFO_LOG(SCENET, "Tunnel UDP: From:%u.%u.%u.%u:%u TO:%u.%u.%u.%u:%u datalen:%u", sip[0], sip[1], sip[2], sip[3], packet->sourcePort, dip[0], dip[1], dip[2], dip[3], packet->destPort, packet->datalen);
if (isLocalMAC(&packet->destMac)) {
//local port exist forward to it
if (g->ip == packet->destIP && g->port == packet->destPort) {
sendUdpPacket((const char*)&packet->data, packet->datalen, packet->destIP, packet->destPort);
}
}
else {
//forward packet to peer tunnel
sendUdpPacket((const char *)&packet, packetlen, packet->destIP, 30000);
}
}
}
// Unique UDP port
else
{
// Allocate gameport Memory
udpGamePortWrapper * gameport = (udpGamePortWrapper *)malloc(sizeof(udpGamePortWrapper));

if (gameport != NULL)
{
// Clear Memory
memset(gameport, 0, sizeof(udpGamePortWrapper));

// Save IP
gameport->ip = ip;
gameport->port = port;

// Link into tunnel List
gameport->next = db_udp_tunnel;
if (db_udp_tunnel != NULL) db_udp_tunnel->prev = gameport;
db_udp_tunnel = gameport;

// LOG successful tunnel
uint8_t * ipa = (uint8_t *)&gameport->ip;
INFO_LOG(SCENET, "Tunnel UDP: Store IP %u.%u.%u.%u:%u to db UDP", ipa[0], ipa[1], ipa[2], ipa[3], gameport->port);

// increate ip Counter
db_udp_tunnel_count++;

// Exit Function
return;
}
}
memset(buff, 0, UDP_TUNNEL_BUFFER_SIZE);
}
int udpTunnel(int port) {

int result = 0;
utunnelsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (utunnelsocket != INVALID_SOCKET) {
int one = 1;
setsockopt(utunnelsocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&one, sizeof(one));
setSockBufferSize(utunnelsocket, SO_RCVBUF, UDP_TUNNEL_BUFFER_SIZE);
setSockBufferSize(utunnelsocket, SO_SNDBUF, UDP_TUNNEL_BUFFER_SIZE);
//int timeout = 15;
//setsockopt(usocket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
// Binding Information for tunnel Port
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
int ubind = bind(utunnelsocket, (sockaddr *)&addr, sizeof(addr));
if (ubind != -1) {
INFO_LOG(SCENET, "Tunnel UDP : Tunnel Port Listening on UDP Port %u", port);
}
else {
ERROR_LOG(SCENET, "Socket error (%i) when binding tunnel port %u", ntohs(addr.sin_port));
}
}

if (utunnelsocket != -1) {
result = udpTunnelLoop(utunnelsocket);
}

udpTunnelRunning = false;
INFO_LOG(SCENET, "Tunnel UDP : End of UDP Tunnel");
return result;
}

int udpTunnelLoop(int udptunnel) {

char * buff = (char *) malloc(sizeof(char) * UDP_TUNNEL_BUFFER_SIZE);
udpTunnelRunning = true;
while (udpTunnelRunning) {

sockaddr_in addr_in;
socklen_t addrlen = sizeof(addr_in);
int receive = recvfrom(udptunnel, buff, UDP_TUNNEL_BUFFER_SIZE, 0, (sockaddr *)&addr_in, &addrlen);
if (receive >= 0) {
u32_le sip = addr_in.sin_addr.s_addr;
uint16_t sport = ntohs(addr_in.sin_port);
uint8_t * ip4 = (uint8_t *)&sip;
INFO_LOG(SCENET, "Tunnel UDP: Received %u bytes from %u.%u.%u.%u:%u\n", receive, ip4[0], ip4[1], ip4[2], ip4[3], sport);
storeUdpGameSocket(sip, sport, buff,receive);

This comment has been minimized.

Copy link
@anr2me

anr2me Jul 12, 2017

When someone sent 1 chunk of 6000 bytes of data, the receiver might not always received it in 1 chunk, sometimes it's splitted into several chunks of smaller data, so it's not safe to call recv one time and assume it to return full frame of data.

if buff contains udpTunnelData header+data, make sure total received data (may need to call recvfrom several times when not using blocking socket) matched the size of udpTunnelData header + datalen before storing it with storeUdpGameSocket.

First recv upto the size of the udpTunnelData header in order to access datalen field, after that recv more to cover the whole datalen (datalen minus 1byte if the first byte is part of udpTunnelData header size), after that pass it to storeUdpGameSocket.

Reading less than or more (if there are more than 1 frame of wrapped data in queue) than what to be expected size of 1 frame of wrapped data may ended corrupting the next frame of wrapped data.

This comment has been minimized.

Copy link
@adenovan

adenovan Jul 13, 2017

Author Owner

What is the receive variable behavior if the data splitted into chunk? Does its return chunk size on every receive or increment the receive chunk before?

Im not familiar with udp socket this is the firstime.

On my new code I give the udptunneldata opcode to identify the packet but accessing buff[0] give an unknown int not the opcode I give on the pdpsend.

I Want to make sure its the correct data to be processed. Because sometimes the socket receive uknown data which is not the tunneldata and make the app crash when copying the buff

This comment has been minimized.

Copy link
@anr2me

anr2me Jul 14, 2017

Usually the first field is the length of data to make things simpler since it will be received first.
And i recommend using 65536 for TUNNEL_BUFFER_SIZE just to be safe.

You'll need to loop the recvfrom until expected size is fully received, something like this:

int expsize = sizeof(udpTunnelData); //assuming you also sent 1 byte of data (which is part of header) even when datalen is 0, otherwise minus 1 here instead of at datalen checking below
int ofs = 0;

while (ofs < expsize) {
    receive = recvfrom(udptunnel, (buff+ofs), UDP_TUNNEL_BUFFER_SIZE-ofs, 0, (sockaddr *)&addr_in, &addrlen);
    ofs += receive;
}

uint16_t datalen = (udpTunnelData *)buff->datalen;
if (datalen > 0) expsize += datalen - 1; //minus 1 because first byte of data is already taken by header right? don't forget to check whether it's 0 or not
while (ofs < expsize) {
    receive = recvfrom(udptunnel, (buff+ofs), UDP_TUNNEL_BUFFER_SIZE-ofs, 0, (sockaddr *)&addr_in, &addrlen);
    ofs += receive;
}
// at this point ofs should be matched with expected size (header + data)

This comment has been minimized.

Copy link
@adenovan

adenovan Jul 28, 2017

Author Owner

can you review my last commit? im cant solve the heap corruption , this is related to the struct https://stackoverflow.com/questions/688471/variable-sized-struct-c but i guess the hack is not working on C++ and lead to memory corruption. debugging it 3 days but i can't solve the heap problem. the packet is arrived on other tunneler well.

}

// Prevent needless CPU Overload (1ms Sleep)
sleep_ms(1);

This comment has been minimized.

Copy link
@anr2me

anr2me Jul 13, 2017

You may want to change this to sleep_ms(0) if possible and call the sleep every 100ms instead of on each frame of wrapped data being tunneled to reduce communication lags, since game data need to be transmitted as soon as possible,
unlike friendfinder/adhocserver thread where a few ms delays doesn't makes much difference.


// Don't do anything if it's paused, otherwise the log will be flooded
while (udpTunnelRunning && Core_IsStepping()) sleep_ms(1);
}
closesocket(udptunnel);
return 0;
}
64 changes: 64 additions & 0 deletions Core/HLE/proAdhocServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -382,3 +382,67 @@ int proAdhocServerThread(int port); // (int argc, char * argv[])
//extern int _status;
extern bool adhocServerRunning;
extern std::thread adhocServerThread;


// tunnel function
#define TCP_TUNNEL_BACKLOG 20
extern bool tunnelRunning;
extern bool tcpTunnelRunning;
extern bool udpTunnelRunning;
extern std::thread tcpTunnelThread;
extern std::thread udpTunnelThread;

int tcpTunnel(int port);
void storeTcpGameSocket(int stream, uint32_t ip, uint16_t port);
int tcpTunnelLoop(int tcptunnel);


#define UDP_TUNNEL_BUFFER_SIZE 16384
extern int utunnelsocket;
int udpTunnel (int port);
void storeUdpGameSocket(uint32_t ip,uint16_t port, char * buff,int packetlen);
int udpTunnelLoop(int udptunnel);

//double linked tcp stream
typedef struct tcpGamePortWrapper {
int stream;
uint32_t ip;
uint16_t port;
uint8_t rx[16384];
uint32_t rxpos;
struct tcpGamePortWrapper * next;
struct tcpGamePortWrapper * prev;
} tcpGamePortWrapper;

//double linked udp address
typedef struct udpGamePortWrapper {
uint32_t ip;
uint32_t port;
struct udpGamePortWrapper * next;
struct udpGamePortWrapper * prev;
} udpGamePortWrapper;


typedef struct {
uint16_t datalen;
uint32_t sourceIP;
uint16_t sourcePort;
SceNetEtherAddr sourceMac;
uint32_t destIP;
uint16_t destPort;
SceNetEtherAddr destMac;
uint8_t data[1]; // need dynamic struct hack without triggering violation
} PACK udpTunnelData;

typedef struct {
uint32_t datalen;
uint32_t sourceIP;
uint16_t sourcePort;
SceNetEtherAddr sourceMac;
uint32_t destIP;
uint16_t destPort;
SceNetEtherAddr destMac;
uint8_t data[1];
} PACK tcpTunnelData;


Loading

0 comments on commit 03a9c31

Please sign in to comment.