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