ntopng/scripts/lua/modules/system_config/system_config.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