No description
  • Go 92.7%
  • Shell 2.8%
  • Makefile 2.2%
  • Nix 1.4%
  • Dockerfile 0.5%
  • Other 0.4%
Find a file
Antoine Viallon 2b0685afc3
Some checks failed
ci / test (push) Failing after 4m54s
feat(agent): node-local VIP delivery so in-cluster clients reach NATed sites
A NATed site's public IPv4 is unreachable from the site's own nodes/pods:
the router's NAT hairpin drops packets whose source is the NAT internal
target itself (observed on the home Bbox: SYN to 89.95.58.186:443 leaves
enp3s0, no SYN-ACK; LAN hosts and the internet are fine). A-only services
(e.g. mail.lesviallon.fr) are therefore unreachable from in-cluster
monitors — an L3 failure that looks like a TLS/gateway problem.

The agent now programs node-local VIP delivery: a nat PREROUTING DNAT chain
(disagglb-dnat, jumped at position 0) that rewrites the PUBLIC address to
the same delivery point the site router's own DNAT picks for WAN traffic —
the match is on the public address only, so WAN traffic is untouched. Same
lifecycle as the INPUT accept rules: sync (atomic replace, deduplicated and
sorted for determinism), removal, and the disagglb.io/firewall-hooks=none
override is a clean cleanup, not a freeze.

Controller and agent share ONE provider-selection so they can never diverge
(a controller saying "VIP, no DNAT" while the agent programs a
kube-proxy-shadowing DNAT is the forbidden divergence):
api/v1alpha1/portmapping.ProviderFor picks over the location's FULL pool
set (only Spec.IPv4 != nil pools count, name-sorted defensive copy,
all-agree => that provider, disagree/none => unknown => NOT NATed => the
safe direction). The controller's poolByLocation last-write-wins pick over
the cache's unordered List() is gone.

Agreement regression tests (controller vs agent rule generation) plus
mutation-tested provider-selection coverage; evidence in
research/review-bg12-mutation-tests.md. Also updates docs/datapath.md
(the NAT-hairpin pitfall + delivery paths), TASKS.md and the repo paths in
AGENTS.md/TASKS.md.

Co-Authored-By: xiaomi/mimo-v2.6-pro
2026-10-09 12:32:51 +02:00
.devcontainer
.forgejo/workflows
api
charts/disagglb
cmd
config
deploy/forgejo
docs
hack
internal
nix/golangci-lint
plugins
protocol
research
test
.custom-gcl.yml
.dockerignore
.gitignore
.golangci.yml
AGENTS.md
Dockerfile
flake.lock
flake.nix
go.mod
go.sum
LICENSE
Makefile
mkdocs.yml
PROJECT
README.md
ROADMAP.md
TASKS.md

DisaggLB

A Kubernetes LoadBalancer operator for disaggregated networks: several homelab sites, each with its own ISP, no BGP, one NATed public IPv4 per site, and L3-routable IPv6. IPs are location-bound and can never move — failover and migration happen through DNS (ExternalDNS + multi-value A/AAAA RRsets), not through IP mobility.

Status: early development. The design is settled; M0 (scaffold & CRDs) is in progress. See below.

Documentation

Quick summary

  • Standard Service + loadBalancerClass: disagglb.io/site; optional LoadBalancerConfig CRD for rich config (locations, DNS, health, drain).
  • LocationPool CRD models each site's location-bound inventory.
  • IPv6 via DHCPv6-PD; IPv4 via UPnP/NAT-PMP/PCP/webhook/manual port mapping.
  • Envoy site proxies (xDS from the manager), prefer-local with cross-site fallback.
  • DNS via ExternalDNS (--source=crd); phase 2 adds a dedicated authoritative NS (dnsd) with weighted/GeoIP answers.

License

Apache-2.0, see LICENSE.