LibrePortal/scripts/network/ip/ip_in_subnet.sh
librelad b7a0743d8b feat(network): add ipInSubnet + IP-only network reset scope
Foundations for network-drift healing:

- ipInSubnet(ip, cidr): prefix-aware CIDR membership (pure bash), so
  stored IPs can be checked against docker's real subnet. Honours the
  actual prefix, so a healthy /16-subnet + /24-ip-range install is not
  mistaken for drift.

- dockerInstallApp now accepts reset_network="ip": re-roll the static IP
  from the current subnet but PRESERVE published host ports (clears only
  IP rows; LIBREPORTAL_RESET_IP_ONLY keeps port_allocate reusing existing
  ports). This is the heal path — a subnet move strands the IP, not the
  port, so we don't churn bookmarks/forwards/proxy upstreams. reset="true"
  still re-rolls both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 15:54:55 +01:00

33 lines
1.5 KiB
Bash

#!/bin/bash
# CIDR membership test: is $ip inside $cidr?
# ipInSubnet 10.123.154.5 10.123.154.0/24 -> 0 (in subnet)
# ipInSubnet 10.100.0.5 10.123.154.0/24 -> 1 (out of subnet)
#
# Pure-bash 32-bit integer masking that HONOURS the real prefix length, so a /16
# genuinely contains its /24s. That matters here: the network is created with a
# /24 ip-range but its declared subnet can be /16 (configs/network/network_docker
# default), and a healthy "/16 subnet + /24 ip-range" install must NOT read as
# drift. Compare stored IPs against docker's actual subnet CIDR, not a forced /24.
# Returns 0 (in-subnet) or 1 (out-of-subnet / malformed input).
ipInSubnet()
{
local ip="$1" cidr="$2"
[[ "$cidr" == */* ]] || return 1
local base="${cidr%/*}" prefix="${cidr#*/}"
[[ "$prefix" =~ ^[0-9]+$ ]] && (( prefix >= 0 && prefix <= 32 )) || return 1
local re='^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$'
[[ "$ip" =~ $re ]] || return 1
local i1=$((10#${BASH_REMATCH[1]})) i2=$((10#${BASH_REMATCH[2]})) i3=$((10#${BASH_REMATCH[3]})) i4=$((10#${BASH_REMATCH[4]}))
[[ "$base" =~ $re ]] || return 1
local b1=$((10#${BASH_REMATCH[1]})) b2=$((10#${BASH_REMATCH[2]})) b3=$((10#${BASH_REMATCH[3]})) b4=$((10#${BASH_REMATCH[4]}))
local ip_int=$(( (i1<<24) | (i2<<16) | (i3<<8) | i4 ))
local base_int=$(( (b1<<24) | (b2<<16) | (b3<<8) | b4 ))
local mask=0
(( prefix > 0 )) && mask=$(( (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF ))
(( (ip_int & mask) == (base_int & mask) ))
}