• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/procd/jail/uxc-net

  1 #!/usr/bin/ucode -R
  2 
  3 let fs = require("fs");
  4 let ubus = require("ubus");
  5 let uci = require("uci");
  6 
  7 let fnv1a = function(s) {
  8         let h = 2166136261, i;
  9         for (i = 0; i < length(s); i++) {
 10                 h ^= ord(s, i);
 11                 h = (h * 16777619) & 0xffffffff;
 12         }
 13         return h;
 14 };
 15 let slug = function(name) { return sprintf("%08x", fnv1a(name)); };
 16 
 17 let host_veth    = function(name) { return "vh-" + slug(name); };
 18 let cont_veth    = function(name) { return "vc-" + slug(name); };
 19 let bh_host_veth = function(name) { return "bh-" + slug(name); };
 20 let bh_cont_veth = function(name) { return "bc-" + slug(name); };
 21 let bh_bridge    = function(id)   { return "bhr-" + slug(id); };
 22 
 23 let accif    = function(name) { return "ca" + slug(name); };
 24 let gwif     = function(name) { return "cg" + slug(name); };
 25 let bhif     = function(name) { return "cb" + slug(name); };
 26 let bhseg    = function(id)   { return "cs" + slug(id); };
 27 let contzone = function(name) { return "cz" + slug(name); };
 28 
 29 let injail_dir = "/tmp/run/uxc-net";
 30 let injail_path = function(name) { return injail_dir + "/" + name + ".network"; };
 31 let shared_lock_path = injail_dir + "/.shared.lock";
 32 
 33 let loopback_section = "config interface 'loopback'\n" +
 34         "\toption device 'lo'\n" +
 35         "\toption proto 'static'\n" +
 36         "\toption ipaddr '127.0.0.1'\n" +
 37         "\toption netmask '255.0.0.0'\n\n";
 38 
 39 let call = function(object, method, data) {
 40         ubus.call({ object: object, method: method, data: data });
 41         let err = ubus.error();
 42         if (err)
 43                 warn(sprintf("uxc-net: ubus %s.%s failed: %s\n", object, method, err));
 44         return err ? -1 : 0;
 45 };
 46 
 47 let pkg_reload = function(pkg) {
 48         ubus.call({ object: "service", method: "event",
 49                     data: { type: "config.change", data: { package: pkg } } });
 50 };
 51 
 52 let quiet_call = function(object, method, data) {
 53         ubus.call({ object: object, method: method, data: data });
 54         return ubus.error() ? -1 : 0;
 55 };
 56 
 57 let network_reload = function() {
 58         return call("network", "reload", {});
 59 };
 60 
 61 let autonet_service = "uxc-net";
 62 
 63 let icmpv6_types = [
 64         "echo-request", "echo-reply", "destination-unreachable",
 65         "packet-too-big", "time-exceeded", "bad-header",
 66         "unknown-header-type", "router-solicitation",
 67         "neighbour-solicitation", "router-advertisement",
 68         "neighbour-advertisement",
 69 ];
 70 
 71 let sidecar_path = function(name) { return "/tmp/run/uvol/.meta/uxc/" + name + ".annotations"; };
 72 
 73 let state_dir = "/tmp/run/uvol/.meta/uxc/state";
 74 let macstore_dir  = function(name) { return state_dir + "/" + name; };
 75 let macstore_path = function(name) { return macstore_dir(name) + "/macaddrs"; };
 76 
 77 let rand_mac = function() {
 78         let f = fs.open("/dev/urandom", "r");
 79         if (!f)
 80                 return null;
 81         let b = f.read(6);
 82         f.close();
 83         if (type(b) != "string" || length(b) != 6)
 84                 return null;
 85         return sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
 86                 (ord(b, 0) & 0xfc) | 0x02,
 87                 ord(b, 1), ord(b, 2), ord(b, 3), ord(b, 4), ord(b, 5));
 88 };
 89 
 90 let ensure_macs = function(name, roles) {
 91         let store = {}, out = {}, changed = false, raw, s, r, m;
 92 
 93         raw = fs.readfile(macstore_path(name));
 94         if (raw) {
 95                 try { s = json(raw); } catch (e) { s = null; }
 96                 if (type(s) == "object")
 97                         store = s;
 98         }
 99 
100         for (r in roles) {
101                 if (!store[r]) {
102                         m = rand_mac();
103                         if (m) {
104                                 store[r] = m;
105                                 changed = true;
106                         }
107                 }
108                 out[r] = store[r];
109         }
110 
111         if (changed) {
112                 fs.mkdir(state_dir, 0700);
113                 fs.mkdir(macstore_dir(name), 0700);
114                 let f = fs.open(macstore_path(name), "w");
115                 if (f) {
116                         f.write(sprintf("%J\n", store));
117                         f.close();
118                 }
119         }
120 
121         return out;
122 };
123 
124 let with_macs = function(dev, m, hostrole, peerrole) {
125         if (m[hostrole])
126                 dev.macaddr = m[hostrole];
127         if (m[peerrole])
128                 dev.peer_macaddr = m[peerrole];
129         return dev;
130 };
131 
132 let veth_device = function(vc, m, hostrole, peerrole) {
133         return with_macs({ type: "veth", peer_name: vc }, m, hostrole, peerrole);
134 };
135 
136 let jailed_interface = function(name, vc, jail_device, zone) {
137         let ifc = { proto: "none", device: vc, jail: name, jail_device: jail_device };
138         if (zone)
139                 ifc.zone = zone;
140         return ifc;
141 };
142 
143 let read_annotations = function(bundle, name) {
144         let ann = {}, raw, side, cfg, s, k, v;
145 
146         if (bundle) {
147                 raw = fs.readfile(bundle + "/config.json");
148                 if (!raw)
149                         return null;
150                 try { cfg = json(raw); } catch (e) { return null; }
151                 ann = cfg?.annotations ?? {};
152         }
153 
154         side = fs.readfile(sidecar_path(name));
155         if (side) {
156                 try { s = json(side); } catch (e) { s = null; }
157                 if (type(s) == "object")
158                         for (k, v in s)
159                                 ann[k] = v;
160         }
161 
162         return ann;
163 };
164 
165 let parse_attach = function(ann) {
166         let attach = ann["org.openwrt.network.attach"];
167         if (attach == null || attach == "" || attach == "none")
168                 return { kind: "none" };
169         if (attach == "host")
170                 return { kind: "host" };
171         if (attach == "routed")
172                 return { kind: "routed" };
173 
174         let m = match(attach, /^bridged:([A-Za-z0-9_]+)$/);
175         if (m)
176                 return { kind: "bridged", network: m[1] };
177 
178         return { kind: "unknown", raw: attach };
179 };
180 
181 let parse_backhaul = function(ann) {
182         let id = ann["org.openwrt.network.backhaul"];
183         if (id == null || id == "")
184                 return null;
185         return { id: id, address: ann["org.openwrt.network.backhaul-address"] };
186 };
187 
188 let iface_status = function(iface) {
189         let r = ubus.call({ object: "network.interface." + iface, method: "status", data: {} });
190         if (ubus.error() || type(r) != "object")
191                 return null;
192         return r;
193 };
194 
195 let uci_network_exists = function(net) {
196         let cursor = uci.cursor();
197 
198         if (!cursor || !cursor.load("network"))
199                 return false;
200         return cursor.get("network", net) != null;
201 };
202 
203 let device_status = function(dev) {
204         let d = ubus.call({ object: "network.device", method: "status", data: { name: dev } });
205 
206         if (ubus.error() || type(d) != "object")
207                 return null;
208         return d;
209 };
210 
211 let device_is_bridge = function(dev) {
212         return type(device_status(dev)?.["bridge-members"]) == "array";
213 };
214 
215 let bridge_vlan_ids = function(br) {
216         return map(device_status(br)?.["bridge-vlans"] ?? [], function(v) { return v.id; });
217 };
218 
219 let bridge_port = function(br, pvid, vlans) {
220         let port = { device: br }, flags = [], have, v;
221 
222         if (pvid != null)
223                 push(flags, pvid + ":*");
224         if (length(vlans))
225                 have = bridge_vlan_ids(br);
226         for (v in vlans) {
227                 if (v.vid == pvid) {
228                         warn(sprintf("uxc-net: VLAN %d on '%s' is already the network's own VLAN\n", v.vid, br));
229                         return null;
230                 }
231                 if (index(have, v.vid) < 0) {
232                         warn(sprintf("uxc-net: bridge '%s' has no VLAN %d\n", br, v.vid));
233                         return null;
234                 }
235                 push(flags, v.vid + (v.tagged ? ":t" : ""));
236         }
237         if (length(flags))
238                 port.vlans = flags;
239         return port;
240 };
241 
242 let bridge_of = function(net, status, vlans) {
243         let dev = status.device, m;
244 
245         if (type(dev) != "string") {
246                 warn(sprintf("uxc-net: network '%s' has no device to attach to\n", net));
247                 return null;
248         }
249         if (device_is_bridge(dev))
250                 return bridge_port(dev, null, vlans);
251 
252         m = match(dev, /^(.+)\.([0-9]+)$/);
253         if (m && device_is_bridge(m[1]))
254                 return bridge_port(m[1], +m[2], vlans);
255 
256         warn(sprintf("uxc-net: device '%s' of network '%s' is not a bridge\n", dev, net));
257         return null;
258 };
259 
260 
261 let append_injail = function(name, section) {
262         let existed, f;
263         fs.mkdir(injail_dir, 0755);
264         existed = !!fs.stat(injail_path(name));
265         f = fs.open(injail_path(name), "a");
266         if (!f)
267                 return;
268         if (!existed)
269                 f.write(loopback_section);
270         f.write(section);
271         f.close();
272 };
273 
274 let render_section = function(iface, device, proto, opts) {
275         let s = "config interface '" + iface + "'\n" +
276                 "\toption device '" + device + "'\n" +
277                 "\toption proto '" + proto + "'\n", k, v;
278 
279         for (k, v in opts)
280                 s += "\toption " + k + " '" + v + "'\n";
281         return s;
282 };
283 
284 let injail_proto = function(ann, kind) {
285         let proto = ann["org.openwrt.network.proto"];
286 
287         if (proto == null || proto == "")
288                 return kind == "routed" ? "static" : "dhcp";
289         if (proto == "static")
290                 return proto;
291         if (proto == "dhcp") {
292                 if (kind != "routed")
293                         return proto;
294                 warn("uxc-net: routed cannot use proto 'dhcp': the /31 gateway link has no DHCP server\n");
295                 return null;
296         }
297         warn(sprintf("uxc-net: unsupported org.openwrt.network.proto '%s'\n", proto));
298         return null;
299 };
300 
301 let bridged_static_opts = function(ann) {
302         let addr = ann["org.openwrt.network.address"], opts;
303 
304         if (addr == null || index(addr, "/") < 0) {
305                 warn("uxc-net: bridged proto 'static' needs org.openwrt.network.address as address/prefix\n");
306                 return null;
307         }
308         opts = { ipaddr: addr };
309         if (ann["org.openwrt.network.gateway"])
310                 opts.gateway = ann["org.openwrt.network.gateway"];
311         if (ann["org.openwrt.network.dns"])
312                 opts.dns = ann["org.openwrt.network.dns"];
313         return opts;
314 };
315 
316 let injail_proto6 = function(ann) {
317         let proto6 = ann["org.openwrt.network.proto6"];
318 
319         if (proto6 == null || proto6 == "" || proto6 == "none")
320                 return "none";
321         if (proto6 == "dhcpv6" || proto6 == "slaac" || proto6 == "static")
322                 return proto6;
323         warn(sprintf("uxc-net: unsupported org.openwrt.network.proto6 '%s'\n", proto6));
324         return null;
325 };
326 
327 let injail6_section = function(ann) {
328         let proto6 = injail_proto6(ann);
329         let ifaceid = ann["org.openwrt.network.ip6ifaceid"];
330         let addr6 = ann["org.openwrt.network.address6"];
331         let opts = {};
332 
333         if (!proto6)
334                 return null;
335         if (proto6 == "none")
336                 return "";
337 
338         if (proto6 == "static") {
339                 if (addr6 == null || index(addr6, "/") < 0) {
340                         warn("uxc-net: proto6 'static' needs org.openwrt.network.address6 as address/prefix\n");
341                         return null;
342                 }
343                 opts.ip6addr = addr6;
344                 if (ann["org.openwrt.network.gateway6"])
345                         opts.ip6gw = ann["org.openwrt.network.gateway6"];
346                 return render_section("lan6", "eth0", "static", opts);
347         }
348 
349         if (proto6 == "slaac") {
350                 opts.reqaddress = "none";
351                 opts.reqprefix = "no";
352         }
353         if (ifaceid) {
354                 if (!match(ifaceid, /^::/)) {
355                         warn(sprintf("uxc-net: org.openwrt.network.ip6ifaceid '%s' must have a zero network part\n", ifaceid));
356                         return null;
357                 }
358                 opts.ip6ifaceid = ifaceid;
359         }
360         return render_section("lan6", "eth0", "dhcpv6", opts);
361 };
362 
363 let render_vlan_device = function(parent, vid) {
364         return "config device\n" +
365                 "\toption type '8021q'\n" +
366                 "\toption name '" + parent + "." + vid + "'\n" +
367                 "\toption ifname '" + parent + "'\n" +
368                 "\toption vid '" + vid + "'\n";
369 };
370 
371 let injail_vlan_section = function(ann, vid) {
372         let addr = ann["org.openwrt.network.vlan." + vid + ".address"];
373         let proto = "none", opts = {};
374 
375         if (addr != null && addr != "") {
376                 if (index(addr, "/") < 0) {
377                         warn(sprintf("uxc-net: org.openwrt.network.vlan.%d.address must be address/prefix\n", vid));
378                         return null;
379                 }
380                 proto = "static";
381                 opts.ipaddr = addr;
382         }
383         return render_vlan_device("eth0", vid) +
384                render_section("vlan" + vid, "eth0." + vid, proto, opts);
385 };
386 
387 let injail_vlan_sections = function(ann, vlans) {
388         let out = "", s, v;
389 
390         for (v in vlans) {
391                 if (!v.tagged)
392                         continue;
393                 s = injail_vlan_section(ann, v.vid);
394                 if (s == null)
395                         return null;
396                 out += s;
397         }
398         return out;
399 };
400 
401 let csv = function(val) {
402         let list = [], i, parts;
403         if (type(val) != "string")
404                 return list;
405         parts = split(val, /[ ,]+/);
406         for (i = 0; i < length(parts); i++)
407                 if (parts[i] != "")
408                         push(list, parts[i]);
409         return list;
410 };
411 
412 let parse_vlans = function(ann) {
413         let list = [], vids = [], tok, m, vid, tagged, i;
414 
415         for (tok in csv(ann["org.openwrt.network.vlans"])) {
416                 m = match(tok, /^([0-9]+)(:t)?$/);
417                 vid = m ? +m[1] : 0;
418                 if (vid < 1 || vid > 4094) {
419                         warn(sprintf("uxc-net: bad org.openwrt.network.vlans entry '%s'\n", tok));
420                         return null;
421                 }
422                 tagged = m[2] != null;
423                 i = index(vids, vid);
424                 if (i >= 0 && list[i].tagged == tagged)
425                         continue;
426                 if (i >= 0) {
427                         warn(sprintf("uxc-net: VLAN %d is listed both tagged and untagged in org.openwrt.network.vlans\n", vid));
428                         return null;
429                 }
430                 push(vids, vid);
431                 push(list, { vid: vid, tagged: tagged });
432         }
433         return list;
434 };
435 
436 let zone_of = function(token) {
437         let m = match(token, /^(vpn|container):([A-Za-z0-9_.]+)$/);
438         if (m)
439                 return m[1] == "container" ? contzone(m[2]) : m[2];
440         return token;
441 };
442 
443 
444 let ip2int = function(s) {
445         let p = split(s ?? "", ".");
446         if (length(p) != 4)
447                 return null;
448         return (+p[0] * 16777216) + (+p[1] * 65536) + (+p[2] * 256) + (+p[3]);
449 };
450 
451 let range_add = function(ranges, ipint, plen) {
452         let size, start;
453         plen = +plen;
454         if (ipint == null || plen < 0 || plen > 32)
455                 return;
456         size = (plen == 0) ? 4294967296 : (1 << (32 - plen));
457         start = ipint - (ipint % size);
458         push(ranges, [ start, start + size - 1 ]);
459 };
460 
461 let range_add_cidr = function(ranges, cidr) {
462         let p = split(cidr ?? "", "/");
463         range_add(ranges, ip2int(p[0]), p[1] ?? 32);
464 };
465 
466 let published_ranges = function(ranges, shared) {
467         let ifc, a;
468         for (ifc in values(shared.interfaces))
469                 for (a in ifc.ipaddr ?? [])
470                         range_add_cidr(ranges, a);
471 };
472 
473 let used_ranges = function(shared) {
474         let ranges = [];
475         let d = ubus.call({ object: "network.interface", method: "dump", data: {} });
476         let list = (type(d) == "object" && type(d.interface) == "array") ? d.interface : [];
477         for (let intf in list) {
478                 for (let a in intf["ipv4-address"] ?? [])
479                         range_add(ranges, ip2int(a.address), a.mask);
480                 for (let r in intf.route ?? [])
481                         if (r.target != "0.0.0.0")
482                                 range_add(ranges, ip2int(r.target), r.mask);
483         }
484         published_ranges(ranges, shared);
485         return ranges;
486 };
487 
488 let pick_subnet = function(shared) {
489         let used = used_ranges(shared);
490         let avail = function(prefix) {
491                 let base = ip2int(prefix + ".0");
492                 for (let r in used)
493                         if (base <= r[1] && r[0] <= base + 255)
494                                 return false;
495                 return true;
496         };
497         for (let x = 16; x <= 254; x++)
498                 if (avail(sprintf("192.168.%d", x)))
499                         return sprintf("192.168.%d", x);
500         for (let a = 16; a <= 31; a++)
501                 for (let x = 0; x <= 255; x++)
502                         if (avail(sprintf("172.%d.%d", a, x)))
503                                 return sprintf("172.%d.%d", a, x);
504         for (let a = 0; a <= 255; a++)
505                 for (let x = 0; x <= 255; x++)
506                         if (avail(sprintf("10.%d.%d", a, x)))
507                                 return sprintf("10.%d.%d", a, x);
508         return null;
509 };
510 
511 
512 let shared_lock = function() {
513         let f;
514 
515         fs.mkdir(injail_dir, 0755);
516         f = fs.open(shared_lock_path, "w");
517         if (!f || !f.lock("x")) {
518                 warn("uxc-net: cannot lock the shared network state\n");
519                 return null;
520         }
521         return f;
522 };
523 
524 let shared_unlock = function(f) {
525         if (!f)
526                 return;
527         f.lock("u");
528         f.close();
529 };
530 
531 let shared_read = function() {
532         let d = ubus.call({ object: "service", method: "get_data", data: { name: autonet_service } });
533         let s = {}, cur;
534 
535         if (!ubus.error() && type(d) == "object")
536                 cur = d[autonet_service]?.["*"];
537         if (type(cur) == "object")
538                 s = cur;
539 
540         return {
541                 present: type(d?.[autonet_service]) == "object",
542                 devices: type(s["network-device"]) == "object" ? s["network-device"] : {},
543                 interfaces: type(s["network-interface"]) == "object" ? s["network-interface"] : {},
544         };
545 };
546 
547 let shared_empty = function(shared) {
548         return !length(shared.devices) && !length(shared.interfaces);
549 };
550 
551 let collect_devices = function(inst, names) {
552         let dev;
553 
554         if (type(inst?.["network-device"]) != "object")
555                 return;
556         for (dev in keys(inst["network-device"]))
557                 names[dev] = true;
558 };
559 
560 let published_devices = function() {
561         let d = ubus.call({ object: "service", method: "get_data",
562                             data: { type: "network-device" } });
563         let names = {}, svc_name, svc, inst_name, inst;
564 
565         if (ubus.error() || type(d) != "object")
566                 return names;
567 
568         for (svc_name, svc in d) {
569                 if (type(svc) != "object")
570                         continue;
571                 for (inst_name, inst in svc) {
572                         if (svc_name == autonet_service && inst_name == "*")
573                                 continue;
574                         collect_devices(inst, names);
575                 }
576         }
577 
578         return names;
579 };
580 
581 let shared_prune = function(shared, live) {
582         let dead = [], br, dev, ifc_name, ifc;
583 
584         for (br, dev in shared.devices) {
585                 if (dev.type != "bridge")
586                         continue;
587                 dev.ports = filter(dev.ports ?? [], function(p) { return live[p] == true; });
588                 if (!length(dev.ports))
589                         push(dead, br);
590         }
591         for (br in dead)
592                 delete shared.devices[br];
593 
594         dead = [];
595         for (ifc_name, ifc in shared.interfaces)
596                 if (!shared.devices[ifc.device])
597                         push(dead, ifc_name);
598         for (ifc_name in dead)
599                 delete shared.interfaces[ifc_name];
600 };
601 
602 let shared_bridge_ensure = function(shared, br, ipv6) {
603         let dev = shared.devices[br];
604 
605         if (dev)
606                 return dev;
607         dev = { type: "bridge" };
608         if (ipv6 == false)
609                 dev.ipv6 = false;
610         dev.bridge_empty = true;
611         dev.ports = [];
612         shared.devices[br] = dev;
613         return dev;
614 };
615 
616 let shared_add_port = function(shared, br, port) {
617         let dev = shared.devices[br];
618 
619         if (index(dev.ports, port) < 0)
620                 push(dev.ports, port);
621         sort(dev.ports);
622 };
623 
624 let autonet_nets = function(shared) {
625         let nets = [], n, ifc;
626 
627         for (n, ifc in shared.interfaces)
628                 if (ifc.zone == n)
629                         push(nets, n);
630         return sort(nets);
631 };
632 
633 let autonet_specs = function(nets) {
634         let fw = [], dh = [], net;
635 
636         for (net in nets) {
637                 push(fw, {
638                         type: "zone",
639                         name: net,
640                         network: [ net ],
641                         input: "ACCEPT",
642                         output: "ACCEPT",
643                         forward: "REJECT",
644                 });
645                 push(dh, {
646                         type: "dhcp",
647                         interface: net,
648                         start: 100,
649                         limit: 150,
650                         leasetime: "12h",
651                         dhcpv4: "server",
652                 });
653         }
654 
655         return { firewall: fw, dhcp: dh };
656 };
657 
658 let shared_write = function(shared, before) {
659         let specs, data;
660 
661         if (shared_empty(shared)) {
662                 if (!shared.present)
663                         return 0;
664                 quiet_call("service", "delete", { name: autonet_service });
665                 return 1;
666         }
667 
668         if (sprintf("%J", [ shared.devices, shared.interfaces ]) == before)
669                 return 0;
670 
671         specs = autonet_specs(autonet_nets(shared));
672         data = {
673                 "network-device": shared.devices,
674                 "network-interface": shared.interfaces,
675                 firewall: specs.firewall,
676                 dhcp: specs.dhcp,
677         };
678         if (call("service", "set", { name: autonet_service, data: data }))
679                 return -1;
680         return 1;
681 };
682 
683 let shared_snapshot = function(shared) {
684         if (shared_empty(shared))
685                 return null;
686         return sprintf("%J", [ shared.devices, shared.interfaces ]);
687 };
688 
689 let autonet_create = function(shared, net, vh) {
690         let br = "br-" + net;
691         let subnet = pick_subnet(shared);
692 
693         if (!subnet) {
694                 warn(sprintf("uxc-net: no free subnet for network '%s'\n", net));
695                 return -1;
696         }
697 
698         shared_bridge_ensure(shared, br, null);
699         shared_add_port(shared, br, vh);
700         shared.interfaces[net] = {
701                 proto: "static",
702                 device: br,
703                 ipaddr: [ subnet + ".1/24" ],
704                 ip6assign: 64,
705                 ip6ifaceid: "::1",
706                 force_link: true,
707                 zone: net,
708         };
709         return 0;
710 };
711 
712 let spec_new = function() {
713         return { devices: {}, interfaces: {}, bridge_ports: {}, firewall: [], dhcp: [] };
714 };
715 
716 let instance_publish = function(name, spec) {
717         return call("service", "set_data", { name: name, instance: name, data: {
718                 "network-device": spec.devices,
719                 "network-interface": spec.interfaces,
720                 "bridge-port": spec.bridge_ports,
721                 firewall: spec.firewall,
722                 dhcp: spec.dhcp,
723         } });
724 };
725 
726 let instance_withdraw = function(name) {
727         return quiet_call("service", "set_data", { name: name, instance: name, data: {} });
728 };
729 
730 let instance_published = function(name) {
731         let d = ubus.call({ object: "service", method: "get_data",
732                             data: { name: name, instance: name } });
733         let inst;
734 
735         if (ubus.error() || type(d) != "object")
736                 return false;
737         inst = d[name]?.[name];
738         return type(inst) == "object" && length(inst) > 0;
739 };
740 
741 
742 let bridged_up = function(name, ann, attach, m, spec, shared, owned) {
743         let net = attach.network;
744         let vh = host_veth(name), vc = cont_veth(name);
745         let proto = injail_proto(ann, "bridged"), opts = {};
746         let section6 = injail6_section(ann);
747         let vlans = parse_vlans(ann);
748         let vlan_sections, on_demand, status, br;
749 
750         if (!proto || section6 == null || vlans == null)
751                 return 1;
752         vlan_sections = injail_vlan_sections(ann, vlans);
753         if (vlan_sections == null)
754                 return 1;
755         if (proto == "static") {
756                 opts = bridged_static_opts(ann);
757                 if (!opts)
758                         return 1;
759         }
760 
761         if (ann["org.openwrt.network.egress"] || ann["org.openwrt.network.ingress"])
762                 warn("uxc-net: bridged takes no egress/ingress (L2 inherits the joined network's zone); ignoring\n");
763 
764         on_demand = shared.interfaces[net] != null || index(owned, net) >= 0 || !uci_network_exists(net);
765         if (on_demand && length(vlans)) {
766                 warn(sprintf("uxc-net: network '%s' is created on demand and has no VLANs to attach to\n", net));
767                 return 1;
768         }
769 
770         if (shared.interfaces[net]) {
771                 shared_add_port(shared, "br-" + net, vh);
772         } else if (on_demand) {
773                 if (autonet_create(shared, net, vh))
774                         return 1;
775         } else {
776                 status = iface_status(net);
777                 if (!status) {
778                         warn(sprintf("uxc-net: network '%s' is not known to netifd\n", net));
779                         return 1;
780                 }
781                 br = bridge_of(net, status, vlans);
782                 if (!br)
783                         return 1;
784                 spec.bridge_ports[vh] = br;
785         }
786 
787         spec.devices[vh] = veth_device(vc, m, "h", "c");
788         spec.interfaces[accif(name)] = jailed_interface(name, vc, "eth0", net);
789 
790         append_injail(name, render_section("lan", "eth0", proto, opts));
791         if (section6 != "")
792                 append_injail(name, section6);
793         if (vlan_sections != "")
794                 append_injail(name, vlan_sections);
795 
796         return 0;
797 };
798 
799 
800 let routed_subnet = function(name, ann) {
801         let h = 0, i, a, b, c;
802 
803         for (i = 0; i < length(name); i++)
804                 h = (h * 31 + ord(name, i)) & 0xffffff;
805         a = 1 + ((h & 0xff) % 254);
806         b = (h >> 8) & 0xff;
807         c = ((h >> 16) & 0x7f) * 2;
808 
809         return {
810                 gw_cidr:   ann["org.openwrt.network.gateway"] ?? sprintf("10.%d.%d.%d/31", a, b, c + 1),
811                 container: ann["org.openwrt.network.address"] ?? sprintf("10.%d.%d.%d", a, b, c),
812         };
813 };
814 
815 let fw_specs = function(name, ann, net, proto6) {
816         let czone = contzone(name);
817         let list = [], i, m, sub;
818 
819         push(list, {
820                 type: "zone",
821                 name: czone,
822                 network: [ gwif(name) ],
823                 input: "DROP",
824                 output: "ACCEPT",
825                 forward: "DROP",
826         });
827 
828         sub = csv(ann["org.openwrt.network.egress"]);
829         for (i = 0; i < length(sub); i++)
830                 push(list, {
831                         type: "forwarding",
832                         src: czone,
833                         dest: zone_of(sub[i]),
834                 });
835 
836         sub = csv(ann["org.openwrt.network.ingress"]);
837         for (i = 0; i < length(sub); i++) {
838                 m = match(sub[i], /^([A-Za-z0-9_:]+):(tcp|udp)\/([0-9]+(-[0-9]+)?)$/);
839                 if (!m) {
840                         warn(sprintf("uxc-net: ignoring bad ingress '%s'\n", sub[i]));
841                         continue;
842                 }
843                 push(list, {
844                         type: "redirect",
845                         name: czone + "-in" + i,
846                         src: zone_of(m[1]),
847                         dest: czone,
848                         proto: m[2],
849                         src_dport: m[3],
850                         dest_ip: net.container,
851                         dest_port: m[3],
852                         target: "DNAT",
853                 });
854         }
855 
856         sub = csv(ann["org.openwrt.network.host"]);
857         for (i = 0; i < length(sub); i++) {
858                 m = match(sub[i], /^(tcp|udp)\/([0-9]+(-[0-9]+)?)$/);
859                 if (!m) {
860                         warn(sprintf("uxc-net: ignoring bad host port '%s'\n", sub[i]));
861                         continue;
862                 }
863                 push(list, {
864                         type: "rule",
865                         name: czone + "-host" + i,
866                         src: czone,
867                         proto: m[1],
868                         dest_port: m[2],
869                         target: "ACCEPT",
870                 });
871         }
872 
873         push(list, {
874                 type: "rule",
875                 name: czone + "-dns",
876                 src: czone,
877                 proto: [ "tcp", "udp" ],
878                 dest_port: "53",
879                 target: "ACCEPT",
880         });
881 
882         if (proto6 != "none") {
883                 push(list, {
884                         type: "rule",
885                         name: czone + "-icmpv6",
886                         src: czone,
887                         proto: "icmp",
888                         family: "ipv6",
889                         icmp_type: icmpv6_types,
890                         limit: "1000/sec",
891                         target: "ACCEPT",
892                 });
893         }
894 
895         if (proto6 == "dhcpv6") {
896                 push(list, {
897                         type: "rule",
898                         name: czone + "-dhcpv6",
899                         src: czone,
900                         proto: "udp",
901                         family: "ipv6",
902                         dest_port: "547",
903                         target: "ACCEPT",
904                 });
905         }
906 
907         return list;
908 };
909 
910 let dhcp_specs = function(name) {
911         return [ {
912                 type: "dhcp",
913                 interface: gwif(name),
914                 ra: "server",
915                 dhcpv6: "server",
916         } ];
917 };
918 
919 let routed_up = function(name, ann, m, spec) {
920         let czone = contzone(name);
921         let net = routed_subnet(name, ann);
922         let vh = host_veth(name), vc = cont_veth(name);
923         let gw_ip = split(net.gw_cidr, "/")[0];
924         let proto = injail_proto(ann, "routed");
925         let section6 = injail6_section(ann);
926 
927         if (!proto || section6 == null)
928                 return 1;
929 
930         spec.devices[vh] = veth_device(vc, m, "h", "c");
931         spec.interfaces[gwif(name)] = {
932                 proto: "static",
933                 device: vh,
934                 ipaddr: [ net.gw_cidr ],
935                 ip6assign: 64,
936                 ip6ifaceid: "::1",
937                 force_link: true,
938                 zone: czone,
939         };
940         spec.interfaces[accif(name)] = jailed_interface(name, vc, "eth0", czone);
941         spec.firewall = fw_specs(name, ann, net, injail_proto6(ann));
942         spec.dhcp = dhcp_specs(name);
943 
944         append_injail(name, render_section("lan", "eth0", proto, {
945                 ipaddr: net.container,
946                 netmask: "255.255.255.254",
947                 gateway: gw_ip,
948                 dns: gw_ip,
949         }));
950         if (section6 != "")
951                 append_injail(name, section6);
952 
953         return 0;
954 };
955 
956 let backhaul_up = function(name, bh, m, spec, shared) {
957         let br = bh_bridge(bh.id);
958         let iface = bhseg(bh.id);
959         let vh = bh_host_veth(name), vc = bh_cont_veth(name);
960 
961         shared_bridge_ensure(shared, br, false);
962         shared_add_port(shared, br, vh);
963         if (!shared.interfaces[iface])
964                 shared.interfaces[iface] = { proto: "none", device: br };
965 
966         spec.devices[vh] = veth_device(vc, m, "bh", "bc");
967         spec.interfaces[bhif(name)] = jailed_interface(name, vc, "bh0", null);
968 
969         if (bh.address)
970                 append_injail(name, render_section("backhaul", "bh0", "static", {
971                         ipaddr: bh.address,
972                         netmask: "255.255.255.0",
973                 }));
974 
975         return 0;
976 };
977 
978 
979 let shared_reconcile = function() {
980         let lock = shared_lock(), shared, before, changed;
981 
982         if (!lock)
983                 return 0;
984         shared = shared_read();
985         before = shared_snapshot(shared);
986         shared_prune(shared, published_devices());
987         changed = shared_write(shared, before);
988         shared_unlock(lock);
989         return changed;
990 };
991 
992 let instance_rollback = function(name) {
993         instance_withdraw(name);
994         shared_reconcile();
995 };
996 
997 let do_up = function(name, bundle) {
998         let attach, bh, ann, roles, m, spec, shared, owned, before, lock, rc, written;
999 
1000         if (!bundle) {
1001                 warn("uxc-net: 'up' needs a bundle path\n");
1002                 return 1;
1003         }
1004 
1005         ann = read_annotations(bundle, name);
1006         if (ann == null) {
1007                 warn("uxc-net: cannot read annotations from " + bundle + "/config.json\n");
1008                 return 1;
1009         }
1010 
1011         attach = parse_attach(ann);
1012         bh = parse_backhaul(ann);
1013 
1014         fs.unlink(injail_path(name));
1015 
1016         if (attach.kind == "unknown") {
1017                 warn(sprintf("uxc-net: attach '%s' not implemented\n", attach.raw ?? attach.kind));
1018                 return 1;
1019         }
1020         if (attach.kind != "bridged" && attach.kind != "routed" && !bh)
1021                 return 0;
1022 
1023         roles = [];
1024         if (attach.kind == "bridged" || attach.kind == "routed") {
1025                 push(roles, "h");
1026                 push(roles, "c");
1027         }
1028         if (bh) {
1029                 push(roles, "bh");
1030                 push(roles, "bc");
1031         }
1032         m = ensure_macs(name, roles);
1033         spec = spec_new();
1034 
1035         lock = shared_lock();
1036         if (!lock)
1037                 return 1;
1038         shared = shared_read();
1039         owned = autonet_nets(shared);
1040         before = shared_snapshot(shared);
1041         shared_prune(shared, published_devices());
1042 
1043         rc = 0;
1044         if (attach.kind == "bridged")
1045                 rc = bridged_up(name, ann, attach, m, spec, shared, owned);
1046         else if (attach.kind == "routed")
1047                 rc = routed_up(name, ann, m, spec);
1048         if (!rc && bh)
1049                 rc = backhaul_up(name, bh, m, spec, shared);
1050         if (!rc && shared_write(shared, before) < 0)
1051                 rc = 1;
1052         written = !rc;
1053         if (!rc && instance_publish(name, spec))
1054                 rc = 1;
1055         shared_unlock(lock);
1056         if (!rc && network_reload())
1057                 rc = 1;
1058         if (rc) {
1059                 if (written)
1060                         instance_rollback(name);
1061                 return 1;
1062         }
1063 
1064         pkg_reload("firewall");
1065         pkg_reload("dhcp");
1066 
1067         return 0;
1068 };
1069 
1070 let do_down = function(name) {
1071         let had, changed;
1072 
1073         fs.unlink(injail_path(name));
1074 
1075         had = instance_published(name);
1076         if (had)
1077                 instance_withdraw(name);
1078 
1079         changed = shared_reconcile();
1080 
1081         if (!had && changed <= 0)
1082                 return 0;
1083 
1084         network_reload();
1085         pkg_reload("firewall");
1086         pkg_reload("dhcp");
1087 
1088         return 0;
1089 };
1090 
1091 let name = ARGV[0];
1092 let action = ARGV[1];
1093 let bundle = ARGV[2];
1094 
1095 if (!name || !action) {
1096         warn("usage: uxc-net <name> <up|down> [bundle]\n");
1097         exit(22);
1098 }
1099 
1100 if (action == "up")
1101         exit(do_up(name, bundle));
1102 else if (action == "down")
1103         exit(do_down(name));
1104 
1105 warn(sprintf("uxc-net: unknown action '%s'\n", action));
1106 exit(22);

This page was automatically generated by LXR 0.3.1.  •  OpenWrt