ntopng/scripts/lua/modules/system_config/system_config.lua
2020-11-26 15:21:05 +01:00

1311 lines
37 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"
local DATA_RESET_KEY = "ntopng.prefs.data_reset"
-- At this id start the fwmark ids for gateways ping
system_config.BASE_GATEWAY_PING_FWMARK_ID = 3000
-- At this id start the routing tables allocated for gateway pings
system_config.BASE_GATEWAY_PING_ROUTING_ID = 200
-- ##############################################
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.configChanged()
return ntop.exists(CONF_FILE_EDITED)
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)
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
config.dhcp_server.enabled = false
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)
self:setDhcpFromLan()
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
-- ##############################################
-- Returns true if the DHCP server is enabled
function system_config:isDhcpServerEnabled()
return self.config.dhcp_server.enabled
end
-- Gets the current DHCP server configuration
function system_config:getDhcpServerConfig()
return self.config.dhcp_server
end
-- Setup DHCP server
function system_config:setDhcpServerConfig(config)
self.config.dhcp_server = config
end
function system_config:dhcpInterfaceGetGateway(iface)
return self.conf_handler.dhcpInterfaceGetGateway(iface)
end
-- Note: for now we assume network.online target gives us a valid IP address on the DHCP bridge interfaces.
-- Dropping this assumption would require to implement ntopng AddressList shadow for runtime add.
function system_config:getLocalNetwork()
local lan_iface = self:getLanInterface()
-- table.clone needed to modify some parameters while keeping the original unchanged
local lan_config = table.clone(self.config.interfaces.configuration[lan_iface])
if lan_config ~= nil then
if lan_config.network.mode == "dhcp" then
-- Get from system (e.g. on dhcp bridge interfaces)
local address, netmask = self:getInterfaceAddress(lan_iface)
local gateway
if address and netmask then
gateway = self:dhcpInterfaceGetGateway(lan_iface)
else
traceError(TRACE_WARNING, TRACE_CONSOLE, "Cannot determine LAN network. Falling back to default 192.168.1.0/24")
netmask = "255.255.255.0"
gateway = "192.168.1.1"
address = "192.168.1.2"
end
return {
netmask = netmask,
gateway = gateway,
mode = "dhcp",
ip = address,
cidr = ipv4_utils.addressToNetwork(address, netmask),
}
elseif lan_config.network.mode == "static" then
local cidr = ipv4_utils.addressToNetwork(lan_config.network.ip, lan_config.network.netmask)
lan_config.network.cidr = cidr
return lan_config.network
elseif lan_config.network.mode == "vlan_trunk" then
-- not possible to guess any lan configuration when the
-- bridge is operating on a VLAN trunk
return nil
end
end
end
local function isValidDhcpRangeBound(lan_config, lan_network, broadcast, range_bound)
return (lan_config.ip ~= range_bound) and
(broadcast ~= range_bound) and ipv4_utils.includes(lan_network, lan_config.netmask, range_bound)
end
local function isValidDhcpRange(lan_config, first_ip, last_ip)
local lan_network = ntop.networkPrefix(lan_config.ip, ipv4_utils.netmask(lan_config.netmask))
local broadcast = ipv4_utils.broadcast_address(lan_config.ip, lan_config.netmask)
if isValidDhcpRangeBound(lan_config, lan_network, broadcast, first_ip) and
isValidDhcpRangeBound(lan_config, lan_network, broadcast, last_ip) then
return (ipv4_utils.cmp(lan_config.ip, first_ip) < 0) and
(ipv4_utils.cmp(broadcast, last_ip) > 0) and
(ipv4_utils.cmp(first_ip, last_ip) <= 0)
end
return false
end
function system_config:hasValidDhcpRange(first_ip, last_ip)
local lan_config = self:getLocalNetwork()
if not lan_config then
return false
end
return isValidDhcpRange(lan_config, first_ip, last_ip)
end
function system_config:_fix_dhcp_from_lan(config, lan_iface)
local dhcp_config = config.dhcp_server
local lan_network = config.interfaces.configuration[lan_iface].network
local ip = lan_network.ip
local netmask = lan_network.netmask
local network = ntop.networkPrefix(ip, ipv4_utils.netmask(netmask))
local broadcast = ipv4_utils.broadcast_address(network, netmask)
dhcp_config.subnet.netmask = netmask
dhcp_config.subnet.gateway = ip
dhcp_config.subnet.network = network
dhcp_config.subnet.broadcast = broadcast
if not isValidDhcpRange(lan_network, dhcp_config.subnet.first_ip, dhcp_config.subnet.last_ip) then
local dhcp_range = ipv4_utils.get_possible_dhcp_range(ip, network, broadcast)
dhcp_config.subnet.first_ip = dhcp_range.first_ip
dhcp_config.subnet.last_ip = dhcp_range.last_ip
end
end
function system_config:setDhcpFromLan()
local lan_iface = self:getLanInterface()
return self:_fix_dhcp_from_lan(self.config, lan_iface)
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
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
end
end
-- You should only call this in single port routing mode
function system_config:getWanInterface()
local mode = self:getOperatingMode()
if mode == "single_port_router" then
return self.config.globals.available_modes.single_port_router.interfaces.wan
end
return nil
end
-- Get all the interfaces, along with their roles
function system_config:getAllInterfaces()
local ifaces = {}
for _, iface in pairs(self:getPhysicalLanInterfaces()) do
ifaces[iface] = "lan"
end
for _, iface in pairs(self:getPhysicalWanInterfaces()) do
ifaces[iface] = "wan"
end
for _, iface in pairs(self:getUnusedInterfaces()) do
ifaces[iface] = "unused"
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
if mode == "bridging" then
self.config.globals.available_modes.bridging.interfaces.lan = lan_ifaces
else
self.config.globals.available_modes[mode].interfaces.lan = lan_ifaces[1]
end
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
local function 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 = 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()
local lan_iface = self:getLanInterface()
local lan_address = self:getInterfaceAddress(lan_iface)
if isEmptyString(lan_address) then
if not self:isBridgeOverVLANTrunkEnabled() then
traceError(TRACE_WARNING, TRACE_CONSOLE, "Cannot get LAN interface " .. lan_iface .. " address")
end
-- This is possibly wrong (e.g. in transparent bridge)
lan_address = self.config.interfaces.configuration[lan_iface].network.ip or "192.168.1.1"
end
return lan_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)
-- Note: we must update DHCP also when interfaces/dns changes
if changed_sections["dhcp_server"] or changed_sections["globals"] then
self:_writeDhcpServerConfiguration()
self:_enableDisableDhcpService()
if not is_rebooting then
if self:isDhcpServerEnabled() then
sys_utils.restartService("isc-dhcp-server")
else
sys_utils.stopService("isc-dhcp-server")
end
end
end
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"]
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
function system_config:_writeRoutingModeNetworkConfig(f)
local network_config = self.config.interfaces.configuration
local mode_config = self.config.globals.available_modes["routing"].interfaces
-- Lan interface
self:_writeNetworkInterfaceConfig(f, mode_config.lan, network_config[mode_config.lan].network)
-- 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"]
local lan_iface = self:getLanInterface()
local wan_iface = self:getWanInterface()
-- Lan interface
self:_writeNetworkInterfaceConfig(f, lan_iface, network_config[lan_iface].network)
-- Wan interface
self:_writeNetworkInterfaceConfig(f, wan_iface, network_config[wan_iface].network)
end
function system_config:_writeNetworkInterfaces()
local mode = self:getOperatingMode()
local f = self.conf_handler.openNetworkInterfacesConfigFile()
local recovery_conf = self:getLanRecoveryIpConfig()
local recovery_iface = self:getLanInterface() .. ":2"
local is_configured, fnames = self.conf_handler.isConfiguredInterface("lo")
if not is_configured then
self:_writeNetworkInterfaceConfig(f, "lo", {mode="loopback"})
end
if mode == "bridging" then
self:_writeBridgeModeNetworkConfig(f)
elseif mode == "routing" then
self:_writeRoutingModeNetworkConfig(f)
elseif mode == "single_port_router" then
self:_writeSinglePortModeInterfaces(f)
end
self:_writeNetworkInterfaceConfig(f, recovery_iface, {mode="static", ip=recovery_conf.ip, netmask=recovery_conf.netmask})
self.conf_handler.closeNetworkInterfacesConfigFile(f)
end
function system_config:_writeDhcpServerConfiguration()
local lan_iface = self:getLanInterface()
local dhcp_config = self.config.dhcp_server
local global_config = self.config.globals
local dns_config = self:getDnsConfig()
local f = sys_utils.openFile("/etc/default/isc-dhcp-server", "w")
f:write("INTERFACES=\""..lan_iface.."\"\n")
f:close()
f = sys_utils.openFile("/etc/dhcp/dhcpd.conf", "w")
for _, opt in ipairs(dhcp_config.options) do
f:write(opt .. ";\n")
end
f:write("\n")
f:write("subnet ".. dhcp_config.subnet.network .." netmask ".. dhcp_config.subnet.netmask .." {\n")
f:write(" range " .. dhcp_config.subnet.first_ip .. " " .. dhcp_config.subnet.last_ip .. ";\n")
f:write(" option domain-name-servers " .. table.concat({
dns_config.global,
ternary(not isEmptyString(dns_config.secondary), dns_config.secondary, nil)
},", ") .. ";\n")
f:write(" option routers " .. dhcp_config.subnet.gateway .. ";\n")
f:write(" option broadcast-address " .. dhcp_config.subnet.broadcast .. ";\n")
for _, opt in ipairs(dhcp_config.subnet.options) do
f:write(" " .. opt .. ";\n")
end
f:write("}\n")
for mac, lease in pairs(dhcp_config.leases) do
f:write("\n")
f:write("host " .. lease.hostname .. " {\n")
f:write(" hardware ethernet " .. mac .. ";\n")
f:write(" fixed-address " .. lease.ip .. ";\n")
f:write("}\n")
end
f:close()
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
-- ##############################################
local function 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
local function 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:getStaticLanNetwork()
local lan_iface = self:getLanInterface()
local lan_config = self.config.interfaces.configuration[lan_iface].network
local lan_network = ipv4_utils.addressToNetwork(lan_config.ip, lan_config.netmask)
return {
iface = lan_iface,
network = lan_network,
cidr = lan_network,
ip = lan_config.ip,
netmask = lan_config.netmask,
}
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:getGatewayPingAddress(gwname)
local gw = self.config.gateways[gwname]
if (gw == nil) or (gw.ping_address == nil) then
return "8.8.8.8"
end
return gw.ping_address
end
-- ##############################################
function system_config:getGatewayMaxRTT(gwname)
local gw = self.config.gateways[gwname]
if (gw == nil) or (gw.max_rtt_ms == nil) then
return 5000
end
return gw.max_rtt_ms
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: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()
if self:isDhcpServerEnabled() then
sys_utils.enableService("isc-dhcp-server")
else
sys_utils.disableService("isc-dhcp-server")
end
end
-- ##############################################
local function 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 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
-- ##############################################
function system_config:getStaticLeases()
return self.config.dhcp_server.leases or {}
end
function system_config:setStaticLeases(leases)
self.config.dhcp_server.leases = leases
end
-- ##############################################
-- nf_config overrides this
function system_config:isMultipathRoutingEnabled()
return false
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
-- 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
-- ##############################################
-- 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
-- ##############################################
return system_config