EVOLUTION-MANAGER
Edit File: system_config.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 = "netplan" if string.find(os, "Ubuntu 16%.") then config_target = "network_ifaces" 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 WAN interfaces, based on the current operating mode function system_config:getWanInterfaces() local mode = self:getOperatingMode() return self.config.globals.available_modes[mode].interfaces.wan end -- Get the LAN interfaces, based on the current operating mode function system_config:getLanInterfaces() 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 -- Get the first (if many) LAN interface, based on the current operating mode function system_config:getFirstLanInterface() local lan_interfaces = self:getLanInterfaces() return lan_interfaces[1] 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 lan_interfaces = self:getLanInterfaces() return lan_interfaces[1] 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.getInterfaceAddresses(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.getInterfaceAddresses(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:getLocalIpv4Addresses(ifname) local ifaces = {} local addresses = {} if ifname then ifaces = {ifname} else ifaces = self:getLanInterfaces() end for _, iface in ipairs(ifaces) do local address = self:getInterfaceAddress(iface) if not isEmptyString(address) then table.insert(addresses, address) end end if #addresses == 0 then if not self:isBridgeOverVLANTrunkEnabled() then traceError(TRACE_WARNING, TRACE_CONSOLE, "Cannot get local IP addresses") end if ifname and self.config.interfaces.configuration[ifname] then -- This is possibly wrong (e.g. in transparent bridge) local address = self.config.interfaces.configuration[ifname].network.ip or "192.168.1.1" table.insert(addresses, address) end end return addresses 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 -- ############################################## -- Settings that do not require a reboot or self restart local runtime_sections = { dhcp_server = 1, shapers = 1, gateways = 1, routing = 1, date_time = 1, disabled_wans = 1, } -- Settings that require just a self restart local self_restart_sections = { static_routes = 1, captive_portal = 1, policy = 1, repeaters = 1, port_forwarding = 1, } local function isRebootRequired(changed_sections) -- Always reboot on first start if system_config.isFirstStart() then return true end for section in pairs(changed_sections) do if not runtime_sections[section] and not self_restart_sections[section] then return true -- reboot required end end return false end -- ############################################## function system_config:needsReboot() return isRebootRequired(self:_get_changed_sections(self.config)) end -- ############################################## local function isSelfRestartRequired(changed_sections) for section in pairs(changed_sections) do if self_restart_sections[section] then return true -- self restart required 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() -- tprint("Reboot! (debug mode - reboot is disabled)") sys_utils.rebootSystem() elseif is_self_restarting then sys_utils.restartSelf() end end -- ############################################## function system_config:_getNetworkInterfaceConfig(iface, if_type) local network_config = self.config.interfaces.configuration local if_config = network_config[iface] if not if_config then return {} end if if_type and if_type == 'lan' then -- LAN interfaces are nor supposed to have gateway/nameserver -- removing before dumping the interface/netplan configuration -- in case they have been detected from an old configuration if if_config.network and if_config.network.gateway then if_config.network.gateway = nil end if if_config.network and if_config.network.nameservers then if_config.network.nameservers = nil end end return if_config 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 mode_config = self.config.globals.available_modes["bridging"] local br_name = mode_config.name local br_config = self:_getNetworkInterfaceConfig(br_name) if ntop.isIoTBridge() then 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 self:_writeNetworkInterfaceConfig(f, br_name, br_config.network, bridge_ifaces) end end function system_config:_writeRoutingModeNetworkConfig(f) local mode_config = self.config.globals.available_modes["routing"].interfaces -- Lan interface for _, iface in ipairs(mode_config.lan) do local if_config = self:_getNetworkInterfaceConfig(iface, 'lan') self:_writeNetworkInterfaceConfig(f, iface, if_config.network) end -- Wan interfaces for _, iface in ipairs(mode_config.wan) do local if_config = self:_getNetworkInterfaceConfig(iface, 'wan') self:_writeNetworkInterfaceConfig(f, iface, if_config.network) end end function system_config:_writeSinglePortModeInterfaces(f) local mode_config = self.config.globals.available_modes["single_port_router"] -- Lan interface local lan_iface = self:getFirstLanInterface() local lan_if_config = self:_getNetworkInterfaceConfig(lan_iface, 'lan') self:_writeNetworkInterfaceConfig(f, lan_iface, lan_if_config.network) -- Wan interface local wan_iface = mode_config.interfaces.wan[1] local wan_if_config = self:_getNetworkInterfaceConfig(wan_iface, 'wan') self:_writeNetworkInterfaceConfig(f, wan_iface, wan_if_config.network) end function system_config:_writePassiveModeNetworkConfig(f) local mode_config = self.config.globals.available_modes["passive"] -- Lan interface local lan_iface = mode_config.interfaces.lan[1] local lan_if_config = self:_getNetworkInterfaceConfig(lan_iface, 'lan') self:_writeNetworkInterfaceConfig(f, lan_iface, lan_if_config.network) end function system_config:_getRecoveryInterface() return self:getFirstLanInterface() .. ":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() if system_config.isFirstStart() then system_config.setFirstStart(false) end 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) -- Check if vlan local devnamevlan = string.split(ifname, "%.") if devnamevlan and devnamevlan[1] then ifname = devnamevlan[1] -- vlan = devnamevlan[2] end 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:getInterfaceAlias(if_name, if_alias) if self.config.interfaces.configuration[if_name] and self.config.interfaces.configuration[if_name].alias then return self.config.interfaces.configuration[if_name].alias end return if_name end function system_config:setInterfaceAlias(if_name, if_alias) if self.config.interfaces.configuration[if_name] then self.config.interfaces.configuration[if_name].alias = if_alias return true end return false 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:_enableDisableSmcrouteService(enable_smcroute) local smcroute_service_utils = require "smcroute_service_utils" if (enable_smcroute) then smcroute_service_utils.startSmcrouteService() else smcroute_service_utils.stopSmcrouteService() 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 -- ############################################## function system_config:isMDNSRepeaterEnabled() return self.config.globals.mdns_repeater end function system_config:setMDNSRepeaterEnabled(enabled) self.config.globals.mdns_repeater = ternary(enabled, true, 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 -- 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_names = self:getLanInterfaces() local lan_ifaces = self:getPhysicalLanInterfaces() local wan_ifaces = self:getPhysicalWanInterfaces() local ifaces = {} for _, iface in ipairs(lan_ifaces_names) do ifaces[iface] = 1 end 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