The helpers were tested in isolation but nothing tied them together, and
what actually has to hold is that the key SetCIDR writes for a decision is
one of the keys GetCIDR looks up for an IP that decision covers. A
formatting change on either side would have silently stopped every range
decision from matching with all tests green.
TestCIDRKeys_MatchNormalizeCIDR pins that both ways, including the cases
worth being explicit about: a decision that is not on a network address,
IPv4 mapped clients against an IPv4 range, and that the two families do not
mix. Dropping the mask in cidrKey fails 9 of its cases.
Also covers CIDRLookupKeys against CIDRKeys length by length, the IPv6 side
of the network address test, and CIDRPrefixLen.
In stream mode nothing caches a negative result per IP, so the exact IP
lookup misses on every legitimate request and each one fell through to
GetCIDR, which probed every possible prefix length: 33 cache reads for an
IPv4 client, 129 for an IPv6 one, even when no range decision existed at
all. On the local cache that is wasted work on the request path; with
redis it is 33 to 129 sequential round trips per request.
Keep the set of prefix lengths that have at least one decision under a
single key, written before the decision itself, and probe only those.
Measured cache reads per request: 1 with no range decision (was 33 / 129),
2 with a single /24 in use, 4 with four prefix lengths in use.
The set only grows, so a deleted or expired decision leaves a length
behind that costs one extra read rather than risking an unmatched
decision, and it is written with an effectively infinite duration since it
has to outlive every decision it describes. If it is ever missing while
decisions live (a redis eviction under maxmemory), range decisions stop
matching until the next one arrives; it is the hottest key of the
namespace, so an LRU policy evicts it last.
CIDRKeys masked the address byte by byte and formatted the result with
string concatenation, while SetCIDR/DeleteCIDR format their keys with
net.IPNet.String() via NormalizeCIDR. The two agreed only by coincidence:
any divergence in formatting silently stops every range decision from
matching, with no test covering the invariant.
Mask with net.IP.Mask and format through net.IPNet.String() so both sides
go through the same formatter. Output is byte for byte identical to the
previous implementation (checked against a golden dump of both IPv4 and
IPv6 keys, including ::ffff: forms).
Dropping the inner byte loops also removes the only intrange violation in
the tree, so the linter exclusion added for them is no longer needed, and
the redundant import alias on pkg/ip goes away with it.
* ✨ add one instance of cache for all the service
* 🐛 fix the healthy issue
* 🐛 fix lint
* 🍱 upgrade codebase
* 🍱 fix lint
* 🐛 when there is master node shut down
* Set live mode as default, clean docker and recipes
* Restore live mode in readme as default
* 🍱 add channel management
* 🍱 order funcs
* 🍱 revert service in docker compose
* ✨ logLevel added + cache put out the bouncer
* 🍱 fix
* 🍱 fix
* 🍱 fix
Co-authored-by: MathieuHa <mathieu@hanotaux.fr>