Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VPN Server & Client

This project implements a full VPN tunnel between a client and a server.
The client creates a utun virtual interface, routes all its traffic through it, and sends it to the server over UDP.
The server accepts connections from multiple clients, creates a separate utun for each one, forwards traffic, and provides internet access.

The project is written in C, uses libsodium for encryption (ChaCha20-Poly1305), and runs on macOS (with minor changes required for Linux).


How It Works

The client opens a utun interface, assigns it an IP address (e.g., 10.0.0.2), and sends all traffic through an encrypted UDP tunnel to the server.
The server receives a packet, identifies which client it came from (by IP and port), creates a new utun for a new client (e.g., utun5 with IP 10.0.0.1), and forwards the traffic.

Screenshot 2026-05-08 at 18 40 27

Dependencies

  • macOS
  • libsodium (for encryption)

Installing libsodium on macOS

brew install libsodium

Only libsodium is required for compilation. Everything else is built-in.


Build

make          # builds both server and client
make server   # builds only the server
make client   # builds only the client
make clean    # removes compiled files

Key Generation (Required)

Both the client and the server must use the same 32-byte key.

You can generate the key using a separate program or with the following code:

#include <sodium.h>
// ...
unsigned char key[crypto_aead_xchacha20poly1305_ietf_KEYBYTES];
crypto_aead_xchacha20poly1305_ietf_keygen(key);
FILE *f = fopen("vpn.key", "wb");
fwrite(key, sizeof(key), 1, f);
fclose(f);

Then copy the resulting vpn.key file to both the client and the server.


Configuration

Copy the vpn_serv.conf and vpn_cli.conf files to the directory with the binaries.

Example vpn_serv.conf

UDP_PORT = 12346
TUN_IP = 10.0.0.1
MAX_CLIENTS = 256
LOG_FILE = /var/log/vpn.log

Example vpn_cli.conf

REMOTE_IP = 127.0.0.1
REMOTE_PORT = 12346
TUN_IP = 10.0.0.2
SERVER_TUN_IP = 10.0.0.1
Screenshot 2026-05-08 at 18 42 39 Screenshot 2026-05-08 at 18 42 48

Running

Server

sudo ./bin/vpn_server

The server will listen on the UDP port specified in the configuration file and create TUN interfaces as clients connect.

Screenshot 2026-05-08 at 18 43 44

Client

sudo ./bin/vpn_client

The client will connect to the server, create its own utun interface, and route all its traffic through the tunnel.

Screenshot 2026-05-08 at 18 44 48

Technical Explanation (Tutorial Section)

1. Why UDP Instead of TCP

TCP inside TCP causes problems: packet loss triggers retransmission at both the inner and outer levels, leading to delays and confusion. UDP is simpler, faster, and does not interfere.

2. What Is TUN

TUN is a virtual network interface at Layer 3 (IP). When a program writes to it, the kernel processes the IP packet as if it came from the network. Conversely, packets routed to the TUN interface can be read by the program.

3. How the Client Forces All Traffic Through the VPN

After startup, the client creates a TUN interface and adds a default route through it:

route add default 10.0.0.2

Now every packet not destined for the local network goes into the TUN interface.

4. How the Handshake Works

The client sends the first UDP packet (this can be regular encrypted traffic).
The server receives a packet from an unknown address, creates a new TUN for that client, and assigns it an IP address from the internal network.
All subsequent packets from that client are directed into its dedicated TUN interface.

5. Why There Is No listen/accept

UDP is connectionless. Each datagram arrives independently, and the server does not need to establish a persistent connection. It only needs to remember the sender’s address.

6. How Routing Works on the Server

For each client, the server creates a separate TUN interface and assigns an IP address (typically 10.0.0.1 for the server and 10.0.0.x for the client).
The kernel automatically creates a route to the client. Internet access may require masquerading (NAT), but this project focuses only on tunneling.

7. A Note on select and poll

Network operations must not block each other. The program uses poll (or select) to monitor multiple file descriptors at once:

  • TUN for incoming packets from the kernel.
  • UDP socket for incoming packets from the remote peer.

Control

Press Ctrl+C to stop the program. It will close the TUN interface and display statistics showing how many bytes and packets were transferred.


Project Structure

vpn/
├── vpn_server          
├── vpn_client          
├── vpn.key             (shared secret key, 32 bytes)
├── vpn_serv.conf
├── vpn_cli.conf
├── common.h / common.c
├── server.h / server.c
└── client.h / client.c

Testing

From the client, ping the server’s tunnel IP address:

ping 10.0.0.1

You should receive replies.

Снимок экрана 2026-05-08 в 18 52 30

References


Notes

  • To run this project on Linux, replace the socket(PF_SYSTEM, ...) calls with open("/dev/net/tun", ...).
  • The project uses ChaCha20-Poly1305 from libsodium, a modern and fast encryption algorithm.
  • This is an educational implementation, not optimized for high performance or production use.

About

multi-client VPN server written in C for macOS.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages