How to share the same port for VPN and HTTP

Author: Nikos Mavrogiannopoulos

One of the advantages of ocserv is that is an HTTPS-based protocol and it is often used over 443 to allow bypassing certain firewalls. However the 443 TCP port is typically used by an HTTP server on a system. This section will describe how to collocate ocserv with a web server.

SSL termination on ocserv with haproxy

An method to collocate ocserv and an HTTPS server on port 443, is by using the server name indication (SNI) present on the first SSL/TLS ClientHello message, and forwarding traffic according to the name present.

In the example below we assume that the web server and ocserv have to be setup to use an alternative port, e.g., ocserv uses 4443, and the web server uses 4444. We also assume that the web server responds to www.example.com, while the vpn server, to vpn.example.com. An example configuration of haproxy that will redirect the traffic to the appropriate server is shown below.

frontend www-https
   bind 0.0.0.0:443
   mode tcp
   tcp-request inspect-delay 5s
   timeout client 300s
   default_backend bk_ssl_default

backend bk_ssl_default
   mode tcp
   acl vpn-app req_ssl_sni -i vpn.example.com
   acl web-app req_ssl_sni -i www.example.com

   use-server server-vpn if vpn-app
   use-server server-web if web-app
   use-server server-vpn if !vpn-app !web-app

   timeout server 300s

   option ssl-hello-chk
   server server-vpn 127.0.0.1:4443 send-proxy-v2
   server server-web 127.0.0.1:4444 check

In order for ocserv to obtain information on the incoming session, we have enabled the proxy protocol in haproxy’s configuration (with the send-proxy-v2 option). That requires ocserv’s configuration to contain the following:

listen-proxy-proto = true

Another point to keep in mind are the connection timeouts. Many reverse proxies, especially haproxy in particular, will have some sort of a connection timeout. This timeout setting in reverse proxies must be aligned to the ocserv configuration keepalive so that the main TCP/TLS connection that is established between a client/ocserv, doesn’t die out due to the reverse-proxy detecting a no-activity timeout.

The config on the haproxy side are called out in the timeout server directive, as well as the timeout client directive.

Corresponding to the above, what may need to be tweaked in the ocserv.config is the keepalive directive. Note, for optimal performance, the keepalive number in ocserv config must be LESS THAN the reverse-proxy connection timeouts

keepalive = 290

If you are hosting multiple websites on your webserver this alternative configuration may be more suitable for you as it does not require you to have each hostname fully specified (tested on haproxy 2.2 LTS):

defaults
    timeout connect         10s
    timeout client          1m
    timeout server          1m

frontend www-https
   bind 0.0.0.0:443
   mode tcp
   tcp-request inspect-delay 5s
   tcp-request content accept if { req.ssl_hello_type 1 }

   use_backend bk_vpn         if { req_ssl_sni vpn.example.nl }

   use_backend bk_ssl_default if { req_ssl_sni -i -m end .nl  }
   use_backend bk_ssl_default if { req_ssl_sni -i -m end .com }
   use_backend bk_ssl_default if { req_ssl_sni -i -m end .eu  }

   # In case of missing SNI information fallback to the VPN backend.
   # This is needed for older openconnect versions (as present in Ubuntu 18.04 LTS)
   default_backend bk_vpn

backend bk_vpn
   mode tcp
   option ssl-hello-chk
   server server-vpn 127.0.0.1:4443 send-proxy-v2

backend bk_ssl_default
   mode tcp
   option ssl-hello-chk
   server server-web 127.0.0.1:4444 check

DTLS Note

Some reverse proxies, e.g. haproxy, will NOT proxy out UDP connections. Generally, this is NOT a blocker on many implementations, but there are a few things that need to be accounted for.

DTLS, if required to be implemented on the VPN, requires ocserv to be able to listen to a UDP port where the DTLS communication happens.

This will often lead to a conflict if the ocserv configuration does not account for the reverse proxy NOT being able to proxy out UDP. In such cases while for the TCP channel, the communication would typically go

VPNClient->HAProxy->OCServ

… the UDP channel communication needs to be

VPNClient->OCServ

This can be accomplished by having ocserv LISTEN for UDP on a DIFFERENT host IP, by implementing the udp-listen-host directive in the config

listen-host=127.0.0.1
udp-listen-host=192.168.x.x