# shellcheck shell=sh # # Die Brücke über die primäre Netzkarte — Erkennen, Sichern, Zeitgeber, Bauen, # Nachsehen. # # --------------------------------------------------------------------------- # Warum das eine eigene Datei ist # --------------------------------------------------------------------------- # # Diese Zeilen standen in `network.sh`, geschrieben für den stillgelegten # Rettungssystem-Weg, und borgten sich vier Dinge von woanders: # `detect_primary_interface` aus `proxmox.sh`, `log`, `http_get` und # `CLUPILOT_PROBE_URL` aus `clupilot-bootstrap.sh`. Der Debian-Weg lädt sie # einzeln auf den Host und fährt sie dort — geborgte Helfer wären dann nicht da. # # Also allein lauffähig. `network.sh` behält seine WireGuard- und # nftables-Hälfte; diese Datei wird von beiden Wegen benutzt und existiert genau # einmal. Eine zweite Fassung wären zwei Installationen, die bei jeder # Proxmox-Version nachgezogen werden müssten — und die zweite fiele erst auf, # wenn jemand sie benutzt. # # --------------------------------------------------------------------------- # Warum aus dem LAUFENDEN Zustand abgeleitet wird # --------------------------------------------------------------------------- # # Nicht aus `/etc/network/interfaces`. Was läuft, ist bei jedem Anbieter gleich # strukturiert; wie es aufgeschrieben wurde, nicht. Das ist der Teil, der # Hetzner und netcup zugleich trägt. # --------------------------------------------------------------------------- # Stellschrauben — überschreibbar, damit die Bibliothek ohne Host prüfbar ist # --------------------------------------------------------------------------- CLUPILOT_BRIDGE="${CLUPILOT_BRIDGE:-vmbr0}" CLUPILOT_WORK_DIR="${CLUPILOT_WORK_DIR:-/var/lib/clupilot/bridge}" CLUPILOT_INTERFACES_FILE="${CLUPILOT_INTERFACES_FILE:-/etc/network/interfaces}" CLUPILOT_INTERFACES_D="${CLUPILOT_INTERFACES_D:-/etc/network/interfaces.d}" CLUPILOT_SYS_NET="${CLUPILOT_SYS_NET:-/sys/class/net}" CLUPILOT_IP="${CLUPILOT_IP:-ip}" CLUPILOT_IFRELOAD="${CLUPILOT_IFRELOAD:-ifreload}" CLUPILOT_PROBE_URL="${CLUPILOT_PROBE_URL:-http://deb.debian.org/}" CLUPILOT_ROLLBACK_UNIT="${CLUPILOT_ROLLBACK_UNIT:-clupilot-network-rollback}" CLUPILOT_NET_BACKUP="${CLUPILOT_NET_BACKUP:-/var/lib/clupilot/interfaces.vor-der-bruecke}" CLUPILOT_WG_HUB_PUBKEY="${CLUPILOT_WG_HUB_PUBKEY:-}" CLUPILOT_WG_HANDSHAKE_MAX_AGE="${CLUPILOT_WG_HANDSHAKE_MAX_AGE:-180}" # --------------------------------------------------------------------------- # Die zwei Helfer, die diese Datei früher geborgt hat # --------------------------------------------------------------------------- # # Unter Definitionsschutz: `clupilot-bootstrap.sh` bringt eigene mit, und wer # zuerst da ist, behält recht. Sonst überschriebe das Laden dieser Bibliothek # die Protokollierung des Rettungssystem-Wegs. if ! command -v log >/dev/null 2>&1; then log() { printf '%s %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$1" } fi if ! command -v http_get >/dev/null 2>&1; then http_get() { if command -v curl >/dev/null 2>&1; then curl -fsSL --retry 3 --retry-delay 2 --max-time 20 "$1" else wget -qO- --timeout=20 "$1" fi } fi # --------------------------------------------------------------------------- # Feststellen, was da ist # --------------------------------------------------------------------------- # Die Schnittstelle, über die die Vorgabe-Route geht. Nicht „die erste, die # nicht lo heißt": eine Maschine mit zwei Karten hat oft eine angeschlossene und # eine nicht. detect_primary_interface() { "$CLUPILOT_IP" -4 route show default 2>/dev/null \ | awk '{ for (i = 1; i < NF; i++) if ($i == "dev") { print $(i+1); exit } }' } # Ist das eine physische Karte? # # `bridge_ports` auf einem Bond oder einer bestehenden Bridge ist falsch — die # Brücke nähme sich ihren eigenen Unterbau als Port, und was dabei herauskommt, # ist von hier aus nicht mehr zu reparieren. VLAN-Geräte und Bonds haben keinen # `device`-Verweis; die beiden weiteren Prüfungen sind der Gürtel dazu. interface_is_physical() { _if="${1:-}" [ -n "$_if" ] || return 1 [ -e "${CLUPILOT_SYS_NET}/${_if}/device" ] || return 1 [ ! -d "${CLUPILOT_SYS_NET}/${_if}/bridge" ] || return 1 [ ! -d "${CLUPILOT_SYS_NET}/${_if}/bonding" ] || return 1 return 0 } # Kam die Adresse per DHCP? # # Aus dem laufenden Zustand: der Kernel markiert eine geleaste Adresse mit # `dynamic`. Das steht bei jedem Anbieter gleich da — anders als die Datei, in # die er es geschrieben hat. address_is_dynamic() { "$CLUPILOT_IP" -4 -o addr show dev "$1" scope global 2>/dev/null \ | grep -q '[[:space:]]dynamic[[:space:]]' } # Wie der Anbieter das Netz aufzieht. Eine Brücke, die für den einen Fall # richtig ist, nimmt den anderen vom Netz. # # - `dhcp` — die Adresse kommt per DHCP (Cloud-Produkte). # - `routed` — geroutete Einzeladresse, Gateway AUSSERHALB des eigenen Subnetzes # (Hetzner-dediziert mit /32). Braucht eine pointopoint-Route, # sonst findet der Kernel das Gateway nicht. # - `subnet` — gewöhnliches Subnetz, Gateway darin. detect_network_style() { _iface="${1:-}" [ -n "$_iface" ] || _iface="$(detect_primary_interface)" # Laufender Zustand zuerst. Die Datei des Anbieters ist nur das Zweitsignal # — sie sagt, was jemand aufgeschrieben hat, nicht was gilt. if address_is_dynamic "$_iface"; then printf 'dhcp' return 0 fi if grep -qsE "iface[[:space:]]+${_iface}[[:space:]]+inet[[:space:]]+dhcp" \ "$CLUPILOT_INTERFACES_FILE" "${CLUPILOT_INTERFACES_D}"/* 2>/dev/null; then printf 'dhcp' return 0 fi _cidr="$("$CLUPILOT_IP" -4 -o addr show dev "$_iface" scope global 2>/dev/null | awk '{ print $4; exit }')" _gw="$("$CLUPILOT_IP" -4 route show default 2>/dev/null | awk '{ print $3; exit }')" _prefix="${_cidr##*/}" # /32 heißt: das eigene Subnetz besteht aus der eigenen Adresse. Ein Gateway # darin kann es nicht geben. if [ "$_prefix" = '32' ]; then printf 'routed' return 0 fi # Liegt das Gateway im eigenen Subnetz? `ip route get` beantwortet das, ohne # dass dieses Skript Netzmasken rechnen muss — und rechnet dabei mit # derselben Logik, die der Kernel später anwendet. if [ -n "$_gw" ] && "$CLUPILOT_IP" -4 route get "$_gw" 2>/dev/null | grep -q "dev ${_iface}.*src"; then printf 'subnet' return 0 fi printf 'routed' } bridge_exists() { "$CLUPILOT_IP" link show "${1:-$CLUPILOT_BRIDGE}" >/dev/null 2>&1 } # Trägt die Brücke wirklich den Verkehr, oder existiert sie nur? # # Eine `vmbr0` ohne Adresse und ohne Vorgaberoute ist eine Brücke im Sinne von # `ip link show` und sonst nichts. Der alte Schritt prüfte genau das und warf das # Ergebnis weg; sein Kommentar behauptete, er halte die Abwesenheit fest, und er # hielt nichts fest. bridge_carries_default_route() { _want="${1:-$CLUPILOT_BRIDGE}" _dev="$("$CLUPILOT_IP" -4 route show default 2>/dev/null \ | awk '{ for (i = 1; i < NF; i++) if ($i == "dev") { print $(i+1); exit } }')" [ "$_dev" = "$_want" ] } bridge_has_address() { [ -n "$("$CLUPILOT_IP" -4 -o addr show dev "${1:-$CLUPILOT_BRIDGE}" scope global 2>/dev/null | awk '{ print $4; exit }')" ] } # Die drei zusammen. „Da" reicht nicht — sie muss tragen. bridge_is_up() { bridge_exists "$CLUPILOT_BRIDGE" \ && bridge_carries_default_route "$CLUPILOT_BRIDGE" \ && bridge_has_address "$CLUPILOT_BRIDGE" }