Firewalls can cause challenges, by blocking the ports that you want to use for websockets.
Many firewalls in use today are so called stateful firewalls and in short their function can be described as follows
Stateful firewalls only verify that a packet correlates to an existing, unclosed, connection. It tracks the state of the connection (opening, open, closing, closed) hence the name. When it detects a packet is part of an already open, authorized connection, it can short circuit all of the other rule checks and let the packet through.Most sites that are using node.js for websockets are also utilizing the library socket.io.
(http://stackoverflow.com/questions/1967943/will-html5-websockets-be-crippled-by-firewalls)
A good list outlining some of the challenges when combining websockets/socket.io with various firewalls can be found at Socket.IO and firewall software.
So the question is how do you get your website running in spite of corporate firewall blocking various ports?
Possible solutions:
- ask the IT department to open the needed ports (in my experience hardly likely unless #2 fails)
- piggy-back on one of the existing established connections using http (port 80) or https (port 443) and hope the firewall is not a deep packet inspection based firewall.
Since I seldom have luck with suggestion number 1, I am not able to offer any guidance that is likely to help you succeed.
Suggestion number 2 however we can achieve that by letting the websockets use port 80/443 and let the website use the same port. You cannot however run 2 different services on the same port, so you cannot do this in a straightforward manner. The answer is a websocket aware reverse proxy.
There are quite a few different setups that can solve this. Below you can find 3 ways I investigated when I needed to solve this:
- Apache acting as a reverse proxy for node.js (this was my first choice, but I did not manage to get this working with out-of-the-box available packages for the Linux distribution I am using, but you can more than likely build your own version that will work just fine Backporting Apache support for websockets reverse proxy (aka getting GateOne to work behind Apache) and Using apache-websocket to proxy tcp connections).
- Use node.js to proxy Apache (see more here https://github.com/nodejitsu/node-http-proxy). (I never managed to get this working properly either but that is more than likely due to my slightly limited exposure to node.js, and to be honest my experience with node has been a slightly turbulent one, so I feel that Apache/Nginx are the more stable options for me, that is not to say that this is not working or an great option.
- Nginx being a reverse proxy for both apache and websockets/socket.io. This is what I will describe below.
Setup
- CentOS based Linux distribution
- existing website installed as a Apache virtual host running on port 80 and 443
- existing node.js socket.io based application running on port 8081
(setup before reverse proxy)
Please find an image of the wished for setup here below.
We need to do the following to achieve the reverse proxy setup
- Install Nginx
- Remove http -> https rewrite and ssl configuration from Apache as well as change Apache from listening on port 80/443 to listen on port 81
- Configure http -> https rewrite and reverse proxy in Nginx
- Change the socket.io node.js application to just use ws instead of wss (since Nginx is the SSL termination proxy now)
- Change the client application from using port 8081 to using 80/443
1. Install Nginx
RHEL / Centos 6: Install Nginx Using Yum Command
2. Remove http -> https rewrite and ssl configuration from Apache as well as change Apache from listening on port 80/443 to listen on port 81
Open the virtual hosts file for the site
vi /etc/httpd/conf.d/www_somewhere_com.conf
Change your virtual hosts file from the following
# Catch those that are trying to access the site using http # redirect to https <VirtualHost *:80> ServerName "www.somewhere.com" DocumentRoot /var/www/sites/www.somewhere.com/www DirectoryIndex index.html index.php ErrorLog logs/www.somewhere.com-error_log CustomLog logs/www.somewhere.com-access_log common RewriteEngine On RewriteCond %{HTTPS} !=on RewriteRule ^(.*) https://www.somewhere.com [R,L] </VirtualHost> # Main site <VirtualHost *:443> ServerName "www.somewhere.com" DocumentRoot /var/www/sites/www.somewhere.com/www DirectoryIndex index.html index.php ErrorLog logs/www.somewhere.com-error_log CustomLog logs/www.somewhere.com-access_log common SSLEngine on SSLCertificateFile /etc/pki/tls/certs/www_somewhere.com.crt SSLCertificateKeyFile /etc/pki/tls/private/www_somewhere.com.key SSLCertificateChainFile /etc/pki/tls/certs/Intermediate_Certificate.crt <Directory /var/www/sites/www.somewhere.com/www> Order Deny,Allow Allow from all </Directory> </VirtualHost>
to the following
# Main site <VirtualHost *:81> ServerName "www.somewhere.com" DocumentRoot /var/www/sites/www.somewhere.com/www DirectoryIndex index.html index.php ErrorLog logs/www.somewhere.com-error_log CustomLog logs/www.somewhere.com-access_log common <Directory /var/www/sites/www.somewhere.com/www> Order Deny,Allow Allow from all </Directory> </VirtualHost>
vi /etc/httpd/conf/httpd.conf
Look for the "Listen 80" in the file and change it to "Listen 81"
Save the file
3. Configure http -> https rewrite and reverse proxy in Nginx
vi /etc/nginx/conf.d/default.conf
replace the deafult content with this
Please note that in apache config you have a seperate intermidiate certificate and the site certificate. In Nginx you need to concatenate the primary certificate file (your_domain_name.crt) and the intermediate certificate file (DigiCertCA.crt) into a single pem file by running the following command:
cat DigiCertCA.crt >> your_domain_name.crt
# Listeners # nginx :80 responsible for redirecting http://www.somewhere.com to https://www.somewhere.com # nginx :443 responsible for handling all trafic on the https://www.somewhere.com site # acts as a reverse proxy for # /socket.io/ -> port 127.0.0.1:8081 (node.js websocket server) # / -> port 127.0.0.1:81 (apache main site) # # node.js :8081 node.js websocket server # apache :81 apache main site upstream www.somewhere.com { server 127.0.0.1:81; least_conn; } # nginx :80 responsible for redirecting http://www.somewhere.com to https://www.somewhere.com server { listen 80; server_name www.somewhere.com; rewrite ^(.*) https://www.somewhere.com$1 permanent; } # nginx :443 responsible for handling all trafic on the https://www.somewhere.com site # acts as a reverse proxy for # /socket.io/ -> port 127.0.0.1:8081 (node.js websocket server) # / -> port 127.0.0.1:81 (apache main site) server { listen 443 ssl; server_name www.somewhere.com; ssl_certificate /etc/pki/tls/certs/www_somewhere.com.crt; ssl_certificate_key /etc/pki/tls/private/www_somewhere.com.key; ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers RC4:HIGH:!aNULL:!MD5; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Path based websocket proxy location for nginx # to handle the reverseproxying # # inital connect url: https://www.somewhere.com:8081/socket.io/1/?t=1374751734771 # websocket url : wss://www.somewhere.com:8081/socket.io/1/websocket/x55Ch0B-SCACmDpW8rZd location /socket.io/ { proxy_pass http://localhost:8081; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 86400; } # Path based site proxy location for nginx # to handle the main apache site location / { proxy_pass http://localhost:81; } }
4. Change the socket.io node.js application to just use ws instead of wss (since Nginx is the SSL termination proxy now)
Change you node.js socket.io application from this
var fs = require('fs'); var sslCertificate = { key: fs.readFileSync(config.sslCertificate.key), cert: fs.readFileSync(config.sslCertificate.cert), ca: fs.readFileSync(config.sslCertificate.ca) }; // socket.io wss (websocket secure) var io = require('socket.io').listen(8081, sslCertificate); ...
to the following
var fs = require('fs'); var sslCertificate = { // key: fs.readFileSync(config.sslCertificate.key), // cert: fs.readFileSync(config.sslCertificate.cert), // ca: fs.readFileSync(config.sslCertificate.ca) }; // socket.io ws (websocket) var io = require('socket.io').listen(8081, sslCertificate); ...
5. Change the client application from using port 8081 to using 80/443
Find the location in your client code where you are connecting to the websocket/socket.io application/server and change it from hardcoding the port in the url to just omitting it
So instead of (in a angular.js application / javascript single page application) doing this
var socketIO = function(data){ // force https / wss var hostURL = window.location.href.replace('/'+window.location.hash, '8081').replace('http:', 'https:'); ... try { var socket = io.connect(hostURL, {'sync disconnect on unload':true}); ... } catch(e){ console.log('Error connecting'); ... } ... }
do something like this
var socketIO = function(data){ // force https / wss var hostURL = window.location.href.replace('/'+window.location.hash, '').replace('http:', 'https:'); ... try { var socket = io.connect(hostURL, {'sync disconnect on unload':true}); ... } catch(e){ console.log('Error connecting'); ... } ... }
run the following command to check if the services are running on the correct ports
#netstat -tulpn Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 13146/nginx tcp 0 0 0.0.0.0:8081 0.0.0.0:* LISTEN 13225/node tcp 0 0 127.0.0.1:81 0.0.0.0:* LISTEN 13190/httpd tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 13146/nginx ...
Finally some before and after pictures from chrome
Before
After
No comments:
Post a Comment