Total Pageviews

Tuesday, 12 July 2016

vpn-ws,建立在websockets上的vpn

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.

Virtualhosting

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')
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:
make vpn-ws-static
the resulting binary (vpn-ws) will have no library dependancies.

Binary packages

updated to [20141121]

Running the server

by default the server binary takes a single argument, the name of the socket to bind (the one which the proxy will connect to):
./vpn-ws /run/vpn.sock
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

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)
location /vpn {
  include uwsgi_params;
  uwsgi_pass unix:/run/vpn.sock;
}
this a setup without authentication, a better one (with basicauth) could be:
location /vpn {
  include uwsgi_params;
  uwsgi_pass unix:/run/vpn.sock;
  auth_basic "VPN";
  auth_basic_user_file /etc/nginx/.htpasswd;
}
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:
vpn-ws-client <tap> <server>
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.
For OSX you need to install:
while on Windows (ensure to select utils too, when running the installer)
The client must be run as root/sudo (as it requires to create a network interface [TODO: drop privileges after having created the interface).
On linux (you can name devices as you want):
./vpn-ws-client vpn-ws0 wss://foo:bar@example.com/vpn
On OSX (you have /dev/tapNumber devices you can use)
./vpn-ws-client /dev/tap0 wss://foo:bar@example.com/vpn
On FreeBSD (you need to create the interface to access the device):
ifconfig tap0 create
./vpn-ws-client /dev/tap0 wss://foo:bar@example.com/vpn
On windows (you need to create a tap device via the provided utility and assign it a name, like 'foobar')
./vpn-ws-client foobar wss://foo:bar@example.com/vpn
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

Client bridge-mode

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:
vpn-ws-client --exec "ifconfig vpn17 192.168.173.17 netmask 255.255.255.0" vpn17 wss://example.com/
or to add your server to a tuntap to an already existent bridge:
vpn-ws --exec "brctl addif br0 vpn0" --bridge --tuntap vpn0 /run/vpn.sock
You can chain multiple commands with ;
vpn-ws --exec "brctl addif br0 vpn0; ifconfig br0 192.168.173.30" --bridge --tuntap vpn0 /run/vpn.sock

Required permissions

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:
vpn-ws --tuntap vpn0 --uid www-data --gid www-data /run/vpn.sock
The client instead requires privileged operations (future releases may allow dropping privileges in the client too)

Client-certificate authentication

Your client can supply a certificate for authenticating to the server.
On OpenSSL-based clients (Linux, FreeBSD) you need a key file and a certificate in pem format:
vpn-ws-client --key foobar.key --crt foobar.crt vpn0 wss://example.com/vpn
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)
location /vpn {
  include uwsgi_params;
  uwsgi_pass unix:/run/vpn.sock;
  auth_basic "VPN";
  auth_basic_user_file /etc/nginx/.htpasswd;
}

location /vpn_admin {
  include uwsgi_params;
  uwsgi_modifier1 1;
  uwsgi_pass unix:/run/vpn.sock;
  auth_basic "VPN ADMIN";
  auth_basic_user_file /etc/nginx/.htpasswd;
}
You can now connect to /vpn_admin to see a json representation of connected clients. Each peer has an id. you can kick-out that peer/client adding a query string to the bar:
/vpn_admin?kill=n
where n is the id of the specific client.
If needed, more commands could be added in the future.

Example Clients

In the clients/ directory there are a bunch of clients you can run on your nodes or you can use as a base for developing more advanced ones.
Clients must run as root/sudo as they need to create/interact with tuntap devices
  • vpn_linux_tornado.py - a linux-only client based on tornado and ws4py
sudo pip install tornado ws4py python-pytun
sudo python clients/vpn_linux_tornado.py ws://your_server/
  • vpn.pl - more-or-less platform independent perl client (works with OSX and FreeBSD)
sudo cpanm AnyEvent::WebSocket::Client
sudo perl clients/vpn.pl /dev/tap0 ws://your_server/
As the official client you need to ensure a tuntap device implementation is available on the system
then (after having connected to the vpn server) you can assign the ip to it
Remember that we are at layer-2, so if you place a dhcp server on one of those nodes it will work as expected.

Multicast and Broadcast

They are both supported, (yes bonjour, mdns, samba will work !).
You can eventually turn off them selectively adding
  • --no-broadcast
  • --no-multicast
to the server command line

Tutorials

Support

from https://github.com/unbit/vpn-ws
---------------

VPN-WS源码解析

vpn-ws一个开源的,承载协议使用Websocket的VPN实现。代码简单易读,其中对于tuntap/SSL/socket/event的C API使用,有三个平台的版本(Win、Linux和MacOSX1),具有参考价值。

参考

词汇

  • peer,VPN中的节点,对于服务器而言包括自己的tuntap和每个客户端socket
  • tuntap,Linux/Unix中创建的虚拟网卡,读取和写入数据和其它文件设备如socket、file差不多

实现

VPN的实现无非是客户端创建一个虚拟网卡tun/tap,从tun/tap中读取数据包,发送到服务端,服务端写入到它的tun/tap虚拟网卡中,从而让两端像是在同一个局域网内。发送数据无论是走UDP/TCP还是websocket,都只是为了转发虚拟网卡中接收到的数据包(以太包或者ip包)。
客户端与服务端的交互如下:
vpn-ws client <—HTTP/websocket—> Nginx <—uWSGI—> vpn-ws server
VPN-WS的客户端与Nginx Web交互,通过HTTP/HTTPS传送HTTP数据,而后Nginx通过uWSGI接口协议发送至应用服务即VPN-WS服务端。在完成了websocket的upgrade协商后,整个通路变成了websocket数据帧的转发通路。
用Nginx作前端的好处是,可以直接在现有的基于Nginx的服务上添加入口布置这个VPN服务器。客户端有重连机制,但是只有一条连接到服务端,如果拿来翻墙,可就太弱了.