TV6XBGUBAOEZD65UD5VZAT4DD3BTKKPAXX6AJOKNE2HUODJVSIDQC (setq ula-ipv6-48 "fd74:1c54:7975")(setq global-ipv6-48 "2001:470:e10b")(setq internal-ipv4-16 "172.19")(setq outside-mac-prefix "02:8f:54:fb:58")(defun host-v6-subnet ()(cond((equal *fact-short-hostname* "aang") "6564") ;; on EDna((equal *fact-short-hostname* "zuko") "6875") ;; on HUph(true (error "don't know per-host IPv6 subnet for %s"*fact-short-hostname*))))(setq vlans(label((t true)(f false))(list(list :kw :vid :name :radvp :v6s :v4s :fwp :carpp :hostp :fib :dhcpp :dnsp);; kw vid name radv? v6 v4 fw?carp?h? fib dhcp?dns?(list :mgmt 1 "mgmt" f "fffe" 254 t t t 0 f f)(list :dmz 68 "dmz" t "0008" 68 t t t 2 t t)(list :guest 71 "guest" t "0002" 71 t t f nil t t);; the host vlan, 72, is not treated by this table.(list :inside 73 "inside" t "0009" 73 t t f nil t t)(list :local 76 "local" t "004c" 76 t t f nil t t)(list :pfsync 80 "pfsync" f nil nil t f f nil f f)(list :wan 87 "wan" f nil nil t f f nil f f))))(defun mapc-table (kwthunk table)(let ((titles (car table))(rows (cdr table)))(dolist (row rows)(let ((alis (zip titles row)))(apply kwthunk alis)))))(macro table-kw-lambda(lambda (args)`(lambda (:kw ,@(mapcar keyword-symbol (car (eval (car args))))),@(cdr args))))(macro do-table(lambda (args)`(mapc-table (table-kw-lambda ,(car args) ,@(cdr args)),(car args))));; this macro runs over all the physical interfaces, and all the vlans;; each interface uses, described by the list of all vlans above, and;; the networking arrangement described by e.g. chromebox-networking.;; e.g.;;;; (do-ifs-and-vlans chromebox-networking;; (have-fun-with if vid carpp));;;; that is to say, quite unhygienically, inside the body of this;; macro, the current interface is bound to the symbol ~if~, also its;; ~mtu~, and all the keywords from the first row of the vlan table;; are bound as symbols, e.g. name, vid, fib, etc. also, quite;; FreeBSD-ically, the interface name dot the vid is available as vif.(macro do-ifs-and-vlans(lambda (args)`(let* ((networking ,(car args))(ifs (cadr (assoc :ethernet-interfaces networking))))(dolist (if ifs)(let ((mtu (assoc-path (list :mtus if) networking))(if-on-vlan (cadr (assoc :interface-on-vlan networking))))(do-table vlans(when (if-on-vlan if vid)(let ((vif (sprintf "%s.%d" if vid))),@(cdr args)))))))))(defun vlan-ula-host (vlan-kw octet)(let ((vlan (assoc vlan-kw vlan-rows)))(eval `(multilet (,vlan-columns vlan)(sprintf "%s:%s::%s" ula-ipv6-48 v6sub octet)))))
inside="INTERFACE_inside"guest="INTERFACE_guest"host="INTERFACE_host"dmz="INTERFACE_dmz"mgmt="INTERFACE_mgmt"local="INTERFACE_local"outside="INTERFACE_wan"pfsync="INTERFACE_pfsync"wg_dmz="" # TODOcaddy_ipv4="192.168.68.51"table <mgmt> const { 172.19.254.0/24, fd74:1c54:7975:fffe::/64 }table <dmz> const { 192.168.68.0/24, fd74:1c54:7975:8::/64, 2001:470:e10b:8::/64 }table <guest> const { 192.168.71.0/24, fd74:1c54:7975:2::/64, 2001:470:e10b:2::/64 }table <collectd> const { fd74:1c54:7975:8::7079 }# this is the Epson printertable <printer> const { 192.168.71.134, fd74:1c54:7975:2:e2bb:9eff:fe1f:82f1 }table <hanuman> const { 192.168.68.71, 2001:470:e10b:8::4841 }set skip on { lo, $pfsync }set block-policy returnset syncookies adaptiveset reassemble yesnat on $outside inet from ! ($outside) to any -> ($outside)no nat on $outside inet proto udp from ! ($outside) port {domain, ntp}rdr pass on $outside proto {tcp, udp} to port https -> $caddy_ipv4rdr pass on $outside proto tcp to port http -> $caddy_ipv4 # ACMEmatch in on $outside scrub (random-id)block in allblock out allblock drop in log on $outsideblock return out log on $outside# our ULA should stay insidepass in quick on $outside inet6 proto udp from fe80::/10 to fe80::/10 port dhcpv6-serverblock in quick on $outside inet6 from fc00::/7 to anyblock out quick on $outside inet6 from any to fc00::/7pass out on $outside proto udp from port bootpc to port bootpspass out on $outside proto udp from ($outside) to port 123pass in on $host proto udp from ($host:network) to any port 123pass out on $outside proto udp from ($host:network) to any port 123# by avoiding specifying `from ($outside)`, we should allow# inet6-forwarded client traffic on these ports in one swell foop.# right?pass out on $outside proto {tcp, udp} to port httpspass out on $outside proto tcp to port httppass out on $outside proto tcp to port sshpass out on $outside proto {tcp, udp} from ($outside) to port domainpass out on $outside inet proto icmp all icmp-type echoreq# avoid advertising our routes to the outside. right?pass out on $outside inet6 proto icmp6 from ($outside) icmp6-type { unreach, toobig, timex, paramprob, echoreq, routersol, neighbrsol, neighbradv }pass in on $outside inet6 proto icmp6 to ($outside) icmp6-type { unreach, toobig, timex, paramprob, echorep, routeradv, neighbrsol, neighbradv }# see rdr passes above for incoming traffic from outside.pass out on $outside inet proto 41 to 216.66.22.2 label "HEnet 6in4"# TODO: bittorrent# TODO: ipfs# TODO: ssbpass in on $outside proto udp to ($outside) port 53280pass in on $mgmt from <mgmt>pass in on $mgmt from fe00::/7pass out on $mgmt to <mgmt>pass out on $mgmt to fe00::/7pass in on { $dmz, $guest, $inside, $local } proto udp to { ($dmz), ($guest), ($inside), ($local) } port { ntp, domain, bootps, dhcpv6-server }pass in on { $dmz, $guest, $inside, $local } proto tcp to { ($dmz), ($guest), ($inside), ($local) } port { domain }pass in on { $dmz, $guest, $inside, $local } inet6 proto icmp6 to { ($dmz), ($guest), ($inside), ($local) } icmp6-type { unreach, toobig, timex, paramprob, echorep, routersol, neighbrsol, neighbradv }pass out on { $dmz, $guest, $inside, $local } inet6 proto icmp6 icmp6-type { unreach, toobig, timex, paramprob, echorep, routeradv, neighbrsol, neighbradv }pass in on { $inside, $local, $guest } proto tcp to <dmz> port { ssh, http, https }pass in on { $inside, $local, guest } proto {tcp,udp} to <hanuman> port { ldap, kerberos, kpasswd }pass on { $inside, $local, $guest } inet proto igmp to 224.0.0.1# firefox tickle timepass in on { $inside, $local, $guest } proto udp to any port 4886pass in on $inside proto tcp to <collectd> port 1883pass out on $dmz proto tcp from ($dmz) to <collectd> port 1883# TODO: metrics from wg_dmz# no calling home, printerblock in quick on $guest from <printer> to any# octopi, printerpass in on { $inside, $local } proto tcp to <guest> port { ssh, http, https }pass in on { $inside, $local } proto tcp to <printer> port ippspass in on { $inside, $guest } proto {tcp,udp} to any port { http, https }pass in on { $inside, $guest } proto tcp to any port { ssh, ftp, 1968 }# FIXME: lock down tv >_># TODO: ssh to dmz/inside from guest
#!/bin/sh# netflow collector startup script## PROVIDE: netflow_collect# REQUIRE: NETWORK## Add the following to /etc/rc.conf[.local] to enable this service## netflow_collect_enable="YES"#. /etc/rc.subrname="netflow_collector"desc="Collect netflow data (src/dest addresses, ports, bytes) and send off"rcvar=netflow_collector_enablestart_cmd="netflow_collector_start"stop_cmd="netflow_collector_stop"netflow_collector_start(){NGCTL_SETUP_COMMANDS}netflow_collector_stop(){NGCTL_TEARDOWN_COMMANDS}run_rc_command "$1"
# See dhclient-script(8)hgt_interface=HGT_INTERFACEhgt_if_file=/var/run/dhclient/henet_gif_tunnel_interfacehgt_start () {local far_ipv4="HGT_FAR_IPV4"local near_ipv4=$new_ip_addresslocal username="HGT_USERNAME"local password="HGT_PASSWORD"local tunnelid="HGT_TUNNELID"local far_ipv6="HGT_FAR_IPV6"local near_ipv6="HGT_NEAR_IPV6"local endpoint="ipv4.tunnelbroker.net/nic/update"# when we ask for a gif to be created we get its name backlocal g=$(/sbin/ifconfig gif create)echo $g > $hgt_if_file/sbin/ifconfig $g tunnel $near_ipv4 $far_ipv4/usr/bin/fetch -o - "https://${username}:${password}@${endpoint}?hostname=${tunnelid}"/sbin/ifconfig $g inet6 ${near_ipv6}/128 $far_ipv6/sbin/route add -inet6 default $far_ipv6}hgt_stop () {local far_ipv6="HGT_FAR_IPV6"/sbin/route delete -inet6 default $far_ipv6if [ -f $hgt_if_file ]; theng=$(cat $hgt_if_file)/sbin/ifconfig $g destroyrm -f $hgt_if_filefi}# ---- active code ----if [ "$interface" = "$hgt_interface" ]; thencase "$reason" inBOUND|REBOOT) hgt_start ;;EXPIRE|FAIL) hgt_stop ;;esacfi
# See dhclient-script(8) and https://dns.he.net/docs.html.hedd_interface="HEDD_INTERFACE"hedd_ext_lookup_ipv4 () {local name="$1"local ext_dns_ip="$2"/usr/bin/host -4 $name $ext_dns_ip | /usr/bin/awk '{print $NF}'}hedd_update () {local name="HEDD_NAME"local pw="HEDD_PASSWORD"local ext_dns_ip="HEDD_EXT_DNS_IP"# WARNING: this has to be https because the password is in a query# parameterlocal url='https://dyn.dns.he.net/nic/update'local dns_says="$(hedd_ext_lookup_ipv4 $name $ext_dns_ip)"if [ "$dns_says" = "$new_ip_address" ]; then:else# What happens if this fails? ... OpenWRT's DDNS update# scripts are better developed, but also specialized for# OpenWRT's configuration and init systems. And GPLed./usr/bin/fetch -o - "${url}?hostname=${name}&password=${pw}&myip=${new_ip_address}"fi}# ---- active code ----if [ "$interface" = "$hedd_interface" ]; thencase "$reason" inBOUND|REBOOT) hedd_update ;;esacfi
NAME {host.hostname = NAME;vnet = new;vnet.interface = COMMA_DELIMITED_EPAIR_BS;path = "JAILS/NAME/root";mount.devfs;devfs_ruleset = 10;mount.fstab = "JAILS/NAME/fstab";# QCOM. see doc/stable-epair-mac.org; note a's and b'sexec.created += "ifconfig OUTSIDE_EPAIR_A ether OUTSIDE_MAC_PREFIX:0a";exec.created += "ifconfig OUTSIDE_EPAIR_B ether OUTSIDE_MAC_PREFIX:0b";exec.start = "/bin/sh /etc/rc";exec.stop = "/bin/sh /etc/rc.shutdown jail";persist;}
bogus-priv # don't forward reverse lookups for local addressesstop-dns-rebindrebind-localhost-okdomain-needed # don't forward A queries for plain nameslocal=/agrue.info/ # serve agrue.info, don't forwarddomain=agrue.info # for the DHCP serverlog-queries=extralog-dhcplog-asyncexpand-hostsdhcp-authoritativeread-ethersenable-raconf-file=/usr/local/share/dnsmasq/trust-anchors.confdnssecdnssec-check-unsignedinterface=DNS_INTERFACESbind-interfaces# enable-tftp# tftp_root /srv/tftpDHCP_IPV4_RANGESDHCP_IPV6_RANGES
notify 0 {match "system" "CARP";match "subsystem" "[0-9]+@[0-9a-z.]+";match "type" "(MASTER|BACKUP)";action "CARP_SCRIPTS_DIR/on-$subsystem-$type.sh";};
;; implements QR4N, QR4R(defun pf ()(let ((template-inputs nil)((ifs (assoc-path '(:ethernet-interfaces) chromebox-networking)))(if-on-vlan (assoc-path '(:interface-on-vlan)chromebox-networking)))(dolist (if ifs)(do-vlans(when on-firewall-p(when (interface-on-vlan if number)(push (list (sprintf "INTERFACE_%d" name)(sprintf "%s.%d" if number))template-inputs)))))(file-exists-with-entire-contents-gsubbed"/etc/pf.conf" "644" "root" "wheel""templates/pf.conf.m4"template-inputs);; on a host running 14.2-RELEASE, building a jail for 15-CURRENT,;; you can't run <={rooted /sbin/pfctl} because it links against;; some different libraries than /sbin/pfctl does. but if you;; chroot in to run it instead, then it needs device files. so we;; just use the host's pfctl to check syntax here, hoping there is;; not some new cool pf.conf syntax supported on the target.;; pf is pretty stable i think, so not too worried.(cond((probe-success "/sbin/pfctl" "-n" "-f" (*rooted* "/etc/pf.conf"))(sysrc-set "pf_enable" "YES"))(true(error "pf.conf.m4 leads to a pf.conf with a syntax error")))))
(defun firewall-jail-netflow-collect-ngctl-script (ksocket-connect-string)(let ((ifs (assoc-path '(:ethernet-interfaces) chromebox-networking))(if-for-vlan (assoc-path '(:interface-for-vlan)chromebox-networking))(ifs-to-collect '())(first-if true)(nf-port 0)(ngc '()))(dolist (if ifs)(do-vlans(when fw(push (sprintf "%s_%d" if number) ifs-to-collect))));; https://framkant.org/2019/03/collect-and-save-netflow-data-with-freebsd/(dolist (if ifs-to-collect)(cond(first-if(push (sprintf "mkpeer %s: netflow lower iface%d" if nf-port) ngc)(push (sprintf "name %s:lower netflow" if) ngc)(push (sprintf "connect %s: netflow: upper out%d" if nf-port) ngc)(setq first-if false))(true(push (sprintf "connect %s: netflow: lower iface%d" if nf-port) ngc)(push (sprintf "connect %s: netflow: upper out%d" if nf-port) ngc)));; 7 = INGRESS | EGRESS | ONCE; log both incoming and outgoing packets.(push (sprintf "msg netflow: setconfig {iface=%d conf=7}" nf-port) ngc)(setq nf-port (+ 1 nf-port)))(push "mkpeer netflow: ksocket export9 inet/dgram/udp" ngc);; export9 port: use NetFlow V9, which supports IPv6 accounting;; and multiple FIBs(push "name netflow:export9 exporter" ngc)(push "msg exporter connect %s" ksocket-connect-string)(setq ngc (nreverse ngc))ngc));; implements QNN(defun firewall-jail-netflow-collect-rc (ksocket-connect-string)(change "add netflow_collect rc.d script"(lambda ()(dir-exists "/usr/local/etc/rc.d" "755" "root" "wheel")(file-exists "/usr/local/etc/rc.d/netflow_collect""755" "root" "wheel")(file-contents-from-gsubs-template"/usr/local/etc/rc.d/netflow_collect""netflow_collect_rc.tmpl.m4"'(("NGCTL_SETUP_COMMANDS";; the command will be shellquoted in order to be;; passed to m4; we shellquote it again to be parsed;; by the shell as part of the rc script.(mapcar (lambda (cmd) (sprintf "\tngctl %s\n" (shellquote cmd)))(firewall-jail-netflow-collect-ngctl-scriptksocket-connect-string)))("NGCTL_TEARDOWN_COMMANDS""\\tngctlshutdown exporter:\n\\tngctl shutdown netflow:\n"))))nil))
;; implemets QRO4B(defun FreeBSD-devfs-firewall-jail-rules ()(file-exists-with-entire-contents"/etc/devfs.rules" "600" "root" "wheel""devfs ruleset with bpf, pf, login""\[devfsrules_firewall_jail_vnet=10]add include $devfsrules_hide_alladd include $devfsrules_unhide_basicadd include $devfsrules_unhide_loginadd path bpf* unhideadd path pf unhide"))
(defun FreeBSD-boot-fibs (n)(let ((directive (sprintf "net.fibs=%d" n)))(file-exists-with-entire-contents"/boot/loader.conf.d/fibs.conf" "644" "root" "wheel"directive directive)))(defun FreeBSD-boot-load-modules (purpose-identifier module-names)(let* ((loads (mapcar(lambda (na) (sprintf "%s_load=\"YES\"" na))module-names))(file-contents (apply string-join "\n" loads)))(file-exists-with-entire-contents(sprintf "/boot/loader.conf.d/%s.conf" purpose-identifier)"644" "root" "wheel"(sprintf "load modules: %s" (apply string-join " " module-names))file-contents)))(defun FreeBSD-devfs-firewall-jail-rules ()(file-exists-with-entire-contents"/etc/devfs.rules" "600" "root" "wheel""devfs ruleset with bpf, pf, login""\[devfsrules_firewall_jail_vnet=10]add include $devfsrules_hide_alladd include $devfsrules_unhide_basicadd include $devfsrules_unhide_loginadd path bpf* unhideadd path pf unhide"))(defun openssh-listen-only-on ips(let ((c "/etc/ssh/sshd_config")(lines(string-join "\n" (mapcar(lambda (a) (sprintf "ListenAddress %s" a))ips))))(delete-lines-matching c "^ListenAddress")(insert-line-before c lines "^#ListenAddress")))
;; how should this be written elsewhere? and for that matter the;; above? a host which contains a firewall jail must ___. that's the;; only kind of host we're talking about yet -- but it won't be long.(FreeBSD-boot-load-modules "netgraph" '("ng_ether" "ng_eiface" "ng_netflow" "ng_ksocket" "ng_vlan"))
(setq he-ddns-external-dns-ip "8.8.8.8")(setq he-ddns-columns'(kw str name password))(setq he-ddns-rows'((:ebagi "ebagi" "eatenby.agrue.info" "yvavpFv0iPpYSWq0")));; implements QROD(defun he-ddns-updating (interface unique-id)(do-table (he-ddns-columns he-ddns-rows)(file-exists-with-contents-gsubbed(sprintf "/etc/dhclient-exit-hooks.d/20-henet_ddns_%s" str)"644" "root" "wheel""templates/henet_ddns.tmpl"`(("HEDD_INTERFACE" interface)("HEDD_NAME" name)("HEDD_PASSWORD" password)("HEDD_EXT_DNS_IP" he-ddns-external-dns-ip)))))
(setq he-6in4s'(("re0.87"((:agi((:name "agi") ;; not that kind gosh(:far-ipv4 "216.66.22.2")(:ipv6-48 e"U2FsdGVkX1+z/ql2oFGgZLHS/8AB3iiW53oesBM=")(:near-ipv6 e"U2FsdGVkX19R8GZ/BwDc+KqHSi5huYvgdZ74vljVXxUM")(:far-ipv6 e"U2FsdGVkX1+p/oOSDxekiEEiFQZJRWAIgtiFiOCa/mu+")(:username e"U2FsdGVkX19hru20Wug8k5BIduP6ZA==")(:password e"U2FsdGVkX1+Ii2fpG3BRR7HZGOB1p7g9sWa2vTvgMWM=")(:tunnel-id e"U2FsdGVkX18DAOH46iqJdiCQLKjfcQ==")))))));; https://wiki.zs64.net/Tunnelbroker.net_and_dynamic_IP_address_on_FreeBSD.html;; implements QROH(defun he-gif-tunnels (interface);; We'll make a script for the tunnel, rather than using the;; gif_interfaces and gifconfig_IF rc.conf variables, so that if;; our external IPv4 address changes, we can change the tunnel to;; match.;;;; ASSUMPTION: our IPv4 address on the external interface is our;; public IPv4 address. If this firewall is behind an ISP-provided;; gateway or router, that may not be the case. But mine is;; bridged.(let ((ifs (assoc-path '(:ethernet-interfaces) chromebox-networking))(if-on-vlan (assoc-path '(:interface-on-vlan)chromebox-networking)))(dolist (if ifs)(dolist (he6 he-6in4s)(let ((v (cadr he6)))(file-exists-templated-from(sprintf "/etc/dhclient-exit-hooks.d/20-henet_gif_%s"(cadr (assoc :name v)))"644" "root" "wheel""templates/henet_gif_tunnel.tmpl"`(("HGT_INTERFACE" interface)("HGT_FAR_IPV4" (cadr (assoc :far-ipv4 v)))("HGT_USERNAME" (cadr (assoc :username v)))("HGT_PASSWORD" (cadr (assoc :password v)))("HGT_TUNNELID" (cadr (assoc :tunnel-id v)))("HGT_NEAR_IPV6" (cadr (assoc :near-ipv6 v)))("HGT_FAR_IPV6" (cadr (assoc :far-ipv6 v))))))))))
(setq chromebox-networking'((:ethernet-interfaces ("re0"))(:mtus ("re0" 9193));; all vlans live on the re0 interface(:interface-on-vlan(lambda (if vid)(cond ((equal if "re0") true)(true false))))))
(defun firewall-jail-exists (name host-v6sub carp-advskew);; create zfs filesystem;; bsdinstall jail, or untar an occambsd, or pkg-base it;; write jail.conf; vnet=new; hand ethernet interface in;; share epair72 with the host;; inside jail, configure epair72b);; implements QNV, QNJ(defun firewall-jail-vlan-setup (my-octet)(let ((mtus-set nil));; for emacs: (put 'do-ifs-and-vlans 'lisp-indent-function 1);;;;(do-ifs-and-vlans chromebox-networking;; this happens per-interface, not per-vlan, but i didn't want;; to make a do-ifs, it doesn't get rid of enough boilerplate.(when (not (member if mtus-set))(change (sprintf "set mtu for %s" if)(lambda ()(sysrc-set (sprintf "ifconfig_%s" if)(sprintf "up mtu %d" mtu))))(push if mtus-set))(when fwp;; our Ethernet adapter can do hardware VLAN tagging and;; checksums; let's let it do them(change(sprintf "add vlan %d to if %s" vid if)(lambda () (sysrc-add(sprintf "vlans_%s" if);; the leading space clues sysrc in on what;; separates values in this variable(sprintf " %d" vid)))nil)(when v6s(let* ((ula-ip (sprintf "%s:%s::%d" ula-ipv6-48 v6s my-octet))(global-ip (sprintf "%s:%s::%d" global-ipv6-48 v6s my-octet)))(change (sprintf "ULA IP for %s" vif)(lambda ()(sysrc-set (sprintf "ifconfig_%s_%d_ipv6" if vid)(sprintf "inet6 %s/%d" ula-ip 64)))nil)(change (sprintf "Global IP for %s" vif)(lambda ()(sysrc-add (sprintf "ifconfig_%s_%d_aliases" if vid)(sprintf " inet6 %s/%d" global-ip 64)))nil)))(when v4sub(let ((ipv4 (sprintf "%s.%d.%d" internal-ipv4-16 v4sub my-octet)))(change (sprintf "IPv4 for %s" vif)(lambda ()(sysrc-set (sprintf "ifconfig_%s_%d" if vid)(sprintf "inet %s/%d" ipv4 24)))nil)))(change (sprintf "add description to %s.%d" if vid)(lambda ()(sysrc-add (sprintf "ifconfig_%s_%d" if vid)(sprintf " descr %s" name))))))));; implements QA for firewall jail(defun firewall-jail-common-bits ()(change "add suitable jjl user" add-jjl nil)(change "let wheel group use doas" doas-for-wheel nil)(change "enable sshd" (lambda () (sysrc-set "sshd_enable" "YES")) nil);;(change "ssh access only via key" openssh-keys-only nil)(firewall-jail-netflow-collect-rc(sprintf "inet6/[%s]:4444" (vlan-ula-host :mgmt "6e66")))(change "enable netflow collector"(lambda () (sysrc-set "netflow_collect_enable" "YES"))nil))(defun firewall-jail (host-v6sub my-octet)(firewall-jail-common-bits)(firewall-jail-vlan-setup my-octet))
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
** DONE QA: access*** QAS: SSH via mgmt network*** QAK: Key-only SSH*** QAD: doas** QN: networking*** DONE QNV: on all VLANs*** DONE QNJ: jumbo frames*** DONE QNN: with netflow monitoring*** QNM: with mirror portsfor zeek. more netgraph conjuration, no big** [7/7] QR: routing*** [3/3] QRO: connect to outside**** QRO4: connect to outside IPv4use dhcp client***** DONE QRO4B: bpf device in jailwrite some devfs.rules**** QRO6: connect to outside IPv6**** DONE QROX: run scripts upon receiving an address**** DONE QROD: update dns.he.net DDNS**** DONE QROH: connect to he.net over 6in4*** [2/2] QR4: route IPv4**** DONE QR4N: NAT**** DONE QR4R: incoming port redirection*** DONE QR6: route IPv6(run rtadvd) (or dnsmasq)*** DONE QRF: firewall** [3/3] QC: CARP redundancyFor- Availability- Ease of change/upgrade*** DONE QCP: pfsync*** DONE QCX: run scripts upon carp state changes*** [1/1] QCO: sync outside dhcp client state**** QCOM: make the interfaces exposed to the ISP have the same MAC addressi'm not sure whether this is necessary**** QCOI: make DHCP host identifiers the same?ditto**** DONE QCOD: start/stop dhclient upon pfsync state change** [0/1] QV: Wireguard VPNs*** TODO QVT: to Tornado VPS*** QVM: to mobile devices** [5/6] QS: local servicesextra points for separate jails. since these chromeboxes are redundantthey should probably replace hanuman.*** DONE QSD: DHCPv4 and DHCPv6*** DONE QSN: DNS**** DONE QSNI: authoritative internal DNSDHCP has to update DNS; this happens over a Unix socket iirc. If yousquint, this may belong with LDAP and Kerberos.Mneh, dnsmasq for the first try.**** DONE QSNR: recursive DNS***** DONE QSNRL: logging*** TODO QST: NTP (time)Hum. A FreeBSD forum thread seems to indicate that ntpd running insidea jail can't modify the system's time. That means the physical hosthas to reach out to NTP servers on the internet. I didn't want thehost to have any internet connectivity.*** QSL: LDAP*** QSK: Kerberos** QD: detection/monitoringThis could be done on a different machine, with suitable mirror portconfig on the switch. Or maybe on the firewalls using netgraph.*** QDC: collectd stats*** QDI: ids/ips*** QDN: netflowbehold, netgraph supports it natively! just needs collection andstats. hmm.https://framkant.org/2019/03/collect-and-save-netflow-data-with-freebsd/so i just did a bunch of stuff /not/ with netgraph.**** QDNC: collector**** QDNE: exporter**** QDNS: statsnfcapd/nfdump, or SiLK*** QDZ: nsm (zeek)*** QDL: forwarded logs**** QDLS: send logs***** QDLSF: from firewalls***** QDLSH: and underlying hosts***** QDLSD: other DMZ jails***** QDLST: Tornado VPS**** QDLR: receive logs**** QDLP: process logs**** QDLI: index logs**** QDLV: view logs** QZ: Routing speedfor fast traffic to dmz (nextcloud, photoprism, jellyfin). with asingle GbE you can only theoretically get 500Mbps routed. not sure howto get more wired network interfaces in a chromebox. usb3 i guess.later.** QB: Build/update process*** DONE QBH: configure host*** DONE QBJ: create jail image*** DONE QBC: make jail.conf*** QBR: make a repo accessible to mgmt*** QBS: sign upgrades and verify signatures*** QBU: automate upgrades- detect new version- fetch new version- only run on BACKUP node- wait until PRIMARY is certified ok- verify upgrade signature- stop jail- snap jail- remove jail files- untar new version- start jailNot automated: promoting BACKUP node, testing function, moving on toupgrade the other node.*** QBX: delete old snapshots*** QBI: make install media
;; implements QSD, QSN, QSNI, QSNR, QR6(defun dnsmasq ()(package-installed "dnsmasq")(let ((ipv4-ranges nil)(ipv6-ranges nil)(no-dhcp-interfaces nil)(dns-listen-interfaces nil)(v6opts '("ra-stateless" "ra-names"))(template-inputs nil)(ifs (assoc-path '(:ethernet-interfaces) chromebox-networking))(if-on-vlan (assoc-path '(:interface-on-vlan)chromebox-networking)))(dolist (if ifs)(do-vlans(if dnsmasq-dhcp-p(progn(let ((min 100) (max 250))(push (sprintf "dhcp-range=set:%s,%s.%d.%d,%s.%d.%d"nameinternal-ipv4-16 v4sub mininternal-ipv4-16 v4sub max)ipv4-ranges))(push (sprintf "dhcp-range=set:%s,::,constructor:%s%s"name (sprintf "%s.%d" if number)(strcat "," (apply string-join "," v6opts)))ipv6-ranges))(push (sprintf "%s.%d" if number) no-dhcp-interfaces))(when dnsmasq-dns-p(push (sprintf "%s.%d" if number) dns-listen-interfaces))))(file-exists-with-contents-gsubbed"/usr/local/etc/dnsmasq.conf" "644" "root" "wheel""templates/dnsmasq.conf.m4"`(("DHCP_IPV4_RANGES" ,(apply string-join "\n" ipv4-ranges))("DHCP_IPV6_RANGES" ,(apply string-join "\n" ipv6-ranges))("NO_DHCP_INTERFACES" ,(apply string-join "," no-dhcp-interfaces))("DNS_INTERFACES" ,(apply string-join "," dns-listen-interfaces))))(sysrc-set "dnsmasq_enable" "YES")(file-exists-with-text-copied-from"/etc/hosts" "644" "root" "wheel" "files/hosts")(file-exists-with-text-copied-from"/etc/ethers" "644" "root" "wheel" "files/ethers")))
;; implements QROX(defun set-up-dhclient-hooks ();; ... I'm not going to do the /usr/local/etc treatment for this.;; This dhclient is part of the base system and our configuration;; for it is too.(dir-exists "/etc/dhclient-enter-hooks.d" "755" "root" "wheel")(dir-exists "/etc/dhclient-exit-hooks.d" "755" "root" "wheel");; note: these scripts may need to run without /usr/bin being;; mounted first(file-exists-with-entire-contents"/etc/dhclient-enter-hooks" "644" "root" "wheel""source everything in /etc/dhclient-enter-hooks.d""\for x in $(/bin/ls /etc/dhclient-enter-hooks.d/* 2>/dev/null); do. $xdone")(file-exists-with-entire-contents"/etc/dhclient-exit-hooks" "644" "root" "wheel""source everything in /etc/dhclient-exit-hooks.d""\for x in $(/bin/ls /etc/dhclient-exit-hooks.d/* 2>/dev/null); do. $xdone"));; implements QCOD;; implements QRO4, i think(defun dhcp-only-on-carp-primary (vhid interface);; this default configuration starts dhclient when the link goes up;; and assumes the link will go down and so does not stop dhclient(file-does-not-exist "/etc/devd/dhclient.conf")(carp-state-change-scriptvhid interface "MASTER" "dhclient"(sprintf "service dhclient quietstart %s\n" interface))(carp-state-change-scriptvhid interface "BACKUP" "dhclient"(sprintf "service dhclient quietstop %s\n" interface)))
(setq carp-passphrases'((:mgmt e"U2FsdGVkX1/wRrkK7cLv1djnzBtddFDqjda6M+/DgO25w6JE2PRPDVaFux9UVJIpHAtcupLOcM0PhQ==")(:dmz e"U2FsdGVkX1+X2Ji0/CCfvHiuB6KOrUl1Hu0BifTVCCc8mxyvbNviogU/2rZ5hoJZfMaULxtaCgOFppCoRSzJRg==")(:guest e"U2FsdGVkX1/4COXc+mEGZBvVTnMzQpvE5NUz04brb6+t0ikIl5CwKalrt/3qnpc8oHn9rMCa5x187PzR")(:inside e"U2FsdGVkX1/avLdcg1Ri7XymD3aD1gXiDp3ethsD9gVd8Elgoh4zPsUbcRJWTLIJ8oA2VGqPeHBtk1k=")(:local e"U2FsdGVkX1+KK2hsz/LBivFGlLK37q5Iqg8C//M4eNtOktQqVvlCJmtneM0mYLIahOF61Urlxw==")));; implements QC(defun carp-ips-configured (my-octet carp-octet advskew)(do-ifs-and-vlans chromebox-networking(when (and fwp carpp)(let ((my-addr(sprintf "%s:%s::%s" ula-ipv6-48 v6sub my-octet))(shared-addr(sprintf "%s:%s::%s" ula-ipv6-48 v6sub carp-octet))(passphrase (cadr (assoc kw carp-passphrases))));; As far as I read, the CARPing happens with multicast by;; default and I don't need to address the individual;; members of my CARP pair. But everybody's tutorial has;; the CARP address as an alias on an interface with a;; per-host address as its primary: the FreeBSD Handbook,;; the CARP article by Mariusz Zaborski, and this other;; person from earlier this year.;;;; https://docs.freebsd.org/en/books/handbook/advanced-networking/#carp;;;; https://freebsdfoundation.org/wp-content/uploads/2022/11/zaborski_CARP.pdf;;;; https://www.subnetspider.com/2025/01/22/building-a-fault-tolerant-reverse-proxy-with-freebsd.html(sysrc-add (sprintf "ifconfig_%s_%d_aliases" if vid)(sprintf " vhid %d advskew %d pass %s alias %s/%d"vid advskew(unsafe-reveal passphrase)shared-addr))))))(setq carp-script-dir "/usr/local/etc/carp-scripts");; implements QCX(defun run-scripts-on-carp-state-changes (vhid interface)(dir-exists carp-script-dir "755" "root" "wheel")(file-exists-with-entire-contents-gsubbed ;; because of all the "'s therein"/etc/devd/carp.conf" "644" "root" "wheel""templates/carp_devd.conf.m4"`(("CARP_SCRIPTS_DIR" ,carp-script-dir)))(let ((subsys (sprintf "%d@%s" vhid interface)))(dolist (state '("MASTER" "BACKUP"))(let* ((event (sprintf "on-%s-%s" subsys state))(this-event-dir(sprintf "%s/%s.d" carp-script-dir event))(this-event-runner-script(sprintf "%s/%s.sh" carp-script-dir event)))(dir-exists this-event-dir "755" "root" "wheel")(file-exists-with-entire-contentsthis-event-runner-script "755" "root" "wheel"(sprintf "run everything in %s" this-event-dir);; this file's contents, by contrast with the above, don't;; have anything we need to escape when expressing as a;; string literal here.(sprintf "\#!/bin/shfor f in $(ls '%s'); do $f; done" this-event-dir)))));; and now, make scripts like, for example,;; /usr/local/etc/carp-scripts/on-87@re0.87-BACKUP.d/dhcp, which;; will run when we become the BACKUP for the :wan vlan's carp ip;; (which has vhid 87 and belongs to interface re0.87).);; you make such scripts using this function.(defun carp-state-change-script (vhid interface state name contents)(let* ((subsys (sprintf "%d@%s" vhid interface))(event (sprintf "on-%s-%s" subsys state))(event-dir (path-join carp-script-dir (strcat event ".d")))(script-filename (path-join event-dir identifier)))(file-exists-with-entire-contentsscript-filename "755" "root" "wheel"(sprintf "when %s goes to %s, deal with aspect %s" subsys state name))));; implements QCP(defun pfsync ()(let ((ifs (assoc-path '(:ethernet-interfaces) chromebox-networking))(if-for-vlan (assoc-path '(:interface-for-vlan)chromebox-networking)))(dolist (if ifs)(do-vlans(when (and fw (eq kw :pfsync))(sysrc-set "pfsync_enable" "YES")(sysrc-set "pfsync_syncdev" if)(sysrc-set "pfsync_syncpeer" "ff12::f0"))))))