├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── clients ├── vpn.pl └── vpn_linux_tornado.py ├── src ├── base64.c ├── bits.c ├── client.c ├── error.c ├── event.c ├── exec.c ├── io.c ├── macmap.c ├── main.c ├── memory.c ├── sha1.c ├── sha1.h ├── socket.c ├── ssl.c ├── tuntap.c ├── utils.c ├── uwsgi.c ├── vpn-ws.h └── websocket.c └── tutorials └── ubuntu_trusty_nginx_bridge_client_certificates.md /.gitignore: -------------------------------------------------------------------------------- 1 | /src/*.o 2 | /vpn-ws 3 | /vpn-ws-client 4 | /vpn-ws.exe 5 | /vpn-ws-client.exe 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 unbit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION=0.2 2 | 3 | SHARED_OBJECTS=src/error.o src/tuntap.o src/memory.o src/bits.o src/base64.o src/exec.o src/websocket.o src/utils.o 4 | OBJECTS=src/main.o $(SHARED_OBJECTS) src/socket.o src/event.o src/io.o src/uwsgi.o src/sha1.o src/macmap.o 5 | 6 | ifeq ($(OS), Windows_NT) 7 | LIBS+=-lws2_32 -lsecur32 8 | SERVER_LIBS = -lws2_32 9 | else 10 | OS=$(shell uname) 11 | ifeq ($(OS), Darwin) 12 | LIBS+=-framework Security -framework CoreFoundation 13 | CFLAGS+=-arch i386 -arch x86_64 14 | else 15 | LIBS+=-lcrypto -lssl 16 | endif 17 | endif 18 | 19 | all: vpn-ws vpn-ws-client 20 | 21 | src/%.o: src/%.c src/vpn-ws.h 22 | $(CC) $(CFLAGS) -Wall -Werror -g -c -o $@ $< 23 | 24 | vpn-ws: $(OBJECTS) 25 | $(CC) $(CFLAGS) $(LDFLAGS) -Wall -Werror -g -o vpn-ws $(OBJECTS) $(SERVER_LIBS) 26 | 27 | vpn-ws-static: $(OBJECTS) 28 | $(CC) -static $(CFLAGS) $(LDFLAGS) -Wall -Werror -g -o vpn-ws $(OBJECTS) $(SERVER_LIBS) 29 | 30 | vpn-ws-client: src/client.o src/ssl.o $(SHARED_OBJECTS) 31 | $(CC) $(CFLAGS) $(LDFLAGS) -Wall -Werror -g -o vpn-ws-client src/client.o src/ssl.o $(SHARED_OBJECTS) $(LIBS) 32 | 33 | linux-tarball: vpn-ws-static 34 | tar zcvf vpn-ws-$(VERSION)-linux-$(shell uname -m).tar.gz vpn-ws 35 | 36 | osxpkg: vpn-ws vpn-ws-client 37 | mkdir -p dist/usr/bin 38 | cp vpn-ws vpn-ws-client dist/usr/bin 39 | pkgbuild --root dist --identifier it.unbit.vpn-ws vpn-ws-$(VERSION)-osx.pkg 40 | 41 | clean: 42 | rm -rf src/*.o vpn-ws vpn-ws-client 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vpn-ws 2 | ====== 3 | 4 | A VPN system over websockets 5 | 6 | This is the client/server implementation of a layer-2 software switch able to route packets over websockets connections. 7 | 8 | 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 9 | 10 | How it works 11 | ============ 12 | 13 | A client creates a tap (ethernet-like) local device and connects to a websocket server (preferably over HTTPS). Once the websocket handshake is done, 14 | every packet received from the tuntap will be forwarded to the websocket server, and every websocket packet received from the server will be forwarded 15 | to the tuntap device. 16 | 17 | 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 18 | 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 19 | you can think of. (The server is voluntary low-level to allow all the paradigms supported by the network stack). 20 | 21 | Authentication/Authorization and Security 22 | ========================================= 23 | 24 | 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. 25 | 26 | By default only HTTPS access (eventually with client certificate authentication) should be allowed, but plain-http mode is permitted for easy debugging. 27 | 28 | Virtualhosting 29 | ============== 30 | 31 | 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. 32 | 33 | 34 | Installation from sources 35 | ========================= 36 | 37 | note: see below for binary packages 38 | 39 | You need gnu make and a c compiler (clang, gcc, and mingw-gcc are supported). 40 | 41 | The server has no external dependancies, while the client requires openssl (except for OSX and Windows where their native ssl/tls implementation is used) 42 | 43 | Just run (remember to use 'gmake' on FreeBSD instead of 'make') 44 | 45 | ```sh 46 | make 47 | ``` 48 | 49 | 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) 50 | 51 | You can eventually build server or client selectively with 52 | ```sh 53 | make vpn-ws 54 | make vpn-ws-client 55 | ``` 56 | 57 | You can build a static binary version too of the server (where supported) with: 58 | 59 | ```sh 60 | make vpn-ws-static 61 | ``` 62 | the resulting binary (vpn-ws) will have no library dependancies. 63 | 64 | Binary packages 65 | =============== 66 | 67 | updated to [20141121] 68 | 69 | * linux x86_64 static server (https://github.com/unbit/vpn-ws/releases/download/v0.2/vpn-ws-0.2-linux-x86_64.tar.gz) 70 | * linux i386 static server (https://github.com/unbit/vpn-ws/releases/download/v0.2/vpn-ws-0.2-linux-i386.tar.gz) 71 | * linux raspberrypi (raspbian) (https://github.com/unbit/vpn-ws/releases/download/v0.2/vpn-ws-0.2-linux-armv6l.tar.gz) 72 | * freebsd x86_64 static server (https://github.com/unbit/vpn-ws/releases/download/v0.2/vpn-ws-0.2-freebsd-amd64.tar.gz) 73 | * osx universal binary client and server (https://github.com/unbit/vpn-ws/releases/download/v0.2/vpn-ws-0.2-osx.pkg) 74 | * windows client (https://github.com/unbit/vpn-ws/releases/download/v0.2/vpn-ws-client.exe) 75 | 76 | 77 | Running the server 78 | ================== 79 | 80 | by default the server binary takes a single argument, the name of the socket to bind (the one to which the proxy will connect to): 81 | 82 | ```sh 83 | ./vpn-ws /run/vpn.sock 84 | ``` 85 | 86 | will bind to /run/vpn.sock 87 | 88 | Now you only need to configure your webserver/proxy to route requests to /run/vpn.sock using the uwsgi protocol (see below) 89 | 90 | Nginx 91 | ===== 92 | 93 | 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 ;) 94 | 95 | You need to choose the location for which nginx will forward requests to the vpn-ws server: 96 | 97 | (we use /vpn) 98 | 99 | ```nginx 100 | location /vpn { 101 | include uwsgi_params; 102 | uwsgi_pass unix:/run/vpn.sock; 103 | } 104 | ``` 105 | 106 | this a setup without authentication, a better one (with basicauth) could be: 107 | ```nginx 108 | location /vpn { 109 | include uwsgi_params; 110 | uwsgi_pass unix:/run/vpn.sock; 111 | auth_basic "VPN"; 112 | auth_basic_user_file /etc/nginx/.htpasswd; 113 | } 114 | ``` 115 | 116 | where /etc/nginx/.htpasswd will be the file containing credentials (you can use the htpasswd tool to generate them) 117 | 118 | The Official Client 119 | =================== 120 | 121 | The official client (vpn-ws-client) is a command line tool (written in C). Its syntax is pretty simple: 122 | 123 | ```sh 124 | vpn-ws-client 125 | ``` 126 | 127 | where 'tap' is a (platform-dependent) tap device path, and 'server' is the url of the nginx /vpn path (in the ws://|wss:// form) 128 | 129 | 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. 130 | 131 | For OSX you need to install 132 | 133 | http://sourceforge.net/projects/tuntaposx/files/tuntap/20141104 134 | 135 | while on Windows (ensure to select utils too, when running the installer) 136 | 137 | http://swupdate.openvpn.org/community/releases/tap-windows-9.9.2_3.exe 138 | 139 | The client must be run as root/sudo (as it requires to create a network interface [TODO: drop privileges after having created the interface). 140 | 141 | On linux (you can name devices as you want): 142 | 143 | ```sh 144 | ./vpn-ws-client vpn-ws0 wss://foo:bar@example.com/vpn 145 | ``` 146 | 147 | On OSX (you have a fixed number of /dev/tapN devices you can use) 148 | 149 | ```sh 150 | ./vpn-ws-client /dev/tap0 wss://foo:bar@example.com/vpn 151 | ``` 152 | 153 | On FreeBSD (you need to create the interface to access the device): 154 | 155 | ```sh 156 | ifconfig tap0 create 157 | ./vpn-ws-client /dev/tap0 wss://foo:bar@example.com/vpn 158 | ``` 159 | 160 | On windows (you need to create a tap device via the provided utility and assign it a name, like 'foobar') 161 | 162 | ```sh 163 | ./vpn-ws-client foobar wss://foo:bar@example.com/vpn 164 | ``` 165 | 166 | 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) 167 | 168 | The mode we are using now is the simple "switch" one, where nodes simply communicates between them like in a lan. 169 | 170 | Server tap and Bridge mode 171 | ========================== 172 | 173 | By default the server acts a simple switch, routing packets to connected peers based on the advertised mac address. 174 | 175 | In addition to this mode you can give the vpn-ws server a virtual device too (with its mac address) to build complex setup. 176 | 177 | To add a device to the vpn-ws server: 178 | 179 | ```sh 180 | ./vpn-ws --tuntap vpn0 /run/vpn.sock 181 | ``` 182 | 183 | the argument of tuntap is platform dependent (the same rules of clients apply). 184 | 185 | The 'vpn0' interface is considered like connected nodes, so once you give it an ip address it will join the switch. 186 | 187 | 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: 188 | 189 | ```sh 190 | ./vpn-ws --bridge --tuntap vpn0 /run/vpn.sock 191 | ``` 192 | 193 | Now you can add 'vpn0' to a pre-existing network bridge: 194 | 195 | ```sh 196 | # linux example 197 | brctl addbr br0 198 | brctl addif br0 eth0 199 | brctl addif br0 vpn0 200 | ``` 201 | 202 | Client bridge-mode 203 | ================== 204 | 205 | This mode allows a client to act as a bridge giving access to its whole network to the vpn (and it clients). 206 | 207 | Just add --bridge to the client command line and attach the tuntap device to a bridge. 208 | 209 | 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). 210 | 211 | On linux, you can use the macvlan interface (it is basically a copy of a physical interface with a different mac address): 212 | 213 | ```sh 214 | ip link add link eth0 name virt0 type macvlan 215 | ifconfig virt0 0.0.0.0 promisc up 216 | brctl addif br0 virt0 217 | # add vpn-ws tuntap device to the bridge 218 | ifconfig vpn17 0.0.0.0 promisc up 219 | brctl addif br0 vpn17 220 | ``` 221 | 222 | 223 | The --exec trick 224 | ================ 225 | 226 | 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. 227 | 228 | As an example you may want to call ifconfig upon connection: 229 | 230 | ```sh 231 | vpn-ws-client --exec "ifconfig vpn17 192.168.173.17 netmask 255.255.255.0" vpn17 wss://example.com/ 232 | ``` 233 | 234 | or to add your server to a tuntap to an already existent bridge: 235 | 236 | ```sh 237 | vpn-ws --exec "brctl addif br0 vpn0" --bridge --tuntap vpn0 /run/vpn.sock 238 | ``` 239 | 240 | You can chain multiple commands with ; 241 | 242 | ```sh 243 | vpn-ws --exec "brctl addif br0 vpn0; ifconfig br0 192.168.173.30" --bridge --tuntap vpn0 /run/vpn.sock 244 | ``` 245 | 246 | Required permissions 247 | ==================== 248 | 249 | 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 scket itself, in this way nginx (or whatever proxy you are using) will be able to connect to it. 250 | 251 | 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: 252 | 253 | ```sh 254 | vpn-ws --tuntap vpn0 --uid www-data --gid www-data /run/vpn.sock 255 | ``` 256 | 257 | The client instead requires privileged operations (future releases may allow dropping privileges in the client too) 258 | 259 | Client-certificate authentication 260 | ================================= 261 | 262 | Your client can supply a certificate for authenticating to the server. 263 | 264 | On OpenSSL-based clients (Linux, FreeBSD) you need a key file and a certificate in pem format: 265 | 266 | ```sh 267 | vpn-ws-client --key foobar.key --crt foobar.crt vpn0 wss://example.com/vpn 268 | ``` 269 | 270 | 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): 271 | 272 | ```sh 273 | vpn-ws-client --crt "My certificate" /dev/tap0 wss://example.com/vpn 274 | ``` 275 | 276 | 277 | The JSON Control interface 278 | ========================== 279 | 280 | 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. 281 | 282 | 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) 283 | 284 | ```nginx 285 | location /vpn { 286 | include uwsgi_params; 287 | uwsgi_pass unix:/run/vpn.sock; 288 | auth_basic "VPN"; 289 | auth_basic_user_file /etc/nginx/.htpasswd; 290 | } 291 | 292 | location /vpn_admin { 293 | include uwsgi_params; 294 | uwsgi_modifier1 1; 295 | uwsgi_pass unix:/run/vpn.sock; 296 | auth_basic "VPN ADMIN"; 297 | auth_basic_user_file /etc/nginx/.htpasswd; 298 | } 299 | ``` 300 | 301 | 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: 302 | 303 | /vpn_admin?kill=n 304 | 305 | where n is the id of the specific client. 306 | 307 | If needed, more commands could be added in the future. 308 | 309 | 310 | Example Clients 311 | =============== 312 | 313 | 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. 314 | 315 | Clients must run as root/sudo as they need to create/interact with tuntap devices 316 | 317 | * vpn_linux_tornado.py - a linux-only client based on tornado and ws4py 318 | 319 | ```sh 320 | sudo pip install tornado ws4py python-pytun 321 | sudo python clients/vpn_linux_tornado.py ws://your_server/ 322 | ``` 323 | 324 | * vpn.pl - more-or-less platform independent perl client (works with OSX and FreeBSD) 325 | 326 | ```sh 327 | sudo cpanm AnyEvent::WebSocket::Client 328 | sudo perl clients/vpn.pl /dev/tap0 ws://your_server/ 329 | ``` 330 | 331 | As the official client you need to ensure a tuntap device implementation is available on the system 332 | 333 | then (after having connected to the vpn server) you can assign the ip to it 334 | 335 | Remember that we are at layer-2, so if you place a dhcp server on one of those nodes it will work as expected. 336 | 337 | 338 | Multicast and Broadcast 339 | ======================= 340 | 341 | They are both supported, (yes bonjour, mdns, samba will work !). 342 | 343 | You can eventually turn off them selectively adding 344 | 345 | * --no-broadcast 346 | * --no-multicast 347 | 348 | to the server command line 349 | 350 | Tutorials 351 | ========= 352 | 353 | https://github.com/unbit/vpn-ws/blob/master/tutorials/ubuntu_trusty_nginx_bridge_client_certificates.md 354 | 355 | 356 | Support 357 | ======= 358 | 359 | https://groups.google.com/d/forum/vpn-ws 360 | 361 | (or drop a mail to info at unbit dot it for commercial support) 362 | 363 | Twitter 364 | ======= 365 | 366 | (mainly for announces) 367 | 368 | @unbit 369 | 370 | Status/TODO/Working on 371 | ====================== 372 | 373 | The server on windows is still a work in progress 374 | 375 | The client on windows has no support for SSL/TLS 376 | 377 | Grant support for NetBSD, and DragonflyBSD 378 | 379 | Investigate solaris/smartos/omnios support 380 | -------------------------------------------------------------------------------- /clients/vpn.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Fcntl; 3 | 4 | use AnyEvent; 5 | use AnyEvent::WebSocket::Client; 6 | 7 | sysopen my $tuntap, $ARGV[0], O_RDWR; 8 | die $! if tell($tuntap) < 0; 9 | 10 | my $client = AnyEvent::WebSocket::Client->new; 11 | 12 | my $connection; 13 | my $tap_event; 14 | 15 | $client->connect($ARGV[1])->cb( 16 | sub { 17 | $connection = eval { shift->recv }; 18 | die $@ if $@; 19 | 20 | print "connected to ".$ARGV[1]."\n"; 21 | 22 | $tap_event = AnyEvent->io( fh => $tuntap, poll => 'r', cb => sub { 23 | my $buf ; 24 | sysread $tuntap, $buf, 1500; 25 | $connection->send( AnyEvent::WebSocket::Message->new(body =>$buf, opcode => 2 ) ) ; 26 | }); 27 | 28 | $connection->on(each_message => sub { 29 | my($connection, $message) = @_; 30 | syswrite $tuntap, $message->body, length($message->body); 31 | }); 32 | } 33 | ); 34 | 35 | AnyEvent->condvar->recv; 36 | -------------------------------------------------------------------------------- /clients/vpn_linux_tornado.py: -------------------------------------------------------------------------------- 1 | from tornado import ioloop 2 | from ws4py.client.tornadoclient import TornadoWebSocketClient 3 | from pytun import TunTapDevice, IFF_TAP, IFF_NO_PI 4 | import sys 5 | 6 | io_loop = ioloop.IOLoop.instance() 7 | 8 | tap = TunTapDevice(flags=IFF_TAP|IFF_NO_PI, name='vpn-ws%d') 9 | 10 | class VpnWSClient(TornadoWebSocketClient): 11 | 12 | def received_message(self, m): 13 | tap.write(str(m)) 14 | 15 | def closed(self, code, reason=None): 16 | print "ooops" 17 | 18 | 19 | ws = VpnWSClient(sys.argv[1]) 20 | ws.connect() 21 | 22 | def tap_callback(fd, event): 23 | ws.send(tap.read(tap.mtu), binary=True) 24 | 25 | io_loop.add_handler(tap.fileno(), tap_callback, io_loop.READ) 26 | io_loop.start() 27 | -------------------------------------------------------------------------------- /src/base64.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | // fast base64 encoding based on nginx and uWSGI 4 | // we use it only for sha1 encoding, so we need at most 32 bytes output 5 | 6 | static char b64_table64_2[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 7 | 8 | uint16_t vpn_ws_base64_encode(uint8_t *src, uint16_t len, uint8_t *dst) { 9 | uint8_t *ptr = dst; 10 | while (len >= 3) { 11 | *ptr++ = b64_table64_2[src[0] >> 2]; 12 | *ptr++ = b64_table64_2[((src[0] << 4) & 0x30) | (src[1] >> 4)]; 13 | *ptr++ = b64_table64_2[((src[1] << 2) & 0x3C) | (src[2] >> 6)]; 14 | *ptr++ = b64_table64_2[src[2] & 0x3F]; 15 | src += 3; 16 | len -= 3; 17 | } 18 | 19 | if (len > 0) { 20 | *ptr++ = b64_table64_2[src[0] >> 2]; 21 | uint8_t tmp = (src[0] << 4) & 0x30; 22 | if (len > 1) 23 | tmp |= src[1] >> 4; 24 | *ptr++ = b64_table64_2[tmp]; 25 | if (len < 2) { 26 | *ptr++ = '='; 27 | } 28 | else { 29 | *ptr++ = b64_table64_2[(src[1] << 2) & 0x3C]; 30 | } 31 | *ptr++ = '='; 32 | } 33 | 34 | return ptr-dst; 35 | } 36 | -------------------------------------------------------------------------------- /src/bits.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | uint16_t vpn_ws_be16(uint8_t *buf) { 4 | uint16_t *src = (uint16_t *) buf; 5 | uint16_t ret = 0; 6 | uint8_t *ptr = (uint8_t *) & ret; 7 | ptr[0] = (uint8_t) ((*src >> 8) & 0xff); 8 | ptr[1] = (uint8_t) (*src & 0xff); 9 | return ret; 10 | } 11 | 12 | uint16_t vpn_ws_le16(uint8_t *buf) { 13 | uint16_t *src = (uint16_t *) buf; 14 | uint16_t ret = 0; 15 | uint8_t *ptr = (uint8_t *) & ret; 16 | ptr[0] = (uint8_t) (*src & 0xff); 17 | ptr[1] = (uint8_t) ((*src >> 8) & 0xff); 18 | return ret; 19 | } 20 | 21 | 22 | 23 | uint64_t vpn_ws_be64(uint8_t *buf) { 24 | uint64_t *src = (uint64_t *) buf; 25 | uint64_t ret = 0; 26 | uint8_t *ptr = (uint8_t *) & ret; 27 | ptr[0] = (uint8_t) ((*src >> 56) & 0xff); 28 | ptr[1] = (uint8_t) ((*src >> 48) & 0xff); 29 | ptr[2] = (uint8_t) ((*src >> 40) & 0xff); 30 | ptr[3] = (uint8_t) ((*src >> 32) & 0xff); 31 | ptr[4] = (uint8_t) ((*src >> 24) & 0xff); 32 | ptr[5] = (uint8_t) ((*src >> 16) & 0xff); 33 | ptr[6] = (uint8_t) ((*src >> 8) & 0xff); 34 | ptr[7] = (uint8_t) (*src & 0xff); 35 | return ret; 36 | } 37 | -------------------------------------------------------------------------------- /src/client.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | #ifndef __WIN32__ 4 | #include 5 | #include 6 | #endif 7 | 8 | struct vpn_ws_config vpn_ws_conf; 9 | 10 | static struct option vpn_ws_options[] = { 11 | {"exec", required_argument, NULL, 1 }, 12 | {"key", required_argument, NULL, 2 }, 13 | {"crt", required_argument, NULL, 3 }, 14 | {"no-verify", no_argument, &vpn_ws_conf.ssl_no_verify, 1 }, 15 | {"bridge", no_argument, &vpn_ws_conf.bridge, 1 }, 16 | {NULL, 0, 0, 0} 17 | }; 18 | 19 | #ifdef __WIN32__ 20 | /* 21 | The amount of code here for opening a socket is astonishing.... 22 | */ 23 | static HANDLE _vpn_ws_win32_socket(int family, int type, int protocol) { 24 | unsigned long pblen = 0; 25 | SOCKET ret; 26 | WSAPROTOCOL_INFOW *pbuff; 27 | WSAPROTOCOL_INFOA pinfo; 28 | int nprotos, i, err; 29 | 30 | if (WSCEnumProtocols(NULL, NULL, &pblen, &err) != SOCKET_ERROR) { 31 | vpn_ws_log("no socket protocols available"); 32 | return NULL; 33 | } 34 | 35 | if (err != WSAENOBUFS) { 36 | vpn_ws_error("WSCEnumProtocols()"); 37 | return NULL; 38 | } 39 | 40 | pbuff = vpn_ws_malloc(pblen); 41 | if ((nprotos = WSCEnumProtocols(NULL, pbuff, &pblen, &err)) == SOCKET_ERROR) { 42 | vpn_ws_error("WSCEnumProtocols()"); 43 | return NULL; 44 | } 45 | 46 | for (i = 0; i < nprotos; i++) { 47 | if (pbuff[i].iAddressFamily != family) continue; 48 | if (pbuff[i].iSocketType != type) continue; 49 | if (!(pbuff[i].dwServiceFlags1 & XP1_IFS_HANDLES)) 50 | continue; 51 | 52 | memcpy(&pinfo, pbuff + i, sizeof(pinfo)); 53 | wcstombs(pinfo.szProtocol, pbuff[i].szProtocol, sizeof(pinfo.szProtocol)); 54 | free(pbuff); 55 | if ((ret = WSASocket(family, type, protocol, &pinfo, 0, 0)) == INVALID_SOCKET) { 56 | vpn_ws_error("WSASocket()"); 57 | return NULL; 58 | } 59 | return (HANDLE) ret; 60 | } 61 | free(pbuff); 62 | return NULL; 63 | } 64 | #endif 65 | 66 | void vpn_ws_client_destroy(vpn_ws_peer *peer) { 67 | if (vpn_ws_conf.ssl_ctx) { 68 | vpn_ws_ssl_close(vpn_ws_conf.ssl_ctx); 69 | } 70 | vpn_ws_peer_destroy(peer); 71 | } 72 | 73 | int vpn_ws_client_read(vpn_ws_peer *peer, uint64_t amount) { 74 | uint64_t available = peer->len - peer->pos; 75 | if (available < amount) { 76 | peer->len += amount; 77 | void *tmp = realloc(peer->buf, peer->len); 78 | if (!tmp) { 79 | vpn_ws_error("vpn_ws_client_read()/realloc()"); 80 | return -1; 81 | } 82 | peer->buf = tmp; 83 | } 84 | 85 | if (vpn_ws_conf.ssl_ctx) { 86 | ssize_t rlen = vpn_ws_ssl_read(vpn_ws_conf.ssl_ctx, peer->buf + peer->pos, amount); 87 | if (rlen == 0) { 88 | return -1; 89 | } 90 | if (rlen > 0) { 91 | peer->pos += rlen; 92 | return 0; 93 | } 94 | return rlen; 95 | } 96 | 97 | vpn_ws_recv(peer->fd, peer->buf + peer->pos, amount, rlen); 98 | if (rlen < 0) { 99 | if (rlen < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS)) return 0; 100 | vpn_ws_error("vpn_ws_client_read()/read()"); 101 | return -1; 102 | } 103 | else if (rlen == 0) { 104 | return -1; 105 | } 106 | peer->pos += rlen; 107 | 108 | return 0; 109 | } 110 | 111 | 112 | int vpn_ws_rnrn(char *buf, size_t len) { 113 | if (len < 17) return 0; 114 | uint8_t status = 0; 115 | size_t i; 116 | for(i=0;i 0) { 179 | vpn_ws_send(fd, ptr, remains, wlen); 180 | if (wlen <= 0) { 181 | if (wlen < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS)) { 182 | #ifndef __WIN32__ 183 | fd_set wset; 184 | FD_ZERO(&wset); 185 | FD_SET(fd, &wset); 186 | if (select(fd+1, NULL, &wset, NULL, NULL) < 0) { 187 | vpn_ws_error("vpn_ws_full_write()/select()"); 188 | return -1; 189 | } 190 | #else 191 | #endif 192 | continue; 193 | } 194 | vpn_ws_error("vpn_ws_full_write()/write()"); 195 | return -1; 196 | } 197 | ptr += wlen; 198 | remains -= wlen; 199 | } 200 | return 0; 201 | } 202 | 203 | int vpn_ws_client_write(vpn_ws_peer *peer, uint8_t *buf, uint64_t len) { 204 | if (vpn_ws_conf.ssl_ctx) { 205 | return vpn_ws_ssl_write(vpn_ws_conf.ssl_ctx, buf, len); 206 | } 207 | return vpn_ws_full_write(peer->fd, (char *)buf, len); 208 | } 209 | 210 | 211 | int vpn_ws_connect(vpn_ws_peer *peer, char *name) { 212 | static char *cpy = NULL; 213 | 214 | if (cpy) free(cpy); 215 | cpy = strdup(name); 216 | 217 | int ssl = 0; 218 | uint16_t port = 80; 219 | if (strlen(cpy) < 6) { 220 | vpn_ws_warning("invalid websocket url: %s", cpy); 221 | return -1; 222 | } 223 | 224 | if (!strncmp(cpy, "wss://", 6)) { 225 | ssl = 1; 226 | port = 443; 227 | } 228 | else if (!strncmp(cpy, "ws://", 5)) { 229 | ssl = 0; 230 | port = 80; 231 | } 232 | else { 233 | vpn_ws_warning("invalid websocket url: %s (requires ws:// or wss://)", cpy); 234 | return -1; 235 | } 236 | 237 | char *path = NULL; 238 | 239 | // now get the domain part 240 | char *domain = cpy + 5 + ssl; 241 | size_t domain_len = strlen(domain); 242 | char *slash = strchr(domain, '/'); 243 | if (slash) { 244 | domain_len = slash - domain; 245 | domain[domain_len] = 0; 246 | path = slash + 1; 247 | } 248 | 249 | // check for basic auth 250 | char *at = strchr(domain, '@'); 251 | if (at) { 252 | *at = 0; 253 | domain = at+1; 254 | domain_len = strlen(domain); 255 | } 256 | 257 | // check for port 258 | char *port_str = strchr(domain, ':'); 259 | if (port_str) { 260 | *port_str = 0; 261 | domain_len = strlen(domain); 262 | port = atoi(port_str+1); 263 | } 264 | 265 | vpn_ws_notice("connecting to %s port %u (transport: %s)", domain, port, ssl ? "wss": "ws"); 266 | 267 | // resolve the domain 268 | #ifndef __WIN32__ 269 | res_init(); 270 | #endif 271 | struct hostent *he = gethostbyname(domain); 272 | if (!he) { 273 | vpn_ws_warning("vpn_ws_connect()/gethostbyname(): unable to resolve name"); 274 | return -1; 275 | } 276 | 277 | #ifndef __WIN32__ 278 | peer->fd = socket(AF_INET, SOCK_STREAM, 0); 279 | #else 280 | peer->fd = _vpn_ws_win32_socket(AF_INET, SOCK_STREAM, 0); 281 | #endif 282 | if (vpn_ws_is_invalid_fd(peer->fd)) { 283 | vpn_ws_error("vpn_ws_connect()/socket()"); 284 | return -1; 285 | } 286 | 287 | struct sockaddr_in sin; 288 | memset(&sin, 0, sizeof(struct sockaddr_in)); 289 | sin.sin_family = AF_INET; 290 | sin.sin_port = htons(port); 291 | sin.sin_addr = *((struct in_addr *) he->h_addr); 292 | 293 | if (connect(vpn_ws_socket_cast(peer->fd), (struct sockaddr *) &sin, sizeof(struct sockaddr_in))) { 294 | vpn_ws_error("vpn_ws_connect()/connect()"); 295 | return -1; 296 | } 297 | 298 | char *auth = NULL; 299 | 300 | if (at) { 301 | char *crd = cpy + 5 + ssl; 302 | auth = vpn_ws_calloc(23 + (strlen(crd) * 2)); 303 | if (!auth) { 304 | return -1; 305 | } 306 | memcpy(auth, "Authorization: Basic ", 21); 307 | uint16_t auth_len = vpn_ws_base64_encode((uint8_t *)crd, strlen(crd), (uint8_t *)auth + 21); 308 | memcpy(auth + 21 + auth_len, "\r\n", 2); 309 | } 310 | 311 | uint8_t *mac = vpn_ws_conf.tuntap_mac; 312 | uint8_t key[32]; 313 | uint8_t secret[10]; 314 | int i; 315 | #ifdef __OpenBSD__ 316 | for(i=0;i<10;i++) secret[i] = arc4random(); 317 | #else 318 | for(i=0;i<10;i++) secret[i] = rand(); 319 | #endif 320 | uint16_t key_len = vpn_ws_base64_encode(secret, 10, key); 321 | // now build and send the request 322 | char buf[8192]; 323 | int ret = snprintf(buf, 8192, "GET /%s HTTP/1.1\r\nHost: %s%s%s\r\n%sUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: %.*s\r\nX-vpn-ws-MAC: %02x:%02x:%02x:%02x:%02x:%02x%s\r\n\r\n", 324 | path ? path : "", 325 | domain, 326 | port_str ? ":" : "", 327 | port_str ? port_str+1 : "", 328 | auth ? auth : "", 329 | key_len, 330 | key, 331 | mac[0], 332 | mac[1], 333 | mac[2], 334 | mac[3], 335 | mac[4], 336 | mac[5], 337 | vpn_ws_conf.bridge ? "\r\nX-vpn-ws-bridge: on" : "" 338 | ); 339 | 340 | if (auth) free(auth); 341 | 342 | if (ret == 0 || ret > 8192) { 343 | vpn_ws_log("vpn_ws_connect()/snprintf()"); 344 | return -1; 345 | } 346 | 347 | if (ssl) { 348 | vpn_ws_conf.ssl_ctx = vpn_ws_ssl_handshake(peer, domain, vpn_ws_conf.ssl_key, vpn_ws_conf.ssl_crt); 349 | if (!vpn_ws_conf.ssl_ctx) { 350 | return -1; 351 | } 352 | if (vpn_ws_ssl_write(vpn_ws_conf.ssl_ctx, (uint8_t *)buf, ret)) { 353 | return -1; 354 | } 355 | } 356 | else { 357 | if (vpn_ws_full_write(peer->fd, buf, ret)) { 358 | return -1; 359 | } 360 | } 361 | 362 | int http_code = vpn_ws_wait_101(peer->fd, vpn_ws_conf.ssl_ctx); 363 | if (http_code != 101) { 364 | vpn_ws_warning("error, websocket handshake returned code: %d", http_code); 365 | return -1; 366 | } 367 | 368 | vpn_ws_notice("connected to %s port %u (transport: %s)", domain, port, ssl ? "wss": "ws"); 369 | return 0; 370 | } 371 | 372 | int main(int argc, char *argv[]) { 373 | 374 | #ifndef __WIN32__ 375 | sigset_t sset; 376 | sigemptyset(&sset); 377 | sigaddset(&sset, SIGPIPE); 378 | sigprocmask(SIG_BLOCK, &sset, NULL); 379 | #else 380 | // initialize winsock2 381 | WSADATA wsaData; 382 | WSAStartup(MAKEWORD(1, 1), &wsaData); 383 | #endif 384 | 385 | int option_index = 0; 386 | for(;;) { 387 | int c = getopt_long(argc, argv, "", vpn_ws_options, &option_index); 388 | if (c < 0) break; 389 | switch(c) { 390 | case 0: 391 | break; 392 | case 1: 393 | vpn_ws_conf.exec = optarg; 394 | break; 395 | case 2: 396 | vpn_ws_conf.ssl_key = optarg; 397 | break; 398 | case 3: 399 | vpn_ws_conf.ssl_crt = optarg; 400 | break; 401 | case '?': 402 | break; 403 | default: 404 | vpn_ws_warning("error parsing arguments"); 405 | vpn_ws_exit(1); 406 | } 407 | } 408 | 409 | if (optind + 1 >= argc) { 410 | vpn_ws_log("syntax: %s ", argv[0]); 411 | vpn_ws_exit(1); 412 | } 413 | 414 | vpn_ws_conf.tuntap_name = argv[optind]; 415 | vpn_ws_conf.server_addr = argv[optind+1]; 416 | 417 | struct timeval tv; 418 | #ifndef __OpenBSD__ 419 | // initialize rnd engine 420 | gettimeofday(&tv, NULL); 421 | srand((unsigned int) (tv.tv_usec * tv.tv_sec)); 422 | #endif 423 | 424 | 425 | vpn_ws_fd tuntap_fd = vpn_ws_tuntap(vpn_ws_conf.tuntap_name); 426 | if (vpn_ws_is_invalid_fd(tuntap_fd)) { 427 | vpn_ws_exit(1); 428 | } 429 | 430 | if (vpn_ws_nb(tuntap_fd)) { 431 | vpn_ws_exit(1); 432 | } 433 | 434 | if (vpn_ws_conf.exec) { 435 | if (vpn_ws_exec(vpn_ws_conf.exec)) { 436 | vpn_ws_exit(1); 437 | } 438 | } 439 | 440 | vpn_ws_peer *peer = NULL; 441 | 442 | int throttle = -1; 443 | // back here whenever the server disconnect 444 | reconnect: 445 | if (throttle > -1) { 446 | vpn_ws_log("disconnected"); 447 | } 448 | if (throttle >= 30) throttle = 0; 449 | throttle++; 450 | if (throttle) sleep(throttle); 451 | 452 | peer = vpn_ws_calloc(sizeof(vpn_ws_peer)); 453 | if (!peer) { 454 | goto reconnect; 455 | } 456 | memcpy(peer->mac, vpn_ws_conf.tuntap_mac, 6); 457 | 458 | if (vpn_ws_connect(peer, vpn_ws_conf.server_addr)) { 459 | vpn_ws_client_destroy(peer); 460 | goto reconnect; 461 | } 462 | 463 | // we set the socket in non blocking mode, albeit the code paths are all blocking 464 | // it is only a secuity measure to avoid dead-blocking the process (as an example select() on Linux is a bit flacky) 465 | if (vpn_ws_nb(peer->fd)) { 466 | vpn_ws_client_destroy(peer); 467 | goto reconnect; 468 | } 469 | 470 | uint8_t mask[4]; 471 | #ifdef __OpenBSD__ 472 | mask[0] = arc4random(); 473 | mask[1] = arc4random(); 474 | mask[2] = arc4random(); 475 | mask[3] = arc4random(); 476 | #else 477 | mask[0] = rand(); 478 | mask[1] = rand(); 479 | mask[2] = rand(); 480 | mask[3] = rand(); 481 | #endif 482 | 483 | #ifndef __WIN32__ 484 | fd_set rset; 485 | // find the highest fd 486 | int max_fd = peer->fd; 487 | if (tuntap_fd > max_fd) max_fd = tuntap_fd; 488 | max_fd++; 489 | #else 490 | WSAEVENT ev = WSACreateEvent(); 491 | WSAEventSelect((SOCKET)peer->fd, ev, FD_READ); 492 | OVERLAPPED overlapped_read; 493 | memset(&overlapped_read, 0, sizeof(OVERLAPPED)); 494 | OVERLAPPED overlapped_write; 495 | memset(&overlapped_write, 0, sizeof(OVERLAPPED)); 496 | overlapped_read.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL); 497 | if (!overlapped_read.hEvent) { 498 | vpn_ws_error("main()/CreateEvent()"); 499 | vpn_ws_exit(1); 500 | } 501 | HANDLE waiting_objects[2]; 502 | waiting_objects[0] = ev; 503 | waiting_objects[1] = overlapped_read.hEvent; 504 | // flag to signal if we need to call RadFile on the tuntap device 505 | int tuntap_is_reading = 0; 506 | #endif 507 | 508 | for(;;) { 509 | #ifndef __WIN32__ 510 | FD_ZERO(&rset); 511 | FD_SET(peer->fd, &rset); 512 | FD_SET(tuntap_fd, &rset); 513 | tv.tv_sec = 17; 514 | tv.tv_usec = 0; 515 | // we send a websocket ping every 17 seconds (if inactive, should be enough 516 | // for every proxy out there) 517 | int ret = select(max_fd, &rset, NULL, NULL, &tv); 518 | if (ret < 0) { 519 | // the process manager will save us here 520 | vpn_ws_error("main()/select()"); 521 | vpn_ws_exit(1); 522 | } 523 | if (ret == 0) { 524 | #else 525 | DWORD ret = WaitForMultipleObjects(2, waiting_objects, FALSE, 17000); 526 | if (ret == WAIT_FAILED) { 527 | vpn_ws_error("main()/WaitForMultipleObjects()"); 528 | vpn_ws_exit(1); 529 | } 530 | if (ret == WAIT_TIMEOUT) { 531 | #endif 532 | 533 | // too much inactivity, send a ping 534 | if (vpn_ws_client_write(peer, (uint8_t *) "\x89\x00", 2)) { 535 | vpn_ws_client_destroy(peer); 536 | goto reconnect; 537 | } 538 | continue; 539 | } 540 | 541 | 542 | #ifndef __WIN32__ 543 | if (FD_ISSET(peer->fd, &rset)) { 544 | #else 545 | if (ret == WAIT_OBJECT_0) { 546 | #endif 547 | if (vpn_ws_client_read(peer, 8192)) { 548 | vpn_ws_client_destroy(peer); 549 | goto reconnect; 550 | } 551 | 552 | #ifdef __WIN32__ 553 | WSAResetEvent(ev); 554 | #endif 555 | // start getting websocket packets 556 | for(;;) { 557 | uint16_t ws_header = 0; 558 | int64_t rlen = vpn_ws_websocket_parse(peer, &ws_header); 559 | if (rlen < 0) { 560 | vpn_ws_client_destroy(peer); 561 | goto reconnect; 562 | } 563 | if (rlen == 0) break; 564 | // ignore packet ? 565 | if (ws_header == 0) goto decapitate; 566 | // is it a masked packet ? 567 | uint8_t *ws = peer->buf + ws_header; 568 | uint64_t ws_len = rlen - ws_header; 569 | if (peer->has_mask) { 570 | uint16_t i; 571 | for (i=0;imask[i % 4]; 573 | } 574 | } 575 | 576 | #ifndef __WIN32__ 577 | if (vpn_ws_full_write(tuntap_fd, (char *)ws, ws_len)) { 578 | // being not able to write on tuntap is really bad... 579 | vpn_ws_exit(1); 580 | } 581 | #else 582 | ssize_t wlen = -1; 583 | if (!WriteFile(tuntap_fd, ws, ws_len, (LPDWORD) &wlen, &overlapped_write)) { 584 | if (GetLastError() != ERROR_IO_PENDING) { 585 | vpn_ws_error("main()/WriteFile()"); 586 | vpn_ws_exit(1); 587 | } 588 | if (!GetOverlappedResult(tuntap_fd, &overlapped_write, (LPDWORD) &wlen, TRUE)) { 589 | vpn_ws_error("main()/GetOverlappedResult()"); 590 | vpn_ws_exit(1); 591 | } 592 | } 593 | #endif 594 | 595 | decapitate: 596 | memmove(peer->buf, peer->buf + rlen, peer->pos - rlen); 597 | peer->pos -= rlen; 598 | } 599 | } 600 | 601 | 602 | #ifndef __WIN32__ 603 | if (FD_ISSET(tuntap_fd, &rset)) { 604 | // we use this buffer for the websocket packet too 605 | // 2 byte header + 2 byte size + 4 bytes masking + mtu 606 | uint8_t mtu[8+1500]; 607 | vpn_ws_recv(tuntap_fd, mtu+8, 1500, rlen); 608 | if (rlen <= 0) { 609 | if (rlen < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS)) continue; 610 | vpn_ws_error("main()/read()"); 611 | vpn_ws_exit(1); 612 | } 613 | #else 614 | if (ret == WAIT_OBJECT_0+1 || WaitForSingleObject(overlapped_read.hEvent, 0) == WAIT_OBJECT_0) { 615 | uint8_t mtu[8+1500]; 616 | ssize_t rlen = -1; 617 | // the tuntap is not reading, call ReadFile 618 | if (!tuntap_is_reading) { 619 | if (!ReadFile(tuntap_fd, mtu+8, 1500, (LPDWORD) &rlen, &overlapped_read)) { 620 | if (GetLastError() != ERROR_IO_PENDING) { 621 | vpn_ws_error("main()/ReadFile()"); 622 | vpn_ws_exit(1); 623 | } 624 | ResetEvent(overlapped_read.hEvent); 625 | tuntap_is_reading = 1; 626 | continue; 627 | } 628 | tuntap_is_reading = 0; 629 | SetEvent(overlapped_read.hEvent); 630 | } 631 | else { 632 | if (!GetOverlappedResult(tuntap_fd, &overlapped_read, (LPDWORD)&rlen, TRUE)) { 633 | vpn_ws_error("main()/GetOverlappedResult()"); 634 | vpn_ws_exit(1); 635 | } 636 | tuntap_is_reading = 0; 637 | SetEvent(overlapped_read.hEvent); 638 | } 639 | #endif 640 | 641 | 642 | // mask packet 643 | ssize_t i; 644 | for (i=0;i> 8) & 0xff); 665 | mtu[3] = (uint8_t) (rlen & 0xff); 666 | if (vpn_ws_client_write(peer, mtu, rlen + 8)) { 667 | vpn_ws_client_destroy(peer); 668 | goto reconnect; 669 | } 670 | } 671 | } 672 | 673 | } 674 | 675 | return 0; 676 | } 677 | -------------------------------------------------------------------------------- /src/error.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | static void vpn_ws_do_log(FILE *stream, const char *fmt, va_list args) 4 | { 5 | time_t t = time(NULL); 6 | fprintf(stream, "[%.*s] ", 24, ctime(&t)); 7 | vfprintf(stream, fmt, args); 8 | fputc('\n', stream); 9 | } 10 | 11 | void vpn_ws_error(const char *msg) { 12 | vpn_ws_warning("%s: %s", msg, strerror(errno)); 13 | } 14 | 15 | void vpn_ws_exit(int code) { 16 | exit(code); 17 | } 18 | 19 | void vpn_ws_log(const char *fmt, ...) { 20 | va_list args; 21 | va_start(args, fmt); 22 | vpn_ws_do_log(stdout, fmt, args); 23 | va_end(args); 24 | } 25 | 26 | void vpn_ws_warning(const char *fmt, ...) 27 | { 28 | va_list args; 29 | va_start(args, fmt); 30 | vpn_ws_do_log(stderr, fmt, args); 31 | va_end(args); 32 | } 33 | 34 | void vpn_ws_notice(const char *fmt, ...) 35 | { 36 | va_list args; 37 | va_start(args, fmt); 38 | vpn_ws_do_log(stdout, fmt, args); 39 | va_end(args); 40 | } 41 | -------------------------------------------------------------------------------- /src/event.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | #if defined(__linux__) 4 | 5 | #include 6 | 7 | int vpn_ws_event_queue(int n) { 8 | int ret = epoll_create(n); 9 | if (ret < 0) { 10 | vpn_ws_error("vpn_ws_event_queue()/epoll_create()"); 11 | return -1; 12 | } 13 | return ret; 14 | } 15 | 16 | int vpn_ws_event_read_to_write(int queue, int fd) { 17 | struct epoll_event ev; 18 | ev.events = EPOLLOUT; 19 | ev.data.fd = fd; 20 | int ret = epoll_ctl(queue, EPOLL_CTL_MOD, fd, &ev); 21 | if (ret < 0) { 22 | vpn_ws_error("vpn_ws_event_read_to_write()/epoll_ctl()"); 23 | return -1; 24 | } 25 | return ret; 26 | } 27 | 28 | int vpn_ws_event_write_to_read(int queue, int fd) { 29 | struct epoll_event ev; 30 | ev.events = EPOLLIN; 31 | ev.data.fd = fd; 32 | int ret = epoll_ctl(queue, EPOLL_CTL_MOD, fd, &ev); 33 | if (ret < 0) { 34 | vpn_ws_error("vpn_ws_event_read_to_write()/epoll_ctl()"); 35 | return -1; 36 | } 37 | return ret; 38 | } 39 | 40 | int vpn_ws_event_add_read(int queue, int fd) { 41 | struct epoll_event ev; 42 | ev.events = EPOLLIN; 43 | ev.data.fd = fd; 44 | int ret = epoll_ctl(queue, EPOLL_CTL_ADD, fd, &ev); 45 | if (ret < 0) { 46 | vpn_ws_error("vpn_ws_event_add_read()/epoll_ctl()"); 47 | return -1; 48 | } 49 | return ret; 50 | } 51 | 52 | int vpn_ws_event_wait(int queue, void *events) { 53 | int ret = epoll_wait(queue, events, 64, -1); 54 | if (ret < 0) { 55 | vpn_ws_error("vpn_ws_event_wait()/epoll_wait()"); 56 | return -1; 57 | } 58 | return ret; 59 | } 60 | 61 | void *vpn_ws_event_events(int n) { 62 | return vpn_ws_malloc(sizeof(struct epoll_event) * n); 63 | } 64 | 65 | int vpn_ws_event_fd(void *events, int i) { 66 | struct epoll_event *epoll_events = (struct epoll_event *) events; 67 | return epoll_events[i].data.fd; 68 | } 69 | 70 | #elif defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) 71 | 72 | #include 73 | 74 | int vpn_ws_event_queue(int n) { 75 | int ret = kqueue(); 76 | if (ret < 0) { 77 | vpn_ws_error("vpn_ws_event_queue()/kqueue()"); 78 | return -1; 79 | } 80 | return ret; 81 | } 82 | 83 | int vpn_ws_event_read_to_write(int queue, int fd) { 84 | struct kevent kev; 85 | 86 | EV_SET(&kev, fd, EVFILT_READ, EV_DELETE, 0, 0, 0); 87 | if (kevent(queue, &kev, 1, NULL, 0, NULL) < 0) { 88 | vpn_ws_error("vpn_ws_event_read_to_write()/kevent()"); 89 | return -1; 90 | } 91 | 92 | EV_SET(&kev, fd, EVFILT_WRITE, EV_ADD, 0, 0, 0); 93 | if (kevent(queue, &kev, 1, NULL, 0, NULL) < 0) { 94 | vpn_ws_error("vpn_ws_event_read_to_write()/kevent()"); 95 | return -1; 96 | } 97 | return 0; 98 | } 99 | 100 | int vpn_ws_event_write_to_read(int queue, int fd) { 101 | struct kevent kev; 102 | 103 | EV_SET(&kev, fd, EVFILT_WRITE, EV_DELETE, 0, 0, 0); 104 | if (kevent(queue, &kev, 1, NULL, 0, NULL) < 0) { 105 | vpn_ws_error("vpn_ws_event_write_to_read()/kevent()"); 106 | return -1; 107 | } 108 | 109 | EV_SET(&kev, fd, EVFILT_READ, EV_ADD, 0, 0, 0); 110 | if (kevent(queue, &kev, 1, NULL, 0, NULL) < 0) { 111 | vpn_ws_error("vpn_ws_event_write_to_read()/kevent()"); 112 | return -1; 113 | } 114 | return 0; 115 | } 116 | 117 | int vpn_ws_event_add_read(int queue, int fd) { 118 | struct kevent kev; 119 | 120 | EV_SET(&kev, fd, EVFILT_READ, EV_ADD, 0, 0, 0); 121 | if (kevent(queue, &kev, 1, NULL, 0, NULL) < 0) { 122 | vpn_ws_error("vpn_ws_event_add_read()/kevent()"); 123 | return -1; 124 | } 125 | return 0; 126 | } 127 | 128 | int vpn_ws_event_wait(int queue, void *events) { 129 | int ret = kevent(queue, NULL, 0, events, 64, NULL); 130 | if (ret < 0) { 131 | vpn_ws_error("vpn_ws_event_wait()/kevent()"); 132 | } 133 | return ret; 134 | } 135 | 136 | void *vpn_ws_event_events(int n) { 137 | return vpn_ws_malloc(sizeof(struct kevent) * n); 138 | } 139 | 140 | int vpn_ws_event_fd(void *events, int i) { 141 | struct kevent *k_events = (struct kevent *) events; 142 | return k_events[i].ident; 143 | } 144 | 145 | #elif defined(__WIN32__) 146 | 147 | int vpn_ws_event_queue(int n) { 148 | return -1; 149 | } 150 | 151 | int vpn_ws_event_read_to_write(int queue, vpn_ws_fd fd) { 152 | return -1; 153 | } 154 | 155 | int vpn_ws_event_write_to_read(int queue, vpn_ws_fd fd) { 156 | return -1; 157 | } 158 | 159 | int vpn_ws_event_add_read(int queue, vpn_ws_fd fd) { 160 | return -1; 161 | } 162 | 163 | int vpn_ws_event_wait(int queue, void *events) { 164 | return -1; 165 | } 166 | 167 | void *vpn_ws_event_events(int n) { 168 | return NULL; 169 | } 170 | 171 | int vpn_ws_event_fd(void *events, int i) { 172 | return -1; 173 | } 174 | 175 | #endif 176 | -------------------------------------------------------------------------------- /src/exec.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | #ifndef __WIN32__ 4 | 5 | int vpn_ws_exec(char *cmd) { 6 | pid_t pid = fork(); 7 | if (pid < 0) { 8 | vpn_ws_error("vpn_ws_exec()/fork()"); 9 | return -1; 10 | } 11 | 12 | if (pid > 0) { 13 | int status = 0; 14 | pid_t diedpid = waitpid(pid, &status, 0); 15 | if (diedpid <= 0) { 16 | vpn_ws_error("vpn_ws_exec()/waitpid()"); 17 | return -1; 18 | } 19 | // get exit code 20 | if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { 21 | return 0; 22 | } 23 | vpn_ws_log("vpn_ws_exec() returned non-zero code: %d", WEXITSTATUS(status)); 24 | return -1; 25 | } 26 | 27 | char *argv[4]; 28 | argv[0] = "/bin/sh"; 29 | argv[1] = "-c"; 30 | argv[2] = cmd; 31 | argv[3] = NULL; 32 | execvp(argv[0], argv); 33 | //never here 34 | vpn_ws_error("vpn_ws_exec()/execvp()"); 35 | vpn_ws_exit(1); 36 | return -1; 37 | } 38 | 39 | #else 40 | 41 | int vpn_ws_exec(char *cmd) { 42 | PROCESS_INFORMATION pinfo = {0}; 43 | DWORD code = -1; 44 | BOOL result = CreateProcess(NULL, cmd, 45 | NULL, NULL, FALSE, 46 | NORMAL_PRIORITY_CLASS|CREATE_NO_WINDOW, 47 | NULL, NULL, NULL, &pinfo); 48 | 49 | if (!result) { 50 | vpn_ws_error("vpn_ws_exec()/CreateProcess()"); 51 | return -1; 52 | } 53 | 54 | WaitForSingleObject(pinfo.hProcess, INFINITE); 55 | result = GetExitCodeProcess(pinfo.hProcess, &code); 56 | if (!result) { 57 | vpn_ws_error("vpn_ws_exec()/GetExitCodeProcess()"); 58 | goto end; 59 | } 60 | 61 | end: 62 | CloseHandle(pinfo.hProcess); 63 | CloseHandle(pinfo.hThread); 64 | if (code == 0) return 0; 65 | vpn_ws_log("vpn_ws_exec() returned non-zero code: %d", code); 66 | return -1; 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /src/io.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | int vpn_ws_continue_write(vpn_ws_peer *peer) { 4 | vpn_ws_send(peer->fd, peer->write_buf, peer->write_pos, wlen); 5 | if (wlen < 0) { 6 | if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS) { 7 | return 0; 8 | } 9 | return -1; 10 | } 11 | if (wlen == 0) return -1; 12 | 13 | peer->tx+=wlen; 14 | 15 | memmove(peer->write_buf, peer->write_buf + wlen, peer->write_pos - wlen); 16 | peer->write_pos -= wlen; 17 | // if the whole buffer has been written, signal it 18 | if (peer->write_pos == 0) return 1; 19 | return 0; 20 | } 21 | 22 | int vpn_ws_write(vpn_ws_peer *peer, uint8_t *buf, uint64_t amount) { 23 | uint64_t available = peer->write_len - peer->write_pos; 24 | if (available < amount) { 25 | peer->write_len += amount; 26 | void *tmp = realloc(peer->write_buf, peer->write_len); 27 | if (!tmp) { 28 | vpn_ws_error("vpn_ws_write()/realloc()"); 29 | return -1; 30 | } 31 | peer->write_buf = tmp; 32 | } 33 | 34 | memcpy(peer->write_buf + peer->write_pos, buf, amount); 35 | peer->write_pos += amount; 36 | 37 | return vpn_ws_continue_write(peer); 38 | } 39 | 40 | int vpn_ws_write_websocket(vpn_ws_peer *peer, uint8_t *buf, uint64_t amount) { 41 | uint8_t header_size = 2; 42 | uint8_t header[10]; 43 | 44 | header[0] = 0x82; 45 | if (amount < 126) { 46 | header[1] = amount; 47 | } 48 | else if (amount <= (uint16_t) 0xffff) { 49 | header_size = 4; 50 | header[1] = 126; 51 | header[2] = (uint8_t) ((amount >> 8) & 0xff); 52 | header[3] = (uint8_t) (amount & 0xff); 53 | } 54 | else { 55 | header_size = 10; 56 | header[1] = 127; 57 | header[2] = (uint8_t) ((amount >> 56) & 0xff); 58 | header[3] = (uint8_t) ((amount >> 48) & 0xff); 59 | header[4] = (uint8_t) ((amount >> 40) & 0xff); 60 | header[5] = (uint8_t) ((amount >> 32) & 0xff); 61 | header[6] = (uint8_t) ((amount >> 24) & 0xff); 62 | header[7] = (uint8_t) ((amount >> 16) & 0xff); 63 | header[8] = (uint8_t) ((amount >> 8) & 0xff); 64 | header[9] = (uint8_t) (amount & 0xff); 65 | } 66 | 67 | uint64_t available = peer->write_len - peer->write_pos; 68 | if (available < (amount+header_size)) { 69 | peer->write_len += amount + header_size; 70 | void *tmp = realloc(peer->write_buf, peer->write_len); 71 | if (!tmp) { 72 | vpn_ws_error("vpn_ws_write_websocket()/realloc()"); 73 | return -1; 74 | } 75 | peer->write_buf = tmp; 76 | } 77 | 78 | memcpy(peer->write_buf + peer->write_pos, header, header_size); 79 | memcpy(peer->write_buf + peer->write_pos +header_size, buf, amount); 80 | peer->write_pos += amount + header_size; 81 | 82 | return vpn_ws_continue_write(peer); 83 | } 84 | 85 | int vpn_ws_read(vpn_ws_peer *peer, uint64_t amount) { 86 | uint64_t available = peer->len - peer->pos; 87 | if (available < amount) { 88 | peer->len += amount; 89 | void *tmp = realloc(peer->buf, peer->len); 90 | if (!tmp) { 91 | vpn_ws_error("vpn_ws_read()/realloc()"); 92 | return -1; 93 | } 94 | peer->buf = tmp; 95 | } 96 | 97 | vpn_ws_recv(peer->fd, peer->buf + peer->pos, amount, rlen); 98 | if (rlen < 0) { 99 | if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS) { 100 | return 0; 101 | } 102 | return -1; 103 | } 104 | if (rlen == 0) return -1; 105 | 106 | peer->rx += rlen; 107 | peer->pos += rlen; 108 | 109 | return 1; 110 | } 111 | 112 | int vpn_ws_manage_fd(int queue, vpn_ws_fd fd) { 113 | // when 1 invoke the event wait loop 114 | int dirty = 0; 115 | 116 | // check if the fd can be in the peers list 117 | #ifndef __WIN32__ 118 | if (fd > vpn_ws_conf.peers_n) { 119 | return -1; 120 | } 121 | // first of all find a valid peer 122 | vpn_ws_peer *peer = vpn_ws_conf.peers[fd]; 123 | #else 124 | // TODO find a solution for windows 125 | vpn_ws_peer *peer = NULL; 126 | #endif 127 | if (!peer) { 128 | vpn_ws_log("[BUG] fd %d not found", fd); 129 | close(fd); 130 | return -1; 131 | } 132 | 133 | // is it valid ? 134 | if (peer->fd != fd) { 135 | vpn_ws_log("[BUG] found invalid peer %d != %d", peer->fd, fd); 136 | vpn_ws_peer_destroy(peer); 137 | close(fd); 138 | return -1; 139 | } 140 | 141 | // is a writing peer ? 142 | 143 | if (peer->is_writing) { 144 | int ret = vpn_ws_continue_write(peer); 145 | if (ret < 0) { 146 | vpn_ws_peer_destroy(peer); 147 | return -1; 148 | } 149 | if (ret == 0) return 0; 150 | peer->is_writing = 0; 151 | // if handshake is higher than 1, it means we want to close the connection 152 | // and to set it as dirty .... 153 | if (peer->handshake > 1) { 154 | vpn_ws_peer_destroy(peer); 155 | return -1; 156 | } 157 | return vpn_ws_event_write_to_read(queue, peer->fd); 158 | } 159 | 160 | int ret = vpn_ws_read(peer, 8192); 161 | if (ret < 0) { 162 | vpn_ws_peer_destroy(peer); 163 | return -1; 164 | } 165 | // again ... 166 | if (ret == 0) return 0; 167 | 168 | again: 169 | 170 | // has completed handshake ? 171 | if (!peer->handshake) { 172 | int64_t hret = vpn_ws_handshake(queue, peer); 173 | if (hret < 0) { 174 | vpn_ws_peer_destroy(peer); 175 | return -1; 176 | } 177 | // again ... 178 | if (hret == 0) return dirty; 179 | peer->handshake++; 180 | memmove(peer->buf, peer->buf + hret, peer->pos - hret); 181 | peer->pos -= hret; 182 | } 183 | 184 | uint8_t *data = NULL; 185 | uint64_t data_len = 0; 186 | uint8_t *mac = NULL; 187 | uint16_t ws_header = 0; 188 | int64_t ws_ret = 0; 189 | 190 | if (peer->raw) { 191 | // check if there are more data to parse ... 192 | if (peer->pos == 0) return dirty; 193 | data = peer->buf; 194 | data_len = peer->pos; 195 | mac = data; 196 | ws_ret = data_len; 197 | goto parsed; 198 | } 199 | 200 | // do we have a full websocket packet ? 201 | ws_ret = vpn_ws_websocket_parse(peer, &ws_header); 202 | if (ws_ret < 0) { 203 | vpn_ws_peer_destroy(peer); 204 | return -1; 205 | } 206 | // again 207 | if (ws_ret == 0) return dirty; 208 | // ignore packet ? 209 | if (ws_header == 0) goto decapitate; 210 | 211 | uint8_t *ws = peer->buf + ws_header; 212 | uint64_t ws_len = ws_ret - ws_header; 213 | 214 | // set body to send 215 | data = peer->buf; 216 | data_len = ws_ret; 217 | 218 | // if the packed is masked, de-mask it 219 | if (peer->has_mask) { 220 | uint16_t i; 221 | for (i=0;imask[i % 4]; 223 | } 224 | // move the header and clear the mask bit 225 | memmove(peer->buf+4, peer->buf, ws_header - 4); 226 | peer->buf[5] &= 0x7f; 227 | 228 | data+=4; 229 | data_len-=4; 230 | } 231 | 232 | // set the mac address 233 | mac = ws; 234 | 235 | parsed: 236 | 237 | // do we have a full ethernet frame header ? 238 | if (data_len < 14) goto decapitate; 239 | 240 | // get src MAC addr 241 | if (!vpn_ws_mac_is_valid(mac+6)) goto decapitate; 242 | 243 | // if the MAC has been already collected, compare it 244 | 245 | if (peer->mac_collected) { 246 | // if the peer is a bridge, collect new macs 247 | if (memcmp(peer->mac, mac+6, 6)) { 248 | // if not a bridge discard packets 249 | if (!peer->bridge) goto decapitate; 250 | if (vpn_ws_bridge_collect_mac(peer, mac+6)) { 251 | vpn_ws_peer_destroy(peer); 252 | return -1; 253 | } 254 | } 255 | } 256 | else { 257 | memcpy(peer->mac, mac+6, 6); 258 | vpn_ws_announce_peer(peer, "registered new"); 259 | peer->mac_collected = 1; 260 | } 261 | 262 | // get dst MAC addr 263 | if (vpn_ws_mac_is_zero(mac)) goto decapitate; 264 | // check if src MAC is different from dst MAC, loops are evil 265 | if (vpn_ws_mac_is_loop(mac, mac+6)) goto decapitate; 266 | 267 | // check for broadcast/multicast 268 | // append packet to each peer write buffer ... 269 | // attempt to call write for each one 270 | if ((!vpn_ws_conf.no_multicast && vpn_ws_mac_is_multicast(mac)) || (!vpn_ws_conf.no_broadcast && vpn_ws_mac_is_broadcast(mac))) { 271 | // iterate over all peers and write to them 272 | uint64_t i; 273 | for(i=0;ifd == peer->fd) continue; 278 | // already accounted ? 279 | if (!b_peer->mac_collected) continue; 280 | 281 | int wret = -1; 282 | // if we are writing a websocket packet to a raw device 283 | // we need to remove the websocket header 284 | if (b_peer->raw && !peer->raw) { 285 | wret = vpn_ws_write(b_peer, peer->buf+ws_header, ws_ret-ws_header); 286 | } 287 | else if (!b_peer->raw && peer->raw) { 288 | wret = vpn_ws_write_websocket(b_peer, data, data_len); 289 | } 290 | else { 291 | wret = vpn_ws_write(b_peer, data, data_len); 292 | } 293 | 294 | if (wret < 0) { 295 | vpn_ws_peer_destroy(b_peer); 296 | dirty = 1; 297 | continue; 298 | } 299 | 300 | if (wret == 0) { 301 | dirty = 1; 302 | if (!b_peer->is_writing) { 303 | if (vpn_ws_event_read_to_write(queue, b_peer->fd)) { 304 | vpn_ws_peer_destroy(b_peer); 305 | } 306 | } 307 | } 308 | } 309 | goto decapitate; 310 | } 311 | 312 | // find the MAC addr in the MAC map 313 | // attempt to call write 314 | vpn_ws_peer *b_peer = vpn_ws_peer_by_mac(mac); 315 | if (!b_peer) { 316 | // if not found, search in bridge peers 317 | b_peer = vpn_ws_peer_by_bridge_mac(mac); 318 | // if not found forward to all bridget peers 319 | if (!b_peer) { 320 | uint64_t i; 321 | for(i=0;ifd == peer->fd) continue; 326 | // already accounted ? 327 | if (!b_peer->mac_collected) continue; 328 | // is a bridge ? 329 | if (!b_peer->bridge) continue; 330 | int wret = -1; 331 | if (b_peer->raw && !peer->raw) { 332 | wret = vpn_ws_write(b_peer, peer->buf+ws_header, ws_ret-ws_header); 333 | } 334 | else if (!b_peer->raw && peer->raw) { 335 | wret = vpn_ws_write_websocket(b_peer, data, data_len); 336 | } 337 | else { 338 | wret = vpn_ws_write(b_peer, data, data_len); 339 | } 340 | if (wret < 0) { 341 | vpn_ws_peer_destroy(b_peer); 342 | dirty = 1; 343 | } 344 | else if (wret == 0) { 345 | dirty = 1; 346 | if (!b_peer->is_writing) { 347 | if (vpn_ws_event_read_to_write(queue, b_peer->fd)) { 348 | vpn_ws_peer_destroy(b_peer); 349 | } 350 | } 351 | } 352 | } 353 | goto decapitate; 354 | } 355 | } 356 | 357 | int wret = -1; 358 | if (b_peer->raw && !peer->raw) { 359 | wret = vpn_ws_write(b_peer, peer->buf+ws_header, ws_ret-ws_header); 360 | } 361 | else if (!b_peer->raw && peer->raw) { 362 | wret = vpn_ws_write_websocket(b_peer, data, data_len); 363 | } 364 | else { 365 | wret = vpn_ws_write(b_peer, data, data_len); 366 | } 367 | if (wret < 0) { 368 | vpn_ws_peer_destroy(b_peer); 369 | dirty = 1; 370 | } 371 | else if (wret == 0) { 372 | dirty = 1; 373 | if (!b_peer->is_writing) { 374 | if (vpn_ws_event_read_to_write(queue, b_peer->fd)) { 375 | vpn_ws_peer_destroy(b_peer); 376 | } 377 | } 378 | } 379 | 380 | decapitate: 381 | memmove(peer->buf, peer->buf + ws_ret, peer->pos - ws_ret); 382 | peer->pos -= ws_ret; 383 | goto again; 384 | // never here 385 | return -1; 386 | } 387 | -------------------------------------------------------------------------------- /src/macmap.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | /* 4 | 5 | a fast-search map for mac addresses 6 | 7 | */ 8 | 9 | int vpn_ws_mac_is_zero(uint8_t *buf) { 10 | if (buf[0] != 0) return 0; 11 | if (buf[1] != 0) return 0; 12 | if (buf[2] != 0) return 0; 13 | if (buf[3] != 0) return 0; 14 | if (buf[4] != 0) return 0; 15 | if (buf[5] != 0) return 0; 16 | return 1; 17 | } 18 | 19 | int vpn_ws_mac_is_broadcast(uint8_t *buf) { 20 | if (buf[0] != 0xff) return 0; 21 | if (buf[1] != 0xff) return 0; 22 | if (buf[2] != 0xff) return 0; 23 | if (buf[3] != 0xff) return 0; 24 | if (buf[4] != 0xff) return 0; 25 | if (buf[5] != 0xff) return 0; 26 | return 1; 27 | } 28 | 29 | int vpn_ws_mac_is_valid(uint8_t *buf) { 30 | if (vpn_ws_mac_is_broadcast(buf)) return 0; 31 | if (vpn_ws_mac_is_zero(buf)) return 0; 32 | return 1; 33 | } 34 | 35 | int vpn_ws_mac_is_loop(uint8_t *buf1, uint8_t *buf2) { 36 | if (buf1[0] != buf2[0]) return 0; 37 | if (buf1[1] != buf2[1]) return 0; 38 | if (buf1[2] != buf2[2]) return 0; 39 | if (buf1[3] != buf2[3]) return 0; 40 | if (buf1[4] != buf2[4]) return 0; 41 | if (buf1[5] != buf2[5]) return 0; 42 | return 1; 43 | } 44 | 45 | int vpn_ws_mac_is_multicast(uint8_t *buf) { 46 | // multicast 47 | if (buf[0] == 1 && buf[1] == 0 && buf[2] == 0x5e) return 1; 48 | // ipv6 multicast 49 | if (buf[0] == 0x33 && buf[1] == 0x33) return 1; 50 | return 0; 51 | } 52 | 53 | vpn_ws_peer *vpn_ws_peer_by_mac(uint8_t *buf) { 54 | uint64_t i; 55 | for(i=0;imac_collected) continue; 59 | if (!memcmp(b_peer->mac, buf, 6)) return b_peer; 60 | } 61 | 62 | return NULL; 63 | } 64 | 65 | vpn_ws_peer *vpn_ws_peer_by_bridge_mac(uint8_t *buf) { 66 | uint64_t i; 67 | for(i=0;imac_collected) continue; 71 | vpn_ws_mac *b_mac = b_peer->macs; 72 | while(b_mac) { 73 | if (!memcmp(b_mac->mac, buf, 6)) return b_peer; 74 | b_mac = b_mac->next; 75 | } 76 | } 77 | 78 | return NULL; 79 | } 80 | 81 | int vpn_ws_bridge_collect_mac(vpn_ws_peer *peer, uint8_t *mac) { 82 | // check if the mac is already collected 83 | vpn_ws_mac *b_mac = peer->macs; 84 | while(b_mac) { 85 | if (!memcmp(b_mac->mac, mac, 6)) return 0; 86 | b_mac = b_mac->next; 87 | } 88 | 89 | b_mac = vpn_ws_malloc(sizeof(vpn_ws_mac)); 90 | if (!b_mac) return -1; 91 | memcpy(b_mac->mac, mac, 6); 92 | b_mac->next = peer->macs; 93 | peer->macs = b_mac; 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | struct vpn_ws_config vpn_ws_conf; 4 | 5 | static struct option vpn_ws_options[] = { 6 | {"tuntap", required_argument, NULL, 1 }, 7 | {"exec", required_argument, NULL, 2 }, 8 | {"bridge", no_argument, &vpn_ws_conf.bridge, 1 }, 9 | {"no-broadcast", no_argument, &vpn_ws_conf.no_broadcast, 1 }, 10 | {"no-multicast", no_argument, &vpn_ws_conf.no_multicast, 1 }, 11 | {"uid", required_argument, NULL, 3 }, 12 | {"gid", required_argument, NULL, 4 }, 13 | {"help", no_argument, NULL, '?' }, 14 | {NULL, 0, 0, 0} 15 | }; 16 | 17 | int main(int argc, char *argv[]) { 18 | int option_index = 0; 19 | int event_queue = -1; 20 | 21 | vpn_ws_fd server_fd; 22 | vpn_ws_fd tuntap_fd; 23 | 24 | setbuf(stdout, NULL); 25 | 26 | #ifndef __WIN32__ 27 | sigset_t sset; 28 | sigemptyset(&sset); 29 | sigaddset(&sset, SIGPIPE); 30 | sigprocmask(SIG_BLOCK, &sset, NULL); 31 | #endif 32 | 33 | for(;;) { 34 | int c = getopt_long(argc, argv, "", vpn_ws_options, &option_index); 35 | if (c < 0) break; 36 | switch(c) { 37 | case 0: 38 | break; 39 | // tuntap 40 | case 1: 41 | vpn_ws_conf.tuntap_name = optarg; 42 | break; 43 | // exec 44 | case 2: 45 | vpn_ws_conf.exec = optarg; 46 | break; 47 | case 3: 48 | vpn_ws_conf.uid = optarg; 49 | break; 50 | case 4: 51 | vpn_ws_conf.gid = optarg; 52 | break; 53 | case '?': 54 | fprintf(stdout, "usage: %s [options]
\n", argv[0]); 55 | fprintf(stdout, "\t--tuntap \tcreate the specified tuntap device and attach to the engine\n"); 56 | fprintf(stdout, "\t--exec \t\texecute the specified command soon after the tuntap device is created\n"); 57 | fprintf(stdout, "\t--bridge\t\tenable bridge mode\n"); 58 | fprintf(stdout, "\t--no-broadcast\t\tdisable broadcast management\n"); 59 | fprintf(stdout, "\t--no-multicast\t\tdisable multicast management\n"); 60 | fprintf(stdout, "\t--uid \tdrop privileges to the specified user/uid\n"); 61 | fprintf(stdout, "\t--gid \tdrop privileges to the specified group/did\n"); 62 | fprintf(stdout, "\t--help\t\t\tthis help\n"); 63 | exit(0); 64 | default: 65 | vpn_ws_warning("error parsing arguments"); 66 | vpn_ws_exit(1); 67 | } 68 | } 69 | 70 | if (optind < argc) { 71 | vpn_ws_conf.server_addr = argv[optind]; 72 | } 73 | 74 | if (!vpn_ws_conf.server_addr) { 75 | vpn_ws_log("you need to specify a socket address"); 76 | vpn_ws_exit(1); 77 | } 78 | 79 | server_fd = vpn_ws_bind(vpn_ws_conf.server_addr); 80 | if (server_fd < 0) { 81 | vpn_ws_exit(1); 82 | } 83 | 84 | if (vpn_ws_nb(server_fd)) { 85 | vpn_ws_exit(1); 86 | } 87 | 88 | event_queue = vpn_ws_event_queue(256); 89 | if (event_queue < 0) { 90 | vpn_ws_exit(1); 91 | } 92 | 93 | if (vpn_ws_conf.tuntap_name) { 94 | tuntap_fd = vpn_ws_tuntap(vpn_ws_conf.tuntap_name); 95 | if (tuntap_fd < 0) { 96 | vpn_ws_exit(1); 97 | } 98 | 99 | vpn_ws_peer_create(event_queue, tuntap_fd, vpn_ws_conf.tuntap_mac); 100 | if (!vpn_ws_conf.peers) { 101 | vpn_ws_exit(1); 102 | } 103 | if (vpn_ws_conf.bridge) { 104 | #ifndef __WIN32__ 105 | 106 | vpn_ws_conf.peers[tuntap_fd]->bridge = 1; 107 | #endif 108 | } 109 | } 110 | 111 | if (vpn_ws_conf.exec) { 112 | if (vpn_ws_exec(vpn_ws_conf.exec)) { 113 | vpn_ws_exit(1); 114 | } 115 | } 116 | 117 | #ifndef __WIN32__ 118 | // drop privileges 119 | if (vpn_ws_conf.gid) { 120 | gid_t gid = 0; 121 | struct group *g = getgrnam(vpn_ws_conf.gid); 122 | if (!g) { 123 | if (vpn_ws_is_a_number(vpn_ws_conf.gid)) { 124 | gid = atoi(vpn_ws_conf.gid); 125 | } 126 | else { 127 | vpn_ws_warning("unable to find group %s", vpn_ws_conf.gid); 128 | vpn_ws_exit(1); 129 | } 130 | } 131 | else { 132 | gid = g->gr_gid; 133 | } 134 | if (!gid) { 135 | vpn_ws_warning("unable to drop to gid"); 136 | vpn_ws_exit(1); 137 | } 138 | if (setgid(gid)) { 139 | vpn_ws_error("setgid()"); 140 | vpn_ws_exit(1); 141 | } 142 | } 143 | 144 | if (vpn_ws_conf.uid) { 145 | uid_t uid = 0; 146 | struct passwd *p = getpwnam(vpn_ws_conf.uid); 147 | if (!p) { 148 | if (vpn_ws_is_a_number(vpn_ws_conf.uid)) { 149 | uid = atoi(vpn_ws_conf.uid); 150 | } 151 | else { 152 | vpn_ws_warning("unable to find user %s", vpn_ws_conf.uid); 153 | vpn_ws_exit(1); 154 | } 155 | } 156 | else { 157 | uid = p->pw_uid; 158 | } 159 | if (!uid) { 160 | vpn_ws_warning("unable to drop to uid"); 161 | vpn_ws_exit(1); 162 | } 163 | if (setuid(uid)) { 164 | vpn_ws_error("setuid()"); 165 | vpn_ws_exit(1); 166 | } 167 | } 168 | #endif 169 | 170 | if (vpn_ws_event_add_read(event_queue, server_fd)) { 171 | vpn_ws_exit(1); 172 | } 173 | 174 | 175 | void *events = vpn_ws_event_events(64); 176 | if (!events) { 177 | vpn_ws_exit(1); 178 | } 179 | 180 | for(;;) { 181 | int ret = vpn_ws_event_wait(event_queue, events); 182 | if (ret <= 0) { 183 | if (ret < 0 && errno == EINTR) continue; 184 | break; 185 | } 186 | 187 | #ifndef __WIN32__ 188 | int i; 189 | for(i=0;ifd; 5 | 6 | #ifndef __WIN32__ 7 | if (fd > -1) { 8 | #else 9 | if (fd) { 10 | #endif 11 | vpn_ws_announce_peer(peer, "removing"); 12 | close(fd); 13 | } 14 | if (peer->remote_addr) free(peer->remote_addr); 15 | if (peer->remote_user) free(peer->remote_user); 16 | if (peer->dn) free(peer->dn); 17 | if (peer->buf) free(peer->buf); 18 | 19 | vpn_ws_mac *macs = peer->macs; 20 | while(macs) { 21 | vpn_ws_mac *next = macs->next; 22 | free(macs); 23 | macs = next; 24 | } 25 | free(peer); 26 | 27 | #ifndef __WIN32__ 28 | if (vpn_ws_conf.peers) 29 | vpn_ws_conf.peers[fd] = NULL; 30 | #else 31 | // TODO find a solution for windows 32 | #endif 33 | } 34 | 35 | void *vpn_ws_malloc(uint64_t amount) { 36 | void *ptr = malloc(amount); 37 | if (ptr == NULL) { 38 | vpn_ws_error("vpn_ws_malloc()/malloc()"); 39 | return NULL; 40 | } 41 | return ptr; 42 | } 43 | 44 | void *vpn_ws_calloc(uint64_t amount) { 45 | void *ptr = calloc(1, amount); 46 | if (ptr == NULL) { 47 | vpn_ws_error("vpn_ws_malloc()/calloc()"); 48 | return NULL; 49 | } 50 | return ptr; 51 | } 52 | -------------------------------------------------------------------------------- /src/sha1.c: -------------------------------------------------------------------------------- 1 | /* $KAME: sha1.c,v 1.5 2000/11/08 06:13:08 itojun Exp $ */ 2 | /* 3 | * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of the project nor the names of its contributors 15 | * may be used to endorse or promote products derived from this software 16 | * without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | 31 | /* 32 | * FIPS pub 180-1: Secure Hash Algorithm (SHA-1) 33 | * based on: http://csrc.nist.gov/fips/fip180-1.txt 34 | * implemented by Jun-ichiro itojun Itoh 35 | */ 36 | 37 | #include "vpn-ws.h" 38 | 39 | /* constant table */ 40 | static uint32_t SHA1_K[] = { 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 }; 41 | #define K(t) SHA1_K[(t) / 20] 42 | 43 | #define F0(b, c, d) (((b) & (c)) | ((~(b)) & (d))) 44 | #define F1(b, c, d) (((b) ^ (c)) ^ (d)) 45 | #define F2(b, c, d) (((b) & (c)) | ((b) & (d)) | ((c) & (d))) 46 | #define F3(b, c, d) (((b) ^ (c)) ^ (d)) 47 | 48 | #define S(n, x) (((x) << (n)) | ((x) >> (32 - n))) 49 | 50 | #define H(n) (ctxt->h.b32[(n)]) 51 | #define COUNT (ctxt->count) 52 | #define BCOUNT (ctxt->c.b64[0] / 8) 53 | #define W(n) (ctxt->m.b32[(n)]) 54 | 55 | #define PUTBYTE(x) { \ 56 | ctxt->m.b8[(COUNT % 64)] = (x); \ 57 | COUNT++; \ 58 | COUNT %= 64; \ 59 | ctxt->c.b64[0] += 8; \ 60 | if (COUNT % 64 == 0) \ 61 | sha1_step(ctxt); \ 62 | } 63 | 64 | #define PUTPAD(x) { \ 65 | ctxt->m.b8[(COUNT % 64)] = (x); \ 66 | COUNT++; \ 67 | COUNT %= 64; \ 68 | if (COUNT % 64 == 0) \ 69 | sha1_step(ctxt); \ 70 | } 71 | 72 | static void sha1_step(struct sha1_ctxt *); 73 | 74 | static void 75 | sha1_step(struct sha1_ctxt *ctxt) 76 | { 77 | uint32_t a, b, c, d, e; 78 | size_t t, s; 79 | uint32_t tmp; 80 | 81 | #if !WORDS_BIGENDIAN 82 | struct sha1_ctxt tctxt; 83 | memmove(&tctxt.m.b8[0], &ctxt->m.b8[0], 64); 84 | ctxt->m.b8[0] = tctxt.m.b8[3]; ctxt->m.b8[1] = tctxt.m.b8[2]; 85 | ctxt->m.b8[2] = tctxt.m.b8[1]; ctxt->m.b8[3] = tctxt.m.b8[0]; 86 | ctxt->m.b8[4] = tctxt.m.b8[7]; ctxt->m.b8[5] = tctxt.m.b8[6]; 87 | ctxt->m.b8[6] = tctxt.m.b8[5]; ctxt->m.b8[7] = tctxt.m.b8[4]; 88 | ctxt->m.b8[8] = tctxt.m.b8[11]; ctxt->m.b8[9] = tctxt.m.b8[10]; 89 | ctxt->m.b8[10] = tctxt.m.b8[9]; ctxt->m.b8[11] = tctxt.m.b8[8]; 90 | ctxt->m.b8[12] = tctxt.m.b8[15]; ctxt->m.b8[13] = tctxt.m.b8[14]; 91 | ctxt->m.b8[14] = tctxt.m.b8[13]; ctxt->m.b8[15] = tctxt.m.b8[12]; 92 | ctxt->m.b8[16] = tctxt.m.b8[19]; ctxt->m.b8[17] = tctxt.m.b8[18]; 93 | ctxt->m.b8[18] = tctxt.m.b8[17]; ctxt->m.b8[19] = tctxt.m.b8[16]; 94 | ctxt->m.b8[20] = tctxt.m.b8[23]; ctxt->m.b8[21] = tctxt.m.b8[22]; 95 | ctxt->m.b8[22] = tctxt.m.b8[21]; ctxt->m.b8[23] = tctxt.m.b8[20]; 96 | ctxt->m.b8[24] = tctxt.m.b8[27]; ctxt->m.b8[25] = tctxt.m.b8[26]; 97 | ctxt->m.b8[26] = tctxt.m.b8[25]; ctxt->m.b8[27] = tctxt.m.b8[24]; 98 | ctxt->m.b8[28] = tctxt.m.b8[31]; ctxt->m.b8[29] = tctxt.m.b8[30]; 99 | ctxt->m.b8[30] = tctxt.m.b8[29]; ctxt->m.b8[31] = tctxt.m.b8[28]; 100 | ctxt->m.b8[32] = tctxt.m.b8[35]; ctxt->m.b8[33] = tctxt.m.b8[34]; 101 | ctxt->m.b8[34] = tctxt.m.b8[33]; ctxt->m.b8[35] = tctxt.m.b8[32]; 102 | ctxt->m.b8[36] = tctxt.m.b8[39]; ctxt->m.b8[37] = tctxt.m.b8[38]; 103 | ctxt->m.b8[38] = tctxt.m.b8[37]; ctxt->m.b8[39] = tctxt.m.b8[36]; 104 | ctxt->m.b8[40] = tctxt.m.b8[43]; ctxt->m.b8[41] = tctxt.m.b8[42]; 105 | ctxt->m.b8[42] = tctxt.m.b8[41]; ctxt->m.b8[43] = tctxt.m.b8[40]; 106 | ctxt->m.b8[44] = tctxt.m.b8[47]; ctxt->m.b8[45] = tctxt.m.b8[46]; 107 | ctxt->m.b8[46] = tctxt.m.b8[45]; ctxt->m.b8[47] = tctxt.m.b8[44]; 108 | ctxt->m.b8[48] = tctxt.m.b8[51]; ctxt->m.b8[49] = tctxt.m.b8[50]; 109 | ctxt->m.b8[50] = tctxt.m.b8[49]; ctxt->m.b8[51] = tctxt.m.b8[48]; 110 | ctxt->m.b8[52] = tctxt.m.b8[55]; ctxt->m.b8[53] = tctxt.m.b8[54]; 111 | ctxt->m.b8[54] = tctxt.m.b8[53]; ctxt->m.b8[55] = tctxt.m.b8[52]; 112 | ctxt->m.b8[56] = tctxt.m.b8[59]; ctxt->m.b8[57] = tctxt.m.b8[58]; 113 | ctxt->m.b8[58] = tctxt.m.b8[57]; ctxt->m.b8[59] = tctxt.m.b8[56]; 114 | ctxt->m.b8[60] = tctxt.m.b8[63]; ctxt->m.b8[61] = tctxt.m.b8[62]; 115 | ctxt->m.b8[62] = tctxt.m.b8[61]; ctxt->m.b8[63] = tctxt.m.b8[60]; 116 | #endif 117 | 118 | a = H(0); b = H(1); c = H(2); d = H(3); e = H(4); 119 | 120 | for (t = 0; t < 20; t++) { 121 | s = t & 0x0f; 122 | if (t >= 16) { 123 | W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s)); 124 | } 125 | tmp = S(5, a) + F0(b, c, d) + e + W(s) + K(t); 126 | e = d; d = c; c = S(30, b); b = a; a = tmp; 127 | } 128 | for (t = 20; t < 40; t++) { 129 | s = t & 0x0f; 130 | W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s)); 131 | tmp = S(5, a) + F1(b, c, d) + e + W(s) + K(t); 132 | e = d; d = c; c = S(30, b); b = a; a = tmp; 133 | } 134 | for (t = 40; t < 60; t++) { 135 | s = t & 0x0f; 136 | W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s)); 137 | tmp = S(5, a) + F2(b, c, d) + e + W(s) + K(t); 138 | e = d; d = c; c = S(30, b); b = a; a = tmp; 139 | } 140 | for (t = 60; t < 80; t++) { 141 | s = t & 0x0f; 142 | W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s)); 143 | tmp = S(5, a) + F3(b, c, d) + e + W(s) + K(t); 144 | e = d; d = c; c = S(30, b); b = a; a = tmp; 145 | } 146 | 147 | H(0) = H(0) + a; 148 | H(1) = H(1) + b; 149 | H(2) = H(2) + c; 150 | H(3) = H(3) + d; 151 | H(4) = H(4) + e; 152 | 153 | memset(&ctxt->m.b8[0], 0, 64); 154 | } 155 | 156 | /*------------------------------------------------------------*/ 157 | 158 | void 159 | sha1_init(struct sha1_ctxt *ctxt) 160 | { 161 | memset(ctxt, 0, sizeof(struct sha1_ctxt)); 162 | H(0) = 0x67452301; 163 | H(1) = 0xefcdab89; 164 | H(2) = 0x98badcfe; 165 | H(3) = 0x10325476; 166 | H(4) = 0xc3d2e1f0; 167 | } 168 | 169 | void 170 | sha1_pad(struct sha1_ctxt *ctxt) 171 | { 172 | size_t padlen; /*pad length in bytes*/ 173 | size_t padstart; 174 | 175 | PUTPAD(0x80); 176 | 177 | padstart = COUNT % 64; 178 | padlen = 64 - padstart; 179 | if (padlen < 8) { 180 | memset(&ctxt->m.b8[padstart], 0, padlen); 181 | COUNT += padlen; 182 | COUNT %= 64; 183 | sha1_step(ctxt); 184 | padstart = COUNT % 64; /* should be 0 */ 185 | padlen = 64 - padstart; /* should be 64 */ 186 | } 187 | memset(&ctxt->m.b8[padstart], 0, padlen - 8); 188 | COUNT += (padlen - 8); 189 | COUNT %= 64; 190 | #if WORDS_BIGENDIAN 191 | PUTPAD(ctxt->c.b8[0]); PUTPAD(ctxt->c.b8[1]); 192 | PUTPAD(ctxt->c.b8[2]); PUTPAD(ctxt->c.b8[3]); 193 | PUTPAD(ctxt->c.b8[4]); PUTPAD(ctxt->c.b8[5]); 194 | PUTPAD(ctxt->c.b8[6]); PUTPAD(ctxt->c.b8[7]); 195 | #else 196 | PUTPAD(ctxt->c.b8[7]); PUTPAD(ctxt->c.b8[6]); 197 | PUTPAD(ctxt->c.b8[5]); PUTPAD(ctxt->c.b8[4]); 198 | PUTPAD(ctxt->c.b8[3]); PUTPAD(ctxt->c.b8[2]); 199 | PUTPAD(ctxt->c.b8[1]); PUTPAD(ctxt->c.b8[0]); 200 | #endif 201 | } 202 | 203 | void 204 | sha1_loop(struct sha1_ctxt *ctxt, const void *input, size_t len) 205 | { 206 | const unsigned char *input_c = input; 207 | size_t gaplen; 208 | size_t gapstart; 209 | size_t off; 210 | size_t copysiz; 211 | 212 | off = 0; 213 | 214 | while (off < len) { 215 | gapstart = COUNT % 64; 216 | gaplen = 64 - gapstart; 217 | 218 | copysiz = (gaplen < len - off) ? gaplen : len - off; 219 | memmove(&ctxt->m.b8[gapstart], &input_c[off], copysiz); 220 | COUNT += copysiz; 221 | COUNT %= 64; 222 | ctxt->c.b64[0] += copysiz * 8; 223 | if (COUNT % 64 == 0) 224 | sha1_step(ctxt); 225 | off += copysiz; 226 | } 227 | } 228 | 229 | void 230 | sha1_result(struct sha1_ctxt *ctxt, void *digest0) 231 | { 232 | uint8_t *digest; 233 | 234 | digest = (uint8_t *)digest0; 235 | sha1_pad(ctxt); 236 | #if WORDS_BIGENDIAN 237 | memmove(digest, &ctxt->h.b8[0], 20); 238 | #else 239 | digest[0] = ctxt->h.b8[3]; digest[1] = ctxt->h.b8[2]; 240 | digest[2] = ctxt->h.b8[1]; digest[3] = ctxt->h.b8[0]; 241 | digest[4] = ctxt->h.b8[7]; digest[5] = ctxt->h.b8[6]; 242 | digest[6] = ctxt->h.b8[5]; digest[7] = ctxt->h.b8[4]; 243 | digest[8] = ctxt->h.b8[11]; digest[9] = ctxt->h.b8[10]; 244 | digest[10] = ctxt->h.b8[9]; digest[11] = ctxt->h.b8[8]; 245 | digest[12] = ctxt->h.b8[15]; digest[13] = ctxt->h.b8[14]; 246 | digest[14] = ctxt->h.b8[13]; digest[15] = ctxt->h.b8[12]; 247 | digest[16] = ctxt->h.b8[19]; digest[17] = ctxt->h.b8[18]; 248 | digest[18] = ctxt->h.b8[17]; digest[19] = ctxt->h.b8[16]; 249 | #endif 250 | memset(ctxt, 0, sizeof(struct sha1_ctxt)); 251 | } 252 | -------------------------------------------------------------------------------- /src/sha1.h: -------------------------------------------------------------------------------- 1 | /* $FreeBSD: src/sys/crypto/sha1.h,v 1.8 2002/03/20 05:13:50 alfred Exp $ */ 2 | /* $KAME: sha1.h,v 1.5 2000/03/27 04:36:23 sumikawa Exp $ */ 3 | 4 | /* 5 | * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * 3. Neither the name of the project nor the names of its contributors 17 | * may be used to endorse or promote products derived from this software 18 | * without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | /* 33 | * FIPS pub 180-1: Secure Hash Algorithm (SHA-1) 34 | * based on: http://csrc.nist.gov/fips/fip180-1.txt 35 | * implemented by Jun-ichiro itojun Itoh 36 | */ 37 | 38 | #ifndef SHA1_H 39 | #define SHA1_H 40 | 41 | struct sha1_ctxt { 42 | union { 43 | uint8_t b8[20]; 44 | uint32_t b32[5]; 45 | } h; 46 | union { 47 | uint8_t b8[8]; 48 | uint64_t b64[1]; 49 | } c; 50 | union { 51 | uint8_t b8[64]; 52 | uint32_t b32[16]; 53 | } m; 54 | uint8_t count; 55 | }; 56 | 57 | extern void sha1_init(struct sha1_ctxt *); 58 | extern void sha1_pad(struct sha1_ctxt *); 59 | extern void sha1_loop(struct sha1_ctxt *, const void *, size_t); 60 | extern void sha1_result(struct sha1_ctxt *, void *); 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/socket.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | vpn_ws_fd vpn_ws_bind_ipv6(char *name) { 4 | struct sockaddr_in6 sin6; 5 | memset(&sin6, 0, sizeof(struct sockaddr_in6)); 6 | 7 | char *port = strrchr(name, ':'); 8 | if (!port) { 9 | vpn_ws_error("invalid ipv6 address, must be in the form [address]:port\n"); 10 | return vpn_ws_invalid_fd; 11 | } 12 | *port = 0; 13 | 14 | sin6.sin6_family = AF_INET6; 15 | sin6.sin6_port = htons(atoi(port + 1)); 16 | if (!strcmp(name, "[::]")) { 17 | sin6.sin6_addr = in6addr_any; 18 | } 19 | else { 20 | char *addr = vpn_ws_strndup(name+1, strlen(name+1) -1); 21 | #ifndef __WIN32__ 22 | inet_pton(AF_INET6, addr, sin6.sin6_addr.s6_addr); 23 | #else 24 | int sin6_len = sizeof(struct sockaddr_in6); 25 | WSAStringToAddress(addr, AF_INET6, NULL, (LPSOCKADDR) &sin6, &sin6_len); 26 | #endif 27 | free(addr); 28 | } 29 | 30 | *port = ':'; 31 | 32 | vpn_ws_fd fd = (vpn_ws_fd) socket(AF_INET6, SOCK_STREAM, 0); 33 | if (fd < 0) { 34 | vpn_ws_error("vpn_ws_bind_ipv6()/socket()"); 35 | return vpn_ws_invalid_fd; 36 | } 37 | 38 | int reuse = 1; 39 | if (setsockopt(vpn_ws_socket_cast(fd), SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, sizeof(int)) < 0) { 40 | vpn_ws_error("vpn_ws_bind_ipv6()/setsockopt()"); 41 | close(fd); 42 | return vpn_ws_invalid_fd; 43 | } 44 | 45 | if (bind(vpn_ws_socket_cast(fd), (struct sockaddr *) &sin6, sizeof(struct sockaddr_in6))) { 46 | vpn_ws_error("vpn_ws_bind_ipv6()/bind()"); 47 | close(fd); 48 | return vpn_ws_invalid_fd; 49 | } 50 | 51 | if (listen(vpn_ws_socket_cast(fd), 100)) { 52 | vpn_ws_error("vpn_ws_bind_ipv6()/listen()"); 53 | close(fd); 54 | return vpn_ws_invalid_fd; 55 | } 56 | return fd; 57 | } 58 | 59 | vpn_ws_fd vpn_ws_bind_ipv4(char *name) { 60 | struct sockaddr_in sin4; 61 | memset(&sin4, 0, sizeof(struct sockaddr_in)); 62 | 63 | char *port = strrchr(name, ':'); 64 | if (!port) { 65 | vpn_ws_error("invalid ipv4 address, must be in the form address:port\n"); 66 | return vpn_ws_invalid_fd; 67 | } 68 | *port = 0; 69 | 70 | sin4.sin_family = AF_INET; 71 | sin4.sin_port = htons(atoi(port + 1)); 72 | if (name[0] == 0) { 73 | sin4.sin_addr.s_addr = INADDR_ANY; 74 | } 75 | else { 76 | sin4.sin_addr.s_addr = inet_addr(name); 77 | } 78 | 79 | *port = ':'; 80 | 81 | vpn_ws_fd fd = (vpn_ws_fd) socket(AF_INET, SOCK_STREAM, 0); 82 | if (fd < 0) { 83 | vpn_ws_error("vpn_ws_bind_ipv4()/socket()"); 84 | return vpn_ws_invalid_fd; 85 | } 86 | 87 | int reuse = 1; 88 | if (setsockopt(vpn_ws_socket_cast(fd), SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, sizeof(int)) < 0) { 89 | vpn_ws_error("vpn_ws_bind_ipv4()/setsockopt()"); 90 | close(fd); 91 | return vpn_ws_invalid_fd; 92 | } 93 | 94 | if (bind(vpn_ws_socket_cast(fd), (struct sockaddr *) &sin4, sizeof(struct sockaddr_in))) { 95 | vpn_ws_error("vpn_ws_bind_ipv4()/bind()"); 96 | close(fd); 97 | return vpn_ws_invalid_fd; 98 | } 99 | 100 | if (listen(vpn_ws_socket_cast(fd), 100)) { 101 | vpn_ws_error("vpn_ws_bind_ipv4()/listen()"); 102 | close(fd); 103 | return vpn_ws_invalid_fd; 104 | } 105 | 106 | return fd; 107 | } 108 | 109 | vpn_ws_fd vpn_ws_bind_unix(char *name) { 110 | 111 | #ifdef __WIN32__ 112 | vpn_ws_log("UNIX domain sockets not supported on windows"); 113 | return NULL; 114 | #else 115 | 116 | // ignore unlink error 117 | unlink(name); 118 | 119 | int fd = socket(AF_UNIX, SOCK_STREAM, 0); 120 | if (fd < 0) { 121 | vpn_ws_error("vpn_ws_bind_unix()/socket()"); 122 | return -1; 123 | } 124 | 125 | struct sockaddr_un s_un; 126 | memset(&s_un, 0, sizeof(struct sockaddr_un)); 127 | s_un.sun_family = AF_UNIX; 128 | strncpy(s_un.sun_path, name, sizeof(s_un.sun_path)); 129 | 130 | if (bind(fd, (struct sockaddr *) &s_un, sizeof(struct sockaddr_un)) < 0) { 131 | vpn_ws_error("vpn_ws_bind_unix()/bind()"); 132 | close(fd); 133 | return -1; 134 | } 135 | 136 | if (listen(fd, 100) < 0) { 137 | vpn_ws_error("vpn_ws_bind_unix()/listen()"); 138 | close(fd); 139 | return -1; 140 | } 141 | 142 | if (chmod(name, 0666)) { 143 | vpn_ws_error("vpn_ws_bind_unix()/chmod()"); 144 | close(fd); 145 | return -1; 146 | } 147 | 148 | return fd; 149 | #endif 150 | } 151 | 152 | /* 153 | this needs to manage AF_UNIX, AF_INET and AF_INET6 154 | */ 155 | vpn_ws_fd vpn_ws_bind(char *name) { 156 | char *colon = strchr(name, ':'); 157 | if (!colon) return vpn_ws_bind_unix(name); 158 | if (name[0] == '[') return vpn_ws_bind_ipv6(name); 159 | return vpn_ws_bind_ipv4(name); 160 | } 161 | 162 | void vpn_ws_peer_create(int queue, vpn_ws_fd client_fd, uint8_t *mac) { 163 | if (vpn_ws_nb(client_fd)) { 164 | close(client_fd); 165 | return; 166 | } 167 | 168 | if (vpn_ws_event_add_read(queue, client_fd)) { 169 | close(client_fd); 170 | return; 171 | } 172 | 173 | // create a new peer structure 174 | // we use >= so we can lazily allocate memory even if fd is 0 175 | #ifndef __WIN32__ 176 | if (client_fd >= vpn_ws_conf.peers_n) { 177 | void *tmp = realloc(vpn_ws_conf.peers, sizeof(vpn_ws_peer *) * (client_fd+1)); 178 | if (!tmp) { 179 | vpn_ws_error("vpn_ws_peer_accept()/realloc()"); 180 | close(client_fd); 181 | return; 182 | } 183 | uint64_t delta = (client_fd+1) - vpn_ws_conf.peers_n; 184 | memset(tmp + (sizeof(vpn_ws_peer *) * vpn_ws_conf.peers_n), 0, sizeof(vpn_ws_peer *) * delta); 185 | vpn_ws_conf.peers_n = client_fd+1; 186 | vpn_ws_conf.peers = (vpn_ws_peer **) tmp; 187 | } 188 | #else 189 | // TODO find a solution for windows 190 | #endif 191 | 192 | vpn_ws_peer *peer = vpn_ws_calloc(sizeof(vpn_ws_peer)); 193 | if (!peer) { 194 | close(client_fd); 195 | return; 196 | } 197 | 198 | peer->fd = client_fd; 199 | 200 | if (mac) { 201 | memcpy(peer->mac, mac, 6); 202 | vpn_ws_announce_peer(peer, "registered new"); 203 | peer->mac_collected = 1; 204 | // if we have a mac, the handshake is not needed 205 | peer->handshake = 1; 206 | // ... and we have a raw peer 207 | peer->raw = 1; 208 | } 209 | 210 | #ifndef __WIN32__ 211 | vpn_ws_conf.peers[client_fd] = peer; 212 | #else 213 | // TODO find a solution for windows 214 | #endif 215 | 216 | } 217 | 218 | void vpn_ws_peer_accept(int queue, int fd) { 219 | #ifndef __WIN32__ 220 | struct sockaddr_un s_un; 221 | memset(&s_un, 0, sizeof(struct sockaddr_un)); 222 | 223 | socklen_t s_len = sizeof(struct sockaddr_un); 224 | #else 225 | struct sockaddr_in6 s_un; 226 | memset(&s_un, 0, sizeof(struct sockaddr_in6)); 227 | 228 | socklen_t s_len = sizeof(struct sockaddr_in6); 229 | #endif 230 | 231 | int client_fd = accept(fd, (struct sockaddr *) &s_un, &s_len); 232 | if (client_fd < 0) { 233 | vpn_ws_error("vpn_ws_peer_accept()/accept()"); 234 | return; 235 | } 236 | 237 | #ifndef __WIN32__ 238 | vpn_ws_peer_create(queue, client_fd, NULL); 239 | #else 240 | // TODO find a solution for windows 241 | #endif 242 | } 243 | -------------------------------------------------------------------------------- /src/ssl.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | #ifndef __WIN32__ 4 | static int _vpn_ws_ssl_wait_read(int fd) { 5 | fd_set rset; 6 | FD_ZERO(&rset); 7 | FD_SET(fd, &rset); 8 | if (select(fd+1, &rset, NULL, NULL, NULL) < 0) { 9 | vpn_ws_error("_vpn_ws_ssl_wait_read()/select()"); 10 | return -1; 11 | } 12 | return 0; 13 | } 14 | 15 | static int _vpn_ws_ssl_wait_write(int fd) { 16 | fd_set wset; 17 | FD_ZERO(&wset); 18 | FD_SET(fd, &wset); 19 | if (select(fd+1, NULL, &wset, NULL, NULL) < 0) { 20 | vpn_ws_error("_vpn_ws_ssl_wait_write()/select()"); 21 | return -1; 22 | } 23 | return 0; 24 | } 25 | #endif 26 | 27 | 28 | #if defined(__APPLE__) 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | static OSStatus _vpn_ws_ssl_read(SSLConnectionRef ctx, void *data, size_t *rlen) { 35 | vpn_ws_peer *peer = (vpn_ws_peer *) ctx; 36 | size_t remains = *rlen; 37 | char *ptr = data; 38 | while(remains) { 39 | ssize_t ret = read(peer->fd, ptr, remains); 40 | if (ret == 0) { 41 | return errSSLClosedGraceful; 42 | } 43 | else if (ret < 0) { 44 | if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS) { 45 | if (_vpn_ws_ssl_wait_read(peer->fd)) { 46 | return errSSLClosedAbort; 47 | } 48 | continue; 49 | } 50 | return errSSLClosedAbort; 51 | } 52 | ptr += ret; 53 | remains -= ret; 54 | } 55 | return noErr; 56 | } 57 | 58 | static OSStatus _vpn_ws_ssl_write(SSLConnectionRef ctx, const void *data, size_t *wlen) { 59 | vpn_ws_peer *peer = (vpn_ws_peer *) ctx; 60 | size_t remains = *wlen; 61 | const char *ptr = data; 62 | while(remains) { 63 | ssize_t ret = write(peer->fd, ptr, remains); 64 | if (ret == 0) { 65 | return errSSLClosedGraceful; 66 | } 67 | else if (ret < 0) { 68 | if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS) { 69 | if (_vpn_ws_ssl_wait_write(peer->fd)) { 70 | return errSSLClosedAbort; 71 | } 72 | continue; 73 | } 74 | return errSSLClosedAbort; 75 | } 76 | ptr += ret; 77 | remains -= ret; 78 | } 79 | return noErr; 80 | } 81 | 82 | void *vpn_ws_ssl_handshake(vpn_ws_peer *peer, char *sni, char *key, char *crt) { 83 | SSLContextRef ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType); 84 | if (!ctx) { 85 | vpn_ws_error("vpn_ws_ssl_handshake()/SSLCreateContext()"); 86 | return NULL; 87 | } 88 | // tls is the minimal supported protocol 89 | (void)SSLSetProtocolVersionMin(ctx, kTLSProtocol1); 90 | (void)SSLSetProtocolVersionMax(ctx, kTLSProtocol12); 91 | 92 | OSStatus err = SSLSetIOFuncs(ctx, _vpn_ws_ssl_read, _vpn_ws_ssl_write); 93 | if (err != noErr) { 94 | vpn_ws_log("vpn_ws_ssl_handshake()/SSLSetIOFuncs(): %d", err); 95 | goto error; 96 | } 97 | 98 | 99 | err = SSLSetConnection(ctx, peer); 100 | if (err != noErr) { 101 | vpn_ws_log("vpn_ws_ssl_handshake()/SSLSetConnection(): %d", err); 102 | goto error; 103 | } 104 | 105 | // disable verification 106 | if (vpn_ws_conf.ssl_no_verify) { 107 | err = SSLSetSessionOption(ctx, kSSLSessionOptionBreakOnServerAuth, true); 108 | if (err != noErr) { 109 | vpn_ws_log("vpn_ws_ssl_handshake()/SSLSetSessionOption(): %d", err); 110 | goto error; 111 | } 112 | } 113 | 114 | // we use the keychain (so only --crt is supported) 115 | if (crt) { 116 | CFStringRef label = CFStringCreateWithCString(NULL, crt, kCFStringEncodingUTF8); 117 | SecPolicyRef policy = SecPolicyCreateSSL(false, label); 118 | CFTypeRef dict_k[4]; 119 | CFTypeRef dict_v[4]; 120 | 121 | dict_k[0] = kSecClass; dict_v[0] = kSecClassIdentity; 122 | dict_k[1] = kSecReturnRef; dict_v[1] = kCFBooleanTrue; 123 | dict_k[2] = kSecMatchLimit; dict_v[2] = kSecMatchLimitOne; 124 | dict_k[3] = kSecMatchPolicy; dict_v[3] = policy; 125 | 126 | CFDictionaryRef dict = CFDictionaryCreate(NULL, dict_k, dict_v, 4, 127 | &kCFCopyStringDictionaryKeyCallBacks, 128 | &kCFTypeDictionaryValueCallBacks); 129 | CFRelease(policy); 130 | CFRelease(label); 131 | 132 | SecIdentityRef sec[1] = {NULL}; 133 | err = SecItemCopyMatching(dict, (CFTypeRef *)&sec[0]); 134 | CFRelease(dict); 135 | if (err != noErr) { 136 | vpn_ws_log("vpn_ws_ssl_handshake()/SecItemCopyMatching(): %d", err); 137 | goto error; 138 | } 139 | CFArrayRef certs = CFArrayCreate(NULL, (const void **)sec, 1, &kCFTypeArrayCallBacks); 140 | err = SSLSetCertificate(ctx, certs); 141 | if(certs) CFRelease(certs); 142 | CFRelease(sec[0]); 143 | if (err != noErr) { 144 | vpn_ws_log("vpn_ws_ssl_handshake()/SSLSetCertificate(): %d", err); 145 | goto error; 146 | } 147 | } 148 | 149 | err = SSLSetPeerDomainName(ctx, sni, strlen(sni)); 150 | if (err != noErr) { 151 | vpn_ws_log("vpn_ws_ssl_handshake()/SSLSetPeerDomainName(): %d", err); 152 | goto error; 153 | } 154 | 155 | for(;;) { 156 | err = SSLHandshake(ctx); 157 | if (err != noErr) { 158 | if (err == errSSLServerAuthCompleted) continue; 159 | vpn_ws_log("vpn_ws_ssl_handshake()/SSLHandshake(): %d", err); 160 | goto error; 161 | } 162 | break; 163 | } 164 | return ctx; 165 | 166 | error: 167 | CFRelease(ctx); 168 | return NULL; 169 | } 170 | 171 | int vpn_ws_ssl_write(void *ctx, uint8_t *buf, uint64_t len) { 172 | size_t processed = -1; 173 | OSStatus err = SSLWrite((SSLContextRef) ctx, (const void *)buf, len, &processed); 174 | if (processed != len) return -1; 175 | if (err == noErr) return 0; 176 | return -1; 177 | } 178 | 179 | ssize_t vpn_ws_ssl_read(void *ctx, uint8_t *buf, uint64_t len) { 180 | size_t processed = -1; 181 | OSStatus err = SSLRead((SSLContextRef) ctx, buf, len, &processed); 182 | if (err == noErr) return processed; 183 | if (err == errSSLClosedGraceful) return 0; 184 | return -1; 185 | } 186 | 187 | void vpn_ws_ssl_close(void *ctx) { 188 | SSLClose((SSLContextRef)ctx); 189 | CFRelease(ctx); 190 | } 191 | 192 | #elif defined(__WIN32__) 193 | #include 194 | #include 195 | #include 196 | void *vpn_ws_ssl_handshake(vpn_ws_peer *peer, char *sni, char *key, char *crt) { 197 | PSecurityFunctionTable sec = InitSecurityInterfaceA(); 198 | vpn_ws_log("%p", sec); 199 | return sec; 200 | } 201 | 202 | ssize_t vpn_ws_ssl_read(void *ctx, uint8_t *buf, uint64_t len) { 203 | return -1; 204 | } 205 | 206 | int vpn_ws_ssl_write(void *ctx, uint8_t *buf, uint64_t len) { 207 | return -1; 208 | } 209 | 210 | void vpn_ws_ssl_close(void *ctx) { 211 | } 212 | 213 | 214 | #else 215 | 216 | // use openssl 217 | 218 | #include "openssl/conf.h" 219 | #include "openssl/ssl.h" 220 | #include 221 | 222 | static int ssl_initialized = 0; 223 | int ssl_peer_index = -1; 224 | static SSL_CTX *ssl_ctx = NULL; 225 | 226 | void *vpn_ws_ssl_handshake(vpn_ws_peer *peer, char *sni, char *key, char *crt) { 227 | if (!ssl_initialized) { 228 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 229 | OPENSSL_config(NULL); 230 | #else 231 | OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG,NULL); 232 | #endif 233 | SSL_library_init(); 234 | SSL_load_error_strings(); 235 | OpenSSL_add_all_algorithms(); 236 | ssl_initialized = 1; 237 | } 238 | 239 | if (!ssl_ctx) { 240 | ssl_ctx = SSL_CTX_new(SSLv23_client_method()); 241 | if (!ssl_ctx) { 242 | vpn_ws_warning("vpn_ws_ssl_handshake(): unable to initialize context"); 243 | return NULL; 244 | } 245 | long ssloptions = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_ALL | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; 246 | #ifdef SSL_OP_NO_COMPRESSION 247 | ssloptions |= SSL_OP_NO_COMPRESSION; 248 | #endif 249 | // release/reuse buffers as soon as possibile 250 | #ifdef SSL_MODE_RELEASE_BUFFERS 251 | SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); 252 | #endif 253 | if (vpn_ws_conf.ssl_no_verify) { 254 | SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); 255 | } 256 | else { 257 | SSL_CTX_load_verify_locations(ssl_ctx, "/etc/ssl/certs/ca-certificates.crt", "/etc/ssl/certs/"); 258 | SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); 259 | 260 | if(SSL_CTX_set_default_verify_paths(ssl_ctx) <= 0) { 261 | vpn_ws_log("vpn_ws_ssl_handshake(): unable to set default peer certificate verify paths\n"); 262 | SSL_CTX_free(ssl_ctx); 263 | ssl_ctx = NULL; 264 | return NULL; 265 | } 266 | } 267 | ssl_peer_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); 268 | 269 | if (key) { 270 | if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key, SSL_FILETYPE_PEM) <= 0) { 271 | vpn_ws_warning("vpn_ws_ssl_handshake(): unable to load key %s", key); 272 | SSL_CTX_free(ssl_ctx); 273 | ssl_ctx = NULL; 274 | return NULL; 275 | } 276 | } 277 | 278 | if (crt) { 279 | if (SSL_CTX_use_certificate_chain_file(ssl_ctx, crt) <= 0) { 280 | vpn_ws_warning("vpn_ws_ssl_handshake(): unable to load certificate %s", crt); 281 | SSL_CTX_free(ssl_ctx); 282 | ssl_ctx = NULL; 283 | return NULL; 284 | } 285 | } 286 | } 287 | 288 | 289 | SSL *ssl = SSL_new(ssl_ctx); 290 | if (!ssl) { 291 | vpn_ws_warning("vpn_ws_ssl_handshake(): unable to initialize session"); 292 | return NULL; 293 | } 294 | SSL_set_fd(ssl, peer->fd); 295 | SSL_set_tlsext_host_name(ssl, sni); 296 | 297 | SSL_set_ex_data(ssl, ssl_peer_index, peer); 298 | 299 | int err = 0; 300 | 301 | for(;;) { 302 | int ret = SSL_connect(ssl); 303 | if (ret > 0) break; 304 | err = SSL_get_error(ssl, ret); 305 | if (err == SSL_ERROR_WANT_READ) { 306 | if (_vpn_ws_ssl_wait_read(peer->fd)) goto error; 307 | continue; 308 | } 309 | if (err == SSL_ERROR_WANT_WRITE) { 310 | if (_vpn_ws_ssl_wait_write(peer->fd)) goto error; 311 | continue; 312 | } 313 | goto error; 314 | } 315 | 316 | return ssl; 317 | 318 | error: 319 | err = ERR_get_error(); 320 | vpn_ws_warning("vpn_ws_ssl_handshake(): %s", ERR_error_string(err, NULL)); 321 | ERR_clear_error(); 322 | SSL_free(ssl); 323 | return NULL; 324 | } 325 | 326 | int vpn_ws_ssl_write(void *ctx, uint8_t *buf, uint64_t len) { 327 | vpn_ws_peer *peer = (vpn_ws_peer *) SSL_get_ex_data((SSL *)ctx, ssl_peer_index); 328 | for(;;) { 329 | int ret = SSL_write((SSL *)ctx, buf, len); 330 | if (ret > 0) break; 331 | int err = SSL_get_error((SSL *)ctx, ret); 332 | if (err == SSL_ERROR_WANT_READ) { 333 | if (_vpn_ws_ssl_wait_read(peer->fd)) return -1; 334 | continue; 335 | } 336 | if (err == SSL_ERROR_WANT_WRITE) { 337 | if (_vpn_ws_ssl_wait_write(peer->fd)) return -1; 338 | continue; 339 | } 340 | return -1; 341 | } 342 | return 0; 343 | } 344 | 345 | ssize_t vpn_ws_ssl_read(void *ctx, uint8_t *buf, uint64_t len) { 346 | vpn_ws_peer *peer = (vpn_ws_peer *) SSL_get_ex_data((SSL *)ctx, ssl_peer_index); 347 | ssize_t ret = -1; 348 | for(;;) { 349 | ret = SSL_read((SSL *)ctx, buf, len); 350 | if (ret > 0) break; 351 | int err = SSL_get_error((SSL *)ctx, ret); 352 | if (err == SSL_ERROR_WANT_READ) { 353 | if (_vpn_ws_ssl_wait_read(peer->fd)) return -1; 354 | continue; 355 | } 356 | if (err == SSL_ERROR_WANT_WRITE) { 357 | if (_vpn_ws_ssl_wait_write(peer->fd)) return -1; 358 | continue; 359 | } 360 | return -1; 361 | } 362 | return ret; 363 | } 364 | 365 | void vpn_ws_ssl_close(void *ctx) { 366 | vpn_ws_peer *peer = (vpn_ws_peer *) SSL_get_ex_data((SSL *)ctx, ssl_peer_index); 367 | for(;;) { 368 | int ret = SSL_shutdown((SSL *)ctx); 369 | if (ret > 0) break; 370 | int err = SSL_get_error((SSL *)ctx, ret); 371 | if (err == SSL_ERROR_WANT_READ) { 372 | if (_vpn_ws_ssl_wait_read(peer->fd)) break; 373 | continue; 374 | } 375 | if (err == SSL_ERROR_WANT_WRITE) { 376 | if (_vpn_ws_ssl_wait_write(peer->fd)) break; 377 | continue; 378 | } 379 | break; 380 | } 381 | ERR_clear_error(); 382 | SSL_free((SSL *) ctx); 383 | } 384 | 385 | #endif 386 | -------------------------------------------------------------------------------- /src/tuntap.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) 4 | #include 5 | #include 6 | #endif 7 | 8 | #if defined(__linux__) 9 | 10 | #include 11 | #define TUNTAP_DEVICE "/dev/net/tun" 12 | 13 | int vpn_ws_tuntap(char *name) { 14 | struct ifreq ifr; 15 | int fd = open(TUNTAP_DEVICE, O_RDWR); 16 | if (fd < 0) { 17 | vpn_ws_error("vpn_ws_tuntap()/open()"); 18 | return -1; 19 | } 20 | 21 | memset(&ifr, 0, sizeof(struct ifreq)); 22 | 23 | ifr.ifr_flags = IFF_TAP | IFF_NO_PI; 24 | strncpy(ifr.ifr_name, name, IFNAMSIZ); 25 | 26 | if (ioctl(fd, TUNSETIFF, (void *) &ifr) < 0) { 27 | vpn_ws_error("vpn_ws_tuntap()/ioctl()"); 28 | return -1; 29 | } 30 | 31 | if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { 32 | vpn_ws_error("vpn_ws_tuntap()/ioctl()"); 33 | return -1; 34 | } 35 | 36 | // copy MAC address 37 | memcpy(vpn_ws_conf.tuntap_mac, ifr.ifr_hwaddr.sa_data, 6); 38 | //printf("%x %x\n", vpn_ws_conf.tuntap_mac[0], vpn_ws_conf.tuntap_mac[1]); 39 | 40 | return fd; 41 | } 42 | 43 | #elif defined(__WIN32__) 44 | 45 | #include 46 | #include 47 | 48 | #define NETWORK_CONNECTIONS_KEY "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}" 49 | 50 | #define TAP_WIN_CONTROL_CODE(request,method) \ 51 | CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS) 52 | 53 | #define TAP_WIN_IOCTL_GET_MAC TAP_WIN_CONTROL_CODE (1, METHOD_BUFFERED) 54 | 55 | #define TAP_WIN_IOCTL_SET_MEDIA_STATUS TAP_WIN_CONTROL_CODE (6, METHOD_BUFFERED) 56 | 57 | 58 | HANDLE vpn_ws_tuntap(char *name) { 59 | HANDLE handle; 60 | HKEY adapter_key; 61 | HKEY unit_key; 62 | DWORD data_type; 63 | LONG status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, 64 | NETWORK_CONNECTIONS_KEY, 0, 65 | KEY_READ, &adapter_key); 66 | 67 | if (status != ERROR_SUCCESS) { 68 | vpn_ws_error("vpn_ws_tuntap()/RegOpenKeyEx()"); 69 | return NULL; 70 | } 71 | 72 | int i = 0; 73 | for(;;) { 74 | char enum_name[256]; 75 | char unit_name[256]; 76 | DWORD len = sizeof(enum_name); 77 | 78 | status = RegEnumKeyEx(adapter_key, i, enum_name, &len, 79 | NULL, NULL, NULL, NULL); 80 | 81 | if (status != ERROR_SUCCESS) goto end; 82 | 83 | snprintf(unit_name, sizeof(unit_name), "%s\\%s\\Connection", 84 | NETWORK_CONNECTIONS_KEY, enum_name); 85 | 86 | status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, unit_name, 0, KEY_READ, &unit_key); 87 | 88 | if (status != ERROR_SUCCESS) goto next; 89 | 90 | char *netname_str = "Name"; 91 | char netname[256]; 92 | 93 | len = sizeof(netname); 94 | status = RegQueryValueEx(unit_key, 95 | netname_str, 96 | NULL, &data_type, 97 | (LPBYTE)netname, &len); 98 | 99 | if (status != ERROR_SUCCESS || data_type != REG_SZ) { 100 | RegCloseKey(unit_key); 101 | goto next; 102 | } 103 | 104 | RegCloseKey(unit_key); 105 | 106 | if (!strcmp(netname, name)) { 107 | char dev[256]; 108 | snprintf(dev, 256, "\\\\.\\Global\\%s.tap", enum_name); 109 | handle = CreateFile(dev, 110 | GENERIC_READ|GENERIC_WRITE, 111 | 0, 0, OPEN_EXISTING, 112 | FILE_ATTRIBUTE_SYSTEM|FILE_FLAG_OVERLAPPED, 0); 113 | if (handle == INVALID_HANDLE_VALUE) { 114 | vpn_ws_error("vpn_ws_tuntap()/CreateFile()"); 115 | break; 116 | } 117 | 118 | if (!DeviceIoControl(handle, TAP_WIN_IOCTL_GET_MAC, vpn_ws_conf.tuntap_mac, 6, vpn_ws_conf.tuntap_mac, 6, &len, NULL)) { 119 | vpn_ws_error("vpn_ws_tuntap()/DeviceIoControl()"); 120 | CloseHandle(handle); 121 | break; 122 | } 123 | 124 | ULONG status = TRUE; 125 | if (!DeviceIoControl(handle, TAP_WIN_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL)) { 126 | vpn_ws_error("vpn_ws_tuntap()/DeviceIoControl()"); 127 | CloseHandle(handle); 128 | break; 129 | } 130 | return handle; 131 | } 132 | 133 | 134 | 135 | next: 136 | i++; 137 | 138 | } 139 | end: 140 | RegCloseKey(adapter_key); 141 | return NULL; 142 | } 143 | 144 | #else 145 | 146 | #if defined(__APPLE__) 147 | #include 148 | #include 149 | // like linux 150 | #define SIOCGIFHWADDR 0x8927 151 | #endif 152 | 153 | int vpn_ws_tuntap(char *name) { 154 | int fd = -1; 155 | #if defined(__APPLE__) 156 | // is it it.unbit.utap ? 157 | if (vpn_ws_is_a_number(name)) { 158 | fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); 159 | if (fd < 0) { 160 | vpn_ws_error("vpn_ws_tuntap()/socket()"); 161 | return -1; 162 | } 163 | struct ctl_info info; 164 | memset(&info, 0, sizeof(info)); 165 | strncpy(info.ctl_name, "it.unbit.utap", sizeof(info.ctl_name)); 166 | if (ioctl(fd, CTLIOCGINFO, &info)) { 167 | vpn_ws_error("vpn_ws_tuntap()/ioctl()"); 168 | close(fd); 169 | return -1; 170 | } 171 | struct sockaddr_ctl addr; 172 | memset(&addr, 0, sizeof(addr)); 173 | addr.sc_len = sizeof(addr); 174 | addr.sc_family = AF_SYSTEM; 175 | addr.ss_sysaddr = AF_SYS_CONTROL; 176 | addr.sc_id = info.ctl_id; 177 | addr.sc_unit = atoi(name); 178 | 179 | if (connect(fd, (struct sockaddr *)&addr, sizeof(addr))) { 180 | vpn_ws_error("vpn_ws_tuntap()/connect()"); 181 | close(fd); 182 | return -1; 183 | } 184 | 185 | socklen_t mac_len = 6; 186 | if (getsockopt(fd, SYSPROTO_CONTROL, SIOCGIFHWADDR, vpn_ws_conf.tuntap_mac, &mac_len)) { 187 | vpn_ws_error("vpn_ws_tuntap()/getsockopt()"); 188 | close(fd); 189 | return -1; 190 | } 191 | 192 | return fd; 193 | } 194 | else { 195 | #endif 196 | fd = open(name, O_RDWR); 197 | if (fd < 0) { 198 | vpn_ws_error("vpn_ws_tuntap()/open()"); 199 | return -1; 200 | } 201 | #if defined(__APPLE__) 202 | } 203 | #endif 204 | 205 | #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) 206 | int mib[6]; 207 | mib[0] = CTL_NET; 208 | mib[1] = AF_ROUTE; 209 | mib[2] = 0; 210 | mib[3] = AF_LINK; 211 | mib[4] = NET_RT_IFLIST; 212 | mib[5] = if_nametoindex(name+5); 213 | if (!mib[5]) { 214 | vpn_ws_error("vpn_ws_tuntap()/if_nametoindex()"); 215 | close(fd); 216 | return -1; 217 | } 218 | 219 | size_t len; 220 | if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { 221 | vpn_ws_error("vpn_ws_tuntap()/sysctl()"); 222 | close(fd); 223 | return -1; 224 | } 225 | 226 | char *buf = vpn_ws_malloc(len); 227 | if (!buf) { 228 | close(fd); 229 | return -1; 230 | } 231 | 232 | if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) { 233 | vpn_ws_error("vpn_ws_tuntap()/sysctl()"); 234 | close(fd); 235 | return -1; 236 | } 237 | 238 | struct if_msghdr *ifm = (struct if_msghdr *)buf; 239 | struct sockaddr_dl *sdl = (struct sockaddr_dl *)(ifm + 1); 240 | uint8_t *ptr = (uint8_t *)LLADDR(sdl); 241 | // copy MAC address 242 | memcpy(vpn_ws_conf.tuntap_mac, ptr, 6); 243 | #endif 244 | 245 | return fd; 246 | } 247 | 248 | #endif 249 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | int vpn_ws_nb(vpn_ws_fd fd) { 4 | #ifndef __WIN32__ 5 | int arg = fcntl(fd, F_GETFL, NULL); 6 | if (arg < 0) { 7 | vpn_ws_error("vpn_ws_nb()/fcntl()"); 8 | return -1; 9 | } 10 | arg |= O_NONBLOCK; 11 | if (fcntl(fd, F_SETFL, arg) < 0) { 12 | vpn_ws_error("vpn_ws_nb()/fcntl()"); 13 | return -1; 14 | } 15 | #endif 16 | return 0; 17 | } 18 | 19 | void vpn_ws_announce_peer(vpn_ws_peer *peer, char *msg) { 20 | if (peer->raw) return; 21 | if (!peer->mac_collected) return; 22 | #ifndef __WIN32__ 23 | vpn_ws_log("%s peer %d MAC=%02X:%02X:%02X:%02X:%02X:%02X REMOTE_ADDR=%s REMOTE_USER=%s DN=%s" 24 | #else 25 | vpn_ws_log("%s peer %p MAC=%02X:%02X:%02X:%02X:%02X:%02X REMOTE_ADDR=%s REMOTE_USER=%s DN=%s" 26 | #endif 27 | ,msg, 28 | peer->fd, 29 | peer->mac[0], 30 | peer->mac[1], 31 | peer->mac[2], 32 | peer->mac[3], 33 | peer->mac[4], 34 | peer->mac[5], 35 | peer->remote_addr ? peer->remote_addr : "", 36 | peer->remote_user ? peer->remote_user : "", 37 | peer->dn ? peer->dn : ""); 38 | } 39 | 40 | int vpn_ws_str_to_uint(char *buf, uint64_t len) { 41 | int n = 0; 42 | while(len--) { 43 | n = n*10 + *buf++ - '0'; 44 | } 45 | return n; 46 | } 47 | 48 | char *vpn_ws_strndup(char *s, size_t len) { 49 | #ifndef __WIN32__ 50 | return strndup(s, len); 51 | #else 52 | char *s2 = vpn_ws_malloc(len+1); 53 | if (!s2) return NULL; 54 | memcpy(s2, s, len); 55 | s2[len] = 0; 56 | return s2; 57 | #endif 58 | } 59 | 60 | int vpn_ws_is_a_number(char *s) { 61 | size_t i, len = strlen(s); 62 | for(i=0;ipos < 4) return 0; 11 | *modifier1 = peer->buf[0]; 12 | *modifier2 = peer->buf[3]; 13 | uint16_t uwsgi_pktsize = vpn_ws_le16(peer->buf+1); 14 | if (peer->pos < (4 + uwsgi_pktsize)) return 0; 15 | 16 | uint8_t *pkt = peer->buf+4; 17 | 18 | ssize_t ret = 4 + uwsgi_pktsize; 19 | 20 | while(uwsgi_pktsize) { 21 | if (uwsgi_pktsize < 2) return -1; 22 | uint16_t keylen = vpn_ws_le16(pkt); 23 | uwsgi_pktsize -= 2; 24 | pkt += 2; 25 | if (keylen == 0) return -1; 26 | if (keylen > uwsgi_pktsize) return -1; 27 | char *key = (char *) pkt; 28 | uwsgi_pktsize -= keylen; 29 | pkt += keylen; 30 | 31 | if (uwsgi_pktsize < 2) return -1; 32 | uint16_t vallen = vpn_ws_le16(pkt); 33 | uwsgi_pktsize -= 2; 34 | pkt += 2; 35 | if (vallen > uwsgi_pktsize) return -1; 36 | char *val = (char *) pkt; 37 | uwsgi_pktsize -= vallen; 38 | pkt += vallen; 39 | 40 | if (vpn_ws_peer_add_var(peer, key, keylen, val, vallen)) return -1; 41 | } 42 | 43 | return ret; 44 | } 45 | 46 | int vpn_ws_peer_add_var(vpn_ws_peer *peer, char *key, uint16_t keylen, char *val, uint16_t vallen) { 47 | // max 64 vars 48 | uint16_t pos = peer->vars_n; 49 | if (pos >= 64) return -1; 50 | 51 | peer->vars[pos].key = key; 52 | peer->vars[pos].keylen = keylen; 53 | peer->vars[pos].value = val; 54 | peer->vars[pos].vallen = vallen; 55 | peer->vars_n++; 56 | return 0; 57 | } 58 | 59 | char *vpn_ws_peer_get_var(vpn_ws_peer *peer, char *key, uint16_t keylen, uint16_t *vallen) { 60 | int i; 61 | if (peer->vars_n == 0) return NULL; 62 | for(i=peer->vars_n-1;i>=0;i--) { 63 | if (keylen != peer->vars[i].keylen) continue; 64 | if (!memcmp(key, peer->vars[i].key, keylen)) { 65 | *vallen = peer->vars[i].vallen; 66 | return peer->vars[i].value; 67 | } 68 | } 69 | return NULL; 70 | } 71 | 72 | #define HTTP_RESPONSE "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " 73 | 74 | int64_t vpn_ws_handshake(int queue, vpn_ws_peer *peer) { 75 | uint8_t modifier1 = 0; 76 | uint8_t modifier2 = 0; 77 | ssize_t rlen = vpn_ws_uwsgi_parse(peer, &modifier1, &modifier2); 78 | if (rlen < 0) return -1; 79 | if (rlen == 0) return 0; 80 | 81 | char *remote_addr = vpn_ws_peer_get_var(peer, "REMOTE_ADDR", 11, &peer->remote_addr_len); 82 | if (remote_addr) { 83 | peer->remote_addr = vpn_ws_strndup(remote_addr, peer->remote_addr_len); 84 | } 85 | 86 | char *remote_user = vpn_ws_peer_get_var(peer, "REMOTE_USER", 11, &peer->remote_user_len); 87 | if (remote_user) { 88 | peer->remote_user = vpn_ws_strndup(remote_user, peer->remote_user_len); 89 | } 90 | 91 | char *https_dn = vpn_ws_peer_get_var(peer, "HTTPS_DN", 8, &peer->dn_len); 92 | if (https_dn) { 93 | peer->dn = vpn_ws_strndup(https_dn, peer->dn_len); 94 | } 95 | else { 96 | https_dn = vpn_ws_peer_get_var(peer, "DN", 2, &peer->dn_len); 97 | if (https_dn) { 98 | peer->dn = vpn_ws_strndup(https_dn, peer->dn_len); 99 | } 100 | } 101 | 102 | 103 | // control request ? 104 | if (modifier1 == 1) { 105 | peer->ctrl = 1; 106 | return vpn_ws_ctrl_json(queue, peer); 107 | } 108 | 109 | // now check for websocket request 110 | uint16_t ws_key_len = 0; 111 | char *ws_key = vpn_ws_peer_get_var(peer, "HTTP_SEC_WEBSOCKET_KEY", 22, &ws_key_len); 112 | if (!ws_key) return -1; 113 | 114 | 115 | // check if the X-vpn-ws-MAC header is available 116 | uint16_t ws_mac_len = 0; 117 | char *ws_mac = vpn_ws_peer_get_var(peer, "HTTP_X_VPN_WS_MAC", 17, &ws_mac_len); 118 | if (ws_mac) { 119 | if (ws_mac_len != 17) return -1; 120 | uint8_t i; 121 | for(i=0;i<6;i++) { 122 | ws_mac[(i*3)+2] = 0; 123 | uint8_t n = strtoul(ws_mac + (i*3), NULL, 16); 124 | peer->mac[i] = n; 125 | } 126 | peer->mac_collected = 1; 127 | vpn_ws_announce_peer(peer, "registered new"); 128 | } 129 | 130 | uint16_t ws_bridge_len = 0; 131 | char *ws_bridge = vpn_ws_peer_get_var(peer, "HTTP_X_VPN_WS_BRIDGE", 20, &ws_bridge_len); 132 | if (ws_bridge) { 133 | if (ws_bridge_len == 2 && ws_bridge[0] == 'o' && ws_bridge[1] == 'n') { 134 | peer->bridge = 1; 135 | } 136 | } 137 | 138 | peer->t = time(NULL); 139 | 140 | // build the response to complete the handshake 141 | // use a static malloc'ed are to prebuild the response and changing only 142 | // the result key 143 | static uint8_t *http_response = NULL; 144 | if (!http_response) { 145 | http_response = vpn_ws_malloc(1024); 146 | memcpy(http_response, HTTP_RESPONSE, sizeof(HTTP_RESPONSE)-1); 147 | } 148 | 149 | uint8_t sha1[20]; 150 | struct sha1_ctxt ctxt; 151 | sha1_init(&ctxt); 152 | sha1_loop(&ctxt, ws_key, ws_key_len); 153 | sha1_loop(&ctxt, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 36); 154 | sha1_result(&ctxt, sha1); 155 | 156 | // encode to base64 157 | uint8_t ws_accept[32]; 158 | uint16_t ws_accept_len = vpn_ws_base64_encode(sha1, 20, ws_accept); 159 | 160 | // append the result to the http response 161 | 162 | memcpy(http_response + sizeof(HTTP_RESPONSE)-1, ws_accept, ws_accept_len); 163 | memcpy(http_response + sizeof(HTTP_RESPONSE)-1 + ws_accept_len, "\r\n\r\n", 4); 164 | 165 | // send the response 166 | int ret = vpn_ws_write(peer, http_response, sizeof(HTTP_RESPONSE)-1 + ws_accept_len + 4); 167 | if (ret < 0) return -1; 168 | // again ? 169 | if (ret == 0) { 170 | peer->is_writing = 1; 171 | return vpn_ws_event_read_to_write(queue, peer->fd); 172 | } 173 | 174 | return rlen; 175 | } 176 | 177 | static int json_append(char *json, uint64_t *pos, uint64_t *len, char *buf, uint64_t buf_len) { 178 | if (*pos + buf_len > *len) { 179 | uint64_t delta = (*pos + buf_len) - *len; 180 | if (delta < 8192) delta = 8192; 181 | *len += delta; 182 | char *tmp = realloc(json, *len); 183 | if (!tmp) { 184 | vpn_ws_error("json_append()/realloc()"); 185 | return -1; 186 | } 187 | json = tmp; 188 | } 189 | memcpy(json+*pos, buf, buf_len); 190 | *pos += buf_len; 191 | return 0; 192 | } 193 | 194 | static int json_append_num(char *json, uint64_t *pos, uint64_t *len, int64_t n) { 195 | char buf[30]; 196 | #ifndef __WIN32__ 197 | int ret = snprintf(buf, 30, "%lld", (unsigned long long) n); 198 | #else 199 | // TODO fix it 200 | int ret = snprintf(buf, 30, "%d", (int) n); 201 | #endif 202 | if (ret <= 0 || ret > 30) return -1; 203 | return json_append(json, pos, len, buf, ret); 204 | } 205 | 206 | static int json_append_mac(char *json, uint64_t *pos, uint64_t *len, uint8_t *mac) { 207 | char buf[18]; 208 | int ret = snprintf(buf, 18, "%02X:%02X:%02X:%02X:%02X:%02X", 209 | mac[0], 210 | mac[1], 211 | mac[2], 212 | mac[3], 213 | mac[4], 214 | mac[5]); 215 | if (ret <= 0 || ret > 18) return -1; 216 | return json_append(json, pos, len, buf, ret); 217 | } 218 | 219 | static int json_append_json(char *json, uint64_t *pos, uint64_t *len, char *buf, uint64_t buf_len) { 220 | uint64_t i; 221 | for(i=0;i 0) { 278 | char *value = qs_check(found, found_len, key, key_len, v_len); 279 | if (value) return value; 280 | } 281 | return NULL; 282 | } 283 | 284 | /* 285 | 286 | JSON control interface 287 | 288 | */ 289 | 290 | #define HTTP_RESPONSE_JSON "HTTP/1.0 200 OK\r\nConnection: close\r\nCache-Control: no-cache, no-store, must-revalidate\r\nPragma: no-cache\r\nExpires: 0\r\nContent-Type: application/json\r\n\r\n" 291 | int64_t vpn_ws_ctrl_json(int queue, vpn_ws_peer *peer) { 292 | int ret; 293 | uint64_t json_pos = 0; 294 | uint64_t json_len = 8192; 295 | char *json = vpn_ws_malloc(json_len); 296 | if (!json) return -1; 297 | if (json_append(json, &json_pos, &json_len, HTTP_RESPONSE_JSON, sizeof(HTTP_RESPONSE_JSON)-1)) goto end; 298 | 299 | uint16_t query_string_len = 0; 300 | char *query_string = vpn_ws_peer_get_var(peer, "QUERY_STRING", 12, &query_string_len); 301 | if (query_string) { 302 | uint16_t kill_peer_len = 0; 303 | char *kill_peer = qs_get(query_string, query_string_len, "kill", 4, &kill_peer_len); 304 | if (kill_peer) { 305 | int fd = vpn_ws_str_to_uint(kill_peer, kill_peer_len); 306 | if (fd < 0 || fd > vpn_ws_conf.peers_n) { 307 | json[9] = '4'; 308 | json[10] = '0'; 309 | json[11] = '4'; 310 | if (json_append(json, &json_pos, &json_len, "{\"status\":\"not found\"}", 22)) goto end; 311 | goto commit; 312 | } 313 | vpn_ws_peer *b_peer = vpn_ws_conf.peers[fd]; 314 | if (!b_peer || b_peer->raw || b_peer->ctrl) { 315 | json[9] = '4'; 316 | json[10] = '0'; 317 | json[11] = '4'; 318 | if (json_append(json, &json_pos, &json_len, "{\"status\":\"not found\"}", 22)) goto end; 319 | goto commit; 320 | } 321 | vpn_ws_peer_destroy(b_peer); 322 | if (json_append(json, &json_pos, &json_len, "{\"status\":\"ok\"}", 15)) goto end; 323 | goto commit; 324 | } 325 | } 326 | 327 | if (json_append(json, &json_pos, &json_len, "{\"status\":\"ok\",\"peers\":[", 24)) goto end; 328 | 329 | uint64_t i; 330 | uint8_t found = 0; 331 | for(i=0;ictrl) continue; 336 | 337 | found = 1; 338 | 339 | if (json_append(json, &json_pos, &json_len, "{\"id\":", 6)) goto end; 340 | if (json_append_num(json, &json_pos, &json_len, (int) b_peer->fd)) goto end; 341 | 342 | if (json_append(json, &json_pos, &json_len, ",\"MAC\":\"", 8)) goto end; 343 | if (json_append_mac(json, &json_pos, &json_len, b_peer->mac)) goto end; 344 | 345 | if (json_append(json, &json_pos, &json_len, "\",\"REMOTE_ADDR\":\"", 17)) goto end; 346 | if (json_append_json(json, &json_pos, &json_len, b_peer->remote_addr, b_peer->remote_addr_len)) goto end; 347 | 348 | if (json_append(json, &json_pos, &json_len, "\",\"REMOTE_USER\":\"", 17)) goto end; 349 | if (json_append_json(json, &json_pos, &json_len, b_peer->remote_user, b_peer->remote_user_len)) goto end; 350 | 351 | if (json_append(json, &json_pos, &json_len, "\",\"DN\":\"", 8)) goto end; 352 | if (json_append_json(json, &json_pos, &json_len, b_peer->dn, b_peer->dn_len)) goto end; 353 | 354 | if (json_append(json, &json_pos, &json_len, "\",\"ts\":\"", 8)) goto end; 355 | if (json_append_json(json, &json_pos, &json_len, ctime(&b_peer->t), 24)) goto end; 356 | 357 | if (json_append(json, &json_pos, &json_len, "\",\"bridge\":", 11)) goto end; 358 | if (json_append_num(json, &json_pos, &json_len, b_peer->bridge)) goto end; 359 | 360 | if (json_append(json, &json_pos, &json_len, ",\"macs\":[", 9)) goto end; 361 | 362 | vpn_ws_mac *macs = b_peer->macs; 363 | while(macs) { 364 | if (json_append(json, &json_pos, &json_len, "\"",1)) goto end; 365 | if (json_append_mac(json, &json_pos, &json_len, macs->mac)) goto end; 366 | if (macs->next) { 367 | if (json_append(json, &json_pos, &json_len, "\",",2)) goto end; 368 | } 369 | else { 370 | if (json_append(json, &json_pos, &json_len, "\"",1)) goto end; 371 | } 372 | macs = macs->next; 373 | } 374 | 375 | if (json_append(json, &json_pos, &json_len, "],\"unix\":", 9)) goto end; 376 | if (json_append_num(json, &json_pos, &json_len, b_peer->t)) goto end; 377 | 378 | if (json_append(json, &json_pos, &json_len, ",\"tx\":", 6)) goto end; 379 | if (json_append_num(json, &json_pos, &json_len, b_peer->tx)) goto end; 380 | 381 | if (json_append(json, &json_pos, &json_len, ",\"rx\":", 6)) goto end; 382 | if (json_append_num(json, &json_pos, &json_len, b_peer->rx)) goto end; 383 | 384 | if (json_append(json, &json_pos, &json_len, "},", 2)) goto end; 385 | } 386 | 387 | // remove last comma 388 | if (found) 389 | json_pos--; 390 | 391 | if (json_append(json, &json_pos, &json_len, "]}", 2)) goto end; 392 | 393 | commit: 394 | // send the response 395 | ret = vpn_ws_write(peer, (uint8_t *)json, json_pos); 396 | if (ret < 0) return -1; 397 | // again ? 398 | if (ret == 0) { 399 | peer->handshake++; 400 | peer->is_writing = 1; 401 | return vpn_ws_event_read_to_write(queue, peer->fd); 402 | } 403 | // force connection close 404 | end: 405 | free(json); 406 | return -1; 407 | 408 | } 409 | -------------------------------------------------------------------------------- /src/vpn-ws.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #ifdef __WIN32__ 5 | #include "winsock2.h" 6 | #include "ws2tcpip.h" 7 | #include "ws2spi.h" 8 | #define EWOULDBLOCK EAGAIN 9 | #define EINPROGRESS EAGAIN 10 | #else 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #endif 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "sha1.h" 32 | 33 | #ifndef __WIN32__ 34 | #include 35 | #include 36 | typedef int vpn_ws_fd; 37 | #define vpn_ws_invalid_fd -1 38 | #define vpn_ws_is_invalid_fd(x) x < 0 39 | #define vpn_ws_send(x, y, z, w) ssize_t w = write(x, y, z) 40 | #define vpn_ws_recv(x, y, z, r) ssize_t r = read(x, y, z) 41 | #define vpn_ws_socket_cast(x) x 42 | #else 43 | typedef HANDLE vpn_ws_fd; 44 | #define sleep(x) Sleep(x * 1000); 45 | #define close(x) CloseHandle(x) 46 | #define vpn_ws_invalid_fd NULL 47 | #define vpn_ws_is_invalid_fd(x) !x 48 | #define vpn_ws_send(x, y, z, w) ssize_t w = -1; if (!WriteFile(x, y, z, (LPDWORD) &w, 0)) { w = -1; } 49 | #define vpn_ws_recv(x, y, z, r) ssize_t r = -1; if (!ReadFile(x, y, z, (LPDWORD) &r, 0)) { r = -1; } 50 | #define vpn_ws_socket_cast(x) (SOCKET)x 51 | #endif 52 | 53 | 54 | struct vpn_ws_var { 55 | char *key; 56 | uint16_t keylen; 57 | char *value; 58 | uint16_t vallen; 59 | }; 60 | typedef struct vpn_ws_var vpn_ws_var; 61 | 62 | 63 | struct vpn_ws_mac { 64 | uint8_t mac[6]; 65 | struct vpn_ws_mac *next; 66 | }; 67 | typedef struct vpn_ws_mac vpn_ws_mac; 68 | 69 | struct vpn_ws_peer { 70 | vpn_ws_fd fd; 71 | uint8_t *buf; 72 | uint64_t pos; 73 | uint64_t len; 74 | 75 | uint8_t *write_buf; 76 | uint64_t write_pos; 77 | uint64_t write_len; 78 | 79 | uint16_t vars_n; 80 | vpn_ws_var vars[64]; 81 | 82 | uint8_t handshake; 83 | uint8_t is_writing; 84 | 85 | uint8_t has_mask; 86 | uint8_t mask[4]; 87 | 88 | uint8_t mac_collected; 89 | uint8_t mac[6]; 90 | 91 | uint64_t rx; 92 | uint64_t tx; 93 | 94 | uint8_t raw; 95 | 96 | char *remote_addr; 97 | uint16_t remote_addr_len; 98 | char *remote_user; 99 | uint16_t remote_user_len; 100 | char *dn; 101 | uint16_t dn_len; 102 | 103 | time_t t; 104 | uint8_t bridge; 105 | vpn_ws_mac *macs; 106 | uint8_t ctrl; 107 | }; 108 | typedef struct vpn_ws_peer vpn_ws_peer; 109 | 110 | struct vpn_ws_config { 111 | char *server_addr; 112 | char *tuntap_name; 113 | 114 | char *uid; 115 | char *gid; 116 | 117 | char *exec; 118 | 119 | char *ssl_key; 120 | char *ssl_crt; 121 | 122 | int no_multicast; 123 | int no_broadcast; 124 | int bridge; 125 | int ssl_no_verify; 126 | 127 | uint8_t tuntap_mac[6]; 128 | 129 | // this is the highest fd used 130 | uint64_t peers_n; 131 | // this memory is dynamically increased 132 | vpn_ws_peer **peers; 133 | 134 | // used for ssl/tls context 135 | void *ssl_ctx; 136 | }; 137 | typedef struct vpn_ws_config vpn_ws_config; 138 | 139 | extern vpn_ws_config vpn_ws_conf; 140 | 141 | void vpn_ws_error(const char *); 142 | void vpn_ws_exit(int); 143 | 144 | vpn_ws_fd vpn_ws_bind(char *); 145 | 146 | int vpn_ws_event_queue(int); 147 | int vpn_ws_event_add_read(int, vpn_ws_fd); 148 | int vpn_ws_event_wait(int, void *); 149 | void *vpn_ws_event_events(int); 150 | int vpn_ws_event_fd(void *, int); 151 | int vpn_ws_event_read_to_write(int, vpn_ws_fd); 152 | int vpn_ws_event_write_to_read(int, vpn_ws_fd); 153 | 154 | vpn_ws_fd vpn_ws_tuntap(char *); 155 | 156 | uint16_t vpn_ws_be16(uint8_t *); 157 | uint64_t vpn_ws_be64(uint8_t *); 158 | uint16_t vpn_ws_le16(uint8_t *); 159 | 160 | int vpn_ws_peer_add_var(vpn_ws_peer *, char *, uint16_t, char *, uint16_t); 161 | 162 | void *vpn_ws_malloc(uint64_t); 163 | void *vpn_ws_calloc(uint64_t); 164 | void vpn_ws_peer_destroy(vpn_ws_peer *); 165 | 166 | void vpn_ws_peer_accept(int, int); 167 | 168 | int vpn_ws_manage_fd(int, vpn_ws_fd); 169 | 170 | int64_t vpn_ws_handshake(int, vpn_ws_peer *); 171 | char *vpn_ws_peer_get_var(vpn_ws_peer *, char *, uint16_t, uint16_t *); 172 | 173 | uint16_t vpn_ws_base64_encode(uint8_t *, uint16_t, uint8_t *); 174 | 175 | int vpn_ws_read(vpn_ws_peer *, uint64_t); 176 | int vpn_ws_write(vpn_ws_peer *, uint8_t *, uint64_t); 177 | int vpn_ws_continue_write(vpn_ws_peer *); 178 | 179 | int64_t vpn_ws_websocket_parse(vpn_ws_peer *, uint16_t *); 180 | 181 | int vpn_ws_mac_is_broadcast(uint8_t *); 182 | int vpn_ws_mac_is_zero(uint8_t *); 183 | int vpn_ws_mac_is_valid(uint8_t *); 184 | int vpn_ws_mac_is_loop(uint8_t *, uint8_t *); 185 | int vpn_ws_mac_is_multicast(uint8_t *); 186 | 187 | vpn_ws_peer *vpn_ws_peer_by_mac(uint8_t *); 188 | 189 | int vpn_ws_nb(vpn_ws_fd); 190 | void vpn_ws_peer_create(int, vpn_ws_fd, uint8_t *); 191 | 192 | void vpn_ws_log(const char *, ...); 193 | void vpn_ws_warning(const char *, ...); 194 | void vpn_ws_notice(const char *, ...); 195 | 196 | void *vpn_ws_ssl_handshake(vpn_ws_peer *, char *, char *, char *); 197 | int vpn_ws_ssl_write(void *, uint8_t *, uint64_t); 198 | ssize_t vpn_ws_ssl_read(void *, uint8_t *, uint64_t); 199 | void vpn_ws_ssl_close(void *); 200 | 201 | int vpn_ws_exec(char *); 202 | void vpn_ws_announce_peer(vpn_ws_peer *, char *); 203 | 204 | int64_t vpn_ws_ctrl_json(int, vpn_ws_peer *); 205 | 206 | int vpn_ws_str_to_uint(char *, uint64_t); 207 | char *vpn_ws_strndup(char *, size_t); 208 | int vpn_ws_is_a_number(char *); 209 | 210 | int vpn_ws_bridge_collect_mac(vpn_ws_peer *, uint8_t *); 211 | 212 | vpn_ws_peer * vpn_ws_peer_by_bridge_mac(uint8_t *); 213 | -------------------------------------------------------------------------------- /src/websocket.c: -------------------------------------------------------------------------------- 1 | #include "vpn-ws.h" 2 | 3 | int64_t vpn_ws_websocket_parse(vpn_ws_peer *peer, uint16_t *ws_header) { 4 | if (peer->pos < 2) return 0; 5 | 6 | uint8_t byte1 = peer->buf[0]; 7 | uint8_t byte2 = peer->buf[1]; 8 | 9 | uint8_t opcode = byte1 & 0xf; 10 | peer->has_mask = byte2 >> 7; 11 | uint64_t pktsize = byte2 & 0x7f; 12 | 13 | uint64_t needed = 2; 14 | 15 | // 16bit len 16 | if (pktsize == 126) { 17 | needed += 2; 18 | if (peer->pos < needed) return 0; 19 | pktsize = vpn_ws_be16(peer->buf + 2); 20 | } 21 | // 64bit 22 | else if (pktsize == 127) { 23 | needed += 8; 24 | if (peer->pos < needed) return 0; 25 | pktsize = vpn_ws_be64(peer->buf + 2); 26 | } 27 | 28 | if (peer->has_mask) { 29 | needed += 4; 30 | if (peer->pos < needed) return 0; 31 | memcpy(peer->mask, peer->buf + needed - 4, 4); 32 | } 33 | 34 | if (peer->pos < needed + pktsize) return 0; 35 | 36 | *ws_header = needed; 37 | 38 | switch(opcode) { 39 | // 0/1/2 -> forward 40 | case 0: 41 | case 1: 42 | case 2: 43 | return needed + pktsize; 44 | // 8 -> close connection 45 | case 8: 46 | return -1; 47 | // 9/10 -> ping/pong (ignore them, as they are only used to hold the connection open) 48 | case 9: 49 | case 10: 50 | // set header to 0, so the io engine will ignore it 51 | *ws_header = 0; 52 | return needed + pktsize; 53 | default: 54 | return -1; 55 | } 56 | 57 | // never here 58 | return -1; 59 | } 60 | -------------------------------------------------------------------------------- /tutorials/ubuntu_trusty_nginx_bridge_client_certificates.md: -------------------------------------------------------------------------------- 1 | Building a vpn-ws bridge server with client-certificates authentication on Ubuntu Trusty 2 | ======================================================================================== 3 | 4 | We have a lan on the subnet 192.168.173.0/24 and we want to allow VPN access from the internet. 5 | 6 | The lan has a DHCP server. 7 | 8 | One node of the lan (192.168.173.17) will be the vpn-ws server (the gateway of the lan, with ip 192.168.173.1, has to be configured for allowing access to its 9 | https port from the world) 10 | 11 | Clients connecting to the VPN get a 192.168.173.0/24 address from the DHCP server of the lan. 12 | 13 | Requirements 14 | ============ 15 | 16 | Ubuntu 14.04 (32 or 64 bit) on the vn-ws server 17 | 18 | Installing packages 19 | =================== 20 | 21 | We are going to install packages on the system that will run the vpn-ws/nginx server 22 | 23 | You need only nginx and bridge-utils (we are going to use static binaries for vpn-ws) 24 | 25 | ```sh 26 | sudo apt-get install nginx bridge-utils 27 | ``` 28 | 29 | Now download a vpn-ws linux binary (32 or 64 bit) from https://github.com/unbit/vpn-ws#binary-packages and place it in /usr/local/bin 30 | 31 | Network configuration 32 | ===================== 33 | 34 | /etc/network/interfaces must be adapted for a bridget setup 35 | 36 | ``` 37 | auto lo 38 | 39 | iface lo inet loopback 40 | 41 | iface eth0 inet manual 42 | 43 | auto br0 44 | iface br0 inet static 45 | bridge_ports eth0 46 | address 192.168.173.17 47 | netmask 255.255.255.0 48 | gateway 192.168.173.1 49 | dns-nameservers 192.168.173.1 50 | ``` 51 | 52 | Once rebooted, the server should continue working as before with the difference that traffic is managed by the 'br0' bridge 53 | 54 | Certification authority 55 | ======================= 56 | 57 | We need to create our CA (required for signing client certificates) 58 | 59 | ```sh 60 | openssl genrsa -des3 -out ca.key 4096 61 | openssl req -new -x509 -days 365 -key ca.key -out ca.crt 62 | ``` 63 | 64 | Now we can start signing our clients CSR (note, that the server certificate can be signed from whatever authority you want/need) 65 | 66 | Remember to generate keys directly on the clients and instruct them to send only the csr to the machine signing them (it could be obvious, but the first rule of security is being paranoid) 67 | 68 | generate a key (on the client): 69 | 70 | ```sh 71 | openssl genrsa -des3 -out client.key 2048 72 | ``` 73 | 74 | generate a csr (on the client): 75 | 76 | ```sh 77 | openssl req -new -key client.key -out client.csr 78 | ``` 79 | 80 | send the csr file to the machine signing it, and run (on the signing machine): 81 | 82 | ```sh 83 | openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt 84 | ``` 85 | 86 | now send back client.crt to the client. 87 | 88 | If you need the certificate/key pair to be in pkcs12 format you can concert them with: 89 | 90 | ```sh 91 | openssl pkcs12 -export -in client.crt -inkey client.key -name "Client 01" -out client.p12 92 | ``` 93 | 94 | Configuring nginx 95 | ================= 96 | 97 | Now we need to configure nginx to use client certificates: 98 | 99 | ```nginx 100 | server { 101 | listen 443; 102 | ssl on; 103 | server_name example.com; 104 | 105 | ssl_certificate /etc/ssl/certs/server.crt; 106 | ssl_certificate_key /etc/ssl/private/server.key; 107 | ssl_client_certificate /etc/ssl/certs/ca.crt; 108 | ssl_verify_client on; 109 | 110 | location /vpn { 111 | uwsgi_pass unix:/run/vpn.sock; 112 | include uwsgi_params; 113 | } 114 | 115 | location /admin { 116 | uwsgi_pass unix:/run/vpn.sock; 117 | uwsgi_modifier1 1; 118 | include uwsgi_params; 119 | allow 192.168.173.30; 120 | deny all; 121 | } 122 | } 123 | ``` 124 | 125 | adapt ssl_certificate, ssl_certificate_key and ssl_client_certificate to your patch choices and reload nginx. 126 | 127 | As yo ucan see we have added a /admin location for allowing access to the json control interface from ip 192.168.173.30 (change it to the ip allowed to administer the vpn) 128 | 129 | Starting vpn-ws on boot 130 | ======================= 131 | 132 | Ubuntu trusty is upstart based, so we are going to write a config for it: 133 | 134 | ``` 135 | # vpn-ws script 136 | 137 | description "vpn-ws" 138 | start on runlevel [2345] 139 | stop on runlevel [06] 140 | 141 | exec /usr/local/bin/vpn-ws --uid www-data --gid www-data --tuntap vpn0 --bridge --exec "brctl addif br0 vpn0" /run/vpn.sock >>/var/log/vpn-ws.log 2>&1 142 | ``` 143 | 144 | save it as /etc/init/vpn-ws.conf and run 145 | 146 | ```sh 147 | start vpn-ws 148 | ``` 149 | 150 | now check nginx logs (and /var/log/vpn-ws.log) to ensure it can connect 151 | 152 | Testing a client 153 | ================ 154 | 155 | We are going to use a linux client from an external network (ensure your lan gateway forward requests to port 8443 to 192.168.173.17:443) 156 | 157 | ```sh 158 | vpn-ws-client --key client.key --crt client.crt --exec "dhclient vpn1 &" vpn1 wss://server:8443/vpn 159 | ``` 160 | 161 | be sure the dhclient command is followed by & otherwise it will block the whole client 162 | 163 | --------------------------------------------------------------------------------