From 0a776f94657498f8de7f55e6261a2805657acbda Mon Sep 17 00:00:00 2001 From: Kroese Date: Sun, 5 Jul 2026 19:58:54 +0200 Subject: [PATCH] feat: Refactor network code (#74) --- src/network.sh | 319 +++++++++++++++++++++++++++++-------------------- 1 file changed, 188 insertions(+), 131 deletions(-) diff --git a/src/network.sh b/src/network.sh index 44bf88b..0d3e04d 100644 --- a/src/network.sh +++ b/src/network.sh @@ -13,9 +13,19 @@ set -Eeuo pipefail ADD_ERR="Please add the following setting to your container:" # ###################################### -# Functions +# Generic helpers # ###################################### +isNAT() { + + case "${NETWORK,,}" in + "tap" | "tun" | "tuntap" | "y" | "" ) + return 0 ;; + *) + return 1 ;; + esac +} + getMTU() { local dev="$1" @@ -74,6 +84,10 @@ disableIPv6() { return 0 } +# ###################################### +# DNS / interface helpers +# ###################################### + configureDNS() { local fa="$1" @@ -92,7 +106,7 @@ configureDNS() { fi # Avoid returning IPv6 records when the active network mode is IPv4-only. - if [[ "${NETWORK,,}" == "tap" || "${NETWORK,,}" == "tun" || "${NETWORK,,}" == "tuntap" || "${NETWORK,,}" == "y" || -z "$IP6" ]]; then + if isNAT || [ -z "$IP6" ]; then filter_dns="filter-AAAA" fi @@ -189,38 +203,129 @@ EOF return 0 } -clearTables() { - local table="" line rules +# ###################################### +# Network mode setup +# ###################################### - # Choose between iptables or nftables - if command -v iptables-nft >/dev/null 2>&1 && iptables-nft -V >/dev/null 2>&1; then - update-alternatives --set iptables /usr/sbin/iptables-nft > /dev/null - update-alternatives --set ip6tables /usr/sbin/ip6tables-nft > /dev/null - else - update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null - update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null +createBridge() { + + local gateway="$1" + local broadcast="$2" + local rc + + # Create a bridge with a static IP for the VM LAN + { ip link add dev "$BRIDGE" type bridge; rc=$?; } || : + + if (( rc != 0 )); then + error "failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 fi - # Store the current iptables ruleset - ! rules=$(iptables-save 2> /dev/null) && return 0 - [ -z "$rules" ] && return 0 + if [[ "$LAN_MTU" != "0" ]]; then + setMTU "$BRIDGE" "$LAN_MTU" + fi - # Delete every rule tagged with our unique identifier, leaving all other rules intact. - while IFS= read -r line; do - case "$line" in - \*nat) table="nat" ;; - \*filter) table="filter" ;; - \*mangle) table="mangle" ;; - \*raw) table="raw" ;; - esac - if [[ "$line" == -A* ]]; then - local re="--comment[[:space:]]+\"?remove\"?([[:space:]]|\$)" - if [[ "$line" =~ $re ]]; then - read -ra args <<< "${line/-A /-D }" - iptables -t "$table" "${args[@]}" &> /dev/null || : - fi - fi - done <<< "$rules" + if ! ip address add "$gateway/24" broadcast "$broadcast" dev "$BRIDGE"; then + error "failed to add IP address pool!" && return 1 + fi + + while ! ip link set "$BRIDGE" up; do + info "Waiting for IP address to become available..." + sleep 2 + done + + # NAT networking is IPv4-only; disable IPv6 on the VM bridge if possible. + disableIPv6 "$BRIDGE" + + return 0 +} + +createTap() { + + local tuntap="$1" + + # Set tap to the bridge created + if ! ip tuntap add dev "$TAP" mode tap; then + error "$tuntap" && return 1 + fi + + if [[ "$LAN_MTU" != "0" ]]; then + setMTU "$TAP" "$LAN_MTU" + fi + + if ! ip link set dev "$TAP" address "$GATEWAY_MAC"; then + warn "failed to set gateway MAC address." + fi + + while ! ip link set "$TAP" up promisc on; do + info "Waiting for TAP to become available..." + sleep 2 + done + + # NAT networking is IPv4-only; disable IPv6 on the VM tap if possible. + disableIPv6 "$TAP" + + if ! ip link set dev "$TAP" master "$BRIDGE"; then + error "failed to set master bridge!" && return 1 + fi + + return 0 +} + +configureTables() { + + local subnet="$1" + local rule_tag="remove" + local tables_err="failed to configure IP tables!" + local tables="the 'ip_tables' kernel module is not loaded. Try this command: sudo modprobe ip_tables iptable_nat" + + clearTables + + # NAT traffic from bridge subnet to Docker uplink + if ! iptables -t nat -A POSTROUTING \ + -o "$DEV" \ + -s "$subnet" \ + ! -d "$subnet" \ + -m comment --comment "$rule_tag" \ + -j MASQUERADE; then + error "$tables" && return 1 + fi + + if (( KERNEL > 4 )); then + # Hack for guest VMs complaining about "bad udp checksums in 5 packets" + iptables -t mangle -A POSTROUTING \ + -s "$subnet" \ + -p udp \ + --dport bootpc \ + -m comment --comment "$rule_tag" \ + -j CHECKSUM --checksum-fill > /dev/null 2>&1 || true + fi + + # Clamp TCP MSS to avoid subtle MTU blackholes when the outer path has a smaller MTU. + iptables -t mangle -A FORWARD \ + -s "$subnet" \ + -p tcp \ + --tcp-flags SYN,RST SYN \ + -m comment --comment "$rule_tag" \ + -j TCPMSS --clamp-mss-to-pmtu > /dev/null 2>&1 || true + + # Allow outbound traffic from the Proxmox VM subnet to the Docker uplink. + if ! iptables -A FORWARD \ + -s "$subnet" \ + -o "$DEV" \ + -m comment --comment "$rule_tag" \ + -j ACCEPT; then + error "$tables_err" && return 1 + fi + + # Allow return traffic from the Docker uplink back to the Proxmox VM subnet. + if ! iptables -A FORWARD \ + -d "$subnet" \ + -i "$DEV" \ + -m conntrack --ctstate RELATED,ESTABLISHED \ + -m comment --comment "$rule_tag" \ + -j ACCEPT; then + error "$tables_err" && return 1 + fi return 0 } @@ -228,7 +333,7 @@ clearTables() { configureNAT() { local tuntap="TUN device is missing. $ADD_ERR --device /dev/net/tun" - local tables="the 'ip_tables' kernel module is not loaded. Try this command: sudo modprobe ip_tables iptable_nat" + local rc [[ "$DEBUG" == [Yy1]* ]] && echo "Configuring NAT networking..." @@ -270,108 +375,15 @@ configureNAT() { local subnet="${ip%.*}.0/24" local broadcast="${ip%.*}.255" - # Create a bridge with a static IP for the VM LAN - { ip link add dev "$BRIDGE" type bridge; rc=$?; } || : - - if (( rc != 0 )); then - error "failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 - fi - - if [[ "$LAN_MTU" != "0" ]]; then - setMTU "$BRIDGE" "$LAN_MTU" - fi - - if ! ip address add "$gateway/24" broadcast "$broadcast" dev "$BRIDGE"; then - error "failed to add IP address pool!" && return 1 - fi - - while ! ip link set "$BRIDGE" up; do - info "Waiting for IP address to become available..." - sleep 2 - done - - # NAT networking is IPv4-only; disable IPv6 on the VM bridge if possible. - disableIPv6 "$BRIDGE" - - # Set tap to the bridge created - if ! ip tuntap add dev "$TAP" mode tap; then - error "$tuntap" && return 1 - fi - - if [[ "$LAN_MTU" != "0" ]]; then - setMTU "$TAP" "$LAN_MTU" - fi - - if ! ip link set dev "$TAP" address "$GATEWAY_MAC"; then - warn "failed to set gateway MAC address." - fi - - while ! ip link set "$TAP" up promisc on; do - info "Waiting for TAP to become available..." - sleep 2 - done - - # NAT networking is IPv4-only; disable IPv6 on the VM tap if possible. - disableIPv6 "$TAP" - - if ! ip link set dev "$TAP" master "$BRIDGE"; then - error "failed to set master bridge!" && return 1 - fi + createBridge "$gateway" "$broadcast" || return 1 + createTap "$tuntap" || return 1 # Use the lowest effective VM-LAN MTU, without mutating the parent/uplink MTU. if [[ "$LAN_MTU" != "0" ]]; then LAN_MTU=$(minMTU "$LAN_MTU" "$(getMTU "$BRIDGE")" "$(getMTU "$TAP")") fi - # Flush existing tables - clearTables - - # NAT traffic from bridge subnet to Docker uplink - if ! iptables -t nat -A POSTROUTING \ - -o "$DEV" \ - -s "$subnet" \ - ! -d "$subnet" \ - -m comment --comment "remove" \ - -j MASQUERADE; then - error "$tables" && return 1 - fi - - if (( KERNEL > 4 )); then - # Hack for guest VMs complaining about "bad udp checksums in 5 packets" - iptables -t mangle -A POSTROUTING \ - -s "$subnet" \ - -p udp \ - --dport bootpc \ - -m comment --comment "remove" \ - -j CHECKSUM --checksum-fill > /dev/null 2>&1 || true - fi - - # Clamp TCP MSS to avoid subtle MTU blackholes when the outer path has a smaller MTU. - iptables -t mangle -A FORWARD \ - -s "$subnet" \ - -p tcp \ - --tcp-flags SYN,RST SYN \ - -m comment --comment "remove" \ - -j TCPMSS --clamp-mss-to-pmtu > /dev/null 2>&1 || true - - # Allow outbound traffic from the Proxmox VM subnet to the Docker uplink. - if ! iptables -A FORWARD \ - -s "$subnet" \ - -o "$DEV" \ - -m comment --comment "remove" \ - -j ACCEPT; then - error "failed to configure IP tables!" && return 1 - fi - - # Allow return traffic from the Docker uplink back to the Proxmox VM subnet. - if ! iptables -A FORWARD \ - -d "$subnet" \ - -i "$DEV" \ - -m conntrack --ctstate RELATED,ESTABLISHED \ - -m comment --comment "remove" \ - -j ACCEPT; then - error "failed to configure IP tables!" && return 1 - fi + configureTables "$subnet" || return 1 setInterfaces "$BRIDGE" "$TAP" "$gateway" || return 1 configureDNS "$BRIDGE" "$ip" "$MASK" "$gateway" || return 1 @@ -379,11 +391,43 @@ configureNAT() { return 0 } -blockLicense() { +# ###################################### +# Cleanup +# ###################################### - # Block connection attempts to license server - sed -i -E '/^[[:space:]]*[^#]*[[:space:]]shop\.maurer-it\.com([[:space:]]|$)/d' /etc/hosts 2>/dev/null || true - printf '%s\n' '127.0.0.1 shop.maurer-it.com' '::1 shop.maurer-it.com' >> /etc/hosts 2>/dev/null || true +clearTables() { + local table="" line rules + local rule_tag="remove" + local re="--comment[[:space:]]+\"?$rule_tag\"?([[:space:]]|\$)" + + # Choose between iptables or nftables + if command -v iptables-nft >/dev/null 2>&1 && iptables-nft -V >/dev/null 2>&1; then + update-alternatives --set iptables /usr/sbin/iptables-nft > /dev/null + update-alternatives --set ip6tables /usr/sbin/ip6tables-nft > /dev/null + else + update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null + update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null + fi + + # Store the current iptables ruleset + ! rules=$(iptables-save 2> /dev/null) && return 0 + [ -z "$rules" ] && return 0 + + # Delete every rule tagged with our unique identifier, leaving all other rules intact. + while IFS= read -r line; do + case "$line" in + \*nat) table="nat" ;; + \*filter) table="filter" ;; + \*mangle) table="mangle" ;; + \*raw) table="raw" ;; + esac + if [[ "$line" == -A* ]]; then + if [[ "$line" =~ $re ]]; then + read -ra args <<< "${line/-A /-D }" + iptables -t "$table" "${args[@]}" &> /dev/null || : + fi + fi + done <<< "$rules" return 0 } @@ -400,6 +444,10 @@ closeBridge() { return 0 } +# ###################################### +# Detection +# ###################################### + getInfo() { if [ -z "$DEV" ]; then @@ -479,6 +527,15 @@ getInfo() { return 0 } +blockLicense() { + + # Block connection attempts to license server + sed -i -E '/^[[:space:]]*[^#]*[[:space:]]shop\.maurer-it\.com([[:space:]]|$)/d' /etc/hosts 2>/dev/null || true + printf '%s\n' '127.0.0.1 shop.maurer-it.com' '::1 shop.maurer-it.com' >> /etc/hosts 2>/dev/null || true + + return 0 +} + # ###################################### # Configure Network # ######################################