UniFi Video: Unable to Load Stream

Al configurar Nginx como proxy SSL/TLS OffLoading de un Tomcat ejecutando la aplicación UniFi Video, no es posible reproducir los streaming de videos a través de HTTPS. El streaming de video funciona correctamente a través de HTTP, pero no a través de HTTPS. En el interfaz web de UniFi Video se muestra un tooltip con el siguiente mensaje de error: Unable to load stream. Please make sure port 7446 is open on your NVR.

A continuación una captura de pantalla del error en la interfaz web de UniFi Video:

Unifi Video HTTPS Error Unable to Load Stream

A continuación una captura del Developper Tools de Firefox donde se aprecian las peticiones que hace el navegador al servidor, y a partir del cual ya se puede ver el origen del problema:
Unifi Video HTTPS Error Unable to Load Stream

Y los mensajes que se registran en el log /var/log/unifi-video/evostream.00.log del demonio evostream (el servicio encargado de servir los streaming de video):

1
2
3
4
5
1491415072:6:/thelib/src/netio/epoll/iohandlermanager.cpp:130:UnRegisterIOHandler:Handlers count changed: 21->20 IOHT_TCP_CARRIER
1491415072:6:/thelib/src/application/baseclientapplication.cpp:438:UnRegisterProtocol:Protocol [IWSFMP4(298)] unregistered from application: evostreamms
1491415073:6:/thelib/src/netio/epoll/iohandlermanager.cpp:120:RegisterIOHandler:Handlers count changed: 20->21 IOHT_TCP_CARRIER
1491415073:3:/thelib/src/netio/epoll/tcpacceptor.cpp:179:Accept:Inbound connection accepted: (Far: 192.168.100.21:40834; Near: 192.168.100.50:7446) CTCP(24) <-> TCP(299) <-> ISSL(300) <-> IWS(301) <-> [IWSFMP4(302)]
1491415073:0:/thelib/src/netio/epoll/tcpcarrier.cpp:87:OnEvent:Unable to read data from connection: (Far: 192.168.100.21:40834; Near: 192.168.100.50:7446) CTCP(24) <-> [TCP(299)] <-> ISSL(300) <-> IWS(301) <-> IWSFMP4(302). Error was (104): Connection reset by peer

Arquitectura del servicio

La arquitectura de UniFi Video se puede resumir en:

  • Tomcat: En el se ejecuta la aplicación UniFi Video. Escucha en los puertos 7080 (HTTP) y 7443 (HTTPS).
  • evostream: Servidor de streaming de video. Escucha en los puertos:
    • ems.liveflv.port: 6666.
    • ems.livews.port: 7445 (WebSocket HTTP).
    • ems.livewss.port: 7446 (WebSocket HTTPS).
    • ems.rtmp.port: 1935.
    • ems.rtsp.port: 7447.

Análisis del problema

La conexión por HTTPS al WebSocket por el puerto 7446 no funciona así que la estrategia que seguí para evitar este problema fue interceptar el tráfico dirigido hacia el puerto 7446, mandarlo a Nginx para que se encarge del cifrado/descifrado en el lado del cliente y continuar con el flujo en plano en el lado del servidor; es decir, hacer un SSL Offloading al puerto 7446.

Puesto que el demonio evostream ya estaba escuchando en el puerto 7446, no era posible que Nginx escuchara también en el mismo puerto, por lo que ópte por usar iptables para interceptar el tráfico entrante por la interfaz de red y dirigido hacia el puerto 7446 (HTTPS), mandarlo a Nginx y que Nginx lo enviara en plano al puerto 7445 (HTTPS).

A continuación el esquema de como sería una conexión al Live View de la interfaz web de UniFi Video con la solución propuesta.
UniFi Video HTTPS: Streaming Video Fix

Solución

Configurar Nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
server {
listen 80;
listen 443 ssl;
root /var/www/html/nvr/;
server_name nvr.guillen.io;
ssl on;
ssl_certificate /etc/letsencrypt/live/nvr.guillen.io/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/nvr.guillen.io/privkey.pem;
# Sec options
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
keepalive_timeout 300;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA;
add_header Strict-Transport-Security max-age=31536000;
add_header X-Frame-Options DENY;
location / {
proxy_connect_timeout 5;
proxy_read_timeout 240;
proxy_intercept_errors on;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://localhost:7080;
}
# WebSockets
location /ws/ {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://192.168.100.50:7080/ws/;
}
# Redirect to HTTPS
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
}
server {
listen 7456 ssl;
server_name nvr.guillen.io;
ssl on;
ssl_certificate /etc/letsencrypt/live/nvr.guillen.io/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/nvr.guillen.io/privkey.pem;
# Sec options
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
keepalive_timeout 300;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA;
add_header Strict-Transport-Security max-age=31536000;
add_header X-Frame-Options DENY;
# WebSockets
location / {
proxy_connect_timeout 5;
proxy_read_timeout 240;
proxy_intercept_errors on;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://192.168.100.50:7445;
}
}

Configurar iptables

1
iptables -t nat -A PREROUTING eth0 -p tcp --dport 7446 -j REDIRECT --to-port 7456

Una vez aplicados los cambios el servicio de streaming de video funciona correctamente con HTTPS, a continuación una captura del Developpers Tools de Firefox:
UniFi Video HTTPS Running

Entradas de interés

Contenidos
  1. 1. Arquitectura del servicio
  2. 2. Análisis del problema
  3. 3. Solución
    1. 3.1. Configurar Nginx
    2. 3.2. Configurar iptables