Skip to content

Commit b516884

Browse files
committedMar 6, 2025
init
0 parents  commit b516884

File tree

11 files changed

+927
-0
lines changed

11 files changed

+927
-0
lines changed
 

‎.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ratnat
2+
*.o
3+
config.txt
4+
.vscode

‎LICENSE

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ISC License
2+
3+
Copyright 2025
4+
Pierce Gray
5+
6+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
7+
8+
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

‎Makefile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
objects := $(patsubst %.c,%.o,$(wildcard src/*.c))
2+
3+
ratnat : $(objects)
4+
gcc -o ratnat $(objects) -lsodium
5+
6+
.PHONY : clean
7+
clean :
8+
rm ratnat $(objects)

‎README.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# ratnat
2+
3+
A program to expose UDP services running behind NAT to the internet via a secure tunnel. I created this so I could expose a Minecraft server when I couldn't port forward.
4+
5+
## Usage
6+
7+
Configuration is handle via a file. To generate a template, run:
8+
9+
```console
10+
ratnat config-gen <path>
11+
```
12+
13+
```console
14+
ratnat client <config-file>
15+
```
16+
17+
```console
18+
ratnat server <config-file>
19+
```
20+
21+
## Security
22+
23+
The client and tunnel are mutually authenticated using a shared secret key. All traffic across the tunnel is encypted with a session key using ChaCha20-Poly1305.

‎src/client.c

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
///////////////////////////////////////////////////////////////////
2+
// Author: Pierce Gray
3+
// File Name: client.c
4+
// Purpose: This file implements the client side of the UDP tunnel.
5+
// It listens for packets from the server and forwards them
6+
// to the Minecraft server.
7+
///////////////////////////////////////////////////////////////////
8+
9+
10+
#include <stdio.h>
11+
#include <stdlib.h>
12+
#include <unistd.h>
13+
#include <string.h>
14+
#include <sys/socket.h>
15+
#include <sys/types.h>
16+
#include <netinet/in.h>
17+
#include <arpa/inet.h>
18+
#include <netinet/in.h>
19+
#include <fcntl.h>
20+
#include <time.h>
21+
#include <stdint.h>
22+
#include "packet.h"
23+
#include "config.h"
24+
#include <poll.h>
25+
#include <sodium.h>
26+
27+
28+
#define BUFFER_SIZE 1<<16
29+
#define MAX_CONNECTIONS 100
30+
#define MAX_CONNECTION_INACTIVITY 60
31+
32+
33+
struct connection {
34+
int id;
35+
int socket;
36+
time_t last_activity;
37+
struct sockaddr_in addr;
38+
};
39+
40+
41+
int client(char *config_file)
42+
{
43+
struct config config;
44+
read_config(config_file, &config);
45+
46+
// Define the Minecraft server address
47+
struct sockaddr_in minecraft_addr;
48+
minecraft_addr.sin_family = AF_INET;
49+
minecraft_addr.sin_port = htons(config.internal_port);
50+
minecraft_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
51+
52+
// Define the tunnel address
53+
struct sockaddr_in tunnel_addr;
54+
tunnel_addr.sin_family = AF_INET;
55+
tunnel_addr.sin_port = htons(config.tunnel_port);
56+
tunnel_addr.sin_addr.s_addr = config.tunnel_ip;
57+
58+
// Create the tunnel socket
59+
int tunnel_socket = socket(AF_INET, SOCK_DGRAM, 0);
60+
61+
struct connection connections[MAX_CONNECTIONS];
62+
for (int i = 0; i < MAX_CONNECTIONS; i++) connections[i].socket = -1;
63+
int num_connections = 0;
64+
struct packet packet;
65+
uint64_t message_nonce_counter = 100;
66+
67+
// Generate a session key
68+
uint8_t session_key[32];
69+
randombytes_buf(session_key, 32);
70+
print_hex(session_key, 32);
71+
72+
uint64_t connection_request_nonce = time(NULL);
73+
74+
// Connection request is: CONNECTION_REQUEST (u64) | PACKET (NONCE (u64) | SESSION_KEY (32b) | tag (16b))
75+
char con_req_bytes[8 * 2 + 32 + 16];
76+
*(uint64_t *) con_req_bytes = CONNECTION_REQUEST;
77+
struct packet *con_req = (struct packet *) (con_req_bytes + 8);
78+
memcpy(con_req->data, session_key, 32);
79+
int packet_size = encrypt_packet(config.secret_key, connection_request_nonce, CLIENT_FLAG, (struct encrypted_packet *) con_req, 32);
80+
81+
sendto(tunnel_socket, con_req_bytes, packet_size + 8, 0, (struct sockaddr *) &tunnel_addr, sizeof(tunnel_addr));
82+
83+
printf("Connecting to tunnel at port %d... and minecraft at port %d\n", config.tunnel_port, config.internal_port);
84+
print_hex((uint8_t*) con_req_bytes, packet_size + 8);
85+
// // Connect to the tunnel
86+
// char register_message[8];
87+
// *(uint64_t *) register_message = CONNECTION_REQUEST;
88+
// sendto(tunnel_socket, register_message, strlen(register_message), 0, (struct sockaddr *) &tunnel_addr, sizeof(tunnel_addr));
89+
// printf("Connenting to tunnel...\n");
90+
91+
// // Receive the connection challenge
92+
// buffer_len = recvfrom(tunnel_socket, buffer, BUFFER_SIZE, 0, NULL, NULL);
93+
// if (buffer_len == -1)
94+
// {
95+
// printf("Failed to connect to the tunnel\n"); return 1;
96+
// }
97+
// if (*(uint64_t *) buffer != CONNECTION_CHALLENGE || buffer_len != 64 + 8)
98+
// {
99+
// printf("Invalid connection challenge\n"); return 1;
100+
// }
101+
102+
// uint8_t key[32];
103+
// for (int i = 0; i < 32; i++) key[i] = i; // TODO: Read the key from a file
104+
105+
// // Generate the proof
106+
// memmove(buffer, buffer + 8, buffer_len);
107+
// buffer_len -= 8;
108+
// buffer_len = encrypt_packet(key, CONNECTION_PROOF, CLIENT_FLAG, buffer, buffer_len);
109+
// printf("Given nonce:\n");
110+
// print_hex(buffer, buffer_len);
111+
112+
// // Send the proof
113+
// sendto(tunnel_socket, buffer, buffer_len, 0, (struct sockaddr *) &tunnel_addr, sizeof(tunnel_addr));
114+
115+
fcntl(tunnel_socket, F_SETFL, O_NONBLOCK);
116+
117+
// Statistics
118+
uint64_t packets_count = 0;
119+
uint64_t packets_size = 0;
120+
121+
// Main loop
122+
for (;;)
123+
{
124+
// Wait until a packet is available
125+
struct pollfd poll_fds[MAX_CONNECTIONS + 1];
126+
poll_fds[0].fd = tunnel_socket;
127+
poll_fds[0].events = POLLIN;
128+
for (int i = 1; i <= num_connections; i++)
129+
{
130+
poll_fds[i].fd = connections[i].socket;
131+
poll_fds[i].events = POLLIN;
132+
}
133+
poll(poll_fds, num_connections + 1, -1);
134+
135+
// Receive a packet from the tunnel
136+
int data_len = recvfrom(tunnel_socket, packet.data, BUFFER_SIZE, 0, NULL, NULL);
137+
if (data_len == -1) {goto recv_from_minecraft;}
138+
139+
// Decrypt the packet
140+
int packet_len = decrypt_packet(session_key, TUNNEL_FLAG, (struct encrypted_packet *) &packet, data_len);
141+
if (packet_len == -1) {
142+
printf("Forged packet\n");
143+
continue; // Forged packet, ignore it.
144+
}
145+
146+
packets_count++;
147+
packets_size += packet_len;
148+
printf("Current Size: %8d Avg: %8lu\n", packet_len, packets_size / packets_count);
149+
150+
// Find the connection
151+
int connection_found = 0;
152+
int connection_index = -1;
153+
for (int i = 0; i < num_connections; i++)
154+
{
155+
if (connections[i].id == packet.connection_id)
156+
{
157+
connection_found = 1;
158+
connection_index = i;
159+
break;
160+
}
161+
}
162+
163+
// If the connection was not found, create a new one
164+
if (!connection_found)
165+
{
166+
if (num_connections == MAX_CONNECTIONS)
167+
{
168+
// Try to find an inactive connection and replace it
169+
time_t current_time = time(NULL);
170+
for (int i = 0; i < num_connections; i++)
171+
{
172+
if (current_time - connections[i].last_activity > MAX_CONNECTION_INACTIVITY)
173+
{
174+
close(connections[i].socket);
175+
connections[i] = connections[num_connections - 1];
176+
num_connections--;
177+
break;
178+
}
179+
}
180+
// If there is no connection to replace, print an error and ignore the packet
181+
if (num_connections == MAX_CONNECTIONS)
182+
{
183+
printf("Too many connections\n");
184+
continue;
185+
}
186+
}
187+
188+
struct connection new_connection;
189+
new_connection.id = packet.connection_id;
190+
new_connection.socket = socket(AF_INET, SOCK_DGRAM, 0);
191+
new_connection.addr.sin_family = AF_INET;
192+
new_connection.addr.sin_port = 0; // Bind to any available port
193+
new_connection.addr.sin_addr.s_addr = inet_addr("127.0.0.1");
194+
195+
bind(new_connection.socket, (struct sockaddr *) &new_connection.addr, sizeof(new_connection.addr));
196+
fcntl(new_connection.socket, F_SETFL, O_NONBLOCK);
197+
198+
connections[num_connections] = new_connection;
199+
num_connections++;
200+
201+
printf("Created new connection with ID %d.\n", new_connection.id);
202+
}
203+
204+
// Update the last activity time
205+
connections[connection_index].last_activity = time(NULL);
206+
207+
// Send the packet to the Minecraft server, skipping the first 4 bytes
208+
sendto(connections[connection_index].socket, packet.data, packet_len - PACKET_HEADER_SIZE, 0, (struct sockaddr *) &minecraft_addr, sizeof(minecraft_addr));
209+
210+
#ifdef TRACE
211+
printf("To minecraft server: message %d of len %d\n", recv_message_counter, buffer_len - 8);
212+
print_hex(buffer + 8, buffer_len - 8);
213+
#endif
214+
215+
recv_from_minecraft:;
216+
217+
// Receive packets from the Minecraft server
218+
for (int i = 0; i < num_connections; i++)
219+
{
220+
if (connections[i].socket == -1) continue;
221+
222+
data_len = recvfrom(connections[i].socket, packet.data, BUFFER_SIZE, 0, NULL, NULL);
223+
if (data_len == -1) continue;
224+
225+
packet.connection_id = connections[i].id;
226+
message_nonce_counter++;
227+
packet_len = encrypt_packet(session_key, message_nonce_counter, CLIENT_FLAG, (struct encrypted_packet *) &packet, data_len);
228+
sendto(tunnel_socket, &packet, packet_len, 0, (struct sockaddr *) &tunnel_addr, sizeof(tunnel_addr));
229+
230+
packets_count++;
231+
packets_size++;
232+
}
233+
}
234+
}

0 commit comments

Comments
 (0)
Please sign in to comment.