v1.0. Luaposix, multiple service instances.

This commit is contained in:
gSpot
2023-05-01 14:39:23 +03:00
parent 642265b96d
commit 76acd939a9
24 changed files with 1515 additions and 988 deletions

View File

@@ -3,7 +3,7 @@ Internet-detector is an application for checking the availability of the Interne
**OpenWrt** >= 19.07. **OpenWrt** >= 19.07.
**Dependences:** lua, luci-lib-nixio, libuci-lua. **Dependences:** lua, luaposix, libuci-lua.
**Features:** **Features:**
- It can run continuously as a system service or only in an open web interface. - It can run continuously as a system service or only in an open web interface.
@@ -12,22 +12,22 @@ Internet-detector is an application for checking the availability of the Interne
![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/internet-led.jpg) ![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/internet-led.jpg)
- Performing actions when connecting and disconnecting the Internet (Restarting network, modem or device. Executing custom shell scripts). - Performing actions when connecting and disconnecting the Internet (Restarting network, modem or device. Executing custom shell scripts).
- Sending email notification when Internet access is restored. - Sending email notification when Internet access is restored.
- The daemon is written entirely in Lua using the nixio library. - The daemon is written entirely in Lua using the luaposix library.
## Installation notes ## Installation notes
**OpenWrt >= 21.02:** **OpenWrt >= 21.02:**
opkg update opkg update
wget --no-check-certificate -O /tmp/internet-detector_0.6-0_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector_0.6-0_all.ipk wget --no-check-certificate -O /tmp/internet-detector_1.0-0_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector_1.0-0_all.ipk
opkg install /tmp/internet-detector_0.6-0_all.ipk opkg install /tmp/internet-detector_1.0-0_all.ipk
rm /tmp/internet-detector_0.6-0_all.ipk rm /tmp/internet-detector_1.0-0_all.ipk
/etc/init.d/internet-detector start /etc/init.d/internet-detector start
/etc/init.d/internet-detector enable /etc/init.d/internet-detector enable
wget --no-check-certificate -O /tmp/luci-app-internet-detector_0.6-1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-app-internet-detector_0.6-1_all.ipk wget --no-check-certificate -O /tmp/luci-app-internet-detector_1.0-0_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-app-internet-detector_1.0-0_all.ipk
opkg install /tmp/luci-app-internet-detector_0.6-1_all.ipk opkg install /tmp/luci-app-internet-detector_1.0-0_all.ipk
rm /tmp/luci-app-internet-detector_0.6-1_all.ipk rm /tmp/luci-app-internet-detector_1.0-0_all.ipk
/etc/init.d/rpcd restart /etc/init.d/rpcd restart
Email notification: Email notification:
@@ -36,9 +36,9 @@ Email notification:
i18n-ru: i18n-ru:
wget --no-check-certificate -O /tmp/luci-i18n-internet-detector-ru_0.6-1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-i18n-internet-detector-ru_0.6-1_all.ipk wget --no-check-certificate -O /tmp/luci-i18n-internet-detector-ru_1.0-0_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-i18n-internet-detector-ru_1.0-0_all.ipk
opkg install /tmp/luci-i18n-internet-detector-ru_0.6-1_all.ipk opkg install /tmp/luci-i18n-internet-detector-ru_1.0-0_all.ipk
rm /tmp/luci-i18n-internet-detector-ru_0.6-1_all.ipk rm /tmp/luci-i18n-internet-detector-ru_1.0-0_all.ipk
**[OpenWrt 19.07](https://github.com/gSpotx2f/luci-app-internet-detector/tree/19.07)** **[OpenWrt 19.07](https://github.com/gSpotx2f/luci-app-internet-detector/tree/19.07)**
@@ -46,3 +46,5 @@ i18n-ru:
![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/01.jpg) ![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/01.jpg)
![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/02.jpg) ![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/02.jpg)
![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/03.jpg)
![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/04.jpg)

View File

@@ -1,11 +1,11 @@
# #
# (с) 2021 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector) # (с) 2023 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
# #
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=internet-detector PKG_NAME:=internet-detector
PKG_VERSION:=0.6 PKG_VERSION:=1.0
PKG_RELEASE:=0 PKG_RELEASE:=0
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector> PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
@@ -17,17 +17,19 @@ define Package/$(PKG_NAME)
TITLE:=Internet detector TITLE:=Internet detector
URL:=https://github.com/gSpotx2f/luci-app-internet-detector URL:=https://github.com/gSpotx2f/luci-app-internet-detector
PKGARCH:=all PKGARCH:=all
DEPENDS:=+lua +luci-lib-nixio +libuci-lua DEPENDS:=+lua +luaposix +libuci-lua
endef endef
define Package/$(PKG_NAME)/description define Package/$(PKG_NAME)/description
Internet-detector is a small daemon Internet-detector is a small daemon
for checking Internet availability. for checking Internet availability.
Written in Lua using the nixio library. Written in Lua using the luaposix library.
endef endef
define Package/$(PKG_NAME)/conffiles define Package/$(PKG_NAME)/conffiles
/etc/config/internet-detector /etc/config/internet-detector
/etc/internet-detector/down-script.internet
/etc/internet-detector/up-script.internet
endef endef
define Build/Configure define Build/Configure
@@ -40,8 +42,8 @@ define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/etc/config $(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/etc/config/internet-detector $(1)/etc/config/internet-detector $(INSTALL_CONF) ./files/etc/config/internet-detector $(1)/etc/config/internet-detector
$(INSTALL_DIR) $(1)/etc/internet-detector $(INSTALL_DIR) $(1)/etc/internet-detector
$(INSTALL_BIN) ./files/etc/internet-detector/down-script $(1)/etc/internet-detector/down-script $(INSTALL_DATA) ./files/etc/internet-detector/down-script.internet $(1)/etc/internet-detector/down-script.internet
$(INSTALL_BIN) ./files/etc/internet-detector/up-script $(1)/etc/internet-detector/up-script $(INSTALL_DATA) ./files/etc/internet-detector/up-script.internet $(1)/etc/internet-detector/up-script.internet
$(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/etc/init.d/internet-detector $(1)/etc/init.d/internet-detector $(INSTALL_BIN) ./files/etc/init.d/internet-detector $(1)/etc/init.d/internet-detector
$(INSTALL_DIR) $(1)/usr/bin $(INSTALL_DIR) $(1)/usr/bin

View File

@@ -1,52 +1,48 @@
config main 'config' config main 'config'
option mode '2' option mode '1'
option enable_logger '1'
config ui 'ui'
list hosts '8.8.8.8' list hosts '8.8.8.8'
list hosts '1.1.1.1' list hosts '1.1.1.1'
option check_type '0' option check_type '0'
option tcp_port '53' option tcp_port '53'
option ui_interval_up '6' option interval_up '6'
option ui_interval_down '1' option interval_down '1'
option ui_connection_attempts '1' option connection_attempts '1'
option ui_connection_timeout '1' option connection_timeout '1'
option service_interval_up '30'
option service_interval_down '5'
option service_connection_attempts '2'
option service_connection_timeout '2'
option service_enable_logger '1'
config module 'mod_led_control' config instance 'internet'
option enabled '0' option enabled '1'
list hosts '8.8.8.8'
config module 'mod_reboot' list hosts '1.1.1.1'
option enabled '0' option check_type '0'
option dead_period '3600' option tcp_port '53'
option force_reboot_delay '300' option interval_up '30'
option interval_down '5'
config module 'mod_network_restart' option connection_attempts '2'
option enabled '0' option connection_timeout '2'
option dead_period '900' option mod_led_control_enabled '0'
option attempts '1' option mod_reboot_enabled '0'
option restart_timeout '0' option mod_reboot_dead_period '3600'
option mod_reboot_force_reboot_delay '300'
config module 'mod_modem_restart' option mod_network_restart_enabled '0'
option enabled '0' option mod_network_restart_dead_period '900'
option dead_period '600' option mod_network_restart_attempts '1'
option any_band '0' option mod_network_restart_restart_timeout '0'
option mod_modem_restart_enabled '0'
config module 'mod_public_ip' option mod_modem_restart_dead_period '600'
option enabled '0' option mod_modem_restart_any_band '0'
option provider 'opendns1' option mod_public_ip_enabled '0'
option interval '600' option mod_public_ip_provider 'opendns1'
option timeout '3' option mod_public_ip_qtype '0'
option mod_public_ip_interval '600'
config module 'mod_email' option mod_public_ip_timeout '3'
option enabled '0' option mod_email_enabled '0'
option alive_period '0' option mod_email_alive_period '0'
option mail_smtp 'smtp.gmail.com' option mod_email_mail_smtp 'smtp.gmail.com'
option mail_smtp_port '587' option mod_email_mail_smtp_port '587'
option mail_security 'tls' option mod_email_mail_security 'tls'
option mod_user_scripts_enabled '0'
config module 'mod_user_scripts' option mod_user_scripts_alive_period '0'
option enabled '0' option mod_user_scripts_dead_period '0'
option alive_period '0'
option dead_period '0'

View File

@@ -3,17 +3,23 @@
START=97 START=97
STOP=01 STOP=01
ID="/usr/bin/internet-detector" PROG=/usr/bin/internet-detector
config_app() {
config_get enabled "$1" enabled "0"
if [ $enabled = "1" ]; then
$PROG service "$1"
fi
}
start() { start() {
$ID config_load internet-detector
config_get mode "config" mode "0"
if [ $mode = "1" ]; then
config_foreach config_app "instance"
fi
} }
stop() { stop() {
$ID stop $PROG stop
}
restart() {
stop
start
} }

View File

@@ -5,112 +5,103 @@
Dependences: Dependences:
lua lua
luci-lib-nixio luaposix
libuci-lua libuci-lua
(с) 2021 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector) (с) 2023 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
--]] --]]
-- Importing packages
local dirent = require("posix.dirent")
local fcntl = require("posix.fcntl")
local signal = require("posix.signal")
local socket = require("posix.sys.socket")
local stat = require("posix.sys.stat")
local syslog = require("posix.syslog")
local time = require("posix.time")
local unistd = require("posix.unistd")
local uci = require("uci")
-- Default settings -- Default settings
local Config = { local InternetDetector = {
mode = 2, mode = 0,
enableLogger = true, enableLogger = true,
intervalUp = 30, hostname = "OpenWrt",
intervalDown = 5, appName = "internet-detector",
connectionAttempts = 2, commonDir = "/tmp/run",
connectionTimeout = 2, pingCmd = "/bin/ping",
UIConnectionAttempts = 1, pingParams = "-c 1",
UIConnectionTimeout = 1, debug = false,
serviceConfig = {
hosts = { hosts = {
[1] = "8.8.8.8", [1] = "8.8.8.8",
[2] = "1.1.1.1", [2] = "1.1.1.1",
}, },
tcpPort = 53, check_type = 0, -- 0: TCP, 1: ICMP
pingPacketSize = 56, tcp_port = 53,
icmp_packet_size = 56,
interval_up = 30,
interval_down = 5,
connection_attempts = 2,
connection_timeout = 2,
iface = nil, iface = nil,
checkType = 0, -- 0: TCP, 1: ping instance = nil,
hostname = "OpenWrt", },
appName = "internet-detector",
commonDir = "/tmp/run",
debugLog = "/tmp/internet-detector.debug",
pingCmd = "/bin/ping",
pingParams = "-c 1",
debug = false,
modules = {}, modules = {},
parsedHosts = {}, parsedHosts = {},
} }
Config.configDir = string.format("/etc/%s", Config.appName) InternetDetector.configDir = string.format("/etc/%s", InternetDetector.appName)
Config.modulesDir = string.format("/usr/lib/%s", Config.appName) InternetDetector.modulesDir = string.format("/usr/lib/%s", InternetDetector.appName)
Config.pidFile = string.format("%s/%s.pid", Config.commonDir, Config.appName)
Config.statusFile = string.format("%s/%s.status", Config.commonDir, Config.appName)
-- Importing packages -- Loading settings from UCI
local uciCursor = uci.cursor()
InternetDetector.mode = tonumber(
uciCursor:get(InternetDetector.appName, "config", "mode"))
InternetDetector.enableLogger = (tonumber(
uciCursor:get(InternetDetector.appName, "config", "enable_logger")) ~= 0)
local hostname = uciCursor:get("system", "@[0]", "hostname")
if hostname ~= nil then
InternetDetector.hostname = hostname
end
local function prequire(package) local RUNNING
function InternetDetector:prequire(package)
local retVal, pkg = pcall(require, package) local retVal, pkg = pcall(require, package)
return retVal and pkg return retVal and pkg
end end
local nixio = prequire("nixio") function InternetDetector:loadUCIConfig(sType, instance)
if not nixio then local success
error("You need to install nixio...") local num = 0
uciCursor:foreach(
self.appName,
sType,
function(s)
if s[".name"] == instance then
for k, v in pairs(s) do
if type(v) == "string" and v:match("^[%d]+$") then
v = tonumber(v)
end
self.serviceConfig[k] = v
end
success = true
self.serviceConfig.instanceNum = num
end
num = num + 1
end
)
self.serviceConfig.instance = instance
self.pidFile = string.format(
"%s/%s.%s.pid", self.commonDir, self.appName, instance)
self.statusFile = string.format(
"%s/%s.%s.status", self.commonDir, self.appName, instance)
return success
end end
local uci = prequire("uci") function InternetDetector:writeValueToFile(filePath, str)
if not uci then
error("You need to install libuci-lua...")
end
-- Loading settings from UCI
local uciCursor = uci.cursor()
Config.mode = tonumber(uciCursor:get(
Config.appName, "config", "mode"))
Config.enableLogger = (tonumber(uciCursor:get(
Config.appName, "config", "service_enable_logger")) ~= 0)
Config.intervalUp = tonumber(uciCursor:get(
Config.appName, "config", "service_interval_up"))
Config.intervalDown = tonumber(uciCursor:get(
Config.appName, "config", "service_interval_down"))
Config.connectionAttempts = tonumber(uciCursor:get(
Config.appName, "config", "service_connection_attempts"))
Config.connectionTimeout = tonumber(uciCursor:get(
Config.appName, "config", "service_connection_timeout"))
Config.UIConnectionAttempts = tonumber(uciCursor:get(
Config.appName, "config", "ui_connection_attempts"))
Config.UIConnectionTimeout = tonumber(uciCursor:get(
Config.appName, "config", "ui_connection_timeout"))
Config.hosts = uciCursor:get(Config.appName, "config", "hosts")
local tcpPort = uciCursor:get(
Config.appName, "config", "tcp_port")
if tcpPort ~= nil then
Config.tcpPort = tonumber(tcpPort)
end
local pingPacketSize = uciCursor:get(
Config.appName, "config", "ping_packet_size")
if pingPacketSize ~= nil then
Config.pingPacketSize = tonumber(pingPacketSize)
end
local iface = uciCursor:get(
Config.appName, "config", "iface")
if iface ~= nil then
Config.iface = iface
end
Config.checkType = tonumber(uciCursor:get(
Config.appName, "config", "check_type"))
local hostname = uciCursor:get("system", "@[0]", "hostname")
if hostname ~= nil then
Config.hostname = hostname
end
local function writeValueToFile(filePath, str)
local retValue = false local retValue = false
local fh = io.open(filePath, "w") local fh = io.open(filePath, "w")
if fh then if fh then
@@ -122,7 +113,7 @@ local function writeValueToFile(filePath, str)
return retValue return retValue
end end
local function readValueFromFile(filePath) function InternetDetector:readValueFromFile(filePath)
local retValue local retValue
local fh = io.open(filePath, "r") local fh = io.open(filePath, "r")
if fh then if fh then
@@ -132,72 +123,97 @@ local function readValueFromFile(filePath)
return retValue return retValue
end end
local function statusJson(inet, t) function InternetDetector:statusJson(inet, instance, t)
local lines = { [1] = string.format('"inet":%d', inet) } local lines = { [1] = string.format(
'{"instance":"%s","num":"%d","inet":%d',
instance,
self.serviceConfig.instanceNum,
inet)}
if t then if t then
for k, v in pairs(t) do for k, v in pairs(t) do
lines[#lines + 1] = string.format('"%s":"%s"', k, v) lines[#lines + 1] = string.format('"%s":"%s"', k, v)
end end
end end
return "{" .. table.concat(lines, ",") .. "}" return table.concat(lines, ",") .. "}"
end end
local function writeLogMessage(level, msg) function InternetDetector:writeLogMessage(level, msg)
if Config.enableLogger then if self.enableLogger then
nixio.syslog(level, msg) local levels = {
emerg = syslog.LOG_EMERG,
alert = syslog.LOG_ALERT,
crit = syslog.LOG_CRIT,
err = syslog.LOG_ERR,
warning = syslog.LOG_WARNING,
notice = syslog.LOG_NOTICE,
info = syslog.LOG_INFO,
debug = syslog.LOG_DEBUG,
}
syslog.syslog(levels[level] or syslog.LOG_INFO, string.format(
"%s: %s", self.serviceConfig.instance or "", msg))
end end
end end
local function loadModules() function InternetDetector:loadModules()
package.path = string.format("%s;%s/?.lua", package.path, Config.modulesDir) package.path = string.format("%s;%s/?.lua", package.path, self.modulesDir)
Config.modules = {} self.modules = {}
uciCursor:foreach( local ok, modulesDir = pcall(dirent.files, self.modulesDir)
Config.appName, if ok then
"module", for item in modulesDir do
function(s) if item:match("^mod_") then
local mod_name = s[".name"] local modName = item:gsub("%.lua$", "")
if mod_name and s.enabled == "1" then local modConfig = {}
local m = prequire(mod_name) for k, v in pairs(self.serviceConfig) do
if k:match("^" .. modName) then
modConfig[k:gsub("^" .. modName .. "_", "")] = v
end
end
if modConfig.enabled == 1 then
local m = self:prequire(modName)
if m then if m then
m.config = Config m.config = self
m.syslog = writeLogMessage m.syslog = function(level, msg) self:writeLogMessage(level, msg) end
m.writeValue = writeValueToFile m.writeValue = function(filePath, str) return self:writeValueToFile(filePath, str) end
m.readValue = readValueFromFile m.readValue = function(filePath) return self:readValueFromFile(filePath) end
m:init(s) m:init(modConfig)
Config.modules[#Config.modules + 1] = m self.modules[#self.modules + 1] = m
end
end end
end end
end end
)
end
local function parseHost(host)
local addr, port = host:match("^([^:]+):?(%d*)")
return addr, tonumber(port) or false
end
local function parseHosts()
Config.parsedHosts = {}
for k, v in ipairs(Config.hosts) do
local addr, port = parseHost(v)
Config.parsedHosts[k] = { addr = addr, port = port }
end end
end end
local function pingHost(host) function InternetDetector:parseHost(host)
local addr, port = host:match("^([^%[%]:]+):?(%d?%d?%d?%d?%d?)$")
if not addr then
addr, port = host:match("^%[?([^%[%]]+)%]?:?(%d?%d?%d?%d?%d?)$")
end
return addr, tonumber(port)
end
function InternetDetector:parseHosts()
self.parsedHosts = {}
for k, v in ipairs(self.serviceConfig.hosts) do
local addr, port = self:parseHost(v)
self.parsedHosts[k] = { addr = addr, port = port }
end
end
function InternetDetector:pingHost(host)
local ping = string.format( local ping = string.format(
"%s %s -W %d -s %d%s %s > /dev/null 2>&1", "%s %s -W %d -s %d%s %s > /dev/null 2>&1",
Config.pingCmd, self.pingCmd,
Config.pingParams, self.pingParams,
Config.connectionTimeout, self.serviceConfig.connection_timeout,
Config.pingPacketSize, self.serviceConfig.icmp_packet_size,
Config.iface and (" -I " .. Config.iface) or "", self.serviceConfig.iface and (" -I " .. self.serviceConfig.iface) or "",
host host
) )
local retCode = os.execute(ping) local retCode = os.execute(ping)
-- Debug -- Debug
if Config.debug then if self.debug then
io.stdout:write(string.format( io.stdout:write(string.format(
"--- Ping ---\ntime = %s\n%s\nretCode = %s\n", os.time(), ping, retCode) "--- Ping ---\ntime = %s\n%s\nretCode = %s\n", os.time(), ping, retCode)
) )
@@ -207,53 +223,94 @@ local function pingHost(host)
return retCode return retCode
end end
local function TCPConnectionToHost(host, port) function InternetDetector:TCPConnectionToHost(host, port)
local retCode = 1 local retCode = 1
local addrInfo = nixio.getaddrinfo(host, "any") local saTable, errMsg, errNum = socket.getaddrinfo(host, port or self.serviceConfig.tcp_port)
if addrInfo then
local family = addrInfo[1].family
if family then
local socket = nixio.socket(family, "stream")
socket:setopt("socket", "sndtimeo", Config.connectionTimeout)
socket:setopt("socket", "rcvtimeo", Config.connectionTimeout)
if Config.iface then
socket:setopt("socket", "bindtodevice", Config.iface)
end
local success = socket:connect(host, port or Config.tcpPort)
-- Debug if not saTable then
if Config.debug then if self.debug then
local sockAddr, sockPort = socket:getsockname()
local peerAddr, peerPort = socket:getpeername()
io.stdout:write(string.format( io.stdout:write(string.format(
"--- TCP ---\ntime = %s\nconnectionTimeout = %s\niface = %s\nhost:port = %s:%s\nsockname = %s:%s\npeername = %s:%s\nsuccess = %s\n", "GETADDRINFO ERROR: %s, %s\n", errMsg, errNum))
end
else
local family = saTable[1].family
if family then
local sock, errMsg, errNum = socket.socket(family, socket.SOCK_STREAM, 0)
if not sock then
if self.debug then
io.stdout:write(string.format(
"SOCKET ERROR: %s, %s\n", errMsg, errNum))
end
return retCode
end
socket.setsockopt(sock, socket.SOL_SOCKET,
socket.SO_SNDTIMEO, self.serviceConfig.connection_timeout, 0)
socket.setsockopt(sock, socket.SOL_SOCKET,
socket.SO_RCVTIMEO, self.serviceConfig.connection_timeout, 0)
if self.serviceConfig.iface then
local ok, errMsg, errNum = socket.setsockopt(sock, socket.SOL_SOCKET,
socket.SO_BINDTODEVICE, self.serviceConfig.iface)
if not ok then
if self.debug then
io.stdout:write(string.format(
"SOCKET ERROR: %s, %s\n", errMsg, errNum))
end
return retCode
end
end
local success = socket.connect(sock, saTable[1])
if self.debug then
if not success then
io.stdout:write(string.format(
"SOCKET CONNECT ERROR: %s\n", tostring(success)))
end
local sockTable, err_s, e_s = socket.getsockname(sock)
local peerTable, err_p, e_p = socket.getpeername(sock)
if not sockTable then
sockTable = {}
io.stdout:write(
string.format("SOCKET ERROR: %s, %s\n", err_s, e_s))
end
if not peerTable then
peerTable = {}
io.stdout:write(
string.format("SOCKET ERROR: %s, %s\n", err_p, e_p))
end
io.stdout:write(string.format(
"--- TCP ---\ntime = %s\nconnection_timeout = %s\niface = %s\nhost:port = [%s]:%s\nsockname = [%s]:%s\npeername = [%s]:%s\nsuccess = %s\n",
os.time(), os.time(),
Config.connectionTimeout, self.serviceConfig.connection_timeout,
tostring(Config.iface), tostring(self.serviceConfig.iface),
host, host,
port or Config.tcpPort, port or self.serviceConfig.tcp_port,
tostring(sockAddr), tostring(sockTable.addr),
tostring(sockPort), tostring(sockTable.port),
tostring(peerAddr), tostring(peerTable.addr),
tostring(peerPort), tostring(peerTable.port),
tostring(success)) tostring(success))
) )
io.stdout:flush() io.stdout:flush()
end end
socket:close() unistd.close(sock)
retCode = success and 0 or 1 retCode = success and 0 or 1
end end
end end
return retCode return retCode
end end
local function checkHosts() function InternetDetector:checkHosts()
local checkFunc = (Config.checkType == 1) and pingHost or TCPConnectionToHost local checkFunc = (self.serviceConfig.check_type == 1) and self.pingHost or self.TCPConnectionToHost
local retCode = 1 local retCode = 1
for k, v in ipairs(Config.parsedHosts) do for k, v in ipairs(self.parsedHosts) do
for i = 1, Config.connectionAttempts do for i = 1, self.serviceConfig.connection_attempts do
if checkFunc(v.addr, v.port) == 0 then if checkFunc(self, v.addr, v.port) == 0 then
retCode = 0 retCode = 0
break break
end end
@@ -265,37 +322,51 @@ local function checkHosts()
return retCode return retCode
end end
local function main() function InternetDetector:breakMain(signo)
local lastStatus, currentStatus, timeNow, timeDiff, lastTime RUNNING = false
local interval = Config.intervalUp end
local counter = 0
while true do function InternetDetector:main()
signal.signal(signal.SIGTERM, function(signo) self:breakMain(signo) end)
signal.signal(signal.SIGINT, function(signo) self:breakMain(signo) end)
signal.signal(signal.SIGQUIT, function(signo) self:breakMain(signo) end)
local lastStatus, currentStatus, timeNow, timeDiff, lastTime
local interval = self.serviceConfig.interval_up
local counter = 0
local onStart = true
RUNNING = true
while RUNNING do
if counter == 0 or counter >= interval then if counter == 0 or counter >= interval then
currentStatus = checkHosts() currentStatus = self:checkHosts()
if not nixio.fs.access(Config.statusFile, "r") then if onStart or not stat.stat(self.statusFile) then
writeValueToFile(Config.statusFile, statusJson(currentStatus)) self:writeValueToFile(self.statusFile, self:statusJson(
currentStatus, self.serviceConfig.instance))
onStart = false
end end
if currentStatus == 0 then if currentStatus == 0 then
interval = Config.intervalUp interval = self.serviceConfig.interval_up
if lastStatus ~= nil and currentStatus ~= lastStatus then if lastStatus ~= nil and currentStatus ~= lastStatus then
writeValueToFile(Config.statusFile, statusJson(currentStatus)) self:writeValueToFile(self.statusFile, self:statusJson(
writeLogMessage("notice", "Internet connected") currentStatus, self.serviceConfig.instance))
self:writeLogMessage("notice", "Connected")
end end
else else
interval = Config.intervalDown interval = self.serviceConfig.interval_down
if lastStatus ~= nil and currentStatus ~= lastStatus then if lastStatus ~= nil and currentStatus ~= lastStatus then
writeValueToFile(Config.statusFile, statusJson(currentStatus)) self:writeValueToFile(self.statusFile, self:statusJson(
writeLogMessage("notice", "Internet disconnected") currentStatus, self.serviceConfig.instance))
self:writeLogMessage("notice", "Disconnected")
end end
end end
counter = 0 counter = 0
end end
timeDiff = 0 timeDiff = 0
for _, e in ipairs(Config.modules) do for _, e in ipairs(self.modules) do
timeNow = nixio.sysinfo().uptime timeNow = time.clock_gettime(time.CLOCK_MONOTONIC).tv_sec
if lastTime then if lastTime then
timeDiff = timeDiff + timeNow - lastTime timeDiff = timeDiff + timeNow - lastTime
else else
@@ -306,142 +377,156 @@ local function main()
end end
local modulesStatus = {} local modulesStatus = {}
for k, v in ipairs(Config.modules) do for k, v in ipairs(self.modules) do
if v.status ~= nil then if v.status ~= nil then
modulesStatus[v.name] = v.status modulesStatus[v.name] = v.status
end end
end end
if next(modulesStatus) then if next(modulesStatus) then
writeValueToFile(Config.statusFile, statusJson(currentStatus, modulesStatus)) self:writeValueToFile(self.statusFile, self:statusJson(
currentStatus, self.serviceConfig.instance, modulesStatus))
end end
lastStatus = currentStatus lastStatus = currentStatus
nixio.nanosleep(1) unistd.sleep(1)
counter = counter + 1 counter = counter + 1
end end
end end
local function removeProcessFiles() function InternetDetector:removeProcessFiles()
os.remove(Config.pidFile) os.remove(string.format(
os.remove(Config.statusFile) "%s/%s.%s.pid", self.commonDir, self.appName, self.serviceConfig.instance))
os.remove(string.format(
"%s/%s.%s.status", self.commonDir, self.appName, self.serviceConfig.instance))
end end
local function status() function InternetDetector:status()
if nixio.fs.access(Config.pidFile, "r") then local ok, commonDir = pcall(dirent.files, self.commonDir)
if ok then
local appName = self.appName:gsub("-", "%%-")
for item in commonDir do
if item:match("^" .. appName .. ".-%.pid$") then
return "running" return "running"
else end
end
end
return "stoped" return "stoped"
end
end end
local function poll(attempts, timeout) function InternetDetector:poll()
if Config.mode == 1 then local retCode = self:checkHosts()
Config.connectionAttempts = Config.UIConnectionAttempts return string.format('{"instances":[%s]}', self:statusJson(
Config.connectionTimeout = Config.UIConnectionTimeout retCode, self.serviceConfig.instance))
end
if attempts then
Config.connectionAttempts = attempts
end
if timeout then
Config.connectionTimeout = timeout
end
if checkHosts() == 0 then
return statusJson(0)
else
return statusJson(1)
end
end end
local function inetStatus(json) function InternetDetector:inetStatus()
local inetStat = 1 local inetStat = '{"instances":[]}'
if nixio.fs.access(Config.statusFile, "r") then local ok, commonDir = pcall(dirent.files, self.commonDir)
local inetStatVal = readValueFromFile(Config.statusFile) if ok then
inetStat = inetStatVal local appName = self.appName:gsub("-", "%%-")
elseif Config.mode == 1 then local lines = {}
inetStat = poll() for item in commonDir do
else if item:match("^" .. appName .. ".-%.status$") then
os.exit(126) lines[#lines + 1] = self:readValueFromFile(
string.format("%s/%s", self.commonDir, item))
end end
if not json then
local sVal = inetStat:match('"inet":[0-9]')
if sVal then
sVal = sVal:match("[0-9]")
inetStat = (tonumber(sVal) == 0) and "up" or "down"
end end
inetStat = '{"instances":[' .. table.concat(lines, ",") .. "]}"
end end
return inetStat return inetStat
end end
local function stop() function InternetDetector:stopInstance(pidFile)
local pidValue local retVal
if Config.enableLogger then if stat.stat(pidFile) then
nixio.openlog(Config.appName) pidValue = self:readValueFromFile(pidFile)
end
if nixio.fs.access(Config.pidFile, "r") then
pidValue = readValueFromFile(Config.pidFile)
if pidValue then if pidValue then
local success local ok, errMsg, errNum
for i = 0, 10 do for i = 0, 10 do
success = nixio.kill(tonumber(pidValue), 15) ok, errMsg, errNum = signal.kill(tonumber(pidValue), signal.SIGTERM)
if success then if ok then
break break
end end
end end
if not success then if not ok then
io.stderr:write(string.format('No such process: "%s"\n', pidValue)) io.stderr:write(string.format(
'Process stop error: %s (%s). PID: "%s"\n', errMsg, errNum, pidValue))
end end
writeLogMessage("info", string.format("[%s] stoped", pidValue)) if errNum == 3 then
removeProcessFiles() os.remove(pidFile)
end
retVal = true
end end
end end
if not pidValue then if not pidValue then
io.stderr:write( io.stderr:write(
string.format('PID file "%s" does not exist. %s not running?\n', string.format('PID file "%s" does not exist. %s not running?\n',
Config.pidFile, Config.appName)) pidFile, self.appName))
end
if Config.enableLogger then
nixio.closelog()
end end
return retVal
end end
local function preRun() function InternetDetector:stop()
-- Exit if internet-detector mode != 2(Service) local appName = self.appName:gsub("-", "%%-")
if Config.mode ~= 2 then local success
io.stderr:write(string.format('Start failed, mode != 2\n', Config.appName)) for i = 0, 10 do
success = true
local ok, commonDir = pcall(dirent.files, self.commonDir)
if ok then
for item in commonDir do
if item:match("^" .. appName .. ".-%.pid$") then
self:stopInstance(string.format("%s/%s", self.commonDir, item))
success = false
end
end
if success then
break
end
unistd.sleep(1)
else
break
end
end
end
function InternetDetector:preRun()
-- Exit if internet-detector mode != 1(Service)
if self.mode ~= 1 then
io.stderr:write(string.format('Start failed, mode != 1\n', self.appName))
os.exit(0) os.exit(0)
end end
if nixio.fs.access(Config.pidFile, "r") then if stat.stat(self.pidFile) then
io.stderr:write( io.stderr:write(
string.format('PID file "%s" already exist. %s already running?\n', string.format('PID file "%s" already exist. %s already running?\n',
Config.pidFile, Config.appName)) self.pidFile, self.appName))
return false return false
end end
return true return true
end end
local function run() function InternetDetector:run()
local pidValue = nixio.getpid() local pidValue = unistd.getpid()
writeValueToFile(Config.pidFile, pidValue) self:writeValueToFile(self.pidFile, pidValue)
if Config.enableLogger then if self.enableLogger then
nixio.openlog(Config.appName, "pid") syslog.openlog(self.appName, syslog.LOG_PID, syslog.LOG_DAEMON)
end end
writeLogMessage("info", "started") self:writeLogMessage("info", "started")
loadModules() self:loadModules()
-- Loaded modules -- Loaded modules
local modules = {} local modules = {}
for _, v in ipairs(Config.modules) do for _, v in ipairs(self.modules) do
modules[#modules + 1] = string.format("%s", v.name) modules[#modules + 1] = string.format("%s", v.name)
end end
if #modules > 0 then if #modules > 0 then
writeLogMessage( self:writeLogMessage(
"info", string.format("Loaded modules: %s", table.concat(modules, ", ")) "info", string.format("Loaded modules: %s", table.concat(modules, ", "))
) )
end end
-- Debug -- Debug
if Config.debug then if self.debug then
local function inspectTable() local function inspectTable()
local tables = {}, f local tables = {}, f
f = function(t, prefix) f = function(t, prefix)
@@ -459,92 +544,125 @@ local function run()
end end
io.stdout:write("--- Config ---\n") io.stdout:write("--- Config ---\n")
inspectTable()(Config, "Config.") inspectTable()(self, "self.")
io.stdout:flush() io.stdout:flush()
end end
main() self:writeValueToFile(
if Config.enableLogger then self.statusFile, self:statusJson(-1, self.serviceConfig.instance))
nixio.closelog()
self:main()
self:removeProcessFiles()
if self.enableLogger then
self:writeLogMessage("info", "stoped")
syslog.closelog()
end end
end end
local function noDaemon() function InternetDetector:noDaemon()
if not preRun() then if not self:preRun() then
return return
end end
run() self:run()
end end
local function daemon(debug) function InternetDetector:daemon()
if not preRun() then if not self:preRun() then
return return
end end
-- UNIX double fork -- UNIX double fork
if nixio.fork() == 0 then if unistd.fork() == 0 then
nixio.setsid() unistd.setpid("s")
if nixio.fork() == 0 then if unistd.fork() == 0 then
nixio.chdir("/") unistd.chdir("/")
nixio.umask(0) stat.umask(0)
local output = "/dev/null" local devnull = fcntl.open("/dev/null", fcntl.O_RDWR)
if debug then
output = Config.debugLog
Config.debug = true
end
io.stdout:flush() io.stdout:flush()
io.stderr:flush() io.stderr:flush()
nixio.dup(io.open("/dev/null", "r"), io.stdin) unistd.dup2(devnull, 0) -- io.stdin
nixio.dup(io.open(output, "a+"), io.stdout) unistd.dup2(devnull, 1) -- io.stdout
nixio.dup(io.open(output, "a+"), io.stderr) unistd.dup2(devnull, 2) -- io.stderr
run() self:run()
unistd.close(devnull)
end end
os.exit(0) os.exit(0)
end end
os.exit(0) os.exit(0)
end end
local function restart() function InternetDetector:setServiceConfig(instance)
stop() if self:loadUCIConfig("instance", instance) then
daemon() self:parseHosts()
return true
end
end end
-- Main section -- Main section
parseHosts()
local function help() local function help()
return string.format( return string.format(
"Usage: %s [start|stop|restart|no-daemon|debug|status|inet-status|inet-status-json|poll [<attempts num>] [<timeout sec>]|--help]", "Usage: %s service <UCI instance> | nodaemon <UCI instance> | debug <UCI instance> | stop | status | inet-status | poll [<attempts num>] [<timeout sec>] | --help",
arg[0] arg[0]
) )
end end
local helpArgs = { ["-h"] = true, ["--help"] = true, ["help"] = true } local helpArgs = { ["-h"] = true, ["--help"] = true, ["help"] = true }
if arg[1] == "start" or #arg == 0 then if arg[1] == "service" then
daemon() if arg[2] then
elseif arg[1] == "no-daemon" then if InternetDetector:setServiceConfig(arg[2]) then
noDaemon() InternetDetector:daemon()
else
os.exit(126)
end
else
print(help())
os.exit(1)
end
elseif arg[1] == "nodaemon" then
if arg[2] then
if InternetDetector:setServiceConfig(arg[2]) then
InternetDetector:noDaemon()
else
os.exit(126)
end
else
print(help())
os.exit(1)
end
elseif arg[1] == "debug" then elseif arg[1] == "debug" then
daemon(true) if arg[2] then
if InternetDetector:setServiceConfig(arg[2]) then
InternetDetector.debug = true
InternetDetector:noDaemon()
else
os.exit(126)
end
else
print(help())
os.exit(1)
end
elseif arg[1] == "stop" then elseif arg[1] == "stop" then
stop() InternetDetector:stop()
elseif arg[1] == "restart" then
restart()
elseif arg[1] == "status" then elseif arg[1] == "status" then
print(status()) print(InternetDetector:status())
elseif arg[1] == "inet-status" then elseif arg[1] == "inet-status" then
print(inetStatus()) print(InternetDetector:inetStatus())
elseif arg[1] == "inet-status-json" then
print(inetStatus(true))
elseif arg[1] == "poll" then elseif arg[1] == "poll" then
local attempts, timeout if InternetDetector:loadUCIConfig("ui", "ui") then
InternetDetector:parseHosts()
if arg[2] and arg[2]:match("[0-9]+") then if arg[2] and arg[2]:match("[0-9]+") then
attempts = tonumber(arg[2]) InternetDetector.serviceConfig.connection_attempts = tonumber(arg[2])
if arg[3] and arg[3]:match("[0-9]+") then if arg[3] and arg[3]:match("[0-9]+") then
timeout = tonumber(arg[3]) InternetDetector.serviceConfig.connection_timeout = tonumber(arg[3])
end end
end end
print(poll(attempts, timeout))
print(InternetDetector:poll())
else
os.exit(126)
end
elseif helpArgs[arg[1]] then elseif helpArgs[arg[1]] then
print(help()) print(help())
else else

View File

@@ -2,11 +2,13 @@
Dependences: Dependences:
mailsend mailsend
--]] --]]
local nixio = require("nixio") local unistd = require("posix.unistd")
local Module = { local Module = {
name = "mod_email", name = "mod_email",
config = {}, config = {
debug = false,
},
syslog = function(level, msg) return true end, syslog = function(level, msg) return true end,
writeValue = function(filePath, str) return false end, writeValue = function(filePath, str) return false end,
readValue = function(filePath) return nil end, readValue = function(filePath) return nil end,
@@ -45,7 +47,7 @@ function Module:init(t)
self.mailSmtpPort = t.mail_smtp_port self.mailSmtpPort = t.mail_smtp_port
self.mailSecurity = t.mail_security self.mailSecurity = t.mail_security
if nixio.fs.access(self.mta, "x") then if unistd.access(self.mta, "x") then
self._enabled = true self._enabled = true
else else
self._enabled = false self._enabled = false
@@ -66,16 +68,19 @@ end
function Module:sendMessage(msg) function Module:sendMessage(msg)
local verboseArg = "" local verboseArg = ""
-- Debug -- Debug
if self.config.debug then if self.config.debug then
verboseArg = " -v" verboseArg = " -v"
io.stdout:write("--- mod_email ---\n") io.stdout:write(string.format("--- %s ---\n", self.name))
io.stdout:flush() io.stdout:flush()
end end
local securityArgs = "-starttls -auth-login" local securityArgs = "-starttls -auth-login"
if self.mailSecurity == "ssl" then if self.mailSecurity == "ssl" then
securityArgs = "-ssl -auth" securityArgs = "-ssl -auth"
end end
local mtaCmd = string.format( local mtaCmd = string.format(
'%s%s %s -smtp "%s" -port %s -cs utf-8 -user "%s" -pass "%s" -f "%s" -t "%s" -sub "%s" -M "%s"', '%s%s %s -smtp "%s" -port %s -cs utf-8 -user "%s" -pass "%s" -f "%s" -t "%s" -sub "%s" -M "%s"',
self.mta, verboseArg, securityArgs, self.mailSmtp, self.mailSmtpPort, self.mta, verboseArg, securityArgs, self.mailSmtp, self.mailSmtpPort,
@@ -107,7 +112,9 @@ function Module:run(currentStatus, lastStatus, timeDiff)
self._lastDisconnection = os.date("%Y.%m.%d %H:%M:%S", os.time()) self._lastDisconnection = os.date("%Y.%m.%d %H:%M:%S", os.time())
end end
end end
else else
if not self._msgSent then if not self._msgSent then
if not self._lastConnection then if not self._lastConnection then

View File

@@ -1,5 +1,6 @@
local nixio = require("nixio") local unistd = require("posix.unistd")
local dirent = require("posix.dirent")
local Module = { local Module = {
name = "mod_led_control", name = "mod_led_control",
@@ -20,13 +21,13 @@ local Module = {
} }
function Module:resetLeds() function Module:resetLeds()
local dir = nixio.fs.dir(self.sysLedsDir) local ok, dir = pcall(dirent.files, self.sysLedsDir)
if not dir then if not ok then
return return
end end
for led in dir do for led in dir do
local brightness = string.format("%s/%s/brightness", self.sysLedsDir, led) local brightness = string.format("%s/%s/brightness", self.sysLedsDir, led)
if nixio.fs.access(brightness, "w") then if unistd.access(brightness, "w") then
self.writeValue(brightness, 0) self.writeValue(brightness, 0)
end end
end end
@@ -41,10 +42,11 @@ function Module:init(t)
self._ledMaxBrightnessFile = string.format("%s/max_brightness", self._ledDir) self._ledMaxBrightnessFile = string.format("%s/max_brightness", self._ledDir)
self._ledBrightnessFile = string.format("%s/brightness", self._ledDir) self._ledBrightnessFile = string.format("%s/brightness", self._ledDir)
self._ledMaxBrightness = self.readValue(self._ledMaxBrightnessFile) or 1 self._ledMaxBrightness = self.readValue(self._ledMaxBrightnessFile) or 1
if (not nixio.fs.access(self._ledDir, "r") or if (not unistd.access(self._ledDir, "r") or
not nixio.fs.access(self._ledBrightnessFile, "r", "w")) then not unistd.access(self._ledBrightnessFile, "rw")) then
self._enabled = false self._enabled = false
self.syslog("warning", string.format("%s: LED '%s' is not available", self.name, self.ledName)) self.syslog("warning", string.format(
"%s: LED '%s' is not available", self.name, self.ledName))
else else
self._enabled = true self._enabled = true
-- Reset all LEDs -- Reset all LEDs
@@ -85,6 +87,7 @@ function Module:run(currentStatus, lastStatus, timeDiff)
self._counter = 0 self._counter = 0
end end
self._counter = self._counter + timeDiff self._counter = self._counter + timeDiff
end end

View File

@@ -2,7 +2,7 @@
Dependences: Dependences:
modemmanager modemmanager
--]] --]]
local nixio = require("nixio") local unistd = require("posix.unistd")
local Module = { local Module = {
name = "mod_modem_restart", name = "mod_modem_restart",
@@ -50,11 +50,11 @@ function Module:init(t)
self.iface = t.iface self.iface = t.iface
self.anyBand = (tonumber(t.any_band) ~= 0) self.anyBand = (tonumber(t.any_band) ~= 0)
if not nixio.fs.access(self.mmcli, "x") then if not unistd.access(self.mmcli, "x") then
self.anyBand = false self.anyBand = false
end end
if nixio.fs.access(self.mmInit, "x") then if unistd.access(self.mmInit, "x") then
self._enabled = true self._enabled = true
else else
self._enabled = false self._enabled = false

View File

@@ -1,5 +1,5 @@
local nixio = require("nixio") local unistd = require("posix.unistd")
local Module = { local Module = {
name = "mod_network_restart", name = "mod_network_restart",
@@ -22,9 +22,9 @@ end
function Module:toggleDevice(flag) function Module:toggleDevice(flag)
local ip = "/sbin/ip" local ip = "/sbin/ip"
if nixio.fs.access(ip, "x") then if unistd.access(ip, "x") then
return os.execute( return os.execute(string.format(
string.format("%s link set dev %s %s", ip, self.iface, (flag and "up" or "down")) "%s link set dev %s %s", ip, self.iface, (flag and "up" or "down"))
) )
end end
end end
@@ -71,10 +71,11 @@ function Module:run(currentStatus, lastStatus, timeDiff)
self.syslog("info", string.format( self.syslog("info", string.format(
"%s: restarting network interface '%s'", self.name, self.iface)) "%s: restarting network interface '%s'", self.name, self.iface))
self:ifaceDown() self:ifaceDown()
nixio.nanosleep(self.restartTimeout) unistd.sleep(self.restartTimeout)
self:ifaceUp() self:ifaceUp()
else else
self.syslog("info", string.format("%s: restarting network", self.name)) self.syslog("info", string.format(
"%s: restarting network", self.name))
self:networkRestart() self:networkRestart()
end end
self._deadCounter = 0 self._deadCounter = 0

View File

@@ -1,79 +1,322 @@
local nixio = require("nixio") local socket = require("posix.sys.socket")
local unistd = require("posix.unistd")
local Module = { local Module = {
name = "mod_public_ip", name = "mod_public_ip",
config = {}, config = {
debug = false,
serviceConfig = {
iface = nil,
},
},
syslog = function(level, msg) return true end, syslog = function(level, msg) return true end,
writeValue = function(filePath, str) return false end, writeValue = function(filePath, str) return false end,
readValue = function(filePath) return nil end, readValue = function(filePath) return nil end,
port = 53,
runInterval = 600, runInterval = 600,
nslookup = "/usr/bin/nslookup", runIntervalFailed = 60,
timeout = 3, timeout = 3,
reqAttempts = 3,
providers = { providers = {
opendns1 = { opendns1 = {
name = "opendns1", server = "208.67.222.222", name = "opendns1", host = "myip.opendns.com",
host = "myip.opendns.com", queryType = "a" server = "208.67.222.222", server6 = "2620:119:35::35",
port = 53, queryType = "A", queryType6 = "AAAA",
}, },
opendns2 = { opendns2 = {
name = "opendns2", server = "208.67.220.220", name = "opendns2", host = "myip.opendns.com",
host = "myip.opendns.com", queryType = "a" server = "208.67.220.220", server6 = "2620:119:35::35",
port = 53, queryType = "A", queryType6 = "AAAA",
}, },
opendns3 = { opendns3 = {
name = "opendns3", server = "208.67.222.220", name = "opendns3", host = "myip.opendns.com",
host = "myip.opendns.com", queryType = "a" server = "208.67.222.220", server6 = "2620:119:35::35",
port = 53, queryType = "A", queryType6 = "AAAA",
}, },
opendns4 = { opendns4 = {
name = "opendns4", server = "208.67.220.222", name = "opendns4", host = "myip.opendns.com",
host = "myip.opendns.com", queryType = "a" server = "208.67.220.222", server6 = "2620:119:35::35",
port = 53, queryType = "A", queryType6 = "AAAA",
}, },
akamai = { akamai = {
name = "akamai", server = "ns1-1.akamaitech.net", name = "akamai", host = "whoami.akamai.net",
host = "whoami.akamai.net", queryType = "a" server = "ns1-1.akamaitech.net", server6 = "ns1-1.akamaitech.net",
port = 53, queryType = "A", queryType6 = "AAAA",
}, },
google = { google = {
name = "google", server = "ns1.google.com", name = "google", host = "o-o.myaddr.l.google.com",
host = "o-o.myaddr.l.google.com", queryType = "txt" server = "ns1.google.com", server6 = "ns1.google.com",
port = 53, queryType = "TXT", queryType6 = "TXT",
}, },
}, },
status = nil, status = nil,
_provider = nil, _provider = nil,
_nslookupCmd = nil, _qtype = false,
_currentIp = nil, _currentIp = nil,
_enabled = false, _enabled = false,
_counter = 0, _counter = 0,
_interval = 600,
_DNSPacket = nil,
} }
function Module:parseA(str) function Module:getQueryType(type)
res = str:match("Name:%s+" .. self._provider.host .. "\nAddress:%s+[%w.:]+") local types = {
if res then A = 1,
return res:match("[%w.:]+$") NS = 2,
MD = 3,
MF = 4,
CNAME = 5,
SOA = 6,
MB = 7,
MG = 8,
MR = 9,
NULL = 10,
WKS = 11,
PTS = 12,
HINFO = 13,
MINFO = 14,
MX = 15,
TXT = 16,
AAAA = 28,
}
return types[type]
end
function Module:buildMessage(address, queryType)
if not queryType then
queryType = "A"
end
queryType = self:getQueryType(queryType)
local addressString = ""
for part in address:gmatch("[^.]+") do
local t = {}
for i in part:gmatch(".") do
t[#t + 1] = i
end
addrLen = #part
addrPart = table.concat(t)
addressString = addressString .. string.char(addrLen) .. addrPart
end
local data = (
string.char(
0xaa, 0xaa,
0x01, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
) ..
addressString ..
string.char(
0x00,
0x00, queryType,
0x00, 0x01
)
)
return data
end
function Module:sendUDPMessage(message, server, port)
local success
local retCode = 1
local data
if self.config.debug then
io.stdout:write(string.format("--- %s ---\n", self.name))
io.stdout:flush()
end
local saTable, errMsg, errNum = socket.getaddrinfo(server, port)
if not saTable then
if self.config.debug then
io.stdout:write(string.format(
"GETADDRINFO ERROR: %s, %s\n", errMsg, errNum))
end
else
local family = saTable[1].family
if family then
local sock, errMsg, errNum = socket.socket(family, socket.SOCK_DGRAM, 0)
if not sock then
if self.config.debug then
io.stdout:write(string.format(
"SOCKET ERROR: %s, %s\n", errMsg, errNum))
end
return retCode
end
socket.setsockopt(sock, socket.SOL_SOCKET,
socket.SO_SNDTIMEO, self.timeout, 0)
socket.setsockopt(sock, socket.SOL_SOCKET,
socket.SO_RCVTIMEO, self.timeout, 0)
if self.config.serviceConfig.iface then
local ok, errMsg, errNum = socket.setsockopt(sock, socket.SOL_SOCKET,
socket.SO_BINDTODEVICE, self.config.serviceConfig.iface)
if not ok then
if self.config.debug then
io.stdout:write(string.format(
"SOCKET ERROR: %s, %s\n", errMsg, errNum))
end
return retCode
end
end
local ok, errMsg, errNum = socket.sendto(sock, message, saTable[1])
local response = {}
if ok then
local ret, resp, errNum = socket.recvfrom(sock, 1024)
data = ret
if data then
success = true
response = resp
elseif self.config.debug then
io.stdout:write(string.format(
"SOCKET RECV ERROR: %s, %s\n", tostring(resp), tostring(errNum)))
end
elseif self.config.debug then
io.stdout:write(string.format(
"SOCKET SEND ERROR: %s, %s\n", tostring(errMsg), tostring(errNum)))
end
if self.config.debug then
io.stdout:write(string.format(
"--- UDP ---\ntime = %s\nconnection_timeout = %s\niface = %s\nserver = %s:%s\nsockname = %s:%s\nsuccess = %s\n",
os.time(),
self.timeout,
tostring(self.config.serviceConfig.iface),
server,
tostring(port),
tostring(response.addr),
tostring(response.port),
tostring(success))
)
io.stdout:flush()
end
unistd.close(sock)
retCode = success and 0 or 1
end
end
return retCode, tostring(data)
end
function Module:parseParts(message, start, parts)
local partStart = start + 2
local partLen = message:sub(start, start + 1)
if #partLen == 0 then
return parts
end
local partEnd = partStart + (tonumber(partLen, 16) * 2)
parts[#parts + 1] = message:sub(partStart, partEnd - 1)
if message:sub(partEnd, partEnd + 1) == "00" or partEnd > #message then
return parts
else
return self:parseParts(message, partEnd, parts)
end end
end end
function Module:parseGoogle(str) function Module:decodeMessage(message)
res = str:match(self._provider.host .. '%s+text%s+=%s+"[%w.:]+"') local retTable = {}
if res then
return res:gsub('"', ''):match("[%w.:]+$") local t = {}
for i = 1, #message do
t[#t + 1] = string.format("%.2x", string.byte(message, i))
end end
message = table.concat(t)
local ANCOUNT = message:sub(13, 16)
local NSCOUNT = message:sub(17, 20)
local ARCOUNT = message:sub(21, 24)
local questionSectionStarts = 25
local questionParts = self:parseParts(message, questionSectionStarts, {})
local qtypeStarts = questionSectionStarts + (#table.concat(questionParts)) + (#questionParts * 2) + 1
local qclassStarts = qtypeStarts + 4
local answerSectionStarts = qclassStarts + 4
local numAnswers = math.max(
tonumber(ANCOUNT, 16), tonumber(NSCOUNT, 16), tonumber(ARCOUNT, 16))
if numAnswers > 0 then
for answerCount = 1, numAnswers do
if answerSectionStarts < #message then
local ATYPE = tonumber(
message:sub(answerSectionStarts + 5, answerSectionStarts + 8), 16)
local RDLENGTH = tonumber(
message:sub(answerSectionStarts + 21, answerSectionStarts + 24), 16)
local RDDATA = message:sub(
answerSectionStarts + 25, answerSectionStarts + 24 + (RDLENGTH * 2))
local RDDATA_decoded = ""
if #RDDATA > 0 then
if ATYPE == self:getQueryType("A") or ATYPE == self:getQueryType("AAAA") then
local octets = {}
local sep = "."
if #RDDATA > 8 then
sep = ":"
for i = 1, #RDDATA, 4 do
local string = RDDATA:sub(i, i + 3)
string = string:gsub("^00?0?", "")
octets[#octets + 1] = string
end
else
for i = 1, #RDDATA, 2 do
octets[#octets + 1] = tonumber(RDDATA:sub(i, i + 1), 16)
end
end
RDDATA_decoded = table.concat(octets, sep):gsub("0:[0:]+", "::", 1):gsub("::+", "::")
else
local rdata_t = {}
for _, v in ipairs(self:parseParts(RDDATA, 1, {})) do
local t = {}
for i = 1, #v, 2 do
t[#t + 1] = string.char(tonumber(v:sub(i, i + 1), 16))
end
rdata_t[#rdata_t + 1] = table.concat(t)
end
RDDATA_decoded = table.concat(rdata_t)
end
end
answerSectionStarts = answerSectionStarts + 24 + (RDLENGTH * 2)
if RDDATA_decoded:match("^[a-f0-9.:]+$") then
retTable[#retTable + 1] = RDDATA_decoded
end
end
end
end
return retTable
end end
function Module:resolveIP() function Module:resolveIP()
local res local res
local fh = io.popen(self._nslookupCmd, "r") local qtype = self._qtype and self._provider.queryType6 or self._provider.queryType
if fh then local server = self._qtype and self._provider.server6 or self._provider.server
output = fh:read("*a") local port = self._provider.port or self.port
fh:close()
if self._provider.name == "google" then if not self._DNSPacket then
res = self:parseGoogle(output) self._DNSPacket = self:buildMessage(self._provider.host, qtype)
else end
res = self:parseA(output)
local retCode, response = self:sendUDPMessage(self._DNSPacket, server, port)
if retCode == 0 then
local retTable = self:decodeMessage(response)
if #retTable > 0 then
res = table.concat(retTable, ", ")
end end
else else
self.syslog("err", string.format( self.syslog("err", string.format(
"%s: Nslookup call failed (%s)", self.name, self.nslookup)) "%s: DNS error when requesting an IP address", self.name))
end end
return res or "Undefined"
return res
end end
function Module:init(t) function Module:init(t)
@@ -88,23 +331,11 @@ function Module:init(t)
else else
self._provider = self.providers.opendns1 self._provider = self.providers.opendns1
end end
if not nixio.fs.access(self.nslookup, "x") then self._qtype = (tonumber(t.qtype) ~= 0)
self._enabled = false self._currentIp = nil
self.syslog( self._DNSPacket = nil
"warning", self._interval = self.runInterval
string.format("%s: '%s' does not exists", self.name, self.nslookup)
)
else
self._enabled = true self._enabled = true
self._nslookupCmd = string.format(
"%s -type=%s -timeout=%d %s %s",
self.nslookup,
self._provider.queryType,
self.timeout,
self._provider.host,
self._provider.server
)
end
end end
function Module:run(currentStatus, lastStatus, timeDiff) function Module:run(currentStatus, lastStatus, timeDiff)
@@ -112,8 +343,17 @@ function Module:run(currentStatus, lastStatus, timeDiff)
return return
end end
if currentStatus == 0 then if currentStatus == 0 then
if self._counter == 0 or self._counter >= self.runInterval or currentStatus ~= lastStatus then if self._counter == 0 or self._counter >= self._interval or currentStatus ~= lastStatus then
local ip = self:resolveIP() local ip = self:resolveIP()
if not ip then
ip = "Undefined"
self._interval = self.runIntervalFailed
else
self._interval = self.runInterval
end
if ip ~= self._currentIp then if ip ~= self._currentIp then
self.status = ip self.status = ip
self.syslog( self.syslog(
@@ -123,6 +363,7 @@ function Module:run(currentStatus, lastStatus, timeDiff)
else else
self.status = nil self.status = nil
end end
self._currentIp = ip self._currentIp = ip
self._counter = 0 self._counter = 0
else else
@@ -132,7 +373,9 @@ function Module:run(currentStatus, lastStatus, timeDiff)
self.status = nil self.status = nil
self._currentIp = nil self._currentIp = nil
self._counter = 0 self._counter = 0
self._interval = self.runInterval
end end
self._counter = self._counter + timeDiff self._counter = self._counter + timeDiff
end end

View File

@@ -1,5 +1,5 @@
local nixio = require("nixio") local unistd = require("posix.unistd")
local Module = { local Module = {
name = "mod_reboot", name = "mod_reboot",
@@ -16,9 +16,8 @@ local Module = {
function Module:rebootDevice() function Module:rebootDevice()
self.syslog("warning", string.format("%s: reboot", self.name)) self.syslog("warning", string.format("%s: reboot", self.name))
os.execute("/sbin/reboot &") os.execute("/sbin/reboot &")
if self.forceRebootDelay > 0 then if self.forceRebootDelay > 0 then
nixio.nanosleep(self.forceRebootDelay) unistd.sleep(self.forceRebootDelay)
self.syslog("warning", string.format("%s: force reboot", self.name)) self.syslog("warning", string.format("%s: force reboot", self.name))
self.writeValue("/proc/sys/kernel/sysrq", "1") self.writeValue("/proc/sys/kernel/sysrq", "1")
self.writeValue("/proc/sysrq-trigger", "b") self.writeValue("/proc/sysrq-trigger", "b")

View File

@@ -1,5 +1,5 @@
local nixio = require("nixio") local unistd = require("posix.unistd")
local Module = { local Module = {
name = "mod_user_scripts", name = "mod_user_scripts",
@@ -19,8 +19,8 @@ local Module = {
} }
function Module:runExternalScript(scriptPath) function Module:runExternalScript(scriptPath)
if nixio.fs.access(scriptPath, "x") then if unistd.access(scriptPath, "r") then
os.execute(string.format('/bin/sh -c "%s" &', scriptPath)) os.execute(string.format('/bin/sh "%s" &', scriptPath))
end end
end end
@@ -28,8 +28,10 @@ function Module:init(t)
self.deadPeriod = tonumber(t.dead_period) self.deadPeriod = tonumber(t.dead_period)
self.alivePeriod = tonumber(t.alive_period) self.alivePeriod = tonumber(t.alive_period)
if self.config.configDir then if self.config.configDir then
self.upScript = string.format("%s/up-script", self.config.configDir) self.upScript = string.format(
self.downScript = string.format("%s/down-script", self.config.configDir) "%s/up-script.%s", self.config.configDir, self.config.serviceConfig.instance)
self.downScript = string.format(
"%s/down-script.%s", self.config.configDir, self.config.serviceConfig.instance)
end end
end end
@@ -37,7 +39,6 @@ function Module:run(currentStatus, lastStatus, timeDiff)
if currentStatus == 1 then if currentStatus == 1 then
self._aliveCounter = 0 self._aliveCounter = 0
self._downScriptExecuted = false self._downScriptExecuted = false
if not self._upScriptExecuted then if not self._upScriptExecuted then
if self._deadCounter >= self.deadPeriod then if self._deadCounter >= self.deadPeriod then
self:runExternalScript(self.downScript) self:runExternalScript(self.downScript)
@@ -49,7 +50,6 @@ function Module:run(currentStatus, lastStatus, timeDiff)
else else
self._deadCounter = 0 self._deadCounter = 0
self._upScriptExecuted = false self._upScriptExecuted = false
if not self._downScriptExecuted then if not self._downScriptExecuted then
if self._aliveCounter >= self.alivePeriod then if self._aliveCounter >= self.alivePeriod then
self:runExternalScript(self.upScript) self:runExternalScript(self.upScript)

View File

@@ -1,10 +1,10 @@
# #
# (с) 2021 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector) # (с) 2023 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
# #
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_VERSION:=0.6-1 PKG_VERSION:=1.0-0
LUCI_TITLE:=LuCI support for internet-detector LUCI_TITLE:=LuCI support for internet-detector
LUCI_DEPENDS:=+internet-detector LUCI_DEPENDS:=+internet-detector
LUCI_PKGARCH:=all LUCI_PKGARCH:=all

View File

@@ -6,27 +6,50 @@
document.head.append(E('style', {'type': 'text/css'}, document.head.append(E('style', {'type': 'text/css'},
` `
:root { :root {
--app-id-font-color: #fff; --app-id-font-color: #454545;
--app-id-connected-color: #2ea256; --app-id-font-shadow: #fff;
--app-id-disconnected-color: #ff4e54; --app-id-connected-color: #6bdebb;
--app-id-undefined-color: #8a8a8a; --app-id-disconnected-color: #f8aeba;
--app-id-undefined-color: #dfdfdf;
} }
:root[data-darkmode="true"] { :root[data-darkmode="true"] {
--app-id-font-color: #f6f6f6;
--app-id-font-shadow: #4d4d4d;
--app-id-connected-color: #005F20; --app-id-connected-color: #005F20;
--app-id-disconnected-color: #a93734; --app-id-disconnected-color: #a93734;
--app-id-undefined-color: #4d4d4d; --app-id-undefined-color: #4d4d4d;
} }
.id-connected { .id-connected {
--on-color: var(--app-id-font-color);
background-color: var(--app-id-connected-color) !important; background-color: var(--app-id-connected-color) !important;
border-color: var(--app-id-connected-color) !important;
color: var(--app-id-font-color) !important; color: var(--app-id-font-color) !important;
text-shadow: 0 1px 1px var(--app-id-font-shadow);
} }
.id-disconnected { .id-disconnected {
--on-color: var(--app-id-font-color);
background-color: var(--app-id-disconnected-color) !important; background-color: var(--app-id-disconnected-color) !important;
border-color: var(--app-id-disconnected-color) !important;
color: var(--app-id-font-color) !important; color: var(--app-id-font-color) !important;
text-shadow: 0 1px 1px var(--app-id-font-shadow);
} }
.id-undefined { .id-undefined {
--on-color: var(--app-id-font-color);
background-color: var(--app-id-undefined-color) !important; background-color: var(--app-id-undefined-color) !important;
border-color: var(--app-id-undefined-color) !important;
color: var(--app-id-font-color) !important; color: var(--app-id-font-color) !important;
text-shadow: 0 1px 1px var(--app-id-font-shadow);
}
.id-label-status {
display: inline-block;
word-wrap: break-word;
margin: 2px !important;
padding: 4px 8px;
border: 1px solid;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
font-weight: bold;
} }
`)); `));
@@ -34,85 +57,103 @@ return baseclass.extend({
title : _('Internet'), title : _('Internet'),
appName : 'internet-detector', appName : 'internet-detector',
execPath : '/usr/bin/internet-detector', execPath : '/usr/bin/internet-detector',
uiCheckIntervalUp : null,
uiCheckIntervalDown : null,
currentAppMode : null,
inetStatus : null, inetStatus : null,
publicIp : null,
inetStatusFromJson: function(res) { inetStatusFromJson : function(res) {
let curInetStatus = null; let inetStatData = null;
let curPubIp = null;
if(res.code === 0) { if(res.code === 0) {
try { try {
let json = JSON.parse(res.stdout.trim()); inetStatData = JSON.parse(res.stdout.trim());
curInetStatus = json.inet;
curPubIp = json.mod_public_ip;
} catch(e) {}; } catch(e) {};
}; };
return [ curInetStatus, curPubIp ]; return inetStatData;
}, },
load: async function() { load: async function() {
if(!( if(!(
'uiCheckIntervalUp' in window && this.uiCheckIntervalUp &&
'uiCheckIntervalDown' in window && this.uiCheckIntervalDown &&
'currentAppMode' in window this.currentAppMode
)) { )) {
await uci.load(this.appName).then(data => { await uci.load(this.appName).then(data => {
window.uiCheckIntervalUp = Number(uci.get(this.appName, 'config', 'ui_interval_up')); this.uiCheckIntervalUp = Number(uci.get(this.appName, 'ui', 'interval_up'));
window.uiCheckIntervalDown = Number(uci.get(this.appName, 'config', 'ui_interval_down')); this.uiCheckIntervalDown = Number(uci.get(this.appName, 'ui', 'interval_down'));
window.currentAppMode = uci.get(this.appName, 'config', 'mode'); this.currentAppMode = uci.get(this.appName, 'config', 'mode');
}).catch(e => {}); }).catch(e => {});
}; };
if(window.currentAppMode === '1' || window.currentAppMode === '2') { if(this.currentAppMode === '2') {
window.internetDetectorCounter = ('internetDetectorCounter' in window) ? this.internetDetectorCounter = ('internetDetectorCounter' in this) ?
++window.internetDetectorCounter : 0; ++this.internetDetectorCounter : 0;
if(!('internetDetectorState' in window)) { if((this.internetDetectorStateUi === 0 &&
window.internetDetectorState = 2; this.internetDetectorCounter % this.uiCheckIntervalUp) ||
}; (this.internetDetectorStateUi === 1 &&
this.internetDetectorCounter % this.uiCheckIntervalDown)
if(window.currentAppMode === '1' && ( ) {
(window.internetDetectorState === 0 && window.internetDetectorCounter % window.uiCheckIntervalUp) ||
(window.internetDetectorState === 1 && window.internetDetectorCounter % window.uiCheckIntervalDown)
)) {
return; return;
}; };
window.internetDetectorCounter = 0; this.internetDetectorCounter = 0;
return L.resolveDefault(fs.exec(this.execPath, [ 'inet-status-json' ]), null); return L.resolveDefault(fs.exec(this.execPath, [ 'poll' ]), null);
} }
else { else if(this.currentAppMode === '1') {
window.internetDetectorState = 2; return L.resolveDefault(fs.exec(this.execPath, [ 'inet-status' ]), null);
}; };
}, },
render: function(data) { render: function(data) {
if(window.currentAppMode === '0') { if(this.currentAppMode === '0') {
return return;
}; };
if(data) { if(data) {
[ window.internetDetectorState, this.publicIp ] = this.inetStatusFromJson(data); this.inetStatus = this.inetStatusFromJson(data);
if(this.currentAppMode === '2') {
this.internetDetectorStateUi = this.inetStatus.instances[0].inet;
};
}; };
let internetStatus = E('span', { 'class': 'label' }); let inetStatusArea = E('div', {});
if(window.internetDetectorState === 0) { if(!this.inetStatus || !this.inetStatus.instances || this.inetStatus.instances.length === 0) {
internetStatus.textContent = _('Connected') + (this.publicIp ? ' | %s: %s'.format(_('Public IP'), _(this.publicIp)) : ''); inetStatusArea.append(
internetStatus.className = "label id-connected"; E('span', { 'class': 'id-label-status id-undefined' }, _('Undefined'))
);
} else {
this.inetStatus.instances.sort((a, b) => a.num > b.num);
for(let i of this.inetStatus.instances) {
let status = _('Disconnected');
let className = 'id-label-status id-disconnected';
if(i.inet == 0) {
status = _('Connected');
className = 'id-label-status id-connected';
} }
else if(window.internetDetectorState === 1) { else if(i.inet == -1) {
internetStatus.textContent = _('Disconnected'); status = _('Undefined');
internetStatus.className = "label id-disconnected"; className = 'id-label-status id-undefined spinning';
} };
else {
internetStatus.textContent = _('Undefined'); let publicIp = (i.mod_public_ip) ? ' | %s: %s'.format(
internetStatus.className = "label id-undefined"; _('Public IP'), _(i.mod_public_ip)
) : '';
inetStatusArea.append(
E('span', { 'class': className }, '%s%s%s'.format(
(this.currentAppMode === '1') ? i.instance + ': ' : '',
status, publicIp)
)
);
};
}; };
return E('div', { return E('div', {
'class': 'cbi-section', 'class': 'cbi-section',
'style': 'margin-bottom:1em', 'style': 'margin-bottom:1em',
}, internetStatus); }, inetStatusArea);
}, },
}); });

View File

@@ -24,6 +24,9 @@ msgid ""
"available." "available."
msgstr "<abbr title=\"Светодиод\">LED</abbr> включен если Интернет доступен." msgstr "<abbr title=\"Светодиод\">LED</abbr> включен если Интернет доступен."
msgid "Add instance"
msgstr "Добавить экземпляр"
msgid "Alive interval" msgid "Alive interval"
msgstr "Интервал при подключении" msgstr "Интервал при подключении"
@@ -92,6 +95,9 @@ msgstr "Отключен"
msgid "Dismiss" msgid "Dismiss"
msgstr "Закрыть" msgstr "Закрыть"
msgid "DNS query type"
msgstr "Тип DNS-запроса"
msgid "DNS provider" msgid "DNS provider"
msgstr "DNS провайдер" msgstr "DNS провайдер"
@@ -117,7 +123,7 @@ msgid "Enable"
msgstr "Включить" msgstr "Включить"
msgid "Enable logging" msgid "Enable logging"
msgstr "Включить запись событий в лог" msgstr "Запись событий в лог"
msgid "Enabled" msgid "Enabled"
msgstr "Включен" msgstr "Включен"
@@ -168,6 +174,12 @@ msgstr ""
msgid "Huge: 1492 bytes" msgid "Huge: 1492 bytes"
msgstr "Огромный: 1492 байта" msgstr "Огромный: 1492 байта"
msgid "ICMP-echo request (ping)"
msgstr "Запрос ICMP-echo (ping)"
msgid "ICMP packet data size"
msgstr "Размер данных ICMP-пакета"
msgid "Interface" msgid "Interface"
msgstr "Интерфейс" msgstr "Интерфейс"
@@ -288,19 +300,6 @@ msgstr "Пароль"
msgid "Password for SMTP authentication." msgid "Password for SMTP authentication."
msgstr "Пароль для SMTP-аутентификации." msgstr "Пароль для SMTP-аутентификации."
msgid ""
"Performing actions when connecting and disconnecting the Internet (available "
"in the \"Service\" mode)."
msgstr ""
"Выполнение действий при подключении и отключении Интернет (доступно в режиме "
"\"Служба\")."
msgid "Ping host"
msgstr "Пинг хоста"
msgid "Ping packet size"
msgstr "Размер пакета Ping"
msgid "Polling interval" msgid "Polling interval"
msgstr "Интервал опроса" msgstr "Интервал опроса"
@@ -367,12 +366,12 @@ msgstr "Конфигурация службы"
msgid "Service for determining the public IP address through DNS." msgid "Service for determining the public IP address through DNS."
msgstr "Сервис для определения публичного IP адреса через DNS." msgstr "Сервис для определения публичного IP адреса через DNS."
msgid "Service modules"
msgstr "Модули службы"
msgid "Service: detector always runs as a system service." msgid "Service: detector always runs as a system service."
msgstr "Служба: детектор работает постоянно, как системная служба." msgstr "Служба: детектор работает постоянно, как системная служба."
msgid "Service instances"
msgstr "Экземпляры службы"
msgid "Set the modem to be allowed to use any band." msgid "Set the modem to be allowed to use any band."
msgstr "Разрешить модему использование любой частоты." msgstr "Разрешить модему использование любой частоты."
@@ -400,6 +399,9 @@ msgstr "TCP-порт"
msgid "TCP port connection" msgid "TCP port connection"
msgstr "Подключение к TCP-порту" msgstr "Подключение к TCP-порту"
msgid "The type of record requested in the DNS query (if the service supports it)."
msgstr "Тип записи запрашиваемой в DNS-запросе (если сервис поддерживает)."
msgid "TLS: use STARTTLS if the server supports it." msgid "TLS: use STARTTLS if the server supports it."
msgstr "TLS: использовать STARTTLS если сервер поддерживает." msgstr "TLS: использовать STARTTLS если сервер поддерживает."
@@ -438,8 +440,8 @@ msgstr ""
"Ожидание завершения перезагрузки перед выполнением принудительной " "Ожидание завершения перезагрузки перед выполнением принудительной "
"перезагрузки." "перезагрузки."
msgid "Web UI only" msgid "Web UI only (UI detector)"
msgstr "Только web-интерфейс" msgstr "Только web-интерфейс (UI детектор)"
msgid "Web UI only: detector works only when the Web UI is open (UI detector)." msgid "Web UI only: detector works only when the Web UI is open (UI detector)."
msgstr "" msgstr ""

View File

@@ -12,6 +12,9 @@ msgid ""
"available." "available."
msgstr "" msgstr ""
msgid "Add instance"
msgstr ""
msgid "Alive interval" msgid "Alive interval"
msgstr "" msgstr ""
@@ -80,6 +83,9 @@ msgstr ""
msgid "Dismiss" msgid "Dismiss"
msgstr "" msgstr ""
msgid "DNS query type"
msgstr ""
msgid "DNS provider" msgid "DNS provider"
msgstr "" msgstr ""
@@ -154,6 +160,12 @@ msgstr ""
msgid "Huge: 1492 bytes" msgid "Huge: 1492 bytes"
msgstr "" msgstr ""
msgid "ICMP-echo request (ping)"
msgstr ""
msgid "ICMP packet data size"
msgstr ""
msgid "Interface" msgid "Interface"
msgstr "" msgstr ""
@@ -258,17 +270,6 @@ msgstr ""
msgid "Password for SMTP authentication." msgid "Password for SMTP authentication."
msgstr "" msgstr ""
msgid ""
"Performing actions when connecting and disconnecting the Internet (available "
"in the \"Service\" mode)."
msgstr ""
msgid "Ping host"
msgstr ""
msgid "Ping packet size"
msgstr ""
msgid "Polling interval" msgid "Polling interval"
msgstr "" msgstr ""
@@ -335,10 +336,10 @@ msgstr ""
msgid "Service for determining the public IP address through DNS." msgid "Service for determining the public IP address through DNS."
msgstr "" msgstr ""
msgid "Service modules" msgid "Service: detector always runs as a system service."
msgstr "" msgstr ""
msgid "Service: detector always runs as a system service." msgid "Service instances"
msgstr "" msgstr ""
msgid "Set the modem to be allowed to use any band." msgid "Set the modem to be allowed to use any band."
@@ -368,6 +369,9 @@ msgstr ""
msgid "TCP port connection" msgid "TCP port connection"
msgstr "" msgstr ""
msgid "The type of record requested in the DNS query (if the service supports it)."
msgstr ""
msgid "TLS: use STARTTLS if the server supports it." msgid "TLS: use STARTTLS if the server supports it."
msgstr "" msgstr ""
@@ -404,7 +408,7 @@ msgstr ""
msgid "Waiting for a reboot to complete before performing a forced reboot." msgid "Waiting for a reboot to complete before performing a forced reboot."
msgstr "" msgstr ""
msgid "Web UI only" msgid "Web UI only (UI detector)"
msgstr "" msgstr ""
msgid "Web UI only: detector works only when the Web UI is open (UI detector)." msgid "Web UI only: detector works only when the Web UI is open (UI detector)."

View File

@@ -4,8 +4,8 @@
"read": { "read": {
"file": { "file": {
"/sys/class/leds": [ "list" ], "/sys/class/leds": [ "list" ],
"/etc/internet-detector/up-script": [ "read" ], "/etc/internet-detector/up-script*": [ "read" ],
"/etc/internet-detector/down-script": [ "read" ], "/etc/internet-detector/down-script*": [ "read" ],
"/usr/bin/internet-detector*": [ "exec" ], "/usr/bin/internet-detector*": [ "exec" ],
"/usr/bin/mailsend": [ "exec" ] "/usr/bin/mailsend": [ "exec" ]
}, },
@@ -16,8 +16,8 @@
}, },
"write": { "write": {
"file": { "file": {
"/etc/internet-detector/up-script": [ "write" ], "/etc/internet-detector/up-script*": [ "write" ],
"/etc/internet-detector/down-script": [ "write" ] "/etc/internet-detector/down-script*": [ "write" ]
}, },
"uci": [ "internet-detector" ] "uci": [ "internet-detector" ]
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 115 KiB

BIN
screenshots/03.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

BIN
screenshots/04.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB