Router priority flaps non-deterministically for hosts with multiple Targets/paths (map iteration in findMatchingRouter) (middleware-manager #113)

:octopus: GitHub issue middleware-manager #113, opened by strausmann on GitHub

Summary

When a Pangolin resource uses multi-target path-based routing (one host, multiple Targets each with a different PathPrefix, producing multiple Traefik routers that all share the same Host(...) in their rule), middleware-manager’s /api/v1/traefik-config endpoint returns a different priority value for the same router on different requests, even though the upstream Pangolin config is byte-identical between requests. This happens roughly once per 5-second cache cycle (matching ConfigProxy’s cacheDuration).

Because Traefik polls this endpoint continuously and rebuilds its router table on any content change, this causes:

  1. Constant, unnecessary router-table rebuilds (content changes every ~5s even when nothing changed upstream).
  2. A real routing bug: when the catch-all router’s priority is corrupted to match the more specific router’s priority, a priority tie between the two routers is possible, and Traefik’s tie-break can transiently route requests meant for the specific path to the catch-all router instead.

Root cause (source-verified against main @ 7eb1378d826ca203502332c61d14879bb1436f14 / v4.5.0)

services/config_proxy.go, function findMatchingRouter (~line 989):

func (cp *ConfigProxy) findMatchingRouter(routers map[string]interface{}, host string) (string, map[string]interface{}) {
    ...
    for routerName, routerConfig := range routers {   // unordered Go map iteration
        ...
        if len(hostMatches) > 1 && …

---
💬 Discuss it here. Status, fixes and code review stay on [GitHub](https://github.com/hhftechnology/middleware-manager/issues/113).
<!-- hhf-forum-sync -->

strausmann commented on GitHub:

Hi @​hhftechnology — I’ve opened #114 with a fix for this.

Short version: findMatchingRouter matched routers on Host(...) only while iterating an unordered Go map, so for a host served by more than one non-redirect router (Pangolin’s multi-target path routing — one resource, several Targets sharing a Host() but differing PathPrefix()), the RouterPriority override could land on the wrong sibling router on any of the 5s config refreshes. That corrupted the sibling’s priority and created a priority tie → sporadic misrouting on shared hosts.

The PR sorts the matches deterministically and, when a host is genuinely ambiguous (multiple non-redirect routers), reports no match so the override is skipped rather than applied to a guessed router — leaving Pangolin’s own priorities untouched, which is strictly safer.

It’s validated live against a production Pangolin deployment (17 hosts with multiple Targets: 16 flapping 10–42 priority changes over 6 min → 0 with the fix; 101 single-target hosts unchanged), unit-tested (TDD, red→green), and both automated reviews (Copilot + CodeRabbit) came back clean.

Whenever you get a moment to take a look, that’d be much appreciated — happy to adjust anything. Thanks!

kylepyke commented on GitHub:

Hey @​hhftechnology, will this request be merged?