Flussonic Media Server documentation

Endpoint implementation

Description Anchor Anchor x2

You will need this manual if you have decided to use Flussonic Agent without Flussonic Watcher. Usually we do not advice it but your miles may vary, so read carefully what you need to reimplement to make agent working.

Flussonic Agent connects to Flussonic Media Server that is acting as an endpoint and opens controlling connection. This endpoint will tell Agent to connect to some Flussonic Media Server acting as a streampoint.

Heavy data will flow only to streampoint. One Flussonic Media Server instance can work both: as endpoint and as streampoint. It is ok and is used in Flussonic Watcher single mode.

You will have to implement endpoint auth callback that must be HTTP URL, allowing or disallowing Agent to connect.

Belows goes description of configuration and authorization protocol. We will show how to make things working on example when there is a single Flussonic Media Server.

Also we will show how is possible to implement http auth callback with a lua engine that ships with Flussonic Media Server.

Mention please that our protocol don't require keeping password plaintext on your server: only on agent.

Once you have generated password for agent, you can store it on agent and then keep only encrypted version on server.

Important! We strongly do not recommend changing the address of the endpoint after installation and configuration!

Configuration Anchor Anchor x2

You will need to enable rproxy plugin in Flussonic Media Server and make a lua web backend.

We suppose that you will implement auth web backend in your own service, so in production you can delete lua web backend, but right now for tests it is required:

http 8080;
mysql 3306;
rtsp 5554;
edit_auth admin pass;

web_script agent-example /etc/flussonic/endpoint streampoint_key=0nribro0230aug;

stream clock {
  url fake://fake;

stream remote-clock {
  url rtsp://localhost:5554/clock via=agent://a;

plugin rproxy {
  endpoint_auth http://localhost:8080/agent-example/auth;
  streampoint_key 0nribro0230aug;

This configuration is enough for Flussonic Media Server to run as an endpoint and streampoint.

Here we have added:

  • HTTP port for agent to connect to endpoint and streampoint.
  • MySQL port for us to connect to SQL API.
  • RTSP port for agent to connect when streampoint will request it.
  • Web_script for working Agent endpoint authentication.
  • Original source stream 'clock' that is required just as a working example of stream.
  • Stream 'remote-clock' that will work only via agent (mention url option via=agent://a)
  • rproxy plugin configuration with auth backend for endpoint mode and streampoint_key that is required for streampoint mode.

You are free to select any name of stream that will connect to camera via agent. The only mandatory configuration is to specify option via=agent://AGENTID in stream URL.

Now let's learn protocol between Flussonic Media Server and your agent auth callback on an example.

Example Anchor Anchor x2

Create file /etc/flussonic/endpoint/web.lua

It will be used by Flussonic Media Server to handle all requests to location /agent-example

Here goes fully working simple example of auth script that will allow to connect one agent with login a and password b:

http_handler = {}

agents = {
  ["a"] = {salt = "mysalt", password = "b"}

http_handler.auth = function(req)
  user_id = req.headers["x-peeklio-id"]
  auth_header = req.headers["authorization"]
  user_nonce = req.headers["x-nonce"]

  agent = agents[user_id]
  if not agent then
    return "http", 403, {["Content-Type"] = "application/json"}, json.encode({error = "disabled"})

  if not auth_header or not user_nonce then
    resp_headers = {["X-Nonce"]= flussonic.uuid(), ["X-Salt"] = agent.salt}
    return "http", 401, resp_headers, "Please authorize"

  auth_hash = string.gsub(auth_header, "Peeklio ", "")
  agent_key_hash = crypto.sha512(agent.salt..":"..agent.password)

  proper_hash = crypto.sha512(user_nonce..":"..agent_key_hash)
  if auth_hash == proper_hash then
    streampoint_url = ""..extra.streampoint_key
    resp_headers = {["X-Streampoint-Url"] = streampoint_url}
    return "http", 200, resp_headers, ""
    return "http", 403, {["Content-Type"] = "application/json"}, json.encode({error = "invalid_password"})

It implements following protocol:

  • Agent connects to endpoint and sends only header X-Peeklio-Id
  • Endpoint replies with temporary string in header X-Nonce and with X-Salt
  • Agent takes X-Salt, X-Nonce, prepares hashed authorization digest and sends it in Authorizatin header with prefix "Peeklio "
  • If Agent have sent good password hash, then Flussonic sends url of streampoint with streampoint_key in url

We recommend to store password salt with hashed ( hex(sha512(salt + ":" + password)) ) password on server.

If endpoint returns 403, then Agent will try to reconnect again with exponential growing timeout.

It is normal to connect Agent to endpoint and not send it to streampoint, you just need to remove X-Streampoint-Url header from HTTP 200 reply. To send such agent later to some streampoint, use SQL API (see below).

If agent disconnects from endpoint, it will try to reconnect back and keep connection stable, working and keepalived.

It is possible to tell agent to completely erase all settings and activate again. You can do it by sending header X-Force-Reset: true from endpoint authorization backend.

Agent gets it password from our activation server once on initial start. Our service sends new agent id, password and endpoint address. More info about connection between our activation server and your billing in the article.

Check Anchor Anchor x2

How to check it?

We have put agent compiled for linux into package:

/opt/flussonic/apps/rproxy/priv/peeklio -m http://a:b@
2017-09-12 22:05:32 [info]: start with login [a] password [...]
2017-09-12 22:05:32 [warning]: invalid parse /etc/resolv.conf, set defaults
2017-09-12 22:05:32 [info]: use nameserver 1:
2017-09-12 22:05:32 [info]: use nameserver 2:
2017-09-12 22:05:32 [info]: main: init main ver:v4.6.17-146-g02428e1
2017-09-12 22:05:32 [warning]: set_oom_politic: fail set oom for parent p:478
2017-09-12 22:05:32 [warning]: set_oom_politic: fail set oom p:85672
2017-09-12 22:05:33 [info]: endpoint: upgarded to master
2017-09-12 22:05:33 [info]: streampoint: upgraded to

Your installation is ready.

Protocol Anchor Anchor x2

So there are 2 steps in login protocol.

Step 1: Agent sends login and auth callback replies with nonce and salt.

Mention please that here salt is assumed to be stored in database on server.

Agent sends X-Peeklio-Id, Flussonic Media Server replies with X-Nonce and X-Salt

Step 2: Agent sends hashed password and Flussonic Media Server replies with streampoint url.

Agent first makes a hash from salt and password: password_hash = hex(sha512(salt+":"+password)), then hashes it with temporary nonce: auth = "Peeklio " + hex(sha512(nonce+":"+password_hash))

The resulting auth is sent in header Authorization

If all is ok, Flussonic Media Server replies with X-Streampoint-Url

SQL API Anchor Anchor x2

After agent has connected you can connect to endpoint Flussonic Media Server to cluster database via MySQL protocol and query connected to endpoint clients:
mysql -u admin -ppass -h -P 3306 cluster
mysql> select * from endpoint_agents;
| id   | connected_at | streampoint         | ip        | local_ip                  | version              |
| a    |   1505243355 | | | | v4.6.17-146-g02428e1 |
1 row in set (0.00 sec)

There is also table streampoint_agents that shows agents connected to all flussonic servers in streampoint role that are connected into cluster to current server:

mysql> select * from streampoint_agents;
| id   | server    | connected_at | ip        |
| a    | localhost |   1505243355 | |
1 row in set (0.00 sec)

You can use table endpoint_agents to dynamically move agent from one streampoint to another.

To check this, comment line resp_headers = {["X-Streampoint-Url"] = streampoint_url} in lua script and change it to resp_headers = {}

Now reconnect agent and see:

mysql> select * from endpoint_agents;
| id   | connected_at | streampoint | ip        | local_ip     | version             |
| a    |   1522232276 | NULL        | | | v4.7.3-307-ga3a617f |
1 row in set (0.00 sec)

mysql> select * from streampoint_agents;
Empty set (0.00 sec)

In this state you can control agent, but it will not send any data to any streamer.

Let's connect it to streampoint explicitly:

mysql> update endpoint_agents set streampoint='' where id='a';
Query OK, 1 row affected (0.00 sec)

mysql> select * from endpoint_agents;
| id   | connected_at | streampoint         | ip        | local_ip     | version             |
| a    |   1522232457 | | | | v4.7.3-307-ga3a617f |
1 row in set (0.00 sec)

mysql> select * from streampoint_agents;
| id   | server    | connected_at | ip        |
| a    | localhost |   1522232502 | |
1 row in set (0.00 sec)

Mention that here we do not give any path on streampoint host, but must explicitly send streampoint_key in our SQL request.