Create a Simple VPN Server Using Python

A laptop on the left side represents the VPN client. A server on the right side represents the VPN server. A secure tunnel between them, symbolized by a padlock, showing encrypted data flow. Title text at the top: "Creating a Simple VPN with Python".

Introduction

Sitting in a cafe, sipping delicious coffee, and thinking of enjoying the free Wi-Fi service of the cafe but the concern is safety. Have you ever felt this situation? Yes, of course accessing an unknown public wifi could be a risk to our online security. A Virtual Private Network (VPN) can help protect your data in such situations. VPNs encrypt your internet traffic, creating a secure tunnel between your device and the internet. This encryption makes it virtually impossible for hackers to steal your information.

There are so many paid, and non-paid VPN services available but weā€™re not gonna talk about those here. Instead, in this article, you will learn how to create a simple VPN server using Python. Weā€™ll use the ā€˜socketā€˜ and ā€˜sslā€˜ libraries to establish a secure connection between a client and a server.

Itā€™s important to note that this is for educational purposes only, and these homemade VPNs may not offer the same level of security and features as commercial VPN services.

Recommended: Create a Simple Proxy Server Using Python

Requirements and Installations

Before you begin, make sure Python is installed on your system. You can download it from www.python.org. Next, Install the pyOpenSSL library for SSL/TTLS connections using the following command:

pip install pyopenssl

Set Up the Environment

Create a separate folder named ā€œVPN_Serverā€œā€˜ and navigate to this directory.

Itā€™s mandatory to generate SSL certificates to run the VPN server and client programs securely. So, open your terminal or command prompt and do the following step-by-step:

Generate a private key

openssl genpkey -algorithm RSA -out server.key -pkeyopt rsa_keygen_bits:4096

Create a certificate signing request (CSR)

Create a file named ā€˜server.csr.cnfā€˜ with the following content:

[req]
distinguished_name = req_distinguished_name
req_extensions = req_ext
prompt = no

[req_distinguished_name]
C = US
ST = California
L = San Francisco
O = Example Company
OU = IT
CN = 127.0.0.1

[req_ext]
subjectAltName = @alt_names

[alt_names]
IP.1 = 127.0.0.1

Then run the command:

openssl req -new -key server.key -out server.csr -config server.csr.cnf

Generate the self-signed certificate

Create a file named ā€˜server.crt.cnfā€˜ with the following content:

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
C = US
ST = California
L = San Francisco
O = Example Company
OU = IT
CN = 127.0.0.1

[v3_req]
subjectAltName = @alt_names

[alt_names]
IP.1 = 127.0.0.1

Then run the command:

openssl x509 -req -in server.csr -signkey server.key -out server.crt -days 365 -extfile server.crt.cnf -extensions v3_req

The Program

We will create the VPN server and client programs separately.

vpn_server.py

import socket
import ssl

def start_vpn_server(host, port):
    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    context.load_cert_chain(certfile="server.crt", keyfile="server.key")

    bindsocket = socket.socket()
    bindsocket.bind((host, port))
    bindsocket.listen(5)

    print(f"VPN server listening on {host}:{port}")

    while True:
        newsocket, fromaddr = bindsocket.accept()
        print(f"Connection from {fromaddr}")
        conn = context.wrap_socket(newsocket, server_side=True)
        try:
            data = conn.recv(1024)
            print(f"Received: {data}")
            if data:
                conn.sendall(b'Hello, VPN Client!')
        except Exception as e:
            print(f"Error: {e}")
        finally:
            try:
                conn.shutdown(socket.SHUT_RDWR)
            except OSError as e:
                print(f"Error during shutdown: {e}")
            conn.close()

if __name__ == "__main__":
    start_vpn_server('127.0.0.1', 8443)

Explanation

Letā€™s break down the key parts of the above Python script:

  • start_vpn_server function: This function takes two arguments: ā€˜hostā€˜ (the serverā€™s IP address) and ā€˜portā€˜ (the port number the server listens on).
  • context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH): This line creates an SSL context object. The ā€˜ssl.Purpose.CLIENT_AUTHā€˜ argument specifies that the server expects client authentication (meaning the client will also present a certificate).
  • context.load_cert_chain(certfile="server.crt", keyfile="server.key"): This line loads the serverā€™s SSL certificate (ā€˜server.crtā€˜) and private key (ā€˜server.keyā€˜) into the context object. These files are crucial for establishing a secure connection.
  • bindsocket = socket.socket(): This line creates a new TCP socket object (ā€˜bindsocketā€˜) using the socket library.
  • bindsocket.bind((host, port)): This line binds the socket to the specified host and port. This tells the operating system to listen for incoming connections on that address and port combination.
  • bindsocket.listen(5): This line sets the socket to listen for incoming connections. The 5 specifies the maximum number of pending connections that can be queued before the server starts refusing new connections.
  • newsocket, fromaddr = bindsocket.accept(): This line waits for a new connection and assigns the new socket object (ā€˜newsocketā€˜) and the clientā€™s address (ā€˜fromaddrā€˜) to variables.
  • conn = context.wrap_socket(newsocket, server_side=True): This line wraps the new socket (ā€˜newsocketā€˜) in an SSL socket (ā€˜connā€˜) using the previously created SSL context. The ā€œserver_side=Trueā€ argument specifies that this is a server-side connection.
  • data = conn.recv(1024): This line receives data (up to 1024 bytes) from the client through the secure SSL connection (conn).
  • conn.sendall(b'Hello, VPN Client!'): If data was received, this line sends a simple message back to the client as a response.
  • conn.shutdown(socket.SHUT_RDWR): This line shuts down both reading and writing on the secure connection.
  • conn.close(): This line closes the secure connection (conn).
  • start_vpn_server('127.0.0.1', 8443): This line calls the ā€˜start_vpn_serverā€˜ function with the serverā€™s IP address (127.0.0.1) and port number (8443).

vpn_client.py

import socket
import ssl

def vpn_client(host, port):
    context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
    context.load_verify_locations("server.crt")

    raw_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    raw_socket.connect((host, port))
    conn = context.wrap_socket(raw_socket, server_hostname=host)

    try:
        conn.send(b"Hello, VPN Server!")
        data = conn.recv(1024)
        print(f"Received from server: {data}")
    finally:
        conn.close()

if __name__ == "__main__":
    vpn_client('127.0.0.1', 8443)

Explanation

Here is the breakdown of the key parts of the above Python script:

  • context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH): This line creates an SSL context object (context). The ā€˜ssl.Purpose.SERVER_AUTHā€˜ argument specifies that the client expects server authentication (meaning it will verify the serverā€™s certificate).
  • context.load_verify_locations("server.crt"): This line attempts to load the serverā€™s SSL certificate (ā€˜server.crtā€˜) into the context object. This certificate is used to verify the serverā€™s identity for a secure connection.
  • raw_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM): This line creates a new TCP socket object (ā€˜raw_socketā€˜) using the ā€˜socketā€˜ library. ā€˜socket.AF_INETā€˜ specifies the address family (IPv4 in this case), and ā€˜socket.SOCK_STREAMā€˜ specifies a stream socket for continuous data exchange.
  • raw_socket.connect((host, port)): This line attempts to connect the socket (ā€˜raw_socketā€˜) to the server at the specified host and port.
  • conn = context.wrap_socket(raw_socket, server_hostname=host): This line wraps the raw socket (ā€˜raw_socketā€˜) in an SSL socket (ā€˜connā€˜) using the previously created SSL context (ā€˜contextā€˜). The ā€˜server_hostnameā€˜ argument specifies the expected hostname of the server for certificate verification.

How to run the program?

Start the VPN Server

Open a terminal and navigate to the directory containing ā€˜vpn_server.pyā€˜. Now run the server script:

python vpn_server.py

You should see a message indicating that the server is listening on 127.0.0.1:8443.

Run the VPN Client

Open another terminal and navigate to the directory containing ā€˜vpn_client.pyā€˜. Now run the client script:

python vpn_client.py

The client should connect to the server, send a message, and print the response.

Output

When you run the server and client programs, you should see the following outputs:

Server Terminal Output

VPN server listening on 127.0.0.1:8443
Connection from ('127.0.0.1', 12345)
Received: b'Hello, VPN Server!'

Client Terminal Output

Received from server: b'Hello, VPN Server!'

Recommended: Create a Python Network Scanner: Find IPs & MACs

Summary

In this tutorial, we built a simple VPN server using Python. We used the ā€˜socketā€˜ and ā€˜sslā€˜ libraries to establish a secure connection between a client and a server. But before running the server and client scripts, itā€™s mandatory to generate SSL certificates to establish the connection securely.

Every step described in this tutorial is important to set up the VPN server and client successfully. So, follow every detail carefully. Remember, this is a great starting point for further exploration into secure communications and network programming with Python.

For any query, reach out to me at contact@pyseek.com.

Happy Coding!

Share your love
Subhankar Rakshit
Subhankar Rakshit

Hey there! Iā€™m Subhankar Rakshit, the brains behind PySeek. Iā€™m a Post Graduate in Computer Science. PySeek is where I channel my love for Python programming and share it with the world through engaging and informative blogs.

Articles:Ā 194