#! /usr/bin/lua --[[ Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks Copyright (C) 2021 Tim Wilkinson Orignal Perl Copyright (C) 2015 Conrad Lara See Contributors file for additional contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Additional Terms: Additional use restrictions exist on the AREDN(TM) trademark and logo. See AREDNLicense.txt for more info. Attributions to the AREDN Project must be retained in the source code. If importing this code into a new or existing project attribution to the AREDN project must be added to the source code. You must not misrepresent the origin of the material contained within. Modified versions must be modified to attribute to the original source and be marked in reasonable ways as differentiate it from the original version --]] require("nixio") require("aredn.utils") local aredn_info = require('aredn.info') require("aredn.hardware") -- helpers start function is_null(v) if not v or v == "" or v == 0 or v == "0" then return true else return false end end -- helpers end -- validate args local auto = false local do_basic = true local config = nil for i, a in ipairs(arg) do if a == "-a" then auto = true elseif a == "-p" then do_basic = false elseif a[1] == '-' then break else config = a break end end if not config then print "usage: node-setup [-a] [-p] " print "-a: automatic mode - don't ask any questions" print "-p: only process port forwarding and dhcp settings" return -1 end if not (config == "mesh" and nixio.fs.access("/etc/config.mesh/_setup", "r")) then print (string.format("'%s' is not a valid configuration", config)) return -1 end local lanintf = aredn.hardware.get_board().network.lan.ifname local node = aredn_info.get_nvram("node") local tactical = aredn_info.get_nvram("tactical") local mac2 = mac_to_ip(aredn.hardware.get_interface_mac(aredn.hardware.get_iface_name("wifi")), 0) local dtdmac = mac_to_ip(aredn.hardware.get_interface_mac(lanintf:match("^(%S+)")), 0) -- *not* based of dtdlink local deleteme = {} local cfg = { lan_intf = lanintf, wan_intf = aredn.hardware.get_board().network.wan.ifname, dtdlink_intf = aredn.hardware.get_bridge_iface_names('dtdlink') } if not auto then -- Is this used now? print "Non-auto mode no longer supported." return -1 end -- load the verify the selected configuration for line in io.lines("/etc/config.mesh/_setup") do if not (line:match("^%s*#") or line:match("^%s*$")) then line = line:gsub("", node):gsub("", mac2):gsub("", dtdmac) local k, v = line:match("^([^%s]*)%s*=%s*(.*)%s*$") cfg[k] = v end end if cfg.wifi_enable == "1" then cfg.wifi_intf = aredn.hardware.get_board().network.wifi.ifname:match("^(%S+)") else cfg.wifi_intf = lanintf:match("^([^%.%s]+).*$") .. ".3975" end -- delete some config lines if necessary if cfg.wan_proto == "dhcp" then deleteme.wan_ip = true deleteme.wan_gw = true deleteme.wan_mask = true end if not is_null(cfg.dmz_mode) or cfg.wan_proto ~= "disabled" then deleteme.lan_gw = true end -- lan_dhcp sense is inverted in the dhcp config file -- and it is a checkbox so it may not be defined - this fixes that if cfg.lan_dhcp == "1" then cfg.lan_dhcp = 0 else cfg.lan_dhcp = 1 end -- verify that we have all the variables we need for file in nixio.fs.glob("/etc/config.mesh/*") do for line in io.lines(file) do if line:match("^[^#]") then for parm in line:gmatch("<([^%s]*)>") do if parm:upper() == parm then -- nvram variable if aredn_info.get_nvram(parm:lower()) == "" then print ("nv parameter '" .. parm .. "' in file '" .. file .. "' does not exist") return -1 end else if not cfg[parm] and not deleteme[parm] then print ("parameter '" .. parm .. "' in file '" .. file .. "' does not exist") return -1 end end end end end end -- sensible dmz_mode default if is_null(cfg.dmz_mode) then cfg.dmz_mode = "0" end -- switch to dmz values if needed if not is_null(cfg.dmz_mode) then cfg.lan_ip = cfg.dmz_lan_ip cfg.lan_mask = cfg.dmz_lan_mask cfg.dhcp_start = cfg.dmz_dhcp_start cfg.dhcp_end = cfg.dmz_dhcp_end cfg.dhcp_limit = cfg.dmz_dhcp_limit end -- select ports and dhcp files based on mode local portfile = "/etc/config.mesh/_setup.ports" local dhcpfile = "/etc/config.mesh/_setup.dhcp" local aliasfile = "/etc/config.mesh/aliases" local servfile = "/etc/config.mesh/_setup.services" if is_null(cfg.dmz_mode) then portfile = portfile .. ".nat" dhcpfile = dhcpfile .. ".nat" aliasfile = aliasfile .. ".nat" servfile = servfile .. ".nat" else portfile = portfile .. ".dmz" dhcpfile = dhcpfile .. ".dmz" aliasfile = aliasfile .. ".dmz" servfile = servfile .. ".dmz" end -- check for old aliases file, copy it to .dmz and create symlink -- just in case anyone is already using the file for some script or something if not nixio.fs.readlink("/etc/config.mesh/aliases") then if nixio.fs.stat("/etc/config.mesh/aliases") then filecopy("/etc/config.mesh/aliases", "/etc/config.mesh/aliases.dmz") os.remove("/etc/config.mesh/aliases") else io.open("/etc/config.mesh/aliases.dmz", "a"):close() end nixio.fs.symlink("aliases.dmz", "/etc/config.mesh/aliases") end -- basic configuration if do_basic then remove_all("/tmp/new_config") nixio.fs.mkdir("/tmp/new_config") for file in nixio.fs.glob("/etc/config.mesh/*") do local bfile = nixio.fs.basename(file) if not (bfile:match("^_setup") or bfile:match("^firewall.user") or bfile:match("^olsrd")) then local f = io.open("/tmp/new_config/" .. bfile, "w") if f then for line in io.lines(file) do local out = true local inc = line:match("^include%s+(.*)%s*") if inc then for iline in io.lines(inc) do f:write(iline .. "\n") end out = false elseif line:match("^[^#]") then for parm in line:gmatch("<([^%s]*)>") do if deleteme[parm] then out = false elseif parm == "NODE" then line = line:gsub("", node) elseif parm == "MAC2" then line = line:gsub("", mac2) elseif parm == "DTDMAC" then line = line:gsub("", dtdmac) else line = line:gsub("<" .. parm .. ">", cfg[parm]) end end end if out then f:write(line .. "\n") end end f:close() end end end -- make it official for file in nixio.fs.glob("/etc/config/*") do nixio.fs.remove(file) end for file in nixio.fs.glob("/tmp/new_config/*") do filecopy(file, "/etc/config/" .. nixio.fs.basename(file)) nixio.fs.remove(file) end nixio.fs.rmdir("/etc/new_config") filecopy("/etc/config.mesh/firewall.user", "/etc/firewall.user") aredn_info.set_nvram("config", "mesh") aredn_info.set_nvram("node", node) aredn_info.set_nvram("tactical", tactical) end -- generate the system files local h = io.open("/etc/hosts", "w") local e = io.open("/etc/ethers", "w") if h and e then h:write("# automatically generated file - do not edit\n") h:write("# use /etc/hosts.user for custom entries\n") h:write("127.0.0.1\tlocalhost\n") if not is_null(cfg.wifi_ip) then h:write(cfg.lan_ip .. "\tlocalnode\n") h:write(cfg.wifi_ip .. "\t" .. node .. " " .. tactical .. "\n") else h:write(cfg.lan_ip .. "\tlocalnode " .. node .. " " .. tactical .. "\n") end if not is_null(cfg.dtdlink_ip) then h:write(cfg.dtdlink_ip .. "\tdtdlink." .. node .. ".local.mesh dtdlink." .. node .."\n") end if is_null(cfg.dmz_mode) then h:write(decimal_to_ip(ip_to_decimal(cfg.lan_ip) + 1) .. "\tlocalap\n") end e:write("# automatically generated file - do not edit\n") e:write("# use /etc/ethers.user for custom entries\n") local netaddr = nixio.bit.band(ip_to_decimal(cfg.lan_ip), ip_to_decimal(cfg.lan_mask)) if nixio.fs.access(dhcpfile) then for line in io.lines(dhcpfile) do if not (line:match("^%s*#") or line:match("^%s*$")) then local mac, ip, host, noprop = line:match("(%S+)%s+(%S+)%s+(%S+)%s*(%S*)") if mac and ip and host and noprop then ip = decimal_to_ip(netaddr + ip) if validate_same_subnet(ip, cfg.lan_ip, cfg.lan_mask) and validate_ip_netmask(ip, cfg.lan_mask) then h:write(ip .. "\t" .. host .. " " .. noprop .. "\n") e:write(mac .. "\t" .. ip .. "\n") end end end end end -- aliases need to ba added to /etc/hosts or they will now show up on the localnode -- nor will the services thehy offer -- also add a comment to the hosts file so we can display the aliases differently if needed local f = io.open(aliasfile, "r") if f then for line in f:lines() do if not (line:match("^%s*#") or line:match("^%s*$")) then local ip, host = line:match("(%S+)%s+(%S+)") if ip then h:write(ip .. "\t" .. host .. " #ALIAS\n") end end end f:close() end h:write("\n") if nixio.fs.access("/etc/hosts.user", "r") then for line in io.lines("/etc/hosts.user") do h:write(line .. "\n") end end if nixio.fs.access("/etc/ethers.user", "r") then for line in io.lines("/etc/ethers.user") do e:write(line .. "\n") end end h:close() e:close() end if not do_basic then filecopy("/etc/config.mesh/firewall", "/etc/config/firewall") filecopy("/etc/config.mesh/firewall.user", "/etc/firewall.user") end -- for all the uci changes local c = uci.cursor() -- append to firewall local add_masq = false local fw = io.open("/etc/config/firewall", "a") if fw then if not is_null(cfg.dmz_mode) then fw:write("\nconfig forwarding\n option src wifi\n option dest lan\n") fw:write("\nconfig forwarding\n option src dtdlink\n option dest lan\n") add_masq = true else fw:write("\nconfig 'include'\n option 'path' '/etc/firewall.natmode'\n option 'reload' '1'\n") end if c:get("aredn", "@wan[0]", "olsrd_gw") == "1" then fw:write("\nconfig forwarding\n option src wifi\n option dest wan\n") fw:write("\nconfig forwarding\n option src dtdlink\n option dest wan\n") end if nixio.fs.access(portfile) then for line in io.lines(portfile) do if not (line:match("^%s*#") or line:match("^%s*$")) then local dip = line:match("dmz_ip = (%w+)") if dip and cfg.dmz_mode ~= 0 then fw:write("\nconfig redirect\n option src wifi\n option proto tcp\n option src_dip " .. cfg.wifi_ip .. "\n option dest_ip " .. dip .. "\n") fw:write("\nconfig redirect\n option src wifi\n option proto udp\n option src_dip " .. cfg.wifi_ip .. "\n option dest_ip " .. dip .. "\n") else local intf, type, oport, host, iport, enable = line:match("(.*):(.*):(.*):(.*):(.*):(.*)") if enable == "1" then local match = " option src_dport " .. oport .. "\n" if type == "tcp" then match = match .. " option proto tcp\n" elseif type == "udp" then match = match .. " option proto udp\n" end -- uci the host and then -- set the inside port unless the rule uses an outside port range host = "option dest_ip " .. host .. "\n" if not oport:match("-") then host = host .. " option dest_port " .. iport .. "\n" end if not is_null(cfg.dmz_mode) and intf == "both" then intf = "wan" end if intf == "both" then fw:write("\nconfig redirect\n option src wifi\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n") fw:write("\nconfig redirect\n option src dtdlink\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n") fw:write("config redirect\n option src wan\n " .. match .. " " .. host .. "\n") elseif intf == "wifi" and is_null(cfg.dmz_mode) then fw:write("\nconfig redirect\n option src dtdlink\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n") fw:write("\nconfig redirect\n option src wifi\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n") elseif intf == "wan" then fw:write("\nconfig redirect\n option src dtdlink\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n") fw:write("config redirect\n option src wan\n " .. match .. " " .. host .. "\n") end end end end end end fw:close(); end if add_masq then c:set("firewall", "@zone[2]", "masq", "0") c:commit("firewall") end -- generate the services file local sf = io.open("/etc/config/services", "w") if sf then if nixio.fs.access(servfile) then for line in io.lines(servfile) do if not (line:match("^%s*#") or line:match("^%s*$")) then local name, link, proto, host, port, sffx = line:match("(.*)|(.*)|(.*)|(.*)|(.*)|(.*)") if name and name ~= "" and host ~= "" then if proto == "" then proto = "http" end if link == "0" then port = "0" end sf:write(string.format("%s://%s:%s/%s|tcp|%s\n", proto, host, port, sffx, name)) end end end end sf:close() end local sf = io.open("/etc/local/services", "w") if sf then sf:write("#!/bin/sh\n") if cfg.wifi_proto ~= "disabled" then local wifi_channel = tonumber(cfg.wifi_channel) if is_null(cfg.wifi_txpower) or tonumber(cfg.wifi_txpower) > aredn.hardware.wifi_maxpower(wifi_channel) then cfg.wifi_txpower = aredn.hardware.wifi_maxpower(wifi_channel) elseif tonumber(cfg.wifi_txpower) < 1 then cfg.wifi_txpower = 1 end if cfg.wifi_enable == "1" then sf:write("/usr/sbin/iw dev " .. cfg.wifi_intf .. " set txpower fixed " .. cfg.wifi_txpower .. "00\n") end if not is_null(cfg.aprs_lat) and not is_null(cfg.aprs_lon) then c:set("aredn", "@location[0]", "lat", cfg.aprs_lat) c:set("aredn", "@location[0]", "lon", cfg.aprs_lon) c:commit("aredn") end end sf:close() nixio.fs.chmod("/etc/local/services", "777") end -- generate olsrd.conf if nixio.fs.access("/etc/config.mesh/olsrd", "r") then local of = io.open("/etc/config/olsrd", "w") if of then if nixio.fs.access("/etc/config.mesh/olsrd") then for line in io.lines("/etc/config.mesh/olsrd") do if line:match("") then if is_null(cfg.olsrd_bridge) then line = line:gsub("", '"wifi" "lan"') else line = line:gsub("", '"lan"') end elseif line:match("^[^#]") then for parm in line:gmatch("<([^%s]*)>") do line = line:gsub("<" .. parm .. ">", cfg[parm]) end end of:write(line .. "\n") end if not is_null(cfg.dmz_mode) then local a, b, c, d = cfg.dmz_lan_ip:match("(.*)%.(.*)%.(.*)%.(.*)") of:write(string.format("\nconfig Hna4\n\toption netaddr %s.%s.%s.%d\n\toption netmask 255.255.255.%d\n\n", a, b, c, d - 1, nixio.bit.band(255 * 2 ^ cfg.dmz_mode, 255))) end if cfg.wifi_enable ~= "1" and not is_null(cfg.wifi_ip) then local cwan = aredn.hardware.get_iface_name("wan"):match("^([^%.%s]+)") local cdtd = aredn.hardware.get_iface_name("dtdlink"):match("^([^%.%s]+)") if cwan ~= cdtd then of:write(string.format("config Hna4\n\toption netaddr %s\n\toption netmask 255.255.255.255\n\n", cfg.wifi_ip)) end end if c:get("aredn", "@wan[0]", "olsrd_gw") == "1" then of:write("config LoadPlugin\n\toption library 'olsrd_dyn_gw.so.0.5'\n\toption Interval '60'\n\tlist Ping '8.8.8.8'\n\tlist Ping '8.8.4.4'\n\n\n") end end of:close() end end -- indicate whether lan is running in dmz mode c:set("aredn", "@dmz[0]", "mode", cfg.dmz_mode) c:commit("aredn") -- setup node lan dhcp if c:get("aredn", "@wan[0]", "lan_dhcp_route") == "1" or c:get("aredn", "@wan[0]", "lan_dhcp_defaultroute") == "1" then -- Provide stateless routes and default route c:set("dhcp", "@dhcp[0]", "dhcp_option", { "121,10.0.0.0/8," .. cfg.lan_ip .. ",0.0.0.0/0," .. cfg.lan_ip, "249,10.0.0.0/8," .. cfg.lan_ip .. ",0.0.0.0/0," .. cfg.lan_ip }) else -- Provide stateless routes to the mesh, and a blank default route (option 3 has no values) to -- surpress default route being sent c:set("dhcp", "@dhcp[0]", "dhcp_option", { "121,10.0.0.0/8," .. cfg.lan_ip, "249,10.0.0.0/8," .. cfg.lan_ip, "3" }) end c:commit("dhcp") -- generate the wireless config file os.execute("/usr/local/bin/wifi-setup") if not auto then print "configuration complete."; print "you should now reboot the router."; end return 0