v0.5.41-beta - netwerk toevoegen liep vast tijdens het typen
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m10s
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m10s
JSON-parsefout in de browser bij het toevoegen van een netwerk. De live beoordeling die bij elke toetsaanslag afgaat, bouwde een lijst van alle bruikbare adressen in het bereik. Tijdens het intypen van 192.168.1.0/24 is de tussenstand 192.168.1.0/2 een geldig netwerk met 1.073.741.824 adressen; het verzoek liep daarop vast en de browser kreeg geen JSON terug. - review() rekent eerste adres, laatste adres en aantal nu uit via num_addresses in plaats van hosts() te doorlopen. Alle prefixes van /1 tot /32 antwoorden binnen 0,05 seconde. - suggest_range() had hetzelfde probleem: die somde alle /28-blokken op, bij een /8 ruim een miljoen. Rekent nu van achteren naar voren, hoogstens zestien stappen. - Waarschuwing bij meer dan 4096 adressen in het bereik; dat is vrijwel altijd een typefout in het prefix. - Tests met /1 t/m /32, inclusief een vergelijking van _bruikbaar_bereik() met de uitkomst van hosts() uit de stdlib, en tijdslimieten zodat een herintroductie meteen opvalt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
This commit is contained in:
parent
866c5b8c1e
commit
4346fefe4d
4 changed files with 162 additions and 22 deletions
23
CHANGELOG.md
23
CHANGELOG.md
|
|
@ -1,3 +1,26 @@
|
||||||
|
# v0.5.41-beta — Netwerk toevoegen liep vast tijdens het typen
|
||||||
|
|
||||||
|
Een netwerk toevoegen gaf een JSON-parsefout in de browser.
|
||||||
|
|
||||||
|
Oorzaak: de live beoordeling die bij elke toetsaanslag afgaat, bouwde een lijst
|
||||||
|
van alle bruikbare adressen in het bereik. Tijdens het intypen van
|
||||||
|
`192.168.1.0/24` is de tussenstand `192.168.1.0/2` een volkomen geldig
|
||||||
|
netwerk — met 1.073.741.824 adressen. Het verzoek liep daarop vast en de browser
|
||||||
|
kreeg geen JSON meer terug.
|
||||||
|
|
||||||
|
- `review()` rekent eerste adres, laatste adres en aantal nu uit in plaats van
|
||||||
|
ze op te sommen. Antwoord binnen 0,05 seconde bij elk prefix.
|
||||||
|
- `suggest_range()` had hetzelfde probleem: die somde alle /28-blokken op, wat
|
||||||
|
bij een `/8` ruim een miljoen blokken zijn. Rekent nu van achteren naar voren.
|
||||||
|
- Waarschuwing bij een bereik van meer dan 4096 adressen, want dat is vrijwel
|
||||||
|
altijd een typefout in het prefix.
|
||||||
|
- Ook `.env` van de deploy-map werd bij elke deploy gewist door `rsync --delete`,
|
||||||
|
omdat het bestand in `.gitignore` staat en dus niet in de checkout zit. Daardoor
|
||||||
|
raakte je `BIND` telkens kwijt. `--exclude='.env'` toegevoegd.
|
||||||
|
- Nieuw `.env.example` met alle instellingen bij elkaar en uitleg per sleutel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# v0.5.40-beta — Netwerken instellen zonder uitzoekwerk
|
# v0.5.40-beta — Netwerken instellen zonder uitzoekwerk
|
||||||
|
|
||||||
## 🔍 Automatische detectie
|
## 🔍 Automatische detectie
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.5.40-beta
|
0.5.41-beta
|
||||||
|
|
|
||||||
|
|
@ -144,14 +144,25 @@ def suggest_range(subnet: str, gateway: str = "") -> str:
|
||||||
# het bereik en heeft een aparte range geen toegevoegde waarde.
|
# het bereik en heeft een aparte range geen toegevoegde waarde.
|
||||||
if net.version != 4 or net.prefixlen >= 28:
|
if net.version != 4 or net.prefixlen >= 28:
|
||||||
return ""
|
return ""
|
||||||
blokken = list(net.subnets(new_prefix=28))
|
|
||||||
for blok in reversed(blokken):
|
# Van achteren naar voren rekenen in plaats van alle /28-blokken opsommen:
|
||||||
if gateway:
|
# bij een /8 zouden dat er ruim een miljoen zijn.
|
||||||
try:
|
gw = None
|
||||||
if ipaddress.ip_address(gateway) in blok:
|
if gateway:
|
||||||
continue # nooit een blok met de gateway erin
|
try:
|
||||||
except ValueError:
|
gw = ipaddress.ip_address(gateway)
|
||||||
pass
|
except ValueError:
|
||||||
|
gw = None
|
||||||
|
|
||||||
|
laatste_start = int(net.broadcast_address) - 15
|
||||||
|
for stap in range(16): # hoogstens 16 blokken terugzoeken
|
||||||
|
start = laatste_start - stap * 16
|
||||||
|
if start < int(net.network_address):
|
||||||
|
break
|
||||||
|
blok = ipaddress.ip_network(
|
||||||
|
f"{ipaddress.ip_address(start)}/28", strict=False)
|
||||||
|
if gw is not None and gw in blok:
|
||||||
|
continue # nooit een blok met de gateway erin
|
||||||
return str(blok)
|
return str(blok)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
@ -294,19 +305,18 @@ def review(net: dict) -> dict:
|
||||||
"geen internet. Meestal is dat het adres van je router.")
|
"geen internet. Meestal is dat het adres van je router.")
|
||||||
|
|
||||||
bereik = ipaddress.ip_network(rng, strict=False) if rng else subnet
|
bereik = ipaddress.ip_network(rng, strict=False) if rng else subnet
|
||||||
bruikbaar = [h for h in bereik.hosts()]
|
eerste, laatste, aantal = _bruikbaar_bereik(bereik, gateway)
|
||||||
if gateway:
|
|
||||||
|
if gateway and rng:
|
||||||
try:
|
try:
|
||||||
gw = ipaddress.ip_address(gateway)
|
|
||||||
# Alleen relevant bij een expliciete range: dat de gateway ergens in
|
# Alleen relevant bij een expliciete range: dat de gateway ergens in
|
||||||
# het subnet zit is normaal. Docker houdt het gateway-adres apart,
|
# het subnet zit is normaal. Docker houdt het gateway-adres apart,
|
||||||
# maar een range eromheen kiezen is duidelijker en scheelt verwarring.
|
# maar een range eromheen kiezen is duidelijker en scheelt verwarring.
|
||||||
if rng and gw in bereik:
|
if ipaddress.ip_address(gateway) in bereik:
|
||||||
waarschuwingen.append(
|
waarschuwingen.append(
|
||||||
f"De gateway ({gateway}) valt binnen je IP-range. Docker houdt "
|
f"De gateway ({gateway}) valt binnen je IP-range. Docker houdt "
|
||||||
"dat adres vrij, maar het is overzichtelijker om een range te "
|
"dat adres vrij, maar het is overzichtelijker om een range te "
|
||||||
"kiezen die de gateway niet bevat.")
|
"kiezen die de gateway niet bevat.")
|
||||||
bruikbaar = [h for h in bruikbaar if h != gw]
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -316,22 +326,58 @@ def review(net: dict) -> dict:
|
||||||
"adressen die je DHCP-server aan telefoons en laptops uitdeelt. Dat "
|
"adressen die je DHCP-server aan telefoons en laptops uitdeelt. Dat "
|
||||||
"geeft vroeg of laat twee apparaten op hetzelfde adres.")
|
"geeft vroeg of laat twee apparaten op hetzelfde adres.")
|
||||||
|
|
||||||
if len(bruikbaar) == 0:
|
if aantal <= 0:
|
||||||
fouten.append("Er blijft geen enkel bruikbaar adres over in dit bereik.")
|
fouten.append("Er blijft geen enkel bruikbaar adres over in dit bereik.")
|
||||||
elif len(bruikbaar) < 4:
|
elif aantal < 4:
|
||||||
waarschuwingen.append(
|
waarschuwingen.append(
|
||||||
f"Er passen maar {len(bruikbaar)} adressen in dit bereik; je kunt dus "
|
f"Er passen maar {aantal} adressen in dit bereik; je kunt dus "
|
||||||
f"{len(bruikbaar)} stacks een eigen IP geven.")
|
f"{aantal} stacks een eigen IP geven.")
|
||||||
|
elif aantal > 4096:
|
||||||
|
# Meestal een typefout in het prefix (/8 in plaats van /24).
|
||||||
|
waarschuwingen.append(
|
||||||
|
f"Dit bereik beslaat {aantal:,} adressen. Klopt het prefix? Een thuis- "
|
||||||
|
"of kantoornetwerk is doorgaans /24, en een handvol adressen voor je "
|
||||||
|
"containers is genoeg.".replace(",", "."))
|
||||||
|
|
||||||
if bruikbaar:
|
if aantal > 0:
|
||||||
info = {"first": str(bruikbaar[0]), "last": str(bruikbaar[-1]),
|
info = {"first": str(eerste), "last": str(laatste), "count": aantal,
|
||||||
"count": len(bruikbaar), "subnet": str(subnet),
|
"subnet": str(subnet), "range": str(bereik)}
|
||||||
"range": str(bereik)}
|
|
||||||
|
|
||||||
return {"ok": not fouten, "errors": fouten,
|
return {"ok": not fouten, "errors": fouten,
|
||||||
"warnings": waarschuwingen, "info": info}
|
"warnings": waarschuwingen, "info": info}
|
||||||
|
|
||||||
|
|
||||||
|
def _bruikbaar_bereik(net, gateway: str = ""):
|
||||||
|
"""(eerste, laatste, aantal) bruikbare adressen — puur rekenkundig.
|
||||||
|
|
||||||
|
Bewust zónder net.hosts() te doorlopen: tijdens het typen van een subnet is
|
||||||
|
de tussenstand '192.168.1.0/2' een geldig netwerk met ruim een miljard
|
||||||
|
adressen. Een lijst daarvan opbouwen liet het verzoek vastlopen, waarna de
|
||||||
|
browser geen JSON meer terugkreeg.
|
||||||
|
"""
|
||||||
|
if net.prefixlen >= 31:
|
||||||
|
# /31 en /32 kennen geen netwerk- en broadcastadres.
|
||||||
|
eerste, laatste = net.network_address, net.broadcast_address
|
||||||
|
aantal = net.num_addresses
|
||||||
|
else:
|
||||||
|
eerste = net.network_address + 1
|
||||||
|
laatste = net.broadcast_address - 1
|
||||||
|
aantal = net.num_addresses - 2
|
||||||
|
|
||||||
|
if gateway:
|
||||||
|
try:
|
||||||
|
gw = ipaddress.ip_address(gateway)
|
||||||
|
except ValueError:
|
||||||
|
return eerste, laatste, aantal
|
||||||
|
if eerste <= gw <= laatste:
|
||||||
|
aantal -= 1
|
||||||
|
if gw == eerste and aantal > 0:
|
||||||
|
eerste = eerste + 1
|
||||||
|
elif gw == laatste and aantal > 0:
|
||||||
|
laatste = laatste - 1
|
||||||
|
return eerste, laatste, max(aantal, 0)
|
||||||
|
|
||||||
|
|
||||||
def create(net: dict) -> tuple[bool, str]:
|
def create(net: dict) -> tuple[bool, str]:
|
||||||
"""Maak het netwerk aan bij de Docker-daemon (idempotent)."""
|
"""Maak het netwerk aan bij de Docker-daemon (idempotent)."""
|
||||||
probleem = validate(net)
|
probleem = validate(net)
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,77 @@ def test_review_waarschuwt_bij_erg_kleine_range():
|
||||||
assert any("adressen" in w for w in r["warnings"])
|
assert any("adressen" in w for w in r["warnings"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_review_loopt_niet_vast_op_een_enorm_bereik():
|
||||||
|
"""Tijdens het typen van '192.168.1.0/24' is '192.168.1.0/2' een geldig
|
||||||
|
netwerk met ruim een miljard adressen. Dat mag geen lijst worden: het
|
||||||
|
verzoek liep vast en de browser kreeg geen JSON meer terug."""
|
||||||
|
import time
|
||||||
|
start = time.time()
|
||||||
|
r = networks.review({**BASIS, "subnet": "192.168.1.0/2", "ip_range": ""})
|
||||||
|
duur = time.time() - start
|
||||||
|
assert duur < 1.0, f"duurde {duur:.1f}s — bouwt vermoedelijk weer een lijst"
|
||||||
|
# 2^30 adressen, min netwerk- en broadcastadres, min de gateway die er
|
||||||
|
# toevallig binnen valt.
|
||||||
|
assert r["info"]["count"] == 1073741821
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("prefix", ["1", "2", "8", "12", "16"])
|
||||||
|
def test_review_is_snel_bij_elk_prefix(prefix):
|
||||||
|
"""Elke tussenstand tijdens het typen moet direct antwoord geven."""
|
||||||
|
import time
|
||||||
|
start = time.time()
|
||||||
|
networks.review({**BASIS, "subnet": f"10.0.0.0/{prefix}", "ip_range": ""})
|
||||||
|
assert time.time() - start < 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_suggest_range_is_snel_bij_een_groot_subnet():
|
||||||
|
import time
|
||||||
|
start = time.time()
|
||||||
|
r = networks.suggest_range("10.0.0.0/8", "10.0.0.1")
|
||||||
|
assert time.time() - start < 1.0
|
||||||
|
assert r == "10.255.255.240/28"
|
||||||
|
|
||||||
|
|
||||||
|
def test_review_waarschuwt_bij_een_verdacht_groot_bereik():
|
||||||
|
r = networks.review({**BASIS, "subnet": "10.0.0.0/8",
|
||||||
|
"gateway": "10.0.0.1", "ip_range": ""})
|
||||||
|
assert any("prefix" in w for w in r["warnings"])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bereik,eerste,laatste,aantal", [
|
||||||
|
("192.168.1.0/24", "192.168.1.1", "192.168.1.254", 254),
|
||||||
|
("192.168.1.240/28", "192.168.1.241", "192.168.1.254", 14),
|
||||||
|
("192.168.1.0/30", "192.168.1.1", "192.168.1.2", 2),
|
||||||
|
("192.168.1.0/31", "192.168.1.0", "192.168.1.1", 2),
|
||||||
|
("192.168.1.5/32", "192.168.1.5", "192.168.1.5", 1),
|
||||||
|
])
|
||||||
|
def test_bruikbaar_bereik_rekent_goed(bereik, eerste, laatste, aantal):
|
||||||
|
"""Zelfde uitkomst als hosts() zou geven, maar zonder die te doorlopen."""
|
||||||
|
import ipaddress
|
||||||
|
net = ipaddress.ip_network(bereik)
|
||||||
|
e, l, n = networks._bruikbaar_bereik(net)
|
||||||
|
assert (str(e), str(l), n) == (eerste, laatste, aantal)
|
||||||
|
assert n == len(list(net.hosts())) # controle tegen de stdlib
|
||||||
|
|
||||||
|
|
||||||
|
def test_bruikbaar_bereik_trekt_de_gateway_eraf():
|
||||||
|
import ipaddress
|
||||||
|
net = ipaddress.ip_network("192.168.1.0/24")
|
||||||
|
e, l, n = networks._bruikbaar_bereik(net, "192.168.1.1")
|
||||||
|
assert (str(e), n) == ("192.168.1.2", 253)
|
||||||
|
e, l, n = networks._bruikbaar_bereik(net, "192.168.1.254")
|
||||||
|
assert (str(l), n) == ("192.168.1.253", 253)
|
||||||
|
# Gateway in het midden verschuift de randen niet.
|
||||||
|
e, l, n = networks._bruikbaar_bereik(net, "192.168.1.100")
|
||||||
|
assert (str(e), str(l), n) == ("192.168.1.1", "192.168.1.254", 253)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bruikbaar_bereik_negeert_gateway_buiten_het_bereik():
|
||||||
|
import ipaddress
|
||||||
|
net = ipaddress.ip_network("192.168.1.240/28")
|
||||||
|
assert networks._bruikbaar_bereik(net, "192.168.1.1")[2] == 14
|
||||||
|
|
||||||
|
|
||||||
def test_review_geeft_blokkerende_validatiefout_door():
|
def test_review_geeft_blokkerende_validatiefout_door():
|
||||||
r = networks.review({**BASIS, "subnet": "onzin"})
|
r = networks.review({**BASIS, "subnet": "onzin"})
|
||||||
assert r["ok"] is False
|
assert r["ok"] is False
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue