Luigit
repositories / bugabinga.net

bugabinga.net

personal infrastructure for bugabinga!

owned by admin

scripts/remote/apply-wireguard-mss.sh

Raw
#!/usr/bin/env bash
set -euo pipefail
umask 077

[[ "$#" -eq 1 || ("$#" -eq 2 && "$2" = --render) ]] || {
  echo 'usage: apply-wireguard-mss.sh MTU [--render]' >&2
  exit 2
}
mtu="$1"
[[ "$mtu" =~ ^[1-9][0-9]{3}$ && "$mtu" -ge 1280 && "$mtu" -le 1420 ]] || exit 2
mss4=$((mtu - 40))
mss6=$((mtu - 60))
render() {
  printf 'table inet wireguard_mss {\n  chain forward {\n    type filter hook forward priority mangle; policy accept;\n'
  for direction in iifname oifname; do
    printf '    %s "wg0" meta nfproto ipv4 tcp flags & (syn | rst) == syn tcp option maxseg size > %s tcp option maxseg size set %s\n' "$direction" "$mss4" "$mss4"
    printf '    %s "wg0" meta nfproto ipv6 tcp flags & (syn | rst) == syn tcp option maxseg size > %s tcp option maxseg size set %s\n' "$direction" "$mss6" "$mss6"
  done
  printf '  }\n}\n'
}
if [[ "${2:-}" = --render ]]; then
  render
  exit 0
fi

config=/etc/sysconfig/nftables.conf
fragment=/etc/nftables/wireguard-mss.nft
if [[ "${WG_MSS_TEST:-}" = 1 ]]; then
  config="${WG_MSS_CONFIG:?}"
  fragment="${WG_MSS_FRAGMENT:?}"
fi
[[ -f "$config" && ! -L "$config" && ! -L "$fragment" ]] || exit 1
include="include \"$fragment\""
[[ "$(grep -Fxc "$include" "$config" || true)" -le 1 ]] || exit 1
work="$(mktemp -d /var/tmp/wireguard-mss.XXXXXX)"
cp -a "$config" "$work/config.before"
fragment_existed=false
if [[ -e "$fragment" ]]; then
  cp -a "$fragment" "$work/fragment.before"
  fragment_existed=true
fi
# Fail on inspection errors rather than confusing them with an absent owned table.
nft list tables >"$work/tables"
table_existed=false
if grep -Fxq 'table inet wireguard_mss' "$work/tables"; then
  nft list table inet wireguard_mss >"$work/table.before"
  table_existed=true
fi
render >"$work/fragment"
cp -a "$config" "$work/config"
if ! grep -Fxq "$include" "$work/config"; then printf '\n%s\n' "$include" >>"$work/config"; fi
# Replace only our exact include in the validation copy, never the live ruleset.
awk -v include_line="$include" -v candidate="$work/fragment" '
  $0 == include_line { print "include \"" candidate "\""; next }
  { print }
' "$work/config" >"$work/config.check"
nft --check --file "$work/config.check"
: >"$work/live.batch"
if "$table_existed"; then printf 'delete table inet wireguard_mss\n' >>"$work/live.batch"; fi
cat "$work/fragment" >>"$work/live.batch"
nft --check --file "$work/live.batch"

persistence_changed=false
cleanup() {
  local status=$?
  if [[ "$status" -ne 0 && "$persistence_changed" = true ]]; then
    cp -a "$work/config.before" "$config" || echo 'ERROR: nft persistence rollback failed' >&2
    if "$fragment_existed"; then
      cp -a "$work/fragment.before" "$fragment" || echo 'ERROR: MSS fragment rollback failed' >&2
    else
      rm -f "$fragment" || echo 'ERROR: MSS fragment removal failed' >&2
    fi
  fi
  printf 'MSS backup evidence: %s\n' "$work"
  exit "$status"
}
trap cleanup EXIT
install -d -m 0755 "$(dirname "$fragment")"
persistence_changed=true
install -m 0644 "$work/fragment" "$fragment"
cp -a "$work/config" "$config"
# Last fallible mutation: nft submits this batch atomically; no full flush/restart.
nft --file "$work/live.batch"
printf 'WireGuard MSS caps: IPv4=%s IPv6=%s; no firewall exposure changes\n' "$mss4" "$mss6"