{% /* * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks * Copyright (C) 2024 Tim Wilkinson * 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® 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 */ %} {% function loadPorts(type, def) { const f = fs.open(`/etc/aredn_include/${type}.network.user`); if (!f) { return def; } const r = { vlan: null, ports: {} }; let pcount = 0; for (let l = f.read("line"); length(l); l = f.read("line")) { let m = match(l, /list ports '(.+):([ut])'/); if (m) { r.ports[m[1]] = true; if (m[2] === "u") { r.vlan = 0; } pcount++; } m = match(l, /option vlan '(\d+)'/); if (m) { r.vlan = int(m[1]); } } if (pcount === 0 && ((type === "wan" && r.vlan === 1) || (type === "lan" && r.vlan === 3))) { r.vlan = 0; } f.close(); return r; } if (request.env.REQUEST_METHOD === "PUT") { configuration.prepareChanges(); if ("xlinks" in request.args) { const xs = json(request.args.xlinks); delete request.args.xlinks; const xlinks = {}; for (let i = 0; i < length(xs); i++) { xlinks[xs[i].name] = xs[i]; } let defport = "eth0"; const ports = hardware.getEthernetPorts(); if (length(ports) > 0) { defport = ports[0].k; } const ucixlinks = {}; uciMesh.foreach("xlink", "interface", x => { ucixlinks[x[".name"]] = { peer: x.peer }; }); for (let name in ucixlinks) { const x = ucixlinks[name]; const ux = xlinks[name]; if (ux) { uciMesh.set("xlink", `${name}bridge`, "vlan", ux.vlan); uciMesh.set("xlink", `${name}bridge`, "ports", [ `${ux.port || defport}:t` ]); uciMesh.set("xlink", name, "ifname", `br0.${ux.vlan}`); uciMesh.set("xlink", name, "ipaddr", ux.ipaddr); if (x.peer !== ux.peer) { uciMesh.set("xlink", name, "peer", ux.peer); if (ux.peer) { uciMesh.set("xlink", `${name}route`, "route"); uciMesh.set("xlink", `${name}route`, "interface", name); uciMesh.set("xlink", `${name}route`, "target", ux.peer); } else { uciMesh.delete("xlink", `${name}route`); } } uciMesh.set("xlink", name, "weight", ux.weight); uciMesh.set("xlink", name, "netmask", network.CIDRToNetmask(ux.cidr)); delete xlinks[name]; } else { uciMesh.delete("xlink", name); uciMesh.delete("xlink", `${name}bridge`); uciMesh.delete("xlink", `${name}route`); } } for (let name in xlinks) { const ux = xlinks[name]; uciMesh.set("xlink", `${ux.name}bridge`, "bridge-vlan"); uciMesh.set("xlink", `${ux.name}bridge`, "device", "br0"); uciMesh.set("xlink", `${ux.name}bridge`, "vlan", ux.vlan); uciMesh.set("xlink", `${ux.name}bridge`, "ports", [ `${ux.port || defport}:t` ]); uciMesh.set("xlink", name, "interface"); uciMesh.set("xlink", name, "ifname", `br0.${ux.vlan}`); uciMesh.set("xlink", name, "ipaddr", ux.ipaddr); uciMesh.set("xlink", name, "weight", ux.weight); uciMesh.set("xlink", name, "netmask", network.CIDRToNetmask(ux.cidr)); if (ux.peer) { uciMesh.set("xlink", name, "peer", ux.peer); uciMesh.set("xlink", `${ux.name}route`, "route"); uciMesh.set("xlink", `${ux.name}route`, "interface", ux.name); uciMesh.set("xlink", `${ux.name}route`, "target", ux.peer); } uciMesh.set("xlink", name, "proto", "static"); uciMesh.set("xlink", name, "macaddr", replace("x2:xx:xx:xx:xx:xx", "x", _ => sprintf("%X",math.rand()&15))); } uciMesh.commit("xlink"); } const k = keys(request.args); if (length(k) > 0) { if (length(hardware.getEthernetPorts()) > 1) { const defnet = hardware.getDefaultNetworkConfiguration(); const nets = { wan: loadPorts("wan", defnet.wan), lan: loadPorts("lan", defnet.lan), dtdlink: loadPorts("dtdlink", defnet.dtdlink) }; for (let i = 0; i < length(k); i++) { if (k[i] === "port_wan_vlan") { nets.wan.vlan = int(request.args[k[i]] || 0); } else { const ptp = split(k[i], "_"); const net = nets[ptp[1]]; if (net) { if (request.args[k[i]] === "on") { net.ports[ptp[2]] = true; } else { delete net.ports[ptp[2]]; } } } } if (nets.wan.vlan === 0) { for (let p in nets.wan.ports) { if (nets.lan.ports[p]) { delete nets.wan.ports[p]; } } } function savePort(type, c) { const f = fs.open(`/etc/aredn_include/${type}.network.user`, "w"); if (f) { f.write(`# Generated by advancednetwork config bridge-vlan option device 'br0' option vlan '${c.vlan ? c.vlan : type == "lan" ? 3 : 1}' ${join("\n", map(keys(c.ports), p => "\tlist ports '" + p + (c.vlan ? ":t'" : ":u'")))} config device option name 'br-${type}' option type 'bridge' option macaddr '<${type}_mac>' list ports 'br0.${c.vlan ? c.vlan : type == "lan" ? 3 : 1}' config interface '${type}' option device 'br-${type}' ${type === "dtdlink" ? "\toption proto 'static'" : "\toption proto '<" + type + "_proto>'"} option ipaddr '<${type}_ip>' ${type === "dtdlink" ? "\toption netmask '255.0.0.0'" : "\toption netmask '<" + type + "_mask>'"} ${type === "wan" ? "\toption gateway ''" : ""}${type === "lan" ? "\toption dns ' '" : ""} `); f.close(); } } savePort("wan", nets.wan); savePort("lan", nets.lan); savePort("dtdlink", nets.dtdlink); } } print(_R("changes")); return; } if (request.env.REQUEST_METHOD === "DELETE") { configuration.revertModalChanges(); print(_R("changes")); return; } let haveports = false; let wan_vlan = 0; const ports = hardware.getEthernetPorts(); if (length(ports) > 1) { haveports = true; const defnet = hardware.getDefaultNetworkConfiguration(); const wan = loadPorts("wan", defnet.wan); const lan = loadPorts("lan", defnet.lan); const dtdlink = loadPorts("dtdlink", defnet.dtdlink); for (let i = 0; i < length(ports); i++) { const v = { name: ports[i].k, display: ports[i].d, info: hardware.getEthernetPortInfo(ports[i].k), dtdlink: dtdlink.ports[ports[i].k], lan: lan.ports[ports[i].k], wan: wan.ports[ports[i].k] }; ports[i] = v; } wan_vlan = wan.vlan; } const xlinks = []; uciMesh.foreach("xlink", "interface", x => { push(xlinks, { name: x[".name"], vlan: int(split(x.ifname, ".")[1]), ipaddr: x.ipaddr, peer: x.peer || "", weight: int(x.weight), port: match(uciMesh.get("xlink", `${x[".name"]}bridge`, "ports")[0], /^(.+):/)[1], cidr: network.netmaskToCIDR(x.netmask) }); }); %}
{{_R("dialog-header", `${haveports ? "Ports & " : ""}XLinks`)}}
{% if (haveports) { %}
{% for (let i = 0; i < length(ports); i++) { const p = ports[i]; print(``); } %} {% for (let i = 0; i < length(ports); i++) { const p = ports[i]; print(``); } %} {% for (let i = 0; i < length(ports); i++) { const p = ports[i]; print(``); } %} {% for (let i = 0; i < length(ports); i++) { const p = ports[i]; print(``); } %}
${p.display}
dtd
vlan: 2
lan
vlan: untagged
wan
vlan:
{{_H("

AREDN nodes have three primary networks; DTD, LAN and WAN (shown above in the left column). You can modify the default assignment of these networks to ports (shown across the top) using the checkboxes at the intersection of a network and a port. You can also choose the VLAN to assign to the WAN network. Networks can be assigned to multiple ports, or no ports. Note that on some devices, ports may have names like WAN or LAN. These are just arbitrary names given by the manufacturer and you are not forced to assign networks of the same name to these ports.

Active network ports, where a cable is present and attached to another device, are shown in green.")}}


{% } %} {{_H("

XLinks provide a way of routing AREDN traffic across external non-AREDN networks. Each is created with a specific VLAN, IP address for both ends, a weight of how likely to link is to be used, and an optional network size and port (on multi-port devices). Think of xlinks as extra dtds between devices. How xlink traffic is routed once it leaves the node is dependent on the non-AREDN network, which allows for the greatest flexibility.")}}

{{_R("dialog-footer")}}