Building a VPN from Scratch in C
Building a VPN from Scratch in C
Virtual Private Networks (VPNs) have become essential tools for privacy, security, and accessing restricted content. Today, we're going to build a basic VPN from scratch using C. This will help you understand how VPNs work at a fundamental level.
What is a VPN?
A VPN creates a secure, encrypted tunnel between your device and a remote server. All your internet traffic is routed through this tunnel, making it appear as if you're browsing from the VPN server's location rather than your actual location.
How VPNs Work
- Client connects to VPN server - Establishes encrypted connection
- All traffic is encrypted - Data is wrapped in encryption before sending
- Traffic is tunneled - Encrypted data travels through the VPN tunnel
- Server decrypts and forwards - VPN server decrypts and forwards to destination
- Response travels back - Response comes back through the same encrypted tunnel
VPN Architecture
Our VPN will consist of:
- VPN Client: Encrypts and sends traffic
- VPN Server: Decrypts and forwards traffic
- TUN/TAP Interface: Virtual network interface for routing
- Encryption Layer: AES encryption for data security
Headers and Constants
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object], ,[object Object],
TUN Interface Setup
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object],[object Object], { ,[object Object], ,[object Object], fd, err; ,[object Object], ((fd = open(,[object Object],, O_RDWR)) < ,[object Object],) { perror(,[object Object],); ,[object Object], fd; } ,[object Object],(&ifr, ,[object Object],, ,[object Object],(ifr)); ifr.ifr_flags = IFF_TUN | IFF_NO_PI; ,[object Object], (*dev) { ,[object Object],(ifr.ifr_name, dev, IFNAMSIZ); } ,[object Object], ((err = ioctl(fd, TUNSETIFF, (,[object Object], *)&ifr)) < ,[object Object],) { perror(,[object Object],); close(fd); ,[object Object], err; } ,[object Object],(dev, ifr.ifr_name); ,[object Object], fd; }
VPN Server Implementation
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object], ,[object Object], tun_fd; ,[object Object], socket_fd; ,[object Object], ,[object Object], key[AES_KEY_SIZE]; ,[object Object], ,[object Object], iv[AES_IV_SIZE]; } VPNConnection; ,[object Object], ,[object Object],[object Object], { ,[object Object], server_fd; ,[object Object], server_fd = socket(AF_INET, SOCK_STREAM, ,[object Object],); ,[object Object], (server_fd < ,[object Object],) { perror(,[object Object],); ,[object Object], ,[object Object],; } ,[object Object], opt = ,[object Object],; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, ,[object Object],(opt)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(port); ,[object Object], (bind(server_fd, (,[object Object], sockaddr *)&server_addr, ,[object Object],(server_addr)) < ,[object Object],) { perror(,[object Object],); ,[object Object], ,[object Object],; } ,[object Object], (listen(server_fd, ,[object Object],) < ,[object Object],) { perror(,[object Object],); ,[object Object], ,[object Object],; } ,[object Object],(,[object Object],, port); ,[object Object], server_fd; } ,[object Object], *,[object Object],[object Object], { VPNConnection *conn = (VPNConnection *)arg; ,[object Object], buffer[BUFFER_SIZE]; ,[object Object], n; ,[object Object], ,[object Object], tun_name[IFNAMSIZ] = ,[object Object],; conn->tun_fd = tun_alloc(tun_name); ,[object Object], (conn->tun_fd < ,[object Object],) { ,[object Object],(,[object Object],); ,[object Object],(conn); ,[object Object], ,[object Object],; } ,[object Object],(,[object Object],, tun_name); ,[object Object], ,[object Object], (,[object Object],) { fd_set read_fds; FD_ZERO(&read_fds); FD_SET(conn->tun_fd, &read_fds); FD_SET(conn->socket_fd, &read_fds); ,[object Object], max_fd = (conn->tun_fd > conn->socket_fd) ? conn->tun_fd : conn->socket_fd; ,[object Object], (select(max_fd + ,[object Object],, &read_fds, ,[object Object],, ,[object Object],, ,[object Object],) < ,[object Object],) { perror(,[object Object],); ,[object Object],; } ,[object Object], ,[object Object], (FD_ISSET(conn->tun_fd, &read_fds)) { n = read(conn->tun_fd, buffer, BUFFER_SIZE); ,[object Object], (n < ,[object Object],) ,[object Object],; ,[object Object], ,[object Object], encrypted[BUFFER_SIZE + AES_BLOCK_SIZE]; ,[object Object], encrypted_len = encrypt_packet(buffer, n, conn->key, conn->iv, encrypted); ,[object Object], (send(conn->socket_fd, encrypted, encrypted_len, ,[object Object],) < ,[object Object],) { ,[object Object],; } } ,[object Object], ,[object Object], (FD_ISSET(conn->socket_fd, &read_fds)) { n = recv(conn->socket_fd, buffer, BUFFER_SIZE, ,[object Object],); ,[object Object], (n <= ,[object Object],) ,[object Object],; ,[object Object], ,[object Object], decrypted[BUFFER_SIZE]; ,[object Object], decrypted_len = decrypt_packet(buffer, n, conn->key, conn->iv, decrypted); ,[object Object], (write(conn->tun_fd, decrypted, decrypted_len) < ,[object Object],) { ,[object Object],; } } } close(conn->tun_fd); close(conn->socket_fd); ,[object Object],(conn); ,[object Object], ,[object Object],; }
VPN Client Implementation
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object],[object Object], { ,[object Object], sock_fd; ,[object Object], sock_fd = socket(AF_INET, SOCK_STREAM, ,[object Object],); ,[object Object], (sock_fd < ,[object Object],) { perror(,[object Object],); ,[object Object], ,[object Object],; } server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); inet_pton(AF_INET, server_ip, &server_addr.sin_addr); ,[object Object], (connect(sock_fd, (,[object Object], sockaddr *)&server_addr, ,[object Object],(server_addr)) < ,[object Object],) { perror(,[object Object],); ,[object Object], ,[object Object],; } ,[object Object],(,[object Object],, server_ip, port); ,[object Object], sock_fd; } ,[object Object], ,[object Object],[object Object], { ,[object Object], tun_name[IFNAMSIZ] = ,[object Object],; ,[object Object], tun_fd = tun_alloc(tun_name); ,[object Object], (tun_fd < ,[object Object],) { ,[object Object],(,[object Object],); ,[object Object],; } ,[object Object],(,[object Object],, tun_name); ,[object Object], ,[object Object], cmd[,[object Object],]; ,[object Object],(cmd, ,[object Object],(cmd), ,[object Object],, tun_name); system(cmd); ,[object Object], buffer[BUFFER_SIZE]; ,[object Object], n; ,[object Object], (,[object Object],) { fd_set read_fds; FD_ZERO(&read_fds); FD_SET(tun_fd, &read_fds); FD_SET(sock_fd, &read_fds); ,[object Object], max_fd = (tun_fd > sock_fd) ? tun_fd : sock_fd; ,[object Object], (select(max_fd + ,[object Object],, &read_fds, ,[object Object],, ,[object Object],, ,[object Object],) < ,[object Object],) { perror(,[object Object],); ,[object Object],; } ,[object Object], ,[object Object], (FD_ISSET(tun_fd, &read_fds)) { n = read(tun_fd, buffer, BUFFER_SIZE); ,[object Object], (n < ,[object Object],) ,[object Object],; ,[object Object], ,[object Object], encrypted[BUFFER_SIZE + AES_BLOCK_SIZE]; ,[object Object], encrypted_len = encrypt_packet(buffer, n, key, iv, encrypted); ,[object Object], (send(sock_fd, encrypted, encrypted_len, ,[object Object],) < ,[object Object],) { ,[object Object],; } } ,[object Object], ,[object Object], (FD_ISSET(sock_fd, &read_fds)) { n = recv(sock_fd, buffer, BUFFER_SIZE, ,[object Object],); ,[object Object], (n <= ,[object Object],) ,[object Object],; ,[object Object], ,[object Object], decrypted[BUFFER_SIZE]; ,[object Object], decrypted_len = decrypt_packet(buffer, n, key, iv, decrypted); ,[object Object], (write(tun_fd, decrypted, decrypted_len) < ,[object Object],) { ,[object Object],; } } } close(tun_fd); close(sock_fd); }
Encryption Functions
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object],[object Object], { EVP_CIPHER_CTX *ctx; ,[object Object], len; ,[object Object], ciphertext_len; ,[object Object], (!(ctx = EVP_CIPHER_CTX_new())) { ,[object Object], ,[object Object],; } ,[object Object], (,[object Object], != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), ,[object Object],, key, iv)) { EVP_CIPHER_CTX_free(ctx); ,[object Object], ,[object Object],; } ,[object Object], (,[object Object], != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) { EVP_CIPHER_CTX_free(ctx); ,[object Object], ,[object Object],; } ciphertext_len = len; ,[object Object], (,[object Object], != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) { EVP_CIPHER_CTX_free(ctx); ,[object Object], ,[object Object],; } ciphertext_len += len; EVP_CIPHER_CTX_free(ctx); ,[object Object], ciphertext_len; } ,[object Object], ,[object Object],[object Object], { EVP_CIPHER_CTX *ctx; ,[object Object], len; ,[object Object], plaintext_len; ,[object Object], (!(ctx = EVP_CIPHER_CTX_new())) { ,[object Object], ,[object Object],; } ,[object Object], (,[object Object], != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), ,[object Object],, key, iv)) { EVP_CIPHER_CTX_free(ctx); ,[object Object], ,[object Object],; } ,[object Object], (,[object Object], != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) { EVP_CIPHER_CTX_free(ctx); ,[object Object], ,[object Object],; } plaintext_len = len; ,[object Object], (,[object Object], != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) { EVP_CIPHER_CTX_free(ctx); ,[object Object], ,[object Object],; } plaintext_len += len; EVP_CIPHER_CTX_free(ctx); ,[object Object], plaintext_len; }
Main Functions
VPN Server Main
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object],[object Object], { ,[object Object], server_fd = vpn_server_init(VPN_PORT); ,[object Object], (server_fd < ,[object Object],) { ,[object Object], ,[object Object],; } ,[object Object], ,[object Object], ,[object Object], key[AES_KEY_SIZE]; ,[object Object], ,[object Object], iv[AES_IV_SIZE]; RAND_bytes(key, AES_KEY_SIZE); RAND_bytes(iv, AES_IV_SIZE); ,[object Object], (,[object Object],) { ,[object Object], ,[object Object], client_len = ,[object Object],(client_addr); ,[object Object], client_fd = accept(server_fd, (,[object Object], sockaddr *)&client_addr, &client_len); ,[object Object], (client_fd < ,[object Object],) { ,[object Object],; } VPNConnection *conn = ,[object Object],(,[object Object],(VPNConnection)); conn->socket_fd = client_fd; ,[object Object],(conn->key, key, AES_KEY_SIZE); ,[object Object],(conn->iv, iv, AES_IV_SIZE); ,[object Object], thread_id; pthread_create(&thread_id, ,[object Object],, handle_vpn_client, conn); pthread_detach(thread_id); } close(server_fd); ,[object Object], ,[object Object],; }
VPN Client Main
dev@gcc:~/cdev@gcc:~/cgcc> [object Object], ,[object Object],[object Object], { ,[object Object], (argc < ,[object Object],) { ,[object Object],(,[object Object],, argv[,[object Object],]); ,[object Object], ,[object Object],; } ,[object Object], ,[object Object], ,[object Object], key[AES_KEY_SIZE]; ,[object Object], ,[object Object], iv[AES_IV_SIZE]; ,[object Object], ,[object Object],(key, ,[object Object],, AES_KEY_SIZE); ,[object Object],(iv, ,[object Object],, AES_IV_SIZE); ,[object Object], sock_fd = vpn_client_connect(argv[,[object Object],], atoi(argv[,[object Object],])); ,[object Object], (sock_fd < ,[object Object],) { ,[object Object], ,[object Object],; } vpn_client_run(sock_fd, key, iv); ,[object Object], ,[object Object],; }
Security Considerations
- Key Exchange: Implement proper key exchange (e.g., Diffie-Hellman)
- Authentication: Add client/server authentication
- Perfect Forward Secrecy: Use ephemeral keys
- Traffic Analysis: Implement padding and traffic shaping
- DNS Leaks: Route DNS through VPN tunnel
Testing
user@linux:~/bashuser@linux:~/bash$ [object Object], gcc -o vpn_server vpn_server.c -lssl -lcrypto -lpthread ,[object Object], gcc -o vpn_client vpn_client.c -lssl -lcrypto ,[object Object], ,[object Object], ./vpn_server ,[object Object], ,[object Object], ./vpn_client 127.0.0.1 1194
Conclusion
You've built a basic VPN that demonstrates tunneling, encryption, and secure communication. This is a simplified implementation - production VPNs include many more features like proper key exchange, authentication, and advanced routing. However, this gives you a solid foundation to understand how VPNs work at the network level.
Nishant Gaurav
Full Stack Developer
Related Posts
Write your own HTTP server from scratch using C
Learn how to build a complete HTTP server from scratch using C programming. Understand sockets, threading, and network programming fundamentals.
AI-First Development with Cursor
How AI-powered code editors like Cursor are revolutionizing the development workflow in 2025.