{% /* * 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 */ %} {% if (request.env.REQUEST_METHOD === "PUT") { configuration.prepareChanges(); if ("tunnel_server" in request.args) { uciMesh.set("vtun", "@network[0]", "dns", request.args.tunnel_server); uciMesh.commit("vtun"); } if ("tunnel_range_start" in request.args) { uciMesh.set("vtun", "@network[0]", "start", request.args.tunnel_range_start); uciMesh.commit("vtun"); } if ("tunnel_weight" in request.args) { uciMesh.set("aredn", "@tunnel[0]", "weight", request.args.tunnel_weight); uciMesh.commit("aredn"); } if ("tunnels" in request.args) { const tunnels = json(request.args.tunnels); const found = { ls: {}, lc: {}, ws: {}, wc: {} }; for (let i = 0; i < length(tunnels); i++) { const t = tunnels[i]; found[t.type][t.index] = true; switch (t.type) { case "wc": { if (!uciMesh.get("vtun", t.index)) { uciMesh.set("vtun", t.index, "server"); } uciMesh.set("vtun", t.index, "enabled", t.enabled ? "1" : "0"); uciMesh.set("vtun", t.index, "host", t.name); uciMesh.set("vtun", t.index, "passwd", t.passwd); const np = split(t.network, ":"); const n = iptoarr(np[0]); uciMesh.set("vtun", t.index, "node", `${uc(substr(configuration.getName(), 0, 23))}-${n[0]}-${n[1]}-${n[2]}-${n[3]}:${np[1]}`); uciMesh.set("vtun", t.index, "clientip", `${n[0]}.${n[1]}.${n[2]}.${n[3] + 1}`); uciMesh.set("vtun", t.index, "serverip", `${n[0]}.${n[1]}.${n[2]}.${n[3] + 2}`); uciMesh.set("vtun", t.index, "netip", t.network); uciMesh.set("vtun", t.index, "weight", t.weight); uciMesh.set("vtun", t.index, "contact", t.notes); break; } case "lc": { if (!uciMesh.get("vtun", t.index)) { uciMesh.set("vtun", t.index, "server"); } uciMesh.set("vtun", t.index, "enabled", t.enabled ? "1" : "0"); uciMesh.set("vtun", t.index, "host", t.name); uciMesh.set("vtun", t.index, "passwd", t.passwd); const n = iptoarr(t.network); uciMesh.set("vtun", t.index, "node", `${uc(substr(configuration.getName(), 0, 23))}-${n[0]}-${n[1]}-${n[2]}-${n[3]}`); uciMesh.set("vtun", t.index, "clientip", `${n[0]}.${n[1]}.${n[2]}.${n[3] + 1}`); uciMesh.set("vtun", t.index, "serverip", `${n[0]}.${n[1]}.${n[2]}.${n[3] + 2}`); uciMesh.set("vtun", t.index, "netip", t.network); uciMesh.set("vtun", t.index, "weight", t.weight); uciMesh.set("vtun", t.index, "contact", t.notes); break; } case "ws": { if (!uciMesh.get("wireguard", t.index)) { uciMesh.set("wireguard", t.index, "client"); } uciMesh.set("wireguard", t.index, "enabled", t.enabled ? "1" : "0"); uciMesh.set("wireguard", t.index, "name", t.name); uciMesh.set("wireguard", t.index, "key", t.key); uciMesh.set("wireguard", t.index, "clientip", t.network); uciMesh.set("wireguard", t.index, "weight", t.weight); uciMesh.set("wireguard", t.index, "contact", t.notes); break; } case "ls": { if (!uciMesh.get("vtun", t.index)) { uciMesh.set("vtun", t.index, "client"); } uciMesh.set("vtun", t.index, "enabled", t.enabled ? "1" : "0"); uciMesh.set("vtun", t.index, "passwd", t.passwd); uciMesh.set("vtun", t.index, "name", t.name); const n = iptoarr(t.network); uciMesh.set("vtun", t.index, "node", `${uc(substr(t.name, 0, 23))}-${n[0]}-${n[1]}-${n[2]}-${n[3]}`); uciMesh.set("vtun", t.index, "clientip", `${n[0]}.${n[1]}.${n[2]}.${n[3] + 1}`); uciMesh.set("vtun", t.index, "serverip", `${n[0]}.${n[1]}.${n[2]}.${n[3] + 2}`); uciMesh.set("vtun", t.index, "netip", t.network); uciMesh.set("vtun", t.index, "weight", t.weight); uciMesh.set("vtun", t.index, "contact", t.notes); break; } default: break; } } uciMesh.foreach("vtun", "server", a => { if (!found.lc[a[".name"]] && !found.wc[a[".name"]]) { uciMesh.delete("vtun", a[".name"]); } }); uciMesh.foreach("vtun", "client", a => { if (!found.ls[a[".name"]]) { uciMesh.delete("vtun", a[".name"]); } }); uciMesh.foreach("wireguard", "client", a => { if (!found.ws[a[".name"]]) { uciMesh.delete("wireguard", a[".name"]); } }); uciMesh.commit("vtun"); uciMesh.commit("wireguard"); } print(_R("changes")); return; } if (request.env.REQUEST_METHOD === "DELETE") { configuration.revertModalChanges(); print(_R("changes")); return; } const available = { l: {}, w: {} }; const l = iptoarr(uciMesh.get("vtun", "@network[0]", "start")); const w = [ l[0], l[1], (l[2] === 255 ? 0 : l[2] + 1), l[3] ]; for (let i = 0; i < 62; i++) { available.l[`${l[0]}.${l[1]}.${l[2]}.${l[3]}`] = 1; l[3] += 4; if (l[3] >= 252) { l[2]++; if (l[2] === 256) { l[2] = 0; } l[3] = 4; } } let p = uciMesh.get("aredn", "@supernode[0]", "enable") === 1 ? 6526 : 5525; for (let i = 0; i < 126; i++) { available.w[`${w[0]}.${w[1]}.${w[2]}.${w[3]}:${p}`] = 1; w[3] += 2; if (w[3] >= 254) { w[2]++; if (w[2] === 256) { w[2] = 0; } w[3] = 2; } p++; } const up = { lg: {}, wg: [] }; let f = fs.popen("ps -w | grep vtun | grep ' tun '"); if (f) { for (let l = f.read("line"); length(l); l = f.read("line")) { const m = match(l, /.*:.*-(172-.*) tun tun/); if (m) { up.lg[replace(m[1], "-", ".")] = true; } } f.close(); } const handshaketime = time(); f = fs.popen("/usr/bin/wg show all latest-handshakes"); if (f) { for (let l = f.read("line"); length(l); l = f.read("line")) { const m = split(l, /\t/); if (m && int(m[2]) + 300 > handshaketime) { push(up.wg, m[1]); } } f.close(); } const tunnels = []; uciMesh.foreach("vtun", "server", a => { const wireguard = index(a.node, ":") !== -1; push(tunnels, { index: a[".name"], type: wireguard ? "wc" : "lc", name: a.host, enabled: a.enabled === "1", notes: a.contact || "", network: a.netip, passwd: a.passwd, weight: a.weight ?? "", up: wireguard ? (length(filter(up.wg, v => index(a.key, v) !== -1)) === 0 ? false : true) : (up.lg[a.netip] ? true : false) }); delete available.l[a.clientip]; delete available.w[a.clientip]; }); uciMesh.foreach("vtun", "client", a => { const n = iptoarr(a.netip); const remove = `-${n[0]}-${n[1]}-${n[2]}-${n[3]}`; push(tunnels, { index: a[".name"], type: "ls", name: replace(a.name, remove, ""), enabled: a.enabled === "1", notes: a.contact || "", network: a.netip, passwd: a.passwd, weight: a.weight ?? "", up: up.lg[a.netip] ? true : false }); delete available.l[a.clientip]; }); uciMesh.foreach("wireguard", "client", a => { push(tunnels, { index: a[".name"], type: "ws", name: a.name, enabled: a.enabled === "1", notes: a.contact || "", network: a.clientip, key: a.key, passwd: replace(a.key, /^[A-Za-z0-9+\/]+=/, ""), weight: a.weight ?? "", up: length(filter(up.wg, v => index(a.key, v) !== -1)) === 0 ? false : true }); delete available.w[a.clientip]; }); function t2t(type) { switch (type) { case "ws": return "Wireguard
Server"; case "wc": return "Wireguard
Client"; case "ls": return "Legacy
Server"; case "lc": return "Legacy
Client"; default: return type; } } %}
{{_R("dialog-header", "Tunnels")}}
Tunnel Server
DNS name of this tunnel server
{{_H("Set either the hostname or the Internet IP Address for this tunnel server. This is the target for any tunnels created which will connect to this node (legacy or wireguard).")}}
Add tunnel
Add a tunnel from a template
{{_H("Create a tunnel by selecting the specific type and hitting the +. The tunnel configuration will auto-populate with as much information as possible. For tunnel clients all the fields can be easily populated by copy-n-pasting the entire server configuration from another node into any field.")}}
{% for (let i = 0; i < length(tunnels); i++) { const t = tunnels[i]; const client = t.type === "lc" || t.type === "wc" ? true : false; const wireguard = t.type === "ws" || t.type === "wc" ? true : false; %}
{{t2t(t.type)}}
{% if (t.type === "ws") { %} {% } %}
{{client ? '' : ''}}
{% } %}
{{_R("dialog-advanced")}}
{% if (includeAdvanced) { %}
Tunnel Server Network
IP range to use for tunnel connections
{% if (uciMesh.get("aredn", "@supernode[0]", "enable") === "1") { %} {% } else { %} {% } %}
{% if (uciMesh.get("aredn", "@supernode[0]", "enable") === "1") { %} {{_H("The range of IP addresses allocated to the tunnels. Tunnels always start 172.30")}} {% } else { %} {{_H("The range of IP addresses allocated to the tunnels. Tunnels always start 172.31")}} {% } %} {% if (uciMesh.get("aredn", "@supernode[0]", "enable") !== "1") { %}
Default Tunnel Weight
Default cost of using a tunnel
{{_H("The tunnel weight is the cost of routing traffic via a tunnel. The higher the weight, the less likely a tunnel is used to reach a destination. This value is used by default, but each tunnel may overide it. ")}} {% } %} {% } %}
{{_R("dialog-footer")}}