Last active
March 24, 2021 18:14
-
-
Save nemesifier/cb005dc80a75536cf8537105c3038a71 to your computer and use it in GitHub Desktop.
OpenWISP Monitoring Template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"files": [ | |
{ | |
"path": "/usr/sbin/openwisp-monitoring", | |
"mode": "0744", | |
"contents": "uuid=$(uci get openwisp.http.uuid)\nkey=$(uci get openwisp.http.key)\nbase_url=$(uci get openwisp.http.url)\nverify_ssl=$(uci get openwisp.http.verify_ssl)\nincluded_interfaces=$(uci get openwisp.monitoring.included_interfaces)\nurl=\"$base_url/api/v1/monitoring/device/$uuid/?key=$key\"\ndata=$(/usr/sbin/netjson-monitoring \"$included_interfaces\")\nif [ \"$verify_ssl\" = 0 ]; then\n curl_command='curl -k'\nelse\n curl_command='curl'\nfi\n# send data via POST\n$curl_command -H \"Content-Type: application/json\" \\\n -X POST \\\n -d \"$data\" \\\n -v $url\n" | |
}, | |
{ | |
"path": "/usr/sbin/netjson-monitoring", | |
"mode": "0744", | |
"contents": "#!/usr/bin/env lua\n-- retrieve monitoring information\n-- and return it as NetJSON Output\nio = require('io')\nubus_lib = require('ubus')\ncjson = require('cjson')\nnixio = require('nixio')\nuci = require('uci')\nuci_cursor = uci.cursor()\n\n-- split function\nfunction split(str, pat)\n local t = {}\n local fpat = \"(.-)\" .. pat\n local last_end = 1\n local s, e, cap = str:find(fpat, 1)\n while s do\n if s ~= 1 or cap ~= \"\" then\n table.insert(t, cap)\n end\n last_end = e + 1\n s, e, cap = str:find(fpat, last_end)\n end\n if last_end <= #str then\n cap = str:sub(last_end)\n table.insert(t, cap)\n end\n return t\nend\n\nlocal function has_value(tab, val)\n for index, value in ipairs(tab) do\n if value == val then\n return true\n end\n end\n return false\nend\n\nlocal function starts_with(str, start)\n return str:sub(1, #start) == start\nend\n\n-- parse /proc/net/arp\nfunction parse_arp()\n arp_info = {}\n for line in io.lines('/proc/net/arp 2> /dev/null') do\n if line:sub(1, 10) ~= 'IP address' then\n ip, hw, flags, mac, mask, dev = line:match(\"(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)\")\n table.insert(arp_info, {\n ip = ip,\n mac = mac,\n interface = dev,\n state = ''\n })\n end\n end\n return arp_info\nend\n\nfunction get_ip_neigh_json()\n arp_info = {}\n output = io.popen('ip -json neigh 2> /dev/null'):read()\n if output ~= nil and pcall(function () json_output = cjson.decode(output) end) then\n json_output = cjson.decode(output)\n for _, arp_entry in pairs(json_output) do\n table.insert(arp_info, {\n ip = arp_entry[\"dst\"],\n mac = arp_entry[\"lladdr\"],\n interface = arp_entry[\"dev\"],\n state = arp_entry[\"state\"][1]\n })\n end\n end\n return arp_info\nend\n\nfunction get_ip_neigh()\n arp_info = {}\n output = io.popen('ip neigh 2> /dev/null')\n for line in output:lines() do\n ip, dev, mac, state = line:match(\"(%S+)%s+dev%s+(%S+)%s+lladdr%s+(%S+).*%s(%S+)\")\n if mac ~= nil then\n table.insert(arp_info, {\n ip = ip,\n mac = mac,\n interface = dev,\n state = state\n })\n end\n end\n return arp_info\nend\n\nfunction get_neighbors()\n arp_table = get_ip_neigh_json()\n if next(arp_table) == nil then\n arp_table = get_ip_neigh()\n end\n if next(arp_table) == nil then\n arp_table = parse_arp()\n end\n return arp_table\nend\n\nfunction parse_dhcp_lease_file(path, leases)\n local f = io.open(path, 'r')\n if not f then\n return leases\n end\n\n for line in f:lines() do\n local expiry, mac, ip, name, id = line:match('(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)')\n table.insert(leases, {\n expiry = tonumber(expiry),\n mac = mac,\n ip = ip,\n client_name = name,\n client_id = id\n })\n end\n\n return leases\nend\n\nfunction get_dhcp_leases()\n local dhcp_configs = uci_cursor:get_all('dhcp')\n local leases = {}\n\n if not dhcp_configs or not next(dhcp_configs) then\n return nil\n end\n\n for name, config in pairs(dhcp_configs) do\n if config and config['.type'] == 'dnsmasq' and config.leasefile then\n leases = parse_dhcp_lease_file(config.leasefile, leases)\n end\n end\n return leases\nend\n\nfunction is_table_empty(table_)\n return not table_ or next(table_) == nil\nend\n\nfunction parse_hostapd_clients(clients)\n local data = {}\n for mac, properties in pairs(clients) do\n properties.mac = mac\n table.insert(data, properties)\n end\n return data\nend\n\nfunction parse_iwinfo_clients(clients)\n local data = {}\n for i, p in pairs(clients) do\n client = {}\n client.ht = p.rx.ht\n client.mac = p.mac\n client.authorized = p.authorized\n client.vht = p.rx.vht\n client.wmm = p.wme\n client.mfp = p.mfp\n client.auth = p.authenticated\n client.signal = p.signal\n client.noise = p.noise\n table.insert(data, client)\n end\n return data\nend\n\n-- takes ubus wireless.status clients output and converts it to NetJSON\nfunction netjson_clients(clients, is_mesh)\n return (is_mesh and parse_iwinfo_clients(clients) or parse_hostapd_clients(clients))\nend\n\nubus = ubus_lib.connect()\nif not ubus then\n error('Failed to connect to ubusd')\nend\n\n-- helpers\niwinfo_modes = {\n ['Master'] = 'access_point',\n ['Client'] = 'station',\n ['Mesh Point'] = '802.11s',\n ['Ad-Hoc'] = 'adhoc'\n}\n\n-- collect system info\nsystem_info = ubus:call('system', 'info', {})\nboard = ubus:call('system', 'board', {})\nloadavg_output = io.popen('cat /proc/loadavg'):read()\nloadavg_output = split(loadavg_output, ' ')\nload_average = {tonumber(loadavg_output[1]), tonumber(loadavg_output[2]), tonumber(loadavg_output[3])}\n\nfunction parse_disk_usage()\n file = io.popen('df')\n disk_usage_info = {}\n for line in file:lines() do\n if line:sub(1, 10) ~= 'Filesystem' then\n filesystem, size, used, available, percent, location =\n line:match('(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)')\n if filesystem ~= 'tmpfs' and not string.match(filesystem, 'overlayfs') then\n percent = percent:gsub('%W', '')\n -- available, size and used are in KiB\n table.insert(disk_usage_info, {\n filesystem = filesystem,\n available_bytes = tonumber(available) * 1024,\n size_bytes = tonumber(size) * 1024,\n used_bytes = tonumber(used) * 1024,\n used_percent = tonumber(percent),\n mount_point = location\n })\n end\n end\n end\n file:close()\n return disk_usage_info\nend\n\nfunction get_cpus()\n processors = io.popen('cat /proc/cpuinfo | grep -c processor')\n cpus = tonumber(processors:read('*a'))\n processors:close()\n return cpus\nend\n\nfunction get_vpn_interfaces()\n -- only openvpn supported for now\n local items = uci_cursor:get_all('openvpn')\n local vpn_interfaces = {}\n\n if is_table_empty(items) then\n return {}\n end\n\n for name, config in pairs(items) do\n if config and config.dev then\n vpn_interfaces[config.dev] = true\n end\n end\n return vpn_interfaces\nend\n\n-- init netjson data structure\nnetjson = {\n type = 'DeviceMonitoring',\n general = {\n hostname = board.hostname,\n local_time = system_info.localtime,\n uptime = system_info.uptime\n },\n resources = {\n load = load_average,\n memory = system_info.memory,\n swap = system_info.swap,\n cpus = get_cpus(),\n disk = parse_disk_usage()\n }\n}\n\ndhcp_leases = get_dhcp_leases()\nif not is_table_empty(dhcp_leases) then\n netjson.dhcp_leases = dhcp_leases\nend\n\nneighbors = get_neighbors()\nif not is_table_empty(neighbors) then\n netjson.neighbors = neighbors\nend\n\n-- determine the interfaces to monitor\ntraffic_monitored = arg[1]\ninclude_stats = {}\nif traffic_monitored then\n traffic_monitored = split(traffic_monitored, ' ')\n for i, name in pairs(traffic_monitored) do\n include_stats[name] = true\n end\nend\n\nfunction is_excluded(name)\n return name == 'lo'\nend\n\nfunction find_default_gateway(routes)\n for i = 1, #routes do\n if routes[i].target == '0.0.0.0' then\n return routes[i].nexthop\n end\n end\n return nil\nend\n\n-- collect device data\nnetwork_status = ubus:call('network.device', 'status', {})\nwireless_status = ubus:call('network.wireless', 'status', {})\ninterface_data = ubus:call('network.interface', 'dump', {})\nnixio_data = nixio.getifaddrs()\nvpn_interfaces = get_vpn_interfaces()\nwireless_interfaces = {}\ninterfaces = {}\ndns_servers = {}\ndns_search = {}\n\nfunction new_address_array(address, interface, family)\n proto = interface['proto']\n if proto == 'dhcpv6' then\n proto = 'dhcp'\n end\n new_address = {\n address = address['address'],\n mask = address['mask'],\n proto = proto,\n family = family,\n gateway = find_default_gateway(interface.route)\n }\n return new_address\nend\n\nfunction get_interface_info(name, netjson_interface)\n info = {\n dns_search = nil,\n dns_servers = nil\n }\n for _, interface in pairs(interface_data['interface']) do\n if interface['l3_device'] == name then\n if next(interface['dns-search']) then\n info.dns_search = interface['dns-search']\n end\n if next(interface['dns-server']) then\n info.dns_servers = interface['dns-server']\n end\n if netjson_interface.type == 'bridge' then\n info.stp = uci_cursor.get('network', interface['interface'], 'stp') == '1'\n end\n end\n end\n return info\nend\n\nfunction array_concat(source, destination)\n table.foreach(source, function(key, value)\n table.insert(destination, value)\n end)\nend\n\nfunction dict_merge(source, destination)\n table.foreach(source, function(key, value)\n destination[key] = value\n end)\nend\n\n-- collect interface addresses\nfunction get_addresses(name)\n addresses = {}\n interface_list = interface_data['interface']\n addresses_list = {}\n for _, interface in pairs(interface_list) do\n if interface['l3_device'] == name then\n proto = interface['proto']\n if proto == 'dhcpv6' then\n proto = 'dhcp'\n end\n for _, address in pairs(interface['ipv4-address']) do\n table.insert(addresses_list, address['address'])\n new_address = new_address_array(address, interface, 'ipv4')\n table.insert(addresses, new_address)\n end\n for _, address in pairs(interface['ipv6-address']) do\n table.insert(addresses_list, address['address'])\n new_address = new_address_array(address, interface, 'ipv6')\n table.insert(addresses, new_address)\n end\n end\n end\n for i = 1, #nixio_data do\n if nixio_data[i].name == name then\n if not is_excluded(name) then\n family = nixio_data[i].family\n addr = nixio_data[i].addr\n if family == 'inet' then\n family = 'ipv4'\n -- Since we don't already know this from the dump, we can\n -- consider this dynamically assigned, this is the case for\n -- example for OpenVPN interfaces, which get their address\n -- from the DHCP server embedded in OpenVPN\n proto = 'dhcp'\n elseif family == 'inet6' then\n family = 'ipv6'\n if starts_with(addr, 'fe80') then\n proto = 'static'\n else\n ula = uci_cursor.get('network', 'globals', 'ula_prefix')\n ula_prefix = split(ula, '::')[1]\n if starts_with(addr, ula_prefix) then\n proto = 'static'\n else\n proto = 'dhcp'\n end\n end\n end\n if family == 'ipv4' or family == 'ipv6' then\n if not has_value(addresses_list, addr) then\n table.insert(addresses, {\n address = addr,\n mask = nixio_data[i].prefix,\n proto = proto,\n family = family\n })\n end\n end\n end\n end\n end\n return addresses\nend\n\n-- collect relevant wireless interface stats\n-- (traffic and connected clients)\nfor radio_name, radio in pairs(wireless_status) do\n for i, interface in ipairs(radio.interfaces) do\n name = interface.ifname\n local is_mesh = false\n if name and not is_excluded(name) then\n iwinfo = ubus:call('iwinfo', 'info', {\n device = name\n })\n netjson_interface = {\n name = name,\n type = 'wireless',\n wireless = {\n ssid = iwinfo.ssid,\n mode = iwinfo_modes[iwinfo.mode] or iwinfo.mode,\n channel = iwinfo.channel,\n frequency = iwinfo.frequency,\n tx_power = iwinfo.txpower,\n signal = iwinfo.signal,\n noise = iwinfo.noise,\n country = iwinfo.country\n }\n }\n if iwinfo.mode == 'Ad-Hoc' or iwinfo.mode == 'Mesh Point' then\n clients = ubus:call('iwinfo', 'assoclist', {\n device = name\n }).results\n is_mesh = true\n else\n clients = ubus:call('hostapd.' .. name, 'get_clients', {}).clients\n end\n if clients and next(clients) ~= nil then\n netjson_interface.wireless.clients = netjson_clients(clients, is_mesh)\n end\n wireless_interfaces[name] = netjson_interface\n end\n end\nend\n\nfunction needs_inversion(interface)\n return interface.type == 'wireless' and interface.wireless.mode == 'access_point'\nend\n\nfunction invert_rx_tx(interface)\n for k, v in pairs(interface) do\n if string.sub(k, 0, 3) == \"rx_\" then\n local tx_key = \"tx_\" .. string.sub(k, 4)\n local tx_val = interface[tx_key]\n interface[tx_key] = v\n interface[k] = tx_val\n end\n end\n return interface\nend\n\n-- collect interface stats\nfor name, interface in pairs(network_status) do\n -- only collect data from iterfaces which have not been excluded\n if not is_excluded(name) then\n netjson_interface = {\n name = name,\n type = string.lower(interface.type),\n up = interface.up,\n mac = interface.macaddr,\n txqueuelen = interface.txqueuelen,\n mtu = interface.mtu,\n speed = interface.speed,\n bridge_members = interface['bridge-members'],\n multicast = interface.multicast\n }\n if wireless_interfaces[name] then\n dict_merge(wireless_interfaces[name], netjson_interface)\n interface.type = netjson_interface.type\n end\n if interface.type == 'Network device' then\n link_supported = interface['link-supported']\n if link_supported and next(link_supported) then\n netjson_interface.type = 'ethernet'\n netjson_interface.link_supported = link_supported\n elseif vpn_interfaces[name] then\n netjson_interface.type = 'virtual'\n else\n netjson_interface.type = 'other'\n end\n end\n if include_stats[name] then\n if needs_inversion(netjson_interface) then\n --- ensure wifi access point interfaces\n --- show download and upload values from\n --- the user's perspective and not from the router perspective\n interface.statistics = invert_rx_tx(interface.statistics)\n end\n netjson_interface.statistics = interface.statistics\n end\n addresses = get_addresses(name)\n if next(addresses) then\n netjson_interface.addresses = addresses\n end\n info = get_interface_info(name, netjson_interface)\n if info.stp ~= nil then\n netjson_interface.stp = info.stp\n end\n table.insert(interfaces, netjson_interface)\n -- DNS info is independent from interface\n if info.dns_servers then\n array_concat(info.dns_servers, dns_servers)\n end\n if info.dns_search then\n array_concat(info.dns_search, dns_search)\n end\n end\nend\n\nif next(interfaces) ~= nil then\n netjson.interfaces = interfaces\nend\nif next(dns_servers) ~= nil then\n netjson.dns_servers = dns_servers\nend\nif next(dns_search) ~= nil then\n netjson.dns_search = dns_search\nend\n\nprint(cjson.encode(netjson))\n" | |
}, | |
{ | |
"path": "/etc/crontabs/root", | |
"mode": "0644", | |
"contents": "*/5 * * * * /usr/sbin/openwisp-monitoring\n" | |
}, | |
{ | |
"path": "/etc/rc.local", | |
"mode": "0644", | |
"contents": "# Put your custom commands here that should be executed once\n# the system init finished. By default this file does nothing.\n\n/usr/sbin/openwisp-monitoring\ntouch /etc/crontabs/root\n/etc/init.d/cron start\n\nexit 0\n" | |
}, | |
{ | |
"path": "/usr/sbin/update-openwisp-packages", | |
"mode": "0744", | |
"contents": "#!/bin/sh\nreboot=false\n\n# updates opkg lists only if necessary\nopkg_update(){\n (\n test -d /tmp/opkg-lists/ && \\\n test -f /tmp/opkg-lists/openwrt_base && \\\n test -f /tmp/opkg-lists/openwrt_packages && \\\n test -f /tmp/opkg-lists/openwrt_core\n ) || opkg update;\n}\n\n# installs libubus-lua if necessary\nlibubus_lua_installed=$(opkg list-installed | grep libubus-lua -c)\nif [ \"$libubus_lua_installed\" == \"0\" ]; then\n opkg_update\n opkg install libubus-lua\nfi\n\n# installs lua-cjson if necessary\nluacjson_installed=$(opkg list-installed | grep lua-cjson -c)\nif [ \"$luacjson_installed\" == \"0\" ]; then\n opkg_update\n opkg install lua-cjson\nfi\n\n# installs rpcd-mod-iwinfo if necessary\nrpcd_mod_iwinfo_installed=$(opkg list-installed | grep rpcd-mod-iwinfo -c)\nif [ \"$rpcd_mod_iwinfo_installed\" == \"0\" ]; then\n opkg_update\n opkg install rpcd-mod-iwinfo\n reboot=true \nfi\n\n# upgrades openwisp-config if necessary\nopenwisp_config_version=$(openwisp_config --version)\nif [ \"$openwisp_config_version\" != \"openwisp-config 0.5.0\" ]; then\n # backup config just in case...\n cp /etc/config/openwisp /etc/config/openwisp-backup\n opkg_update\n opkg install http://downloads.openwisp.io/openwisp-config/2021-01-07-162007/openwisp-config-mbedtls_0.5.0-1_all.ipk\n # restore backup\n mv /etc/config/openwisp-backup /etc/config/openwisp\n # remove default conf\n rm /etc/config/openwisp-opkg\n /etc/init.d/openwisp_config restart\nfi\n\n# reboots if rpcd-mod-iwinfo has been installed\nif [ \"$reboot\" == \"true\" ]; then\n sleep 5\n reboot && exit\nfi\n" | |
}, | |
{ | |
"path": "/etc/openwisp/post-reload-hook", | |
"mode": "0744", | |
"contents": "#!/bin/sh\ntouch /etc/crontabs/root\n/etc/init.d/cron start\n/usr/sbin/update-openwisp-packages\n/usr/sbin/openwisp-monitoring\n" | |
} | |
], | |
"openwisp": [ | |
{ | |
"config_name": "monitoring", | |
"config_value": "monitoring", | |
"included_interfaces": "tun0 wlan0 wlan1 br-lan" | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment