mirror of
https://github.com/ntop/ntopng.git
synced 2026-04-30 07:59:35 +00:00
1332 lines
38 KiB
Lua
1332 lines
38 KiB
Lua
--
|
|
-- (C) 2020 - ntop.org
|
|
--
|
|
|
|
local dirs = ntop.getDirs()
|
|
|
|
local json = require("dkjson")
|
|
local os_utils = require "os_utils"
|
|
local sys_utils = require "sys_utils"
|
|
local ipv4_utils = require "ipv4_utils"
|
|
local tz_utils = require "tz_utils"
|
|
|
|
-- ##############################################
|
|
|
|
local system_config = {}
|
|
|
|
-- ##############################################
|
|
|
|
-- Configuration file
|
|
local CONF_FILE = os_utils.fixPath(dirs.workingdir.."/system.config")
|
|
local CONF_FILE_EDITED = CONF_FILE..".edited"
|
|
local CONF_FILE_RELOAD = CONF_FILE..".reload" -- to force first start
|
|
local STOCK_CONF_FILE = "/etc/ntopng/system.config"
|
|
|
|
-- ##############################################
|
|
|
|
system_config.readonly = true
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:create(args)
|
|
local this = args or {key = "base"}
|
|
|
|
setmetatable(this, self)
|
|
self.__index = self
|
|
|
|
return this
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- @brief Writes temporary configuration
|
|
-- @param fname File name
|
|
-- @param config Configuration
|
|
-- @return true on success, false otherwise
|
|
local function dumpConfig(fname, config)
|
|
local f = io.open(fname, "w")
|
|
if f and config then
|
|
f:write(json.encode(config, {indent = true}))
|
|
f:close()
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config.configFile()
|
|
return CONF_FILE
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config.configChanged()
|
|
return ntop.exists(CONF_FILE_EDITED)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Migrate an old configuration format with a single lan item
|
|
-- for routing mode to a generic array compatible with bridge mode
|
|
function system_config:_migrate_config(config)
|
|
for mode_name, mode_conf in pairs(config.globals.available_modes) do
|
|
if mode_conf["interfaces"] then
|
|
if mode_conf["interfaces"]["lan"] and
|
|
type(mode_conf["interfaces"]["lan"]) == "string" then
|
|
config.globals.available_modes[mode_name]["interfaces"]["lan"] = {
|
|
config.globals.available_modes[mode_name]["interfaces"]["lan"]
|
|
}
|
|
end
|
|
if mode_conf["interfaces"]["wan"] and
|
|
type(mode_conf["interfaces"]["wan"]) == "string" then
|
|
config.globals.available_modes[mode_name]["interfaces"]["wan"] = {
|
|
config.globals.available_modes[mode_name]["interfaces"]["wan"]
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
return config
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Loads temporary configuration
|
|
function system_config:_load_config(fname, guess)
|
|
local dirs = ntop.getDirs()
|
|
local config
|
|
|
|
local f = io.open(fname, "r")
|
|
|
|
if f ~= nil then
|
|
config = json.decode((f:read "*a"))
|
|
|
|
if not config then
|
|
traceError(TRACE_ERROR,
|
|
TRACE_CONSOLE,
|
|
"Error while parsing configuration file " .. fname)
|
|
end
|
|
f:close()
|
|
elseif guess then
|
|
-- Load stock configuration if it exists
|
|
if ntop.exists(STOCK_CONF_FILE) then
|
|
traceError(TRACE_WARNING, TRACE_CONSOLE, "Installing stock configuration file " .. STOCK_CONF_FILE)
|
|
sys_utils.execShellCmd("mkdir -p " .. dirs.workingdir)
|
|
sys_utils.execShellCmd("cp " .. STOCK_CONF_FILE .. " " .. CONF_FILE)
|
|
|
|
return self:_load_config(fname, false)
|
|
else
|
|
config = self:_guess_config()
|
|
|
|
if dumpConfig(CONF_FILE, config) then
|
|
traceError(TRACE_NORMAL, TRACE_CONSOLE,
|
|
"Cannot open system configuration file " .. fname .. ", populating with defaults")
|
|
end
|
|
end
|
|
end
|
|
|
|
sys_utils.setRealExec(config.globals and not config.globals.fake_exec)
|
|
|
|
config = self:_migrate_config(config)
|
|
|
|
return config
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:readable()
|
|
self.readonly = true
|
|
|
|
self.config = self:_load_config(CONF_FILE, true)
|
|
self.conf_handler = self:getConfHandler()
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:editable()
|
|
self.readonly = false
|
|
|
|
if ntop.exists(CONF_FILE_EDITED) then
|
|
self.config = self:_load_config(CONF_FILE_EDITED)
|
|
else
|
|
self.config = self:_load_config(CONF_FILE, true)
|
|
end
|
|
|
|
self.conf_handler = self:getConfHandler()
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:getConfHandler()
|
|
package.path = dirs.installdir .. "/scripts/lua/modules/conf_handlers/?.lua;" .. package.path
|
|
|
|
local info = ntop.getInfo(false)
|
|
local os = info.OS
|
|
local config_target = "network_ifaces"
|
|
|
|
if string.find(os, "Ubuntu 18%.") ~= nil or
|
|
string.find(os, "Ubuntu 20%.") ~= nil then
|
|
config_target = "netplan"
|
|
end
|
|
|
|
return require(config_target)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Discards the temporary config and loads the persistent one
|
|
function system_config:discard()
|
|
if not self.readonly and isAdministrator() then
|
|
os.remove(CONF_FILE_EDITED)
|
|
self.config = self:_load_config(CONF_FILE, true)
|
|
else
|
|
traceError(TRACE_ERROR, TRACE_CONSOLE, "Unable to discard a readonly configuration.")
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:_apply_operating_mode_settings(config)
|
|
if config.globals.operating_mode == "bridging" then
|
|
-- We currently force DHCP off on bridge mode
|
|
if config.dhcp_server then
|
|
config.dhcp_server.enabled = false
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Set the current interface mode. mode is one of:
|
|
-- routing
|
|
-- bridging
|
|
-- single_port_router
|
|
--
|
|
function system_config:setOperatingMode(mode)
|
|
if not self.config.globals.available_modes[mode] then
|
|
return false
|
|
end
|
|
|
|
self.config.globals.operating_mode = mode
|
|
self:_apply_operating_mode_settings(self.config)
|
|
|
|
-- Set DHCP on nedge
|
|
if self.config.dhcp_server then
|
|
self:setDhcpFromLan()
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Returns the available modes
|
|
function system_config:getAvailableModes()
|
|
return self.config.globals.available_modes
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Returns the current operating mode
|
|
-- nf_config and appliance_config override this
|
|
function system_config:getOperatingMode()
|
|
return self.config.globals.operating_mode
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- NOTE: do not use this, use the getOperatingMode instead
|
|
function system_config:_getConfiguredOperatingMode()
|
|
return self.config.globals.operating_mode
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:getManagementAccessConfig()
|
|
local default_config = {bind_to = "lan"}
|
|
|
|
if self:isBridgeOverVLANTrunkEnabled() then
|
|
-- on VLAN trunk mode, there is no LAN interface, so we bind to "any"
|
|
return {bind_to = "any"}
|
|
end
|
|
|
|
if not self.config.globals.management_access then
|
|
return default_config
|
|
else
|
|
return self.config.globals.management_access
|
|
end
|
|
end
|
|
|
|
function system_config:setManagementAccessConfig(management_access)
|
|
self.config.globals.management_access = management_access
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config.isFirstStart()
|
|
return ntop.exists(CONF_FILE_RELOAD) or ntop.getPref("ntopng.prefs.nedge_start") ~= "1"
|
|
end
|
|
|
|
function system_config.setFirstStart(first_start)
|
|
ntop.setPref("ntopng.prefs.nedge_start", ternary(first_start, "0", "1"))
|
|
if not first_start and ntop.exists(CONF_FILE_RELOAD) then
|
|
os.remove(CONF_FILE_RELOAD)
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Gets the date and time configuration
|
|
function system_config:getDateTimeConfig()
|
|
return self.config.date_time
|
|
end
|
|
|
|
-- Setup the date and time configuration
|
|
function system_config:setDateTimeConfig(config)
|
|
self.config.date_time = config
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:getUnusedInterfaces()
|
|
local mode = self:getOperatingMode()
|
|
if mode then
|
|
return self.config.globals.available_modes[mode].interfaces.unused or {}
|
|
else
|
|
return {}
|
|
end
|
|
end
|
|
|
|
-- Get the LAN interface, based on the current operating mode
|
|
-- TODO when multiple LAN interfaces will be fully supported, this function will be removed
|
|
function system_config:getLanInterface()
|
|
local mode = self:getOperatingMode()
|
|
|
|
if mode == "bridging" then
|
|
return self.config.globals.available_modes.bridging.name
|
|
else
|
|
return self.config.globals.available_modes[mode].interfaces.lan[1]
|
|
end
|
|
end
|
|
|
|
-- Get all the interfaces, along with their roles
|
|
function system_config:getAllInterfaces(exclude)
|
|
local ifaces = {}
|
|
|
|
for _, iface in pairs(self:getPhysicalLanInterfaces()) do
|
|
if not exclude then
|
|
ifaces[iface] = "lan"
|
|
elseif exclude == 'vlans' then
|
|
local vlan = iface:match('[^.]+$')
|
|
-- Be sure that this is not a vlan, check if the string
|
|
-- is not a number or it is greater then 4096 - max vlan number
|
|
if not vlan or not tonumber(vlan) or
|
|
tonumber(vlan) > 4096 or
|
|
tonumber(vlan) < 0 then
|
|
ifaces[iface] = "lan"
|
|
end
|
|
elseif exclude == 'physicals' then
|
|
local vlan = iface:match('[^.]+$')
|
|
-- Be sure that this is not a phisical interface, check if the string
|
|
-- is a number and it is lesser then 4096 - max vlan number
|
|
if vlan and tonumber(vlan) and
|
|
tonumber(vlan) > 0 and
|
|
tonumber(vlan) < 4096 then
|
|
ifaces[iface] = "lan"
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, iface in pairs(self:getPhysicalWanInterfaces()) do
|
|
if not exclude then
|
|
ifaces[iface] = "wan"
|
|
elseif exclude == 'vlans' then
|
|
local vlan = iface:match('[^.]+$')
|
|
-- Be sure that this is not a vlan, check if the string
|
|
-- is not a number or it is greater then 4096 - max vlan number
|
|
if not vlan or not tonumber(vlan) or
|
|
tonumber(vlan) > 4096 or
|
|
tonumber(vlan) < 0 then
|
|
ifaces[iface] = "wan"
|
|
end
|
|
elseif exclude == 'physicals' then
|
|
local vlan = iface:match('[^.]+$')
|
|
-- Be sure that this is not a phisical interface, check if the string
|
|
-- is a number and it is lesser then 4096 - max vlan number
|
|
if vlan and tonumber(vlan) and
|
|
tonumber(vlan) > 0 and
|
|
tonumber(vlan) < 4096 then
|
|
ifaces[iface] = "wan"
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, iface in pairs(self:getUnusedInterfaces()) do
|
|
if not exclude then
|
|
ifaces[iface] = "unused"
|
|
elseif exclude == 'vlans' then
|
|
local vlan = iface:match('[^.]+$')
|
|
-- Be sure that this is not a vlan, check if the string
|
|
-- is not a number or it is greater then 4096 - max vlan number
|
|
if not vlan or not tonumber(vlan) or
|
|
tonumber(vlan) > 4096 or
|
|
tonumber(vlan) < 0 then
|
|
ifaces[iface] = "unused"
|
|
end
|
|
elseif exclude == 'physicals' then
|
|
local vlan = iface:match('[^.]+$')
|
|
-- Be sure that this is not a phisical interface, check if the string
|
|
-- is a number and it is lesser then 4096 - max vlan number
|
|
if vlan and tonumber(vlan) and
|
|
tonumber(vlan) > 0 and
|
|
tonumber(vlan) < 4096 then
|
|
ifaces[iface] = "unused"
|
|
end
|
|
end
|
|
end
|
|
|
|
return ifaces
|
|
end
|
|
|
|
|
|
function system_config:getBridgeInterfaceName()
|
|
if self.config.globals.available_modes["bridging"] then
|
|
return self.config.globals.available_modes["bridging"].name
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-- Set all the interfaces roles
|
|
function system_config:setLanWanIfaces(lan_ifaces, wan_ifaces)
|
|
local mode = self:getOperatingMode()
|
|
local unused = self:getAllInterfaces()
|
|
|
|
for _, iface in pairs(lan_ifaces) do
|
|
unused[iface] = nil
|
|
end
|
|
|
|
for _, iface in pairs(wan_ifaces) do
|
|
unused[iface] = nil
|
|
end
|
|
|
|
local unused_ifaces = {}
|
|
for iface, _ in pairs(unused) do
|
|
unused_ifaces[#unused_ifaces + 1] = iface
|
|
end
|
|
|
|
self.config.globals.available_modes[mode].interfaces.lan = lan_ifaces
|
|
self.config.globals.available_modes[mode].interfaces.wan = wan_ifaces
|
|
self.config.globals.available_modes[mode].interfaces.unused = unused_ifaces
|
|
end
|
|
|
|
local function isInterfaceUp(ifname)
|
|
local res = sys_utils.execShellCmd("ip link show dev ".. ifname .." | grep ' state UP '")
|
|
return not isEmptyString(res)
|
|
end
|
|
|
|
-- returns all the IP addresses associated to one interface
|
|
function system_config.getAllInterfaceAddresses(ifname)
|
|
local res = sys_utils.execShellCmd("ip addr show ".. ifname .." | grep -Po 'inet \\K[\\d.]+/[\\d]+'")
|
|
local rv = {}
|
|
|
|
if not isEmptyString(res) then
|
|
local lines = string.split(res, "\n")
|
|
|
|
for _, line in ipairs(lines) do
|
|
local ip, netmask = ipv4_utils.cidr_2_addr(line)
|
|
|
|
if (ip ~= nil) and (netmask ~= nil) then
|
|
rv[#rv + 1] = {ip=ip, netmask=netmask}
|
|
end
|
|
end
|
|
end
|
|
|
|
return rv
|
|
end
|
|
|
|
-- returns a single IP address of an interface.
|
|
-- Since an interface can have multiple IPs, this returns the first
|
|
-- available excluding the recovery IP
|
|
function system_config:getInterfaceAddress(ifname)
|
|
local recovery_conf = self:getLanRecoveryIpConfig()
|
|
local addresses = system_config.getAllInterfaceAddresses(ifname)
|
|
|
|
for _, addr in ipairs(addresses) do
|
|
if addr.ip ~= recovery_conf.ip then
|
|
return addr.ip, addr.netmask
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-- Get the LAN address, based on the current operating mode
|
|
function system_config:getLocalIpv4Address(iface)
|
|
if not iface then
|
|
iface = self:getLanInterface()
|
|
end
|
|
local address = self:getInterfaceAddress(iface)
|
|
|
|
if isEmptyString(address) then
|
|
if not self:isBridgeOverVLANTrunkEnabled() then
|
|
traceError(TRACE_WARNING, TRACE_CONSOLE, "Cannot get interface " .. iface .. " address")
|
|
end
|
|
-- This is possibly wrong (e.g. in transparent bridge)
|
|
address = self.config.interfaces.configuration[iface].network.ip or "192.168.1.1"
|
|
end
|
|
|
|
return address
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Get the physical LAN interfaces, based on the current operating mode
|
|
-- nf_config and appliance_config override this
|
|
function system_config:getPhysicalLanInterfaces()
|
|
local interfaces = {}
|
|
return interfaces
|
|
end
|
|
|
|
-- Get the physical WAN interfaces, based on the current operating mode
|
|
-- nf_config and appliance_config override this
|
|
function system_config:getPhysicalWanInterfaces()
|
|
local interfaces = {}
|
|
return interfaces
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
local function configDiffers(a_conf, b_conf)
|
|
return not table.compare(a_conf, b_conf)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:_get_changed_sections(new_config, force_all_changes)
|
|
local orig_config = self:_load_config(CONF_FILE, false)
|
|
local changed = {}
|
|
|
|
-- Check for new / changed sections
|
|
for section in pairs(new_config) do
|
|
if force_all_changes or (orig_config[section] == nil) or configDiffers(new_config[section], orig_config[section]) then
|
|
changed[section] = 1
|
|
end
|
|
end
|
|
|
|
-- Check for removed sections
|
|
for section in pairs(orig_config) do
|
|
if new_config[section] == nil then
|
|
changed[section] = 1
|
|
end
|
|
end
|
|
|
|
return changed
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
local function isRebootRequired(changed_sections)
|
|
-- Always reboot on first start
|
|
if system_config.isFirstStart() then
|
|
return true
|
|
end
|
|
|
|
local non_reboot_sections = {
|
|
dhcp_server = 1,
|
|
date_time = 1,
|
|
gateways = 1,
|
|
static_routes = 1,
|
|
routing = 1,
|
|
disabled_wans = 1,
|
|
shapers = 1,
|
|
port_forwarding = 1,
|
|
}
|
|
|
|
for section in pairs(changed_sections) do
|
|
if non_reboot_sections[section] == nil then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:needsReboot()
|
|
return isRebootRequired(self:_get_changed_sections(self.config))
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
local function isSelfRestartRequired(changed_sections)
|
|
local self_restart_sections = {
|
|
static_routes = 1,
|
|
}
|
|
|
|
for section in pairs(changed_sections) do
|
|
if self_restart_sections[section] then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:needsSelfRestart()
|
|
return isSelfRestartRequired(self:_get_changed_sections(self.config))
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Save the configuration changes as a temporary config
|
|
function system_config:save()
|
|
if not self.readonly and isAdministrator() then
|
|
local orig_config = self:_load_config(CONF_FILE, false)
|
|
|
|
if configDiffers(self.config, orig_config) then
|
|
dumpConfig(CONF_FILE_EDITED, self.config)
|
|
else
|
|
os.remove(CONF_FILE_EDITED)
|
|
end
|
|
else
|
|
traceError(TRACE_ERROR, TRACE_CONSOLE, "Unable to save a readonly configuration.")
|
|
end
|
|
end
|
|
|
|
-- Save the current temporary config as persistent
|
|
function system_config:makePermanent(force_write)
|
|
if (not self.readonly or force_write) and isAdministrator() then
|
|
dumpConfig(CONF_FILE, self.config)
|
|
os.remove(CONF_FILE_EDITED)
|
|
else
|
|
traceError(TRACE_ERROR, TRACE_CONSOLE, "Unable to make a readonly configuration permanent.")
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- This functions handles configuration changes which do not need a reboot
|
|
function system_config:_handleChangedCommonSections(changed_sections, is_rebooting)
|
|
if changed_sections["date_time"] then
|
|
-- drift accounts for the time between the user clicked 'save' and when it actually clicked 'apply'
|
|
-- only when it is requested to set a custom date
|
|
-- drift calculation must go before timezone/ntp changes as they will change the time making it invalid
|
|
local drift
|
|
if self.config.date_time.custom_date_set_req then
|
|
drift = os.time(os.date("!*t", os.time())) - (self.config.date_time.custom_date_set_req or 0)
|
|
self.config.date_time.custom_date_set_req = nil
|
|
end
|
|
|
|
if self.config.date_time.timezone then
|
|
local system_timezone = tz_utils.TimeZone()
|
|
if self.config.date_time.timezone ~= system_timezone then
|
|
sys_utils.execCmd("timedatectl set-timezone "..self.config.date_time.timezone)
|
|
ntop.tzset()
|
|
end
|
|
end
|
|
if self.config.date_time.ntp_sync.enabled then
|
|
sys_utils.execCmd("timedatectl set-ntp yes")
|
|
else
|
|
sys_utils.execCmd("timedatectl set-ntp no")
|
|
|
|
if self.config.date_time.custom_date then
|
|
-- do not specify any timezone here as it is safe to take the one set for the system
|
|
local custom_epoch = makeTimeStamp(self.config.date_time.custom_date)
|
|
if drift then
|
|
custom_epoch = custom_epoch + drift
|
|
end
|
|
-- use a format that timedatectl likes
|
|
local timedatectl_fmt = os.date("%y-%m-%d %X", tonumber(custom_epoch))
|
|
if timedatectl_fmt then
|
|
sys_utils.execCmd('timedatectl set-time "'..timedatectl_fmt..'"')
|
|
end
|
|
end
|
|
self.config.date_time.custom_date = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function system_config:_handleChangedSections(changed_sections, is_rebooting)
|
|
self:_handleChangedCommonSections(changed_sections, is_rebooting)
|
|
end
|
|
|
|
function system_config:applyChanges()
|
|
local changed_sections = self:_get_changed_sections(self.config, system_config.isFirstStart())
|
|
local is_rebooting = isRebootRequired(changed_sections)
|
|
local is_self_restarting = isSelfRestartRequired(changed_sections)
|
|
|
|
self:_handleChangedSections(changed_sections, is_rebooting)
|
|
|
|
self:makePermanent()
|
|
|
|
if is_rebooting then
|
|
self:writeSystemFiles()
|
|
sys_utils.rebootSystem()
|
|
elseif is_self_restarting then
|
|
sys_utils.restartSelf()
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:_writeNetworkInterfaceConfig(f, iface, network_conf, bridge_ifaces)
|
|
local dns_config = self:getDnsConfig()
|
|
self.conf_handler.writeNetworkInterfaceConfig(f, iface, network_conf, dns_config, bridge_ifaces)
|
|
end
|
|
|
|
function system_config:_writeBridgeModeNetworkConfig(f)
|
|
local network_config = self.config.interfaces.configuration
|
|
local mode_config = self.config.globals.available_modes["bridging"]
|
|
|
|
if ntop.isIoTBridge() then
|
|
local br_name = mode_config.name
|
|
local br_config = network_config[br_name]
|
|
|
|
package.path = dirs.installdir .. "/scripts/lua/modules/conf_handlers/?.lua;" .. package.path
|
|
local wireless = require("wireless")
|
|
-- Bridge mode on IoT bridge, setting up Wireless
|
|
if (self.config.wireless.enabled) then
|
|
-- WiFi Access Point (creates a br0)
|
|
wireless.configureWiFiAccessPoint(f,
|
|
self.config.wireless.ssid,
|
|
self.config.wireless.passphrase,
|
|
br_config.network)
|
|
else
|
|
wireless.disableWiFi()
|
|
end
|
|
|
|
-- Bridge interface
|
|
self:_writeNetworkInterfaceConfig(f, br_name, br_config.network, bridge_ifaces)
|
|
|
|
else
|
|
local bridge_ifaces = {}
|
|
|
|
-- Lan interfaces
|
|
for _, iface in ipairs(mode_config.interfaces.lan) do
|
|
self:_writeNetworkInterfaceConfig(f, iface, { mode="manual" })
|
|
bridge_ifaces[#bridge_ifaces + 1] = iface
|
|
end
|
|
|
|
-- Wan interfaces
|
|
for _, iface in ipairs(mode_config.interfaces.wan) do
|
|
self:_writeNetworkInterfaceConfig(f, iface, { mode="manual" })
|
|
bridge_ifaces[#bridge_ifaces + 1] = iface
|
|
end
|
|
|
|
-- Bridge interface
|
|
local br_name = mode_config.name
|
|
local br_config = network_config[br_name]
|
|
self:_writeNetworkInterfaceConfig(f, br_name, br_config.network, bridge_ifaces)
|
|
end
|
|
end
|
|
|
|
function system_config:_writeRoutingModeNetworkConfig(f)
|
|
local network_config = self.config.interfaces.configuration
|
|
local mode_config = self.config.globals.available_modes["routing"].interfaces
|
|
|
|
-- Lan interface
|
|
for _, iface in ipairs(mode_config.lan) do
|
|
self:_writeNetworkInterfaceConfig(f, iface, network_config[iface].network)
|
|
end
|
|
|
|
-- Wan interfaces
|
|
for _, iface in ipairs(mode_config.wan) do
|
|
self:_writeNetworkInterfaceConfig(f, iface, network_config[iface].network)
|
|
end
|
|
end
|
|
|
|
function system_config:_writeSinglePortModeInterfaces(f)
|
|
local network_config = self.config.interfaces.configuration
|
|
local mode_config = self.config.globals.available_modes["single_port_router"]
|
|
|
|
-- Lan interface
|
|
local lan_iface = self:getLanInterface()
|
|
self:_writeNetworkInterfaceConfig(f, lan_iface, network_config[lan_iface].network)
|
|
|
|
-- Wan interface
|
|
local wan_iface = mode_config.interfaces.wan[1]
|
|
self:_writeNetworkInterfaceConfig(f, wan_iface, network_config[wan_iface].network)
|
|
end
|
|
|
|
function system_config:_writePassiveModeNetworkConfig(f)
|
|
local network_config = self.config.interfaces.configuration
|
|
local mode_config = self.config.globals.available_modes["passive"]
|
|
|
|
-- Lan interface
|
|
local lan_iface = mode_config.interfaces.lan[1]
|
|
self:_writeNetworkInterfaceConfig(f, lan_iface, network_config[lan_iface].network)
|
|
end
|
|
|
|
function system_config:_getRecoveryInterface()
|
|
return self:getLanInterface() .. ":2"
|
|
end
|
|
|
|
function system_config:_writeNetworkInterfaces()
|
|
local mode = self:getOperatingMode()
|
|
local f = self.conf_handler.openNetworkInterfacesConfigFile()
|
|
|
|
local recovery_conf = self:getLanRecoveryIpConfig()
|
|
local recovery_iface = self:_getRecoveryInterface()
|
|
local is_configured, fnames = self.conf_handler.isConfiguredInterface("lo")
|
|
|
|
if not is_configured then
|
|
self:_writeNetworkInterfaceConfig(f, "lo", {mode="loopback"})
|
|
end
|
|
|
|
-- Configure interfaces depending on working mode
|
|
if mode == "passive" then
|
|
self:_writePassiveModeNetworkConfig(f)
|
|
elseif mode == "bridging" then
|
|
self:_writeBridgeModeNetworkConfig(f)
|
|
elseif mode == "routing" then
|
|
self:_writeRoutingModeNetworkConfig(f)
|
|
elseif mode == "single_port_router" then
|
|
self:_writeSinglePortModeInterfaces(f)
|
|
end
|
|
|
|
-- Configure backup interface
|
|
self:_writeNetworkInterfaceConfig(f, recovery_iface, { mode="static", ip=recovery_conf.ip, netmask=recovery_conf.netmask })
|
|
|
|
self.conf_handler.closeNetworkInterfacesConfigFile(f)
|
|
end
|
|
|
|
function system_config:writeSystemFiles()
|
|
if system_config.isFirstStart() then
|
|
self:verifyNetworkInterfaces()
|
|
end
|
|
|
|
self:_writeNetworkInterfaces()
|
|
system_config.setFirstStart(false)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:isBridgeOverVLANTrunkEnabled()
|
|
local mode = self:getOperatingMode()
|
|
|
|
if mode == "bridging" then
|
|
local bridge = self.config.globals.available_modes.bridging.name
|
|
if self:getInterfaceMode(bridge) == "vlan_trunk" then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config.gatewayGetInterface(gateway)
|
|
-- Useful to find the interface which would route traffic to some address
|
|
local res = sys_utils.execShellCmd("ip route get " .. gateway)
|
|
|
|
if not isEmptyString(res) then
|
|
return res:gmatch(" dev ([^ ]*)")()
|
|
end
|
|
end
|
|
|
|
-- TODO use more reliable information
|
|
function system_config.ifaceGetNetwork(iface)
|
|
local res = sys_utils.execShellCmd("ip route show | grep \"scope link\" | grep \"proto kernel\" | grep \"" .. iface .. "\"")
|
|
|
|
if not isEmptyString(res) then
|
|
return split(res, " ")[1]
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:getLanNetworks()
|
|
local networks = {}
|
|
|
|
for _, lan_iface in ipairs(self:getPhysicalLanInterfaces()) do
|
|
local lan_config = self.config.interfaces.configuration[lan_iface].network
|
|
local lan_network = ipv4_utils.addressToNetwork(lan_config.ip, lan_config.netmask)
|
|
|
|
table.insert(networks, {
|
|
iface = lan_iface,
|
|
network = lan_network,
|
|
cidr = lan_network,
|
|
ip = lan_config.ip,
|
|
netmask = lan_config.netmask,
|
|
})
|
|
end
|
|
|
|
return networks
|
|
end
|
|
|
|
-- Returns true if the interface is currently up and running
|
|
local function isInterfaceLinkUp(ifname)
|
|
local opstatus = sys_utils.execShellCmd("cat /sys/class/net/" .. ifname .. "/operstate 2>/dev/null")
|
|
return starts(opstatus, "up")
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:getDisabledWans(check_wans_with_linkdown)
|
|
if check_wans_with_linkdown == nil then check_wans_with_linkdown = false end
|
|
|
|
-- table.clone needed to modify some parameters while keeping the original unchanged
|
|
local disabled = table.clone(self.config.disabled_wans)
|
|
|
|
if check_wans_with_linkdown then
|
|
local roles = self:getAllInterfaces()
|
|
|
|
for iface, role in pairs(roles) do
|
|
if role == "wan" then
|
|
if (disabled[iface] ~= true) and (not isInterfaceLinkUp(iface)) then
|
|
disabled[iface] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return disabled
|
|
end
|
|
|
|
function system_config:setDisabledWans(disabled_wans)
|
|
self.config.disabled_wans = disabled_wans
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:_checkDisabledInterfaces()
|
|
local ifaces = self:getAllInterfaces()
|
|
local disabled_wans = self.config.disabled_wans
|
|
|
|
for iface, role in pairs(ifaces) do
|
|
if role == "wan" then
|
|
local is_disabled = (disabled_wans[iface] == true)
|
|
local currently_disabled = not isInterfaceUp(iface)
|
|
|
|
if is_disabled ~= currently_disabled then
|
|
if is_disabled then
|
|
traceError(TRACE_NORMAL, TRACE_CONSOLE, "Disable interface " .. iface)
|
|
sys_utils.execCmd("ip link set dev " .. iface .. " down")
|
|
else
|
|
traceError(TRACE_NORMAL, TRACE_CONSOLE, "Enable interface " .. iface)
|
|
sys_utils.execCmd("ip link set dev " .. iface .. " up")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:getInterfacesConfiguration()
|
|
return self.config.interfaces.configuration or {}
|
|
end
|
|
|
|
function system_config:setInterfacesConfiguration(config)
|
|
self.config.interfaces.configuration = config
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:setInterfaceConfiguration(interface, config)
|
|
if self.config.interfaces.configuration and
|
|
not self.config.interfaces.configuration[interface] then
|
|
self.config.interfaces.configuration[interface] = config
|
|
end
|
|
end
|
|
|
|
function system_config:removeInterfaceConfiguration(interface)
|
|
if self.config.interfaces.configuration[interface] then
|
|
self.config.interfaces.configuration[interface] = null
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:setInterfaceMode(iface, mode)
|
|
local net_config = self.config.interfaces.configuration[iface]
|
|
|
|
if net_config ~= nil then
|
|
net_config.network.mode = mode
|
|
|
|
if mode == "static" and (isEmptyString(net_config.network.ip) or isEmptyString(net_config.network.netmask)) then
|
|
net_config.network.ip = "192.168.1.1"
|
|
net_config.network.netmask = "255.255.255.0"
|
|
end
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function system_config:getInterfaceMode(iface)
|
|
local net_config = self.config.interfaces.configuration[iface]
|
|
|
|
if net_config ~= nil and net_config.network then
|
|
return net_config.network.mode
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:_enableDisableDhcpService()
|
|
local dhcp_service_utils = require "dhcp_service_utils"
|
|
if self:isDhcpServerEnabled() then
|
|
dhcp_service_utils.startDHCPService()
|
|
else
|
|
dhcp_service_utils.stopDHCPService()
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config.findDnsPreset(preset_name)
|
|
require("prefs_utils")
|
|
|
|
for _, preset in pairs(DNS_PRESETS) do
|
|
if preset.id == preset_name then
|
|
return preset
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
function system_config:_get_default_global_dns_preset()
|
|
return system_config.findDnsPreset("google")
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:getDnsConfig()
|
|
return self.config.globals.dns
|
|
end
|
|
|
|
function system_config:setDnsConfig(config)
|
|
self.config.globals.dns = config
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:isGlobalDnsForgingEnabled()
|
|
return self.config.globals.dns.forge_global
|
|
end
|
|
|
|
function system_config:setGlobalDnsForgingEnabled(enabled)
|
|
self.config.globals.dns.forge_global = ternary(enabled, true, false)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:getLanRecoveryIpConfig()
|
|
return self.config.globals.lan_recovery_ip
|
|
end
|
|
|
|
function system_config:setLanRecoveryIpConfig(config)
|
|
self.config.globals.lan_recovery_ip = config
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- NOTE: can't rely on the main routing table when having multiple gateways!
|
|
function system_config._interface_get_default_gateway(iface)
|
|
local res = sys_utils.execShellCmd("ip route show | grep \"^default via\" | grep \"" .. iface .. "\"")
|
|
|
|
if not isEmptyString(res) then
|
|
return split(res, " ")[3]
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
local allowed_interfaces
|
|
|
|
-- @brief Use the logic in ntopng to list available interfaces
|
|
-- and avoid interface name with characters that could
|
|
-- lead to injections
|
|
local function allowedDevName(devname)
|
|
if isEmptyString(devname) then
|
|
return false
|
|
end
|
|
|
|
-- Do some caching
|
|
if not allowed_interfaces then
|
|
allowed_interfaces = ntop.listInterfaces()
|
|
end
|
|
|
|
-- Stripping out vlan id, if any (support vlan interfaces)
|
|
local devnamevlan = string.split(devname, "%.")
|
|
if devnamevlan and devnamevlan[1] then
|
|
devname = devnamevlan[1]
|
|
-- vlan = devnamevlan[2]
|
|
end
|
|
|
|
-- Interface is allowed if it appears in the list retrieved from C
|
|
return allowed_interfaces[devname]
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config._split_dev_names(c, delimiter)
|
|
local ret = {}
|
|
local cmd = sys_utils.execShellCmd(c)
|
|
|
|
if((cmd ~= "") and (cmd ~= nil)) then
|
|
|
|
local devs = split(cmd, "\n")
|
|
|
|
if(delimiter == nil) then
|
|
local rv = {}
|
|
for idx, dev in pairs(devs) do
|
|
if not isEmptyString(dev) and allowedDevName(dev) then
|
|
rv[#rv + 1] = dev
|
|
end
|
|
end
|
|
|
|
return rv
|
|
end
|
|
|
|
for _,a in pairs(devs) do
|
|
local p = split(a, delimiter)
|
|
|
|
if(p ~= nil) then
|
|
local name = p[1]
|
|
local addr = p[2]
|
|
|
|
if(addr and name) then
|
|
name = trimSpace(name)
|
|
addr = trimSpace(addr)
|
|
|
|
if allowedDevName(name) then
|
|
ret[name] = addr:gsub("%s+", "")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return ret
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Check TUN/dummy/lo interface
|
|
function system_config.is_virtual_interface(name)
|
|
return (name == "lo" or
|
|
starts(name, "dummy") or
|
|
ntop.exists('/sys/class/net/'.. name ..'/tun_flags') or
|
|
string.contains(name, ":"))
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Find all interfaces
|
|
function system_config.get_all_interfaces()
|
|
local devs = system_config._split_dev_names('cat /proc/net/dev', ":")
|
|
return devs
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Find all bridge interfaces
|
|
function system_config.get_bridge_interfaces()
|
|
local devs = system_config.get_all_interfaces()
|
|
local bridge_devs = {}
|
|
|
|
for name, _ in pairsByKeys(devs) do
|
|
if not system_config.is_virtual_interface(name) then
|
|
if(ntop.exists('/sys/class/net/'.. name ..'/bridge')) then
|
|
local devs = system_config._split_dev_names('ls /sys/class/net/'.. name ..'/brif/', nil)
|
|
bridge_devs[name] = { ["ports"] = devs }
|
|
end
|
|
end
|
|
end
|
|
|
|
return bridge_devs
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Find WiFi interfaces
|
|
function system_config.get_wifi_interfaces()
|
|
local wifi_devs = system_config._split_dev_names('cat /proc/net/wireless', ":")
|
|
|
|
-- Necessary for inactive wifi devices
|
|
local devs = system_config.get_all_interfaces()
|
|
for dev in pairs(devs) do
|
|
local rv = sys_utils.execShellCmd("iwconfig 2>/dev/null | grep \"" .. dev .. "\" | grep \"IEEE 802.11\"")
|
|
if not isEmptyString(rv) then
|
|
wifi_devs[dev] = {}
|
|
end
|
|
end
|
|
|
|
return wifi_devs
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Find interfaces with IP configured
|
|
function system_config.get_ip_interfaces()
|
|
local ip_devs = system_config._split_dev_names('ip addr | awk \'$1 == "inet" && $7 { split( $2, addr, "/" ); print $7, addr[1] }\'', " ")
|
|
return ip_devs
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Find DHCP interface
|
|
function system_config.get_dhcp_interfaces()
|
|
local dhcp_ifaces = {}
|
|
|
|
local res = sys_utils.execShellCmd('ps aux | grep dhclient')
|
|
if not isEmptyString(res) then
|
|
local devs = system_config.get_all_interfaces()
|
|
for _, line in pairs(split(res, "\n")) do
|
|
local name = line:gmatch(".([^.]+).leases")()
|
|
|
|
if devs[name] ~= nil then
|
|
dhcp_ifaces[name] = {}
|
|
end
|
|
end
|
|
end
|
|
|
|
return dhcp_ifaces
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Find the wan interface reading the default gw
|
|
function system_config.get_default_gw_interface()
|
|
local dev = sys_utils.execShellCmd("ip route show | grep \"default via\" | awk '{printf \"%s\", $5}'")
|
|
if not isEmptyString(dev) then
|
|
return dev
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- nf_config and appliance_config override this
|
|
function system_config:_guess_config()
|
|
local config = {}
|
|
return config
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Verify that we are the only to manage the network interfaces
|
|
function system_config:verifyNetworkInterfaces()
|
|
local lan_ifaces = self:getPhysicalLanInterfaces()
|
|
local wan_ifaces = self:getPhysicalWanInterfaces()
|
|
local lan_iface = self:getLanInterface()
|
|
local ifaces = {[lan_iface] = 1}
|
|
|
|
for _, iface in pairs(lan_ifaces) do
|
|
ifaces[iface] = 1
|
|
end
|
|
for _, iface in pairs(wan_ifaces) do
|
|
ifaces[iface] = 1
|
|
end
|
|
|
|
local has_error = false
|
|
local to_backup = {}
|
|
|
|
for iface in pairs(ifaces) do
|
|
local is_configured, conf_files = self.conf_handler.isConfiguredInterface(iface)
|
|
|
|
if is_configured then
|
|
traceError(TRACE_WARNING, TRACE_CONSOLE, "Network interface " .. iface .. " must be managed by ntopng")
|
|
has_error = true
|
|
|
|
if conf_files ~= nil then
|
|
to_backup = table.merge(to_backup, conf_files)
|
|
end
|
|
end
|
|
end
|
|
|
|
if has_error then
|
|
self.conf_handler.backupNetworkInterfacesFiles(to_backup)
|
|
return true
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function system_config:addDefaultInterfaceConfig(interface_name)
|
|
self:setInterfaceConfiguration(interface_name, {
|
|
family = "wired",
|
|
masquerade = true,
|
|
speed = {
|
|
download = 10000,
|
|
upload = 10000,
|
|
},
|
|
network = {
|
|
mode = "static"
|
|
}
|
|
})
|
|
|
|
self:setInterfaceMode(interface_name, 'static')
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Add to the configuration file a new interface, VLAN supported.
|
|
-- When added, it's going to be added to the unused interfaces of the modes
|
|
function system_config:addInterface(interface_name)
|
|
-- First set the new interface configuration
|
|
self:addDefaultInterfaceConfig(interface_name)
|
|
|
|
-- Then add the interface to the unused ones
|
|
for _, mode_conf in pairs(self.config.globals.available_modes) do
|
|
if mode_conf["interfaces"] then
|
|
if not mode_conf["interfaces"]["unused"] then
|
|
mode_conf["interfaces"]["unused"] = {}
|
|
end
|
|
|
|
local unused_interfaces = mode_conf["interfaces"]["unused"]
|
|
local add_interface = true
|
|
|
|
for _, iface in pairs(unused_interfaces) do
|
|
if iface == interface_name then
|
|
add_interface = false
|
|
break
|
|
end
|
|
end
|
|
|
|
if add_interface == true then
|
|
-- Add to all modes, routing and bridging the new interface and set it to unused
|
|
mode_conf["interfaces"]["unused"][#unused_interfaces + 1] = interface_name
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Remove from the configuration file an unused interface, VLAN supported.
|
|
function system_config:removeInterface(interface_name)
|
|
-- First remove the interface configuration
|
|
self:removeInterfaceConfiguration(interface_name)
|
|
|
|
-- Then remove the interface from the unused ones
|
|
for mode_name, mode_conf in pairs(self.config.globals.available_modes) do
|
|
if mode_conf["interfaces"] then
|
|
local unused_interfaces = mode_conf["interfaces"]["unused"]
|
|
if unused_interfaces and table.len(unused_interfaces) > 0 then
|
|
for index, iface in pairs(unused_interfaces) do
|
|
if iface == interface_name then
|
|
table.remove(mode_conf["interfaces"]["unused"], index)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
return system_config
|