Skip to content

Instantly share code, notes, and snippets.

@jirihnidek
Last active November 17, 2022 20:04
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jirihnidek/9235597 to your computer and use it in GitHub Desktop.
Save jirihnidek/9235597 to your computer and use it in GitHub Desktop.
UDP client-server application using random port number with respect to statefull firewalls aka hole punching.
.cproject
.project

Example of Firewall UDP Hole Punching

This example contains simple echo client and echo server using method called UDP Hole Punching. The statefull firewall could be overcome. The server listens at TCP port and negotiates UDP ports with client over TCP connection (TCP connection has to be opened in statefull firewall). When server start UDP "connection", then it create hole in statefull firewall and client can send UDP to negotiated port.

Building

To build client and server binary it is neccessary to run following commands

$ mkdir build
$ cd build
$ cmake ..
$ make

Running Server and Client

Server does not need any parameters, but it is necessary to opent TCP port 14000 first. When your distribution use iptables, then command for opening this port could look like this (for more information refere iptables documentation):

# iptables -I INPUT 10 -m state --state NEW -m tcp -p tcp --dport 14000 -j ACCEPT

When TCP port is open, then it is possible to run server

$ ./server

Client has to be executed with two parameters (host running server and echo message) e.g.:

$ ./client 127.0.0.1 "Hello!"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#define PORT 14000
#define MAX_BUF_SIZE 2<<16
int main(int argc, char *argv[])
{
int tcp_socketfd = 0, udp_socketfd = 0;
char *buf, resp[MAX_BUF_SIZE];
int buf_size;
struct addrinfo hints, *result, *rp;
struct sockaddr_in client_udp_sock_addr4;
struct sockaddr_in server_udp_sock_addr4;
socklen_t slen;
int ret;
unsigned short client_port, server_port, port;
char str_addr[INET_ADDRSTRLEN];
if(argc != 3) {
printf("Syntax %s server message\n", argv[0]);
return EXIT_FAILURE;
}
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET; /* Allow IPv4 */
hints.ai_socktype = SOCK_STREAM; /* Allow stream protocol */
hints.ai_flags = 0; /* No flags required */
hints.ai_protocol = IPPROTO_TCP; /* Allow TCP protocol only */
ret = getaddrinfo(argv[1], "14000", &hints, &result);
if( ret != 0 ) {
perror("getaddrinfo():");
return EXIT_FAILURE;
}
/* Try to use addrinfo from getaddrinfo() */
for(rp=result; rp!=NULL; rp=rp->ai_next) {
if( (tcp_socketfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) {
perror("socket()");
continue;
} else {
/* Try to "connect" to this address and do TCP handshake */
if(connect(tcp_socketfd, rp->ai_addr, rp->ai_addrlen) != -1)
break;
close(udp_socketfd);
}
}
if( (udp_socketfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
perror("socket()");
return EXIT_FAILURE;
}
memset(&client_udp_sock_addr4, 0, sizeof(struct sockaddr_in));
client_udp_sock_addr4.sin_family = AF_INET;
client_udp_sock_addr4.sin_addr.s_addr = htonl(INADDR_ANY);
client_udp_sock_addr4.sin_port = 0;
bind(udp_socketfd, (struct sockaddr *)&client_udp_sock_addr4, sizeof(client_udp_sock_addr4));
slen = sizeof(client_udp_sock_addr4);
getsockname(udp_socketfd, (struct sockaddr *)&client_udp_sock_addr4, &slen);
client_port = ntohs(client_udp_sock_addr4.sin_port);
printf("Sending port used by client: %d\n", client_port);
if (send(tcp_socketfd, &client_port, sizeof(unsigned short), 0) == -1) {
perror("send()");
return EXIT_FAILURE;
}
printf("Waiting for port used by server ...\n");
/* Get UDP port used by server */
slen = sizeof(client_udp_sock_addr4);
if( recv(tcp_socketfd, &server_port, sizeof(unsigned short), 0) == -1 ) {
perror("recv()");
return EXIT_FAILURE;
}
printf("Received port used by server: %d\n", server_port);
memset(&server_udp_sock_addr4, 0, sizeof(struct sockaddr_in));
server_udp_sock_addr4.sin_family = AF_INET;
server_udp_sock_addr4.sin_addr.s_addr = ((struct sockaddr_in*)rp->ai_addr)->sin_addr.s_addr;
server_udp_sock_addr4.sin_port = htons(server_port);
inet_ntop(AF_INET, &(server_udp_sock_addr4.sin_addr), str_addr, sizeof(str_addr));
port = ntohs(server_udp_sock_addr4.sin_port);
printf("Connecting to: %s:%d\n", str_addr, port);
/* Try to "connect" to this address ... the client will be able to send and
* receive packets only from this address. */
if(connect(udp_socketfd, (struct sockaddr *)&server_udp_sock_addr4, slen) == -1) {
perror("connect()");
return EXIT_FAILURE;
}
buf = argv[2];
buf_size = strlen(buf);
printf("Sending buffer: %s with len: %d\n", buf, buf_size);
if( send(udp_socketfd, buf, buf_size, 0) == -1 ) {
perror("send()");
return EXIT_FAILURE;
}
printf("Waiting for response ...\n");
buf_size = recv(udp_socketfd, resp, MAX_BUF_SIZE, 0);
if ( buf_size == -1) {
perror("recv()");
return EXIT_FAILURE;
}
resp[buf_size] = '\0';
printf("Received data: %s with len %d\n", resp, buf_size);
/* When there is no firewall, then blind packet was received too. */
if(buf_size == 1 && resp[0] == '0') {
buf_size = recv(udp_socketfd, resp, MAX_BUF_SIZE, 0);
if ( buf_size == -1) {
perror("recv()");
return EXIT_FAILURE;
}
resp[buf_size] = '\0';
printf("Received data: %s with len %d\n", resp, buf_size);
}
printf("Done. Exiting.\n");
close(udp_socketfd);
close(tcp_socketfd);
return EXIT_SUCCESS;
}
# Main CMakeFile.txt
# Minimal version of CMake
cmake_minimum_required (VERSION 2.6)
# Build type
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "Setting build type to 'Release' as none was specified.")
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
# Set the possible values of build type for cmake-gui
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
endif ()
# Define project name
project (Client-Server)
# Pthread is *prefered* (only supported) thread library
set (CMAKE_THREAD_PREFER_PTHREAD)
# Try to find required packages
find_package (Threads REQUIRED)
# Source code for server
set (server_src server.c)
# Source code for client
set (client_src client.c)
# Compiler flags
if (CMAKE_COMPILER_IS_GNUCC)
set (CMAKE_C_FLAGS "-D_REETRANT -Wall -Wextra -pedantic -Wno-long-long")
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ggdb -O0")
elseif( CMAKE_BUILD_TYPE STREQUAL "Release" )
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNDEBUG -O3 -fno-strict-aliasing")
endif ()
endif (CMAKE_COMPILER_IS_GNUCC)
# Basic libraries used by verse_server
set ( server_libs ${CMAKE_THREAD_LIBS_INIT} )
# Set up verse server executable
add_executable (server ${server_src})
target_link_libraries (server
${server_libs} )
# Set up verse server executable
add_executable (client ${client_src})
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#define PORT 14000
#define MAX_BUF_SIZE 2<<16
#define MAX_CONNECTION_NUM 1000
typedef struct CTX {
unsigned int tcp_socketfd;
struct sockaddr_in client_addr4;
unsigned int thread_id;
} CTX;
void *echo_loop(void *arg)
{
struct CTX *ctx = (CTX*)arg;
int buf_size;
char buf[MAX_BUF_SIZE];
struct sockaddr_in client_udp_sock_addr4;
struct sockaddr_in server_udp_sock_addr4;
int udp_socketfd;
unsigned int addr_len = sizeof(client_udp_sock_addr4);
unsigned short server_port, client_port;
char str_addr[INET_ADDRSTRLEN];
unsigned short port;
int flag;
/* Create UDP socket */
udp_socketfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(udp_socketfd == -1) {
perror("socket()");
goto end;
}
/* Set socket to reuse addresses */
flag = 1;
setsockopt(udp_socketfd, SOL_SOCKET, SO_REUSEADDR, (const void*)&flag, (socklen_t)sizeof(flag));
/* Set up server address and port */
memset(&server_udp_sock_addr4, 0, sizeof(struct sockaddr_in));
server_udp_sock_addr4.sin_family = AF_INET;
/* Server accept connection from any address */
server_udp_sock_addr4.sin_addr.s_addr = htonl(INADDR_ANY);
/* Generate random port number */
server_udp_sock_addr4.sin_port = htons(0);
/* Bind address and socket */
if( bind(udp_socketfd, (struct sockaddr*)&server_udp_sock_addr4, sizeof(server_udp_sock_addr4)) == -1 ) {
perror("bind()");
goto end;
}
addr_len = sizeof(server_udp_sock_addr4);
getsockname(udp_socketfd, (struct sockaddr *)&server_udp_sock_addr4, &addr_len);
server_port = ntohs(server_udp_sock_addr4.sin_port);
/* Try to receive port used by client */
if( recv(ctx->tcp_socketfd, &client_port, sizeof(unsigned short), 0) == -1 ) {
perror("recv()");
goto end;
}
printf("Received port used by client: %d\n", client_port);
/* Set up server address and port */
memset(&client_udp_sock_addr4, 0, sizeof(struct sockaddr_in));
client_udp_sock_addr4.sin_family = AF_INET;
/* Server accept connection from any address */
client_udp_sock_addr4.sin_addr.s_addr = ctx->client_addr4.sin_addr.s_addr;
/* Generate random port number */
client_udp_sock_addr4.sin_port = htons(client_port);
/* Try to "connect" to this address ... server will be able to send and
* receive packets only from this address. */
if(connect(udp_socketfd, (struct sockaddr *)&client_udp_sock_addr4, addr_len) == -1) {
perror("connect()");
goto end;
}
/* Print address of client */
inet_ntop(AF_INET, &(client_udp_sock_addr4.sin_addr), str_addr, sizeof(str_addr));
port = ntohs(client_udp_sock_addr4.sin_port);
printf("Connected with: %s:%d\n", str_addr, port);
printf("Sending blind packet to the client\n");
/* Try to send blind message to client to simulate opening connection */
if( send(udp_socketfd, "0", 1, 0) == -1 ) {
perror("send()");
goto end;
}
/* TODO: statefull firewall can send ICMP packet and it should be handled */
/* Send port number used by server to client */
printf("Sending server port to client: %d\n", server_port);
if( send(ctx->tcp_socketfd, &server_port, sizeof(unsigned short), 0) == -1 ) {
perror("send()");
goto end;
}
printf("Trying to receive UDP packet from the client ...\n");
/* Try to receive message from client over UDP */
buf_size = recv(udp_socketfd, buf, MAX_BUF_SIZE, 0);
if(buf_size == -1 ) {
if(errno != EHOSTUNREACH) {
perror("recv()");
goto end;
} else {
/* Try to ignore received ICMP packet and try to call recv again */
buf_size = recv(udp_socketfd, buf, MAX_BUF_SIZE, 0);
if(buf_size == -1) {
perror("recv()");
goto end;
}
}
}
/* Try to send response to client over UDP */
buf[buf_size] = '\0';
printf("Received data: %s with len %d and sending response.\n",
buf, buf_size);
if( send(udp_socketfd, buf, buf_size, 0) == -1 ) {
if(errno != EHOSTUNREACH) {
perror("send()");
goto end;
} else {
/* Try to ignore received ICMP packet and call send() again */
if( send(udp_socketfd, buf, buf_size, 0) == -1 ) {
perror("send()");
goto end;
}
}
}
printf("Existing thread %d\n", ctx->thread_id);
end:
free(ctx);
pthread_exit(NULL);
return NULL;
}
int main(void)
{
int thread_id = 0;
pthread_t threads[MAX_CONNECTION_NUM];
pthread_attr_t attr;
int ret, flag;
int tcp_socketfd, client_socketfd;
struct sockaddr_in tcp_sock_addr4;
struct sockaddr_in client_addr4;
socklen_t addr_len;
/* Create TCP socket */
tcp_socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
/* Set socket to reuse addresses */
flag = 1;
setsockopt(tcp_socketfd, SOL_SOCKET, SO_REUSEADDR, (const void*)&flag, (socklen_t)sizeof(flag));
/* Set up server address and port */
memset(&tcp_sock_addr4, 0, sizeof(struct sockaddr_in));
tcp_sock_addr4.sin_family = AF_INET;
/* Server accept connection from any address */
tcp_sock_addr4.sin_addr.s_addr = htonl(INADDR_ANY);
/* Generate random port number */
tcp_sock_addr4.sin_port = htons(PORT);
/* Bind address and socket */
if( bind(tcp_socketfd, (struct sockaddr*)&tcp_sock_addr4, sizeof(tcp_sock_addr4)) == -1) {
perror("bind()");
return EXIT_FAILURE;
}
/* Listen for new connection */
ret = listen(tcp_socketfd, MAX_CONNECTION_NUM);
if(ret == -1) {
perror("listen()");
return EXIT_FAILURE;
}
/* TODO: join threads and free CTXs */
while(1) {
printf("Waiting for new connection\n");
client_socketfd = accept(tcp_socketfd, (struct sockaddr*)&client_addr4, &addr_len);
if(client_socketfd == -1) {
perror("accept()");
return EXIT_FAILURE;
} else {
CTX *ctx = (CTX*)malloc(sizeof(CTX));
char client_hostname[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(client_addr4.sin_addr), client_hostname, INET_ADDRSTRLEN);
printf("New connection from %s\n", client_hostname);
ctx->tcp_socketfd = client_socketfd;
memcpy(&ctx->client_addr4, &client_addr4, sizeof(struct sockaddr_in));
ctx->thread_id = ++thread_id;
printf("Creating thread: %d\n", thread_id);
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
ret = pthread_create(&threads[thread_id], &attr, echo_loop, (void*)ctx);
if(ret != 0) {
perror("pthread_create()");
return EXIT_FAILURE;
}
}
}
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment