About me: My name is Solène Rapenne, pronouns she/her. I like learning and sharing knowledge. Hobbies: '(BSD OpenBSD h+ Lisp cmdline gaming internet-stuff). I love percent and lambda characters. OpenBSD developer solene@.

Contact me: solene on Freenode, solene+www at dataswamp dot org or solene@bsd.network (mastodon). If for some reason you want to give me some money, I accept paypal at the address donate@perso.pw.

Nginx as a TCP/UDP relay

Written by Solène, on 24 February 2021.
Tags: #openbsd #nginx #network

Comments on Mastodon

Introduction

In this tutorial I will explain how to use Nginx as a TCP or UDP relay as an alternative to Haproxy or Relayd. This mean nginx will be able to accept requests on a port (TCP/UDP) and relay it to another backend without knowing about the content. It also permits to negociates a TLS session with the client and relay to a non-TLS backend. In this example I will explain how to configure Nginx to accept TLS requests to transmit it to my Gemini server Vger, Gemini protocol has TLS as a requirement.

I will explain how to install and configure Nginx and how to parse logs to obtain useful information. I will use an OpenBSD system for the examples.

It is important to understand that in this context Nginx is not doing anything related to HTTP.

Installation

On OpenBSD we need the package nginx-stream, if you are unsure about which package is required on your system, search which package provide the file ngx_stream_module.so . To enable Nginx at boot, you can use rcctl enable nginx.

Nginx stream module core documentation

Nginx stream module log documentation

Configuration

The default configuration file for nginx is /etc/nginx/nginx.conf , we will want it to listen on port 1965 and relay to 127.0.0.1:11965.

worker_processes  1;

load_module modules/ngx_stream_module.so;

events {
   worker_connections 5;
}

stream {
    log_format basic '$remote_addr $upstream_addr [$time_local] '
                     '$protocol $status $bytes_sent $bytes_received '
                     '$session_time';

    access_log logs/nginx-access.log basic;

    upstream backend {
        hash $remote_addr consistent;
        server 127.0.0.1:11965;
    }
    server {
        listen 1965 ssl;
        ssl_certificate /etc/ssl/perso.pw:1965.crt;
        ssl_certificate_key /etc/ssl/private/perso.pw:1965.key;
        proxy_pass backend;
    }
}

In the previous configuration file, the backend defines the destination, multiples servers could be defined, with weights and timeouts, there is only one in this example.

The server block will tell on which port Nginx should listen and if it has to handle TLS (which is named ssl because of history), usual TLS configuration can be used here, then for a request, we have to tell to which backend Nginx have to relay the connections.

The configuration file defines a custom log format that is useful for TLS connections, it includes remote host, backend destination, connection status, bytes transffered and duration.

Log parsing

Using awk to calculate time performance

I wrote a quite long shell command parsing the log defined earlier that display the number of requests, and median/min/max session time.

$ awk '{ print $NF }' /var/www/logs/nginx-access.log | sort -n |  awk '{ data[NR] = $1 } END { print "Total: "NR" Median:"data[int(NR/2)]" Min:"data[2]" Max:"data[NR] }'
Total: 566 Median:0.212 Min:0.000 Max:600.487

Find bad clients using awk

Sometimes in the logs there are clients that obtains a status 500, meaning the TLS connection haven't been established correctly. It may be some scanner that doesn't try a TLS connection, if you want to get statistics about those and see if it would be worth to block them if they do too many attempt, it is easy to use awk to get the list.

awk '$(NF-3) == 500 { print $1 }' /var/www/logs/nginx-access.log

Using goaccess for real time log visualization

It is also possible to use the program Goaccess to view logs in real time with many information, it is really an awesome program.

goaccess --date-format="%d/%b/%Y" \
         --time-format="%H:%M:%S" \
         --log-format="%h %r [%d:%t %^] TCP %s %^ %b %L" /var/www/logs/nginx-access.log

Goaccess official website

Conclusion

I was using relayd before trying Nginx with stream module, while relayd worked fine it doesn't provide any of the logs Nginx offer. I am really happy with this use of Nginx because it is a very versatile program that shown to be more than a http server over time. For a minimal setup I would still recommend lighter daemon such as relayd.

Stream live video using nginx

Written by Solène, on 26 August 2019.
Tags: #openbsd68 #openbsd #gaming #nginx

Comments on Mastodon

This blog post is about a nginx rtmp module for turning your nginx server into a video streaming server.

The official website of the project is located on github at: https://github.com/arut/nginx-rtmp-module/

I use it to stream video from my computer to my nginx server, then viewers can use mpv rtmp://perso.pw/gaming in order to view the video stream. But the nginx server will also relay to twitch for more scalability (and some people prefer viewing there for some reasons).

The module will already be installed with nginx package since OpenBSD 6.6 (not already out at this time).

There is no package for install the rtmp module before 6.6. On others operating systems, check for something like “nginx-rtmp” or “rtmp” in an nginx context.

Install nginx on OpenBSD:

pkg_add nginx

Then, add the following to the file /etc/nginx/nginx.conf

load_module modules/ngx_rtmp_module.so;
rtmp {
    server {
        listen 1935;
        buflen 10s;

        application gaming {
            live on;
            allow publish 176.32.212.34;
            allow publish 175.3.194.6;
            deny publish all;
            allow play all;

            record all;
            record_path /htdocs/videos/;
            record_suffix %d-%b-%y_%Hh%M.flv;

        }
    }
}

The previous configuration sample is a simple example allowing 172.32.212.34 and 175.3.194.6 to stream through nginx, and that will record the videos under /htdocs/videos/ (nginx is chrooted in /var/www).

You can add the following line in the “application” block to relay the stream to your Twitch broadcasting server, using your API key.

push rtmp://live-ams.twitch.tv/app/YOUR_API_KEY;

I made a simple scripts generating thumbnails of the videos and generating a html index file.

Every 10 minutes, a cron check if files have to be generated, make thumbnails for videos (tries at 05:30 of the video and then 00:03 if it doesn’t work, to handle very small videos) and then create the html.

The script checking for new stuff and starting html generation:

#!/bin/sh

cd /var/www/htdocs/videos

for file in $(find . -mmin +1 -name '*.flv')
do
        echo $file
        PIC=$(echo $file | sed 's/flv$/jpg/')
        if [ ! -f "$PIC" ]
        then
                ffmpeg -ss 00:05:30 -i "$file" -vframes 1 -q:v 2 "$PIC"
                if [ ! -f "$PIC" ]
                then
                        ffmpeg -ss 00:00:03 -i "$file" -vframes 1 -q:v 2 "$PIC"
                        if [ ! -f "$PIC" ]
                        then
                                echo "problem with $file" | mail user@my-tld.com
                        fi
                fi
        fi
done
cd ~/dev/videos/ && sh html.sh

This one makes the html:

#!/bin/sh

cd /var/www/htdocs/videos

PER_ROW=3
COUNT=0

cat << EOF > index.html
<html>
  <body>
<h1>Replays</h1>
<table>
EOF

for file in $(find . -mmin +3 -name '*.flv')
do
        if [ $COUNT -eq 0 ]
        then
                echo "<tr>" >> index.html
                INROW=1
        fi
        COUNT=$(( COUNT + 1 ))
        SIZE=$(ls -lh $file  | awk '{ print $5 }')
        PIC=$(echo $file | sed 's/flv$/jpg/')

        echo $file
        echo "<td><a href=\"$file\"><img src=\"$PIC\" width=320 height=240 /><br />$file ($SIZE)</a></td>" >> index.html
        if [ $COUNT -eq $PER_ROW ]
        then
                echo "</tr>" >> index.html
                COUNT=0
                INROW=0
        fi
done

if [ $INROW -eq 1 ]
then
        echo "</tr>" >> index.html
fi

cat << EOF >> index.html
    </table>
  </body>
</html>
EOF

Nginx and acme-client on OpenBSD

Written by Solène, on 04 July 2019.
Tags: #openbsd68 #openbsd #nginx #automation

Comments on Mastodon

I write this blog post as I spent too much time setting up nginx and SSL on OpenBSD with acme-client, due to nginx being chrooted and not stripping path and not doing it easily.

First, you need to set up /etc/acme-client.conf correctly. Here is mine for the domain ports.perso.pw:

authority letsencrypt {
        api url "https://acme-v02.api.letsencrypt.org/directory"
        account key "/etc/acme/letsencrypt-privkey.pem"
}

domain ports.perso.pw {
        domain key "/etc/ssl/private/ports.key"
        domain full chain certificate "/etc/ssl/ports.fullchain.pem"
        sign with letsencrypt
}

This example is for OpenBSD 6.6 (which is current when I write this) because of Let’s encrypt API URL. If you are running 6.5 or 6.4, replace v02 by v01 in the api url

Then, you have to configure nginx this way, the most important part in the following configuration file is the location block handling acme-challenge request. Remember that nginx is in chroot /var/www so the path to acme directory is acme.

http {
    include       mime.types;
    default_type  application/octet-stream;
    index         index.html index.htm;
    keepalive_timeout  65;
    server_tokens off;

    upstream backendurl {
        server unix:tmp/plackup.sock;
    }

    server {
      listen       80;
      server_name ports.perso.pw;

      access_log logs/access.log;
      error_log  logs/error.log info;

      root /htdocs/;

      location /.well-known/acme-challenge/ {
          rewrite ^/.well-known/acme-challenge/(.*) /$1 break;
          root /acme;
      } 

      location / {
          return 301 https://$server_name$request_uri;
      }
    }

    server {
      listen 443 ssl;
      server_name ports.perso.pw;
      access_log logs/access.log;
      error_log logs_error.log info;
      root /htdocs/;

      ssl_certificate /etc/ssl/ports.fullchain.pem;
      ssl_certificate_key /etc/ssl/private/ports.key;
      ssl_protocols TLSv1.1 TLSv1.2;
      ssl_prefer_server_ciphers on;
      ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";

      [... stuff removed ...]
    }

}

That’s all! I wish I could have find that on the Internet so I share it here.