Back to blog
Building a VPN from Scratch in C
c
2025-02-25
35 min read

Building a VPN from Scratch in C

CVPNNetworkingSecurityEncryption

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

  1. Client connects to VPN server - Establishes encrypted connection
  2. All traffic is encrypted - Data is wrapped in encryption before sending
  3. Traffic is tunneled - Encrypted data travels through the VPN tunnel
  4. Server decrypts and forwards - VPN server decrypts and forwards to destination
  5. 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:~/c
dev@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:~/c
dev@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:~/c
dev@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:~/c
dev@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:~/c
dev@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:~/c
dev@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:~/c
dev@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

  1. Key Exchange: Implement proper key exchange (e.g., Diffie-Hellman)
  2. Authentication: Add client/server authentication
  3. Perfect Forward Secrecy: Use ephemeral keys
  4. Traffic Analysis: Implement padding and traffic shaping
  5. DNS Leaks: Route DNS through VPN tunnel

Testing

💻user@linux:~/bash
user@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.

N

Nishant Gaurav

Full Stack Developer

Let Down (Choir Version) - Radiohead

0:00
0:00
nishant gaurav