local mymodule = {} -- Load libraries modelfunctions = require("modelfunctions") validator = require("acf.validator") fs = require("acf.fs") -- Set variables local configfile = "/etc/opennhrp/opennhrp.conf" local processname = "opennhrp" local packagename = "opennhrp" local interfaceslist, reverseinterfaceslist local config, configfilecontent local descr = { Type = { ['incomplete'] = "Resolution in progress", ['negative'] = "Address not routable", ['cached'] = "Discovered node", ['shortcut-route'] = "Discovered subnet", ['dynamic'] = "Registered leaf node", ['dynamic-nhs'] = "Discovered core router", ['static'] = "Static core router", ['dynamic-map'] = "Core router cluster", ['local-route'] = "Local subnet", ['local'] = "Local address", }, Flags= { -- ['up']="Connection is fully usable", ['lower-up']="ipsec connections is up, but registration is not yet done", }, } -- ################################################################################ -- LOCAL FUNCTIONS local function list_interfaces(self) if not interfaceslist then -- Get the list of available interfaces local interfaces = self:new("alpine-baselayout/interfaces") local availableinterfaces = interfaces:read() interfaceslist = {} reverseinterfaceslist = {} for i,int in ipairs(availableinterfaces.value) do interfaceslist[#interfaceslist + 1] = int.value.name.value reverseinterfaceslist[int.value.name.value] = #interfaceslist end interfaces:destroy() end return interfaceslist, reverseinterfaceslist end local function parseconfigfile(configfilecontent) local config = {} local currentinterface local words = {} -- remove comments, and then parse word-by-word for word in string.gmatch(string.gsub(configfilecontent, "#[^\n]*", ""), "%S+") do words[#words + 1] = word end local i=1 while i <= table.maxn(words) do local word = words[i] if not currentinterface and word ~= "interface" then currentinterface = "" config[""].errtxt = "Syntax error - undefined interface" end if word == "interface" then i = i+1 currentinterface = words[i] if config[currentinterface] then config[currentinterface].errtxt = "Syntax error - interface defined multiple times" else config[currentinterface] = {} end elseif word == "map" then -- there may be more than one map statement if not config[currentinterface].map then config[currentinterface].map = {} end local temp = {words[i+1], words[i+2]} i = i+2 while words[i+1] == "register" or words[i+1] == "cisco" do temp[#temp + 1] = words[i+1] i = i+1 end table.insert(config[currentinterface].map, temp) elseif word == "dynamic-map" then -- there may be more than one dynamic-map statement if not config[currentinterface]["dynamic-map"] then config[currentinterface]["dynamic-map"] = {} end local temp = {words[i+1], words[i+2]} i = i+2 table.insert(config[currentinterface]["dynamic-map"], temp) elseif word == "multicast" then -- there may be more than one multicast statement if not config[currentinterface].multicast then config[currentinterface].multicast = {} end table.insert(config[currentinterface].multicast, words[i+1]) i = i+2 elseif word == "cisco-authentication" or word == "holding-time" then config[currentinterface][word] = {words[i+1]} i = i+1 elseif word == "redirect" or word == "shortcut" or word == "non-caching" or word == "shortcut-destination" then config[currentinterface][word] = {} else table.insert(config[currentinterface], word) end i = i+1 end -- Check interfaces for errors for interf,val in pairs(config) do if val[1] then local errtxt = "Syntax error - unrecognized keyword" if val.errtxt then val.errtxt = val.errtxt .. "\n" .. errtxt else val.errtxt = errtxt end end end return config end local function validateconfigfile(self, configfile) local config = parseconfigfile(configfile.value.filecontent.value) list_interfaces(self) local errtxt = {} for name,value in pairs(config) do if name ~= "" and not reverseinterfaceslist[name] then errtxt[#errtxt + 1] = name .. " - Nonexistant interface" end if value.errtxt then errtxt[#errtxt + 1] = name .. " - " .. string.gsub(value.errtxt, "\n", "\n"..name.." - ") end end if #errtxt then configfile.value.filecontent.errtxt = table.concat(errtxt, "\n") end return configfile end local function validateinterfacedetails(interfacedetails) local success = modelfunctions.validateselect(interfacedetails.value.type) for i,map in ipairs(interfacedetails.value.map.value) do local words = {} for word in string.gmatch(map, "%S+") do words[#words+1] = word end if not words[1] or not words[2] or (words[3] and words[3] ~= "register" and words[3] ~= "cicso") or (words[4] and words[4] ~= "register" and words[4] ~= "cisco") or words[5] then interfacedetails.value.map.errtxt = "Syntax error on line "..i success = false break end end for i,map in ipairs(interfacedetails.value["dynamic-map"].value) do local words = {} for word in string.gmatch(map, "%S+") do words[#words+1] = word end if not words[1] or not words[2] or words[3] then interfacedetails.value["dynamic-map"].errtxt = "Syntax error on line "..i success = false break end end if not interfacedetails.value.interface.value or string.find(interfacedetails.value.interface.value, "%s") then interfacedetails.value.interface.errtxt = "Invalid interface name" success = false end return success, interfacedetails end -- ################################################################################ -- PUBLIC FUNCTIONS function mymodule.get_startstop(self, clientdata) return modelfunctions.get_startstop(processname) end function mymodule.startstop_service(self, startstop, action) return modelfunctions.startstop_service(startstop, action) end function mymodule.getstatus() return modelfunctions.getstatus(processname, packagename, "OpenNHRP Status") end function mymodule.getshowreport() local peers_list = cfe({ type="structure", value={}, label="Peers" }) local opennhrpstatus = cfe({ value="Show report not available", label="Status" }) local content = modelfunctions.run_executable({"opennhrpctl", "show"}) local current for line in string.gmatch(content, "([^\n]*)\n?") do if string.find(line, "^Status:") then opennhrpstatus.value = string.match(line, ":%s*(%w+)") elseif string.find(line,"^Interface:") then local intf = string.match(line, ":%s*(%w+)") current = {} peers_list.value[intf] = peers_list.value[intf] or {} table.insert(peers_list.value[intf], current) elseif ( current ) and (line ~= "") then local name,val = string.match(line,"^(.-):%s?(.*)") if name and val then current[name] = cfe({value=val, label=name}) if (type(descr[name]) == "table") then current[name].descr = descr[name][val] end end end end return cfe({ type="group", value={peers_list=peers_list, status=opennhrpstatus}, label="OpenNHRP Show Report" }) end function mymodule.getinterfacedetails(interface) local details = {} details.interface = cfe({ readonly=true, value=interface, label="Interface", seq=1 }) details.type = cfe({ type="select", value="Unused", label="Interface type", option={"Unused", "NHRP Enabled", "Shortcut Destination"}, seq=2 }) details.map = cfe({ type="list", value={}, label="Static Peers", descr="List of static peer mappings of protocol-address to nbma-address. Optional 'register' parameter specifies Registration Request sent to this peer on startup. If the statically mapped peer is running Cisco IOS, specify the cisco keyword. (protocol-address[/prefix] nbma-address [register] [cisco])", seq=3 }) details["dynamic-map"] = cfe({ type="list", value={}, label="Dynamic Peers", descr="List of dynamic peer mappings of protocol-address to nbma-domain-name. For each A record in the domain nbma-domain-name, opennhrp creates a dynamic NHS entry. (protocol-address/prefix nbma-domain-name)", seq=4 }) configfilecontent = configfilecontent or fs.read_file(configfile) or "" config = config or parseconfigfile(configfilecontent) if config and config[interface] then if config[interface]["shortcut-destination"] then details.type.value = "Shortcut Destination" else details.type.value = "NHRP Enabled" end for i,map in ipairs(config[interface].map or {}) do table.insert(details.map.value, table.concat(map, " ")) end for i,map in ipairs(config[interface]["dynamic-map"] or {}) do table.insert(details["dynamic-map"].value, table.concat(map, " ")) end details.interface.errtxt = config[interface].errtxt end return cfe({ type="group", value=details, label="OpenNHRP Interface Configuration" }) end function mymodule.updateinterfacedetails(self, interfacedetails) local success, interfacedetails = validateinterfacedetails(interfacedetails) if success then configfilecontent = configfilecontent or fs.read_file(configfile) or "" local startchar = string.find(configfilecontent or "", "interface%s+"..interfacedetails.value.interface.value) local lines = {} if startchar then lines[1] = string.gsub(string.sub(configfilecontent, 1, startchar-1), "\n+$", "") startchar = string.find(configfilecontent, "interface", startchar+1) if startchar then lines[2] = string.sub(configfilecontent, startchar, -1) end else lines[1] = string.gsub(configfilecontent or "", "\n+$", "") end if interfacedetails.value.type.value ~= "Unused" then local intflines = {"interface "..interfacedetails.value.interface.value} if interfacedetails.value.type.value == "Shortcut Destination" then intflines[2] = "\tshortcut-destination" else for i,map in ipairs(interfacedetails.value.map.value) do table.insert(intflines, "\tmap "..map) end for i,map in ipairs(interfacedetails.value["dynamic-map"].value) do table.insert(intflines, "\tdynamic-map "..map) end config = config or parseconfigfile(configfilecontent) local intfconfig = config[interfacedetails.value.interface.value] or {} intfconfig.map = nil intfconfig["dynamic-map"] = nil intfconfig["shortcut-destination"] = nil for name,arr in pairs(intfconfig) do if type(name) ~= "number" and name ~= "errtxt" then table.insert(intflines, "\t"..name.." "..table.concat(arr, " ")) end end end table.insert(lines, 2, table.concat(intflines, "\n")) end fs.write_file(configfile, table.concat(lines, "\n")) configfilecontent = nil config = nil else interfacedetails.errtxt = "Failed to save Interface Details" end return interfacedetails end function mymodule.listinterfaces(self) local interfaces = {} list_interfaces(self) configfilecontent = configfilecontent or fs.read_file(configfile) or "" config = config or parseconfigfile(configfilecontent) for name,intf in pairs(config) do temp = {interface=name, type="NHRP Enabled", errtxt=intf.errtxt} if intf["shortcut-destination"] then temp.type = "Shortcut Destination" end if not reverseinterfaceslist[name] then local errtxt = "Nonexistant interface" if temp.errtxt then temp.errtxt = temp.errtxt .. "\n" .. errtxt else temp.errtxt = errtxt end end table.insert(interfaces, temp) end for i,name in ipairs(interfaceslist) do if not config[name] then table.insert(interfaces, {interface=name, type="Unused"}) end end return cfe({ type="structure", value=interfaces, label="OpenNHRP Interfaces" }) end function mymodule.getconfigfile(self) return validateconfigfile(self, modelfunctions.getfiledetails(configfile)) end function mymodule.setconfigfile(self, filedetails) filedetails = modelfunctions.setfiledetails(self, filedetails, {configfile}) if not filedetails.errtxt then configfilecontent = nil config = nil end return validateconfigfile(self, filedetails) end return mymodule