If someone was intrested how to track individual ports go down and up, here are my findings

My problem was that I whanted to know, when devices connect and disconnect to individual lan ports on my router. This is not possible to do via scripts in /etc/hotplug.d/iface because it can only track entire bridge to lan go down or up, it does not track individual ports. I have found out, that netifd demon is populating events to ubus when this happens. It is possible to create a lua script that will subscribe to such events. Before you can do that, you need to install lua itself and packages libubus-lua, libubox-lua. After that you need to create file subscriber.lua with following content:

#!/usr/bin/env lua

require "ubus"
require "uloop"

uloop.init()

local conn = ubus.connect()
if not conn then
	error("Failed to connect to ubus")
end

local sub = {
	notify = function( msg, name )
		print("name:", name)
		print("  port name:", msg["name"])
		print("  auth_status:", msg["auth_status"])
		print("  present:", msg["present"])
		print("  active:", msg["active"])
		print("  link_active:", msg["link_active"])
	end,
}

conn:subscribe( "network.device", sub )

uloop.run()

You can run it like this lua subscriber.lua and the output will be something like this:

name: link_down
port name: lan3
auth_status: false
present: true
active: true
link_active: false
name: link_up
port name: lan3
auth_status: false
present: true
active: true
link_active: true

I hope this helps, and you will not spend as much time digging as i have.

3 Likes

Extended version with notifications via email:

#!/usr/bin/env lua

ubus = require("ubus")
uloop = require("uloop")
log = require("posix.syslog")
socket = require("socket")
smtp = require("socket.smtp")
ssl = require("ssl")

function info(msg)
  log.syslog(log.LOG_INFO, "NAS Monitor: " .. msg)
end

function error(msg)
  log.syslog(log.LOG_ERROR, "NAS Monitor: " .. msg)
end

function sslCreate()
  local sock = socket.tcp()
  return setmetatable({
    connect = function(_, host, port)
      local r, e = sock:connect(host, port)
      if not r then 
        return r, e
      end

      sock = ssl.wrap(sock, {mode = 'client', protocol = 'tlsv1_2'})
      return sock:dohandshake()
    end
  }, {
    __index = function(t, n)
      return function(_, ...)
        return sock[n](sock, ...)
      end
    end
  })
end

function sendMessage(status, text)
  from = "<iot@example.com>"
  rcpt = "<admin@example.com>"

  messageText = {
    headers = {
      to = "Admin " .. rcpt,
      from = "Router " .. from,
      subject = "NAS is " .. status
    },
    body = text
  }

  local ok, err = smtp.send {
    from = from,
    rcpt = rcpt, 
    source = smtp.message(messageText),
    user = 'iot@example.com',
    password = 'change_me',
    server = 'smtp.example.com',
    port = 465,
    create = sslCreate
  }
  if not ok then
    error("Mail send failed " .. err)
  else
    info("Mail \"NAS is " .. status .. "\" was sent")
  end
end

info("Startup")
uloop.init()

local conn = ubus.connect()
if not conn then
  error("Failed to connect to ubus")
end

local lastSeen = os.time()
local notificationTimeout = 40

local sub = {
  notify = function( msg, name )
    if (msg["name"] == "lan3") and (os.time() - lastSeen > notificationTimeout) then
      lastSeen = os.time()
      if name == "link_down" then
        sendMessage("Down", "NAS server on lan3 is down, probably because of power outage");
      else
        sendMessage("Up", "NAS server on lan3 is back online");
      end
    end
  end
}

conn:subscribe("network.device", sub)

uloop.run()

Additional required packages are: luaposix, luasocket and luasec.

2 Likes

You need a group of witnesses on the net, otherwise you need heisenberg mailer to cross down wan .