blissfulness be with me.
Tuesday, 12 July 2016
A VPN system over websockets.
This is the client/server implementation of a layer-2 software switch able to route packets over websockets connections.
The daemon is meant to be run behind nginx, apache, the uWSGI http router or a HTTP/HTTPS proxy able to speak the uwsgi protocol and to manage websockets connections
How it works
A client creates a tap (ethernet-like) local device and connects to a websocket server (preferably over HTTPS). Once the websocket handshake is done, every packet received from the tuntap will be forwarded to the websocket server, and every websocket packet received from the server will be forwarded to the tuntap device.
The server side of the stack can act as a simple switch (no access to the network, only connected nodes can communicate), a bridge (a tuntap device is created in the server itself that can forward packets to the main network stack) a simpe router/gateway (give access to each node to specific networks without allowing communication between nodes) or whatever you can think of. (The server is voluntary low-level to allow all the paradigms supported by the network stack).
Authentication/Authorization and Security
Authentication and Authorization is delegated to the proxy. We believe that battle-tested webservers (like nginx and apache) cover basically every authentication and security need, so there is no need to reimplement them.
By default only HTTPS access (eventually with client certificate authentication) should be allowed, but plain-http mode is permitted for easy debugging.
A vpn-ws client is required to send the Host header during the handshake and "should" support SNI. In this way virtualhosting can be easily managed by the proxy server.
Installation from sources
note: see below for binary packages
You need gnu make and a c compiler (clang, gcc, and mingw-gcc are supported).
The server has no external dependancies, while the client requires openssl (except for OSX and Windows where their native ssl/tls implementation is used)
Just run (remember to use 'gmake' on FreeBSD instead of 'make')
after having cloned the repository. If all goes well you will end with a binary named vpn-ws (the server) and another named vpn-ws-client (the client)
You can build a static binary version too of the server (where supported) with:
the resulting binary (vpn-ws) will have no library dependancies.
by default the server binary takes a single argument, the name of the socket to bind (the one which the proxy will connect to):
will bind to /run/vpn.sock
Now you only need to configure your webserver/proxy to route requests to /run/vpn.sock using the uwsgi protocol (see below)
Nginx will be your "shield", managing the authentication/authorization phase. HTTPS + basicauth is strongly suggested, but best setup would be HTTPS + certificates authentication. You can run with plain HTTP and without auth, but please, do not do it, unless for testing ;)
You need to choose the location for which nginx will forward requests to the vpn-ws server:
(we use /vpn)
this a setup without authentication, a better one (with basicauth) could be:
where /etc/nginx/.htpasswd will be the file containing credentials (you can use the htpasswd tool to generate them)
The Official Client
The official client (vpn-ws-client) is a command line tool (written in C). Its syntax is pretty simple:
where 'tap' is a (platform-dependent) tap device path, and 'server' is the url of the nginx /vpn path (in the ws:// or wss:// form)
Before using the client, you need to ensure you have some form of tun/tap implementation. Linux, FreeBSD and OpenBSD already have it out of the box.
Once your client is connected you can assign it an ip address (or make a dhcp request if one of the connected nodes has a running dhcp server)
The mode we are using now is the simple "switch" one, where nodes simply communicates between them like in a lan.
Server tap and Bridge mode
By default the server acts a simple switch, routing packets to connected peers based on the advertised mac address.
In addition to this mode you can give the vpn-ws server a virtual device too (with its mac address) to build complex setup.
To add a device to the vpn-ws server:
./vpn-ws --tuntap vpn0 /run/vpn.sock
the argument of tuntap is platform dependent (the same rules of clients apply).
The 'vpn0' interface is considered like connected nodes, so once you give it an ip address it will join the switch.
One of the use case you may want to follow is briding the vpn with your physical network (in the server). For building it you need the server to forward packets without a matching connected peers to the tuntap device. This is the bridge mode. To enable it add --bridge to the server command line:
./vpn-ws --bridge --tuntap vpn0 /run/vpn.sock
Now you can add 'vpn0' to a pre-existing network bridge:
# linux example
brctl addbr br0
brctl addif br0 eth0
brctl addif br0 vpn0
This mode allows a client to act as a bridge giving access to its whole network to the vpn (and its clients).
Just add --bridge to the client command line and attach the tuntap device to a bridge.
The main problem is that you still need a route to the vpn server, so the best approach would be having two network interfaces on the client (one for the connection with the server, and the other for the physical bridge).
On linux, you can use the macvlan interface (it is basically a copy of a physical interface with a different mac address):
ip link add link eth0 name virt0 type macvlan
ifconfig virt0 0.0.0.0 promisc up
brctl addif br0 virt0
# add vpn-ws tuntap device to the bridge
ifconfig vpn17 0.0.0.0 promisc up
brctl addif br0 vpn17
The --exec trick
Both the server and client take an optional argument named '--exec '. This option will instruct the server/client to execute a command soon after the tuntap device is created.
As an example you may want to call ifconfig upon connection:
The server, when no tuntap device is created, does not require specific permissions. If bound to a unix socket, it will give the 666 permission to the socket itself, in this way nginx (or whatever proxy you are using) will be able to connect to it.
If the server needs to create a tap device, root permissions are required. By the way you can drop privileges soon after the device is created (and the --exec option is eventually executed) with the --uid ang --gid options:
On OSX you need to import a .p12 file (or whatever format it support) to the login keychain, then you need to specify the name of the certificate/identity via the --crt option (no --key is involved):
vpn-ws-client --crt "My certificate" /dev/tap0 wss://example.com/vpn
The JSON Control interface
The uwsgi protocol supports a raw form of channel selections using 2 bytes of its header. Thos bytes are called "modifiers". By setting the modifier1 to '1' (by default modifiers are set to 0) you will tell the vpn-ws server to show the JSON control interface. This is a simple way for monitoring the server and for kicking out clients.
When connectin to modifier1, a json blob with the data of all connected clients is shown. Passing a specific QUERY_STRING you can issue commands (currently only killing peers is implemented)