Skip to content

Commit d9fc7a9

Browse files
committed
tunspace: add support for static uplink IP address
This commit: - switches between static IP and DHCP based on the presence of the "uplink_ipv4" config value. - makes reading the configuration more robust against missing values. By frequently refreshing its DHCP lease, TunSpace stresses DHCP servers with faulty configurations. For example, Telekom Speedport modems completely lock up and permanently stop handing out DHCP leases to clients. Another example is a DHCP server that always hands out new IP addresses, even though TunSpace only requests a renew of its existing IP address, which leads to fast exhaustion of the address pool.
1 parent 54d7374 commit d9fc7a9

File tree

1 file changed

+56
-32
lines changed

1 file changed

+56
-32
lines changed

packages/tunspace/tunspace.uc

+56-32
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ function load_config(name) {
1818
let ts = ctx.get_all(name, "tunspace");
1919
let cfg = {
2020
"debug": int(ts.debug) != 0,
21-
"uplink_netns": ""+ts.uplink_netns,
22-
"uplink_ifname": ""+ts.uplink_ifname,
23-
"uplink_mode": ""+ts.uplink_mode,
21+
"uplink_netns": type(ts.uplink_netns) ? ts.uplink_netns : "",
22+
"uplink_ifname": type(ts.uplink_ifname) ? ts.uplink_ifname : "",
23+
"uplink_mode": type(ts.uplink_mode) ? ts.uplink_mode : "",
24+
"uplink_ipv4": type(ts.uplink_ipv4) ? ts.uplink_ipv4 : "",
25+
"uplink_gateway": type(ts.uplink_gateway) ? ts.uplink_gateway : "",
2426
"maintenance_interval": int(ts.maintenance_interval),
2527
"wireguard_servers": {},
2628
"wireguard_interfaces": {},
@@ -30,19 +32,21 @@ function load_config(name) {
3032
};
3133

3234
ctx.foreach(name, "wg-server", function(c) {
33-
cfg.wireguard_servers[""+c.name] = {
34-
"name": ""+c.name,
35-
"url": ""+c.url,
35+
let name = type(c.name) ? c.name : "";
36+
cfg.wireguard_servers[name] = {
37+
"name": name,
38+
"url": type(c.url) ? c.url : "",
3639
"insecure_cert": int(c.insecure_cert) != 0,
3740
"disabled": int(c.disabled) != 0,
3841
};
3942
});
4043

4144
ctx.foreach(name, "wg-interface", function(c) {
42-
cfg.wireguard_interfaces[""+c.ifname] = {
43-
"ifname": ""+c.ifname,
44-
"ipv6": ""+c.ipv6,
45-
"ipv4": ""+c.ipv4,
45+
let ifname = type(c.ifname) ? c.ifname : "";
46+
cfg.wireguard_interfaces[ifname] = {
47+
"ifname": ifname,
48+
"ipv6": type(c.ipv6) ? c.ipv6 : "",
49+
"ipv4": type(c.ipv4) ? c.ipv4 : "",
4650
"mtu": int(c.mtu),
4751
"port": int(c.port),
4852
"disabled": int(c.disabled) != 0,
@@ -382,12 +386,47 @@ function wireguard_maintenance(st, cfg) {
382386
}
383387
}
384388

389+
function uplink_dhcp(netns, netnsifname) {
390+
// if we already have an IP, we'll try to renew it.
391+
// some routers will otherwise give us a different new IP, exhausting the IP pool.
392+
let p = fs.popen("ip -j -n "+netns+" a s "+netnsifname);
393+
let out = p.read("all");
394+
p.close();
395+
if (out == null) {
396+
log("unable to read current ip address of "+netnsifname)
397+
}
398+
let reqip = "0.0.0.0";
399+
let iplist = json(out);
400+
for (ipobj in iplist) {
401+
for (ipaddr in ipobj.addr_info) {
402+
if (ipaddr.family == "inet" && ipaddr.scope == "global") {
403+
reqip = ipaddr.local;
404+
}
405+
}
406+
}
407+
408+
// try dhcp for 5 seconds
409+
shell_command("ip netns exec "+netns+" udhcpc -f -n -q -A 5 -i "+netnsifname+" -r "+reqip+" -s /usr/share/tunspace/udhcpc.script 2>&1 | grep 'ip addr add'");
410+
}
411+
412+
function uplink_static(netns, netnsifname, ipv4, gw) {
413+
shell_command("ip -n "+netns+" addr show dev "+netnsifname+" | grep -F '"+ipv4+"' >/dev/null || ip -n "+netns+" addr add "+ipv4+" dev "+netnsifname);
414+
shell_command("ip -n "+netns+" route show default dev "+netnsifname+" | grep -F '"+gw+"' >/dev/null || ip -n "+netns+" route add default via "+gw);
415+
}
416+
385417
// TODO: ts_uplink interface leaks into default namespace when uplink namespace is deleted
386-
function uplink_maintenance(nsid, netns, ifname, mode) {
418+
function uplink_maintenance(cfg) {
387419
let netnsifname = UPLINK_NETNS_IFNAME;
388420

421+
let netns = cfg.uplink_netns;
422+
let ifname = cfg.uplink_ifname;
423+
let mode = cfg.uplink_mode;
424+
let ipv4 = cfg.uplink_ipv4;
425+
let gw = cfg.uplink_gateway;
426+
389427
if (interface_exists(netnsifname)) {
390-
// the uplink interface will sometimes leak out of the namespace on shutdown
428+
// the uplink interface will sometimes leak out of the namespace on shutdown.
429+
// in that case we'll just reuse it.
391430
shell_command("ip link set "+netnsifname+" netns "+netns);
392431
}
393432

@@ -411,27 +450,12 @@ function uplink_maintenance(nsid, netns, ifname, mode) {
411450
return false;
412451
}
413452

414-
// if we already have an IP, we'll try to renew it.
415-
// some routers will otherwise give us a different new IP, exhausting the IP pool.
416-
let p = fs.popen("ip -j -n "+netns+" a s "+netnsifname);
417-
let out = p.read("all");
418-
p.close();
419-
if (out == null) {
420-
log("unable to read current ip address of "+netnsifname)
421-
}
422-
let reqip = "0.0.0.0";
423-
let iplist = json(out);
424-
for (ipobj in iplist) {
425-
for (ipaddr in ipobj.addr_info) {
426-
if (ipaddr.family == "inet" && ipaddr.scope == "global") {
427-
reqip = ipaddr.local;
428-
}
429-
}
453+
if (length(ipv4) > 0) {
454+
uplink_static(netns, netnsifname, ipv4, gw);
455+
} else {
456+
uplink_dhcp(netns, netnsifname);
430457
}
431458

432-
// try dhcp for 5 seconds
433-
shell_command("ip netns exec "+netns+" udhcpc -f -n -q -A 5 -i "+netnsifname+" -r "+reqip+" -s /usr/share/tunspace/udhcpc.script 2>&1 | grep 'ip addr add'");
434-
435459
return true;
436460
}
437461

@@ -460,7 +484,7 @@ function boot(st, cfg) {
460484
function tick(st, cfg) {
461485
debug("tick");
462486

463-
if (!uplink_maintenance(st.nsid, cfg.uplink_netns, cfg.uplink_ifname, cfg.uplink_mode)) {
487+
if (!uplink_maintenance(cfg)) {
464488
log("uplink maintenance failed");
465489
}
466490
wireguard_maintenance(st, cfg);

0 commit comments

Comments
 (0)