Skip to content

[sample #12451] [v2] Per Pod/LB IP proxy ARP#241

Open
stevegaossou wants to merge 1 commit into
coc-sample-12451-v2-basefrom
coc-sample-12451-v2-head
Open

[sample #12451] [v2] Per Pod/LB IP proxy ARP#241
stevegaossou wants to merge 1 commit into
coc-sample-12451-v2-basefrom
coc-sample-12451-v2-head

Conversation

@stevegaossou

Copy link
Copy Markdown
Collaborator

Benchmark sample reproducing the as-first-reviewed state of projectcalico/calico#12451 (by @MichalFupso).

This PR's diff equals what the human reviewers first saw: fork point bbf9d864bc8b → earliest human-reviewed commit b9eb6ddda003. The original PR drew 85 human top-level review comments — the ground truth to compare the Council's feedback against.

🤖 Generated for the Council of Claudes benchmark.

Copilot AI review requested due to automatic review settings June 23, 2026 20:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

stevegaossou added a commit that referenced this pull request Jun 23, 2026
The orchestrator adds a 6th serial agent call after the 5 personas. On a large/
slow PR the cumulative time (personas ~8m parallel + orchestrator poll up to 10m
+ posting) exceeded the 15m job cap, cancelling the whole job before the
orchestrator's own graceful degradation could post-all — e.g. sample PR #241
posted zero comments. Raise to 30m so the job outlives the worst-case serial path
and graceful degradation can run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
stevegaossou added a commit that referenced this pull request Jun 24, 2026
* eval-benchmark-pr.py: compare two iterations of a duplicate PR

Reworks the eval tool from human-vs-Council (single PR) into a two-iteration
comparison: `eval-benchmark-pr.py <prA> <prB>` reports how the Council's inline
comments changed across two duplicate PRs of the same original — shared,
only-in-A, only-in-B — overall and per persona (same-persona only).

Matching is location-based (option A): same (persona, file, line). Both PRs
reproduce the same diff so lines align; deterministic, no evaluator variance.
Semantic (LLM) matching deferred to the scoring phase. The doc also records the
two-iteration design, the LLM-variance caveat, and the future human-in-the-loop
good/bad labeling direction (path to a single score).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Raise Council job timeout 15→30m for the orchestrator's serial call

The orchestrator adds a 6th serial agent call after the 5 personas. On a large/
slow PR the cumulative time (personas ~8m parallel + orchestrator poll up to 10m
+ posting) exceeded the 15m job cap, cancelling the whole job before the
orchestrator's own graceful degradation could post-all — e.g. sample PR #241
posted zero comments. Raise to 30m so the job outlives the worst-case serial path
and graceful degradation can run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address Council review on eval tool (#242)

1. detail() no longer over-reports multiplicity — uses Counter subtraction
   (ka - kb) to emit exactly the unmatched count per (persona,file,line) key.
2. Only count comments authored by the Council bot (user.type == "Bot") — a
   human reply quoting a marker is no longer tallied as Council output.
3. Robust persona parse via regex council-of-claudes:inline:([\w-]+).
4. Strip the "<badge> **Persona** —" prefix from stored bodies so detail
   bullets don't double the persona (label() already renders it).
5. Fix inaccurate doc strings (PERSONAS emoji comment; clean() overclaim).
Also carry comment id for stable per-key ordering.

Deferred (YAGNI for an internal script that will evolve for the scoring phase):
argparse/--fork/--limit/--json, helper extraction, full markdown sanitization,
line-0 unanchored bucketing. Pagination was a non-issue (gh_lines uses --paginate).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@stevegaossou
stevegaossou force-pushed the coc-sample-12451-v2-head branch from c796b13 to e4939cb Compare June 24, 2026 00:22
@stevegaossou
stevegaossou force-pushed the coc-sample-12451-v2-base branch from b991e60 to 7e29107 Compare June 24, 2026 00:22

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔎 Council of Claudes — Correctness

Note

Correctness lens · bugs · completeness · concurrency · edge cases

3 potential correctness issues

  • Manager lifecycle: the new proxy neighbor managers spin long-lived listener goroutines, but there’s no clear shutdown path when Felix stops. InternalDataplane doesn’t appear to cancel these contexts on shutdown, which can leave sockets/goroutines open until process exit. Consider wiring a cleanup in the dataplane’s Stop/teardown to call m.cancel() for each manager (you already store them in dp.proxyNeighManagers) so listeners are deterministically stopped.

  • Feature enable check is case-sensitive in one place. Config parsing allows case-insensitive values; comparing with a case-sensitive string can leave the feature disabled when users set a different case form.

  • FV test: invalid use of new() with a constant value for HostSubnetNeighResponses. This won’t compile.

Line-specific findings:

🤖 Council of Claudes · 4 inline comment(s)

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Council of Claudes — Maintainability & Tests

Tip

Maintainability & Tests lens · simplicity · tests · docs · idioms

Well-tested overall with strong UT/FV/E2E coverage; a few maintainability nits and one small missing unit test around config gating

  • Good job on shipping comprehensive tests at three layers:
    • Unit tests thoroughly exercise ARP/NDP listeners, reconciliation, GARP/UNA, pool filtering, LB VIP selection, and live-migration.
    • FV drives the calc graph end-to-end including the new Service status → ServiceUpdate path.
    • E2E adds dual-stack coverage with realistic L2 topology and skips appropriately via RequiresKindNetwork.
  • The new manager is a large single file (~700 lines) but logically cohesive. Consider modest refactors over time (e.g., isolating ARP vs NDP listener code) if it grows further.
  • Two maintainability improvements would reduce future friction:
    1. Avoid stringly-typed config checks (use consistent parsing/enum and case-insensitive comparisons) and centralize gating in one place.
    2. Deduplicate docker-network/subnet helper functions shared between e2e and FV into a common utility to reduce drift.
  • One targeted test ask: add a small unit test around the HostSubnetNeighResponses gating in NewIntDataplaneDriver so a future change doesn’t accidentally register the manager when Disabled.

Line-specific findings:

🤖 Council of Claudes · 6 inline comment(s)

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Council of Claudes — Security

Caution

Security lens · validation · secrets · authz · isolation

3 potential security issues

  • Architectural observation: This feature exposes pod and LoadBalancer IPs at L2 to any hosts on the same subnet when enabled. Even if Calico policy still applies at L3/L4, clusters that assume pods are not directly reachable from the host LAN may find this weakens isolation. Carefully consider the default and provide clear operator guidance. Also verify that the interface monitor feeding ifaceAddrsUpdate honors Felix’s InterfaceExclude setting so sensitive uplinks can be excluded from proxy ARP/NDP.

🤖 Council of Claudes · 4 inline comment(s)

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nell Council of Claudes — Nell

Important

Nell lens · simulated reviewer · simplicity · naming · error handling · keep useful comments

2 potential correctness issues

  • Behavioural default: Enabling HostSubnetNeighResponses by default feels risky; I think we usually default new dataplane-affecting features to Disabled and then flip later once hardened. Worth confirming intent?
  • Error handling: newProxyNeighManager panics on netlink init failure. IIUC this would take down Felix rather than propagate a clean error to the dataplane loop/startup; can we return err up instead?

Also, a few simplifications/clean‑ups:

  • We now have near-duplicate helpers (docker network inspect + subnet offset math) in both e2e and felix/fv; can we deduplicate into a small shared test util?
  • Case/normalisation: we compare config and service types by exact string. Given the config parser is case-insensitive, maybe normalise once (or use strings.EqualFold) to avoid surprises.
  • InternalDataplane stores proxyNeighManagers but doesn't use the slice later; do we need to keep that field?

Finally, is the plumb‑through from FelixConfiguration->felix/config.Config->dataplane complete for this field? I see the CRD field, docs, and the Config field/tag, and it’s passed into int_dataplane.Config, but I don’t see any conversion code changes — suspect it’s automatic, but worth a quick double‑check that a non-default YAML actually reaches configParams.HostSubnetNeighResponses.

🤖 Council of Claudes · 3 inline comment(s)

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casey Council of Claudes — Casey

Warning

Casey lens · simulated reviewer · testing discipline · API design · simplicity · robustness

1 robustness issue (panic in felix), plus a few naming/clarity items and small maintainability nits. One policy question: do we really want this enabled by default?

  • Naming/clarity: “Neigh” reads a bit cryptic to me across the API, config, file/type names, and docs. Could we spell out “Neighbor” (and be consistent with American English “neighbor” vs “neighbour”) and/or lean on a term like “ProxyNeighbor” to capture ARP+NDP more directly? WDYT?
  • API/defaults: Perhaps I’ve forgotten the context — is there a reason this ships enabled-by-default? Responding to ARP/NDP from the host is a behavior change that might surprise environments relying on L3 reachability/BGP-only paths. I could be swayed either way, but I’d expect “Disabled” as the safer initial default.
  • Manager enable check: We’re string-matching “PodsAndLoadBalancers” in a few places — can we avoid magic strings (use constants or normalize to a bool)?
  • Regex safety: Building a regexp from WorkloadIfacePrefixes without quoting can misinterpret metacharacters in prefixes; we should QuoteMeta.
  • Tests: e2e/FV look generally good and labeled rather than self-skipping — thanks. One question on FV: adding a new external image (netshoot) tends to add flake surface. Is there a way to reuse our existing infra image or a lighter image we already pull?

🤖 Council of Claudes · 8 inline comment(s)

stevegaossou added a commit that referenced this pull request Jun 24, 2026
* Orchestrator: resubmit a fresh task once if it hangs

#240 (calico#12960) hung the orchestrator twice at the poll cap even though its
input was the *smallest* of the three benchmark PRs (25 comments vs #241's 42,
which completed in ~2.5m) — so it's a server-side stuck task, not payload size.

A healthy orchestrator dedups in ~2-3 min, so a long poll means stuck-not-slow.
reviewWithAgent now takes a maxPollMs; orchestrateDedup makes up to ORCH_ATTEMPTS
(2) attempts, each a *fresh* task (distinct msgId) bounded by ORCH_POLL_MS (7m),
so a stuck task gets a second shot — a fresh submission usually doesn't recur.
Two 7m attempts still fit inside the 30m job timeout alongside personas + posting.
Still lossless: if all attempts produce nothing, post everything unfiltered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Richer error diagnostics for agent calls

The recurring #240 hang only logged "poll error (fetch failed)" and "did not
complete within 600s" — not enough to tell a stuck server-side task from a
network problem. Add:
- errDetail(): surfaces e.cause (ECONNRESET / UND_ERR_* / etc.) that Node's
  fetch hides behind generic "fetch failed" / "terminated" messages.
- rpc(): on non-2xx, include statusText + a short response-body snippet (5xx
  gateways often explain themselves there).
- poll timeout: report last task state + counts of successful polls vs poll
  errors — so "last state: working, 84 polls ok, 0 poll errors" clearly means
  stuck-server-side, vs poll errors meaning network.
- warn explicitly when a task can't be submitted after 3 attempts.

Logging-only (plus reading the error body); behavior unchanged. Validated with
mock tests (happy path, cause surfaced, submit-failure warning, graceful post-all).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address Council review on #244

1. Include GITHUB_RUN_ATTEMPT in agent msgIds (orchestrator + personas). The run
   id alone is stable across GitHub "Re-run jobs", so a re-run could reuse a prior
   msgId and get a stale server-side task. RUN_ATTEMPT (+ per-attempt suffix)
   guarantees a fresh task on every submission.
2. Retry the orchestrator on an UNPARSEABLE response too, not just on no-response:
   parse moved into the attempt loop, so a garbled/fenced reply gets a fresh-task
   retry within the same budget. Still lossless — fall through to post-all.
3. Downgrade the "resubmitting a fresh task" log from warning to info (the
   preceding timeout is already a warning; the resubmit is expected behaviour).

Deferred (consistent w/ prior calls): HTTPS/host allowlist on agent URL (applies
to all agents → hardening backlog), env-overridable knobs, JSDoc, reviewFn
test-seam/Jest harness, maxPollMs clamp.

Validated: mock tests for good/failed/unparseable retry paths, graceful post-all,
and msgId uniqueness — all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirrors the diff the human reviewers first saw on projectcalico#12451
('Per Pod/LB IP proxy ARP' by @MichalFupso) for the Council of Claudes benchmark.
base(fork)=bbf9d864bc8b16ec93522d5eaba0b8f2164c5dbe  head(as-reviewed)=b9eb6ddda00317605a351d98515bfeea3c9ce1e8
@stevegaossou
stevegaossou force-pushed the coc-sample-12451-v2-head branch from e4939cb to 50ec5c0 Compare June 24, 2026 07:27
@stevegaossou
stevegaossou force-pushed the coc-sample-12451-v2-base branch from 7e29107 to 472aef1 Compare June 24, 2026 07:27
// Register the proxy neighbor managers when enabled. They listen on raw sockets
// and respond to ARP (IPv4) / NDP (IPv6) requests for pod and LB IPs that fall
// within the same subnet as a host physical interface.
if config.HostSubnetNeighResponses == "PodsAndLoadBalancers" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔎 Correctness — The feature enable check is case-sensitive: if config.HostSubnetNeighResponses is set via env/YAML with different casing (allowed by the config schema), this equality will fail and the feature won’t start. Suggest using a case-insensitive check, consistent with other flags (e.g., FloatingIPsEnabled above):

if strings.EqualFold(config.HostSubnetNeighResponses, "PodsAndLoadBalancers") {
    // ...
}

Don’t forget to import "strings".

// active from Felix's first calc-graph flush.
fc := api.NewFelixConfiguration()
fc.Name = "default"
fc.Spec.HostSubnetNeighResponses = new(api.HostSubnetNeighResponsesPodsAndLoadBalancers)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔎 Correctness — This line won’t compile: new(api.HostSubnetNeighResponsesPodsAndLoadBalancers) attempts to use a constant as a type. Use a pointer to the constant value instead:

mode := api.HostSubnetNeighResponsesPodsAndLoadBalancers
fc.Spec.HostSubnetNeighResponses = &mode
// or, if available:
// fc.Spec.HostSubnetNeighResponses = ptr.To(api.HostSubnetNeighResponsesPodsAndLoadBalancers)

dp.RegisterManager(proxyNeighMgr6)
dp.proxyNeighManagers = append(dp.proxyNeighManagers, proxyNeighMgr6)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔎 Correctness — The manager starts listener goroutines and stores them in dp.proxyNeighManagers, but there’s no corresponding shutdown to cancel contexts/close sockets when Felix stops. This risks leaking goroutines/file descriptors across teardown. Recommend invoking a cleanup during dataplane shutdown (e.g., in InternalDataplane.Stop or equivalent) to iterate dp.proxyNeighManagers and call a new Stop/Close method or expose/call m.cancel() to ensure listeners terminate.

if ip.To4() != nil {
return "", fmt.Errorf("NeighborSolicit requires an IPv6 address, got %s", ip)
}
out, err := p.exec("rdisc6", "-1", "-w", "5000", extL2Iface, ip.String())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔎 Correctness — NeighborSolicit uses rdisc6 to send a Neighbor Solicitation. rdisc6 is typically for Router Solicitation/Discovery; for NS/NA, the ndisc6 tool (from the same suite) is usually used. On some images/environments rdisc6 may not emit NS and could cause spurious failures. Consider switching to a tool that explicitly sends a single NS (e.g., ndisc6 <addr> <iface>), or verify netshoot’s rdisc6 behavior and adjust invocation accordingly.

// Register the proxy neighbor managers when enabled. They listen on raw sockets
// and respond to ARP (IPv4) / NDP (IPv6) requests for pod and LB IPs that fall
// within the same subnet as a host physical interface.
if config.HostSubnetNeighResponses == "PodsAndLoadBalancers" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Maintainability & Tests — The manager is enabled with a direct string comparison against "PodsAndLoadBalancers". This is brittle (case sensitivity) and spreads stringly-typed policy across components.

  • Suggestion:
    • Normalize once at construction and pass an enum/bool to the dataplane. For example, in StartDataplaneDriver, parse with strings.EqualFold and set a typed field:
      • Add to intdataplane.Config: HostSubnetNeighResponsesEnabled bool
      • In driver.go: HostSubnetNeighResponsesEnabled: strings.EqualFold(configParams.HostSubnetNeighResponses, "PodsAndLoadBalancers")
      • Here: if config.HostSubnetNeighResponsesEnabled { ... }
    • Alternatively, if you prefer strings, at least use strings.EqualFold to match the parsing semantics used for FloatingIPs.

Comment thread felix/dataplane/driver.go
Hostname: felixHostname,
NodeZone: felixNodeZone,
FloatingIPsEnabled: strings.EqualFold(configParams.FloatingIPs, string(apiv3.FloatingIPsEnabled)),
HostSubnetNeighResponses: configParams.HostSubnetNeighResponses,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Maintainability & Tests — HostSubnetNeighResponses is plumbed through as a raw string while FloatingIPs is normalized via strings.EqualFold. This inconsistency invites regressions when values differ in case.

  • Suggestion: Normalize to a boolean/enum at this boundary, matching how FloatingIPs is handled:
    dpConfig.HostSubnetNeighResponsesEnabled = strings.EqualFold(configParams.HostSubnetNeighResponses, string(apiv3.HostSubnetNeighResponsesPodsAndLoadBalancers))
    and pass the normalized value into intdataplane.Config.


FloatingIPsEnabled bool

HostSubnetNeighResponses string

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Maintainability & Tests — HostSubnetNeighResponses is stored in intdataplane.Config as a string. This leaks API details into a low-level component and forces string comparisons later.

  • Suggestion: Change to a typed/normalized field (e.g., HostSubnetNeighResponsesEnabled bool or an internal enum) and keep string decoding at the config/driver boundary.

}
}

// Register the proxy neighbor managers when enabled. They listen on raw sockets

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Maintainability & Tests — Untested branching: the config gate for enabling the proxy-neighbor manager (and the IPv6 sub-manager) isn’t directly covered by a unit/integration test. Since this is the primary on/off switch, accidental regressions here would be easy to miss.

  • Ask for a focused unit test (no FV/E2E needed) that:
    • Constructs an InternalDataplane with HostSubnetNeighResponses disabled and asserts no proxyNeighManagers are registered.
    • Constructs one with HostSubnetNeighResponses enabled and IPv6Enabled true and asserts two managers are registered (v4 + v6).
      This exercises the only branch that’s not covered by your current suite and will guard future refactors.

return fmt.Sprintf("%s/%d", out, maskOnes), nil
}

// v6SubnetWithSuffix returns a CIDR built by appending an IPv6 suffix CIDR

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Maintainability & Tests — v6SubnetWithSuffix logic (IPv6 math via netip) overlaps functionally with FV’s local helpers. Consolidate into a shared test utility as noted above to prevent divergence and reduce mental overhead.

}

m.listeners[ifaceName] = l
log.WithField("iface", ifaceName).Info("Started proxy neighbor listener")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Maintainability & Tests — Starting/stopping listeners logs at Info for every interface. On large hosts this can get noisy and drown out higher-signal logs.

  • Suggestion: Consider downgrading “Started/Stopped proxy neighbor listener” to Debug, keeping a one-time “feature enabled” Info log when the manager registers.

// neighbour entries on host interfaces for local pod IPs and LB VIPs that overlap the
// host subnet. [Default: PodsAndLoadBalancers]
HostSubnetNeighResponses string `config:"oneof(Disabled,PodsAndLoadBalancers);PodsAndLoadBalancers"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Security — Default enables proxy ARP/NDP on host subnets (PodsAndLoadBalancers). Risk: unexpected exposure of pod and VIP addresses to L2-adjacent networks, potentially weakening network isolation and increasing attack surface (scanning/recon, lateral-movement attempts). Mitigation: change default to Disabled; require explicit opt-in. Alternatively, gate by additional scoping (e.g., an allowlist of host interfaces or CIDR filters) and document risks prominently.

return ip.To4() == nil
}

func isNoEncapPool(pool *proto.IPAMPool) bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Security — Pool filtering only checks encapsulation mode (isNoEncapPool) and ignores AllowedUses. Risk: proxy ARP/NDP may respond for any IP within any no-encap pool, regardless of intended use, increasing chance of claiming unintended addresses (especially for LB VIPs). Mitigation: extend checks to ensure the IP belongs to a pool whose AllowedUses includes the relevant purpose (Workload for pod IPs is implicit via IPAM; LoadBalancer for VIPs should be enforced).


// runARPListener listens for ARP requests on a raw socket and replies for
// IPs in the desired set.
func (m *proxyNeighManager) runARPListener(ctx context.Context, l *ifaceListener) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Security — runARPListener replies to every matching ARP request without rate limiting. Risk: L2-adjacent attacker can flood ARP requests for tracked IPs, consuming CPU in user space (potential local DoS). Mitigations: add basic per-interface/global rate limiting/backoff, or consider leveraging kernel-managed proxy_arp entries where feasible; at minimum, log and drop when exceeding thresholds.


// runNDPListener listens for Neighbor Solicitations on a raw ICMPv6 socket
// and replies with Neighbor Advertisements for IPs in the desired set.
func (m *proxyNeighManager) runNDPListener(ctx context.Context, l *ifaceListener) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Security — runNDPListener replies to Neighbor Solicitations without rate limiting and sends unsolicited NAs for DAD to all-nodes multicast. Risk: high-rate NS/DAD floods from an attacker on L2 can cause CPU/bandwidth load (local DoS) and multicast churn. Mitigation: implement simple rate limiting/burst control per interface/IP, and consider bounding unsolicited NA emissions.


case *proto.ServiceUpdate:
svcKey := serviceID{Namespace: msg.Namespace, Name: msg.Name}
if msg.Type != "LoadBalancer" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nell Nell — We compare msg.Type to "LoadBalancer" as a raw string. K8s uses that exact spelling, but worth considering a constant or at least being explicit about case sensitivity. Do we want to handle unknown casings, or is upstream guaranteed?

// detectKindNetworkSubnets returns the IPv4 and IPv6 subnets configured on the
// given docker network. Either return value may be nil if the network has no
// subnet of that family.
func detectKindNetworkSubnets(networkName string) (v4, v6 *net.IPNet, err error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nell Nell — detectKindNetworkSubnets duplicates the docker network inspect logic also added in felix/fv (inspectDockerNetworkV4). Can we share a small helper (e.g., under a common test utils package) rather than copy?

// Both inputs must be IPv4. We normalize to 4-byte form because net.ParseCIDR
// can return either 4- or 16-byte representations depending on the input
// string.
func subnetOffset(parent *net.IPNet, offset string) (string, error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nell Nell — subnetOffset is very close to subnetOffsetV4 in felix/fv. Same thought — can we deduplicate these helpers into a shared test util?

FloatingIPsDisabled FloatingIPType = "Disabled"
)

// +kubebuilder:validation:Enum=Disabled;PodsAndLoadBalancers

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casey Casey — I think “Neigh” here is a bit opaque — any reason not to spell this out?

Suggested change
// +kubebuilder:validation:Enum=Disabled;PodsAndLoadBalancers
// +kubebuilder:validation:Enum=Disabled;PodsAndLoadBalancers
type HostSubnetNeighborResponsesMode string
const (
HostSubnetNeighborResponsesDisabled HostSubnetNeighborResponsesMode = "Disabled"
HostSubnetNeighborResponsesPodsAndLoadBalancers HostSubnetNeighborResponsesMode = "PodsAndLoadBalancers"
)

We tend to prefer descriptive names over abbreviations.

allManagers []Manager
managersWithRouteTables []ManagerWithRouteTables
managersWithRouteRules []ManagerWithRouteRules
proxyNeighManagers []*proxyNeighManager

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casey Casey — Do we still need proxyNeighManagers on the struct? I don’t see it read anywhere — looks like we RegisterManager() and then only keep the slice. Can this be removed?

}

func newProxyNeighManager(dpConfig Config, ipVersion uint8) *proxyNeighManager {
nl, err := netlinkshim.NewRealNetlink()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casey Casey — Not sure we want a panic down in felix — can this be an error return instead so we don’t take down the process if netlink init fails?

Suggested change
nl, err := netlinkshim.NewRealNetlink()
nl, err := netlinkshim.NewRealNetlink()
if err != nil {
log.WithError(err).Error("Failed to create netlink handle for proxy neighbor manager, disabling feature")
// return a no-op manager, or plumb an error up so the caller can skip registration
}

Best not to accidentally take down the cluster if we miss something!

af arpClientFactory,
nf ndpConnFactory,
) *proxyNeighManager {
wlIfacesPattern := "^(" + strings.Join(dpConfig.RulesConfig.WorkloadIfacePrefixes, "|") + ").*"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casey Casey — Small robustness nit: these prefixes aren’t quoted — any regex meta in the configured prefixes could be misinterpreted.

Suggested change
wlIfacesPattern := "^(" + strings.Join(dpConfig.RulesConfig.WorkloadIfacePrefixes, "|") + ").*"
quoted := make([]string, len(dpConfig.RulesConfig.WorkloadIfacePrefixes))
for i, p := range dpConfig.RulesConfig.WorkloadIfacePrefixes {
quoted[i] = regexp.QuoteMeta(p)
}
wlIfacesPattern := "^(" + strings.Join(quoted, "|") + ").*"
wlIfacesRegexp := regexp.MustCompile(wlIfacesPattern)

| `FelixConfiguration` schema | One of: <code>"Disabled"</code>, <code>"Enabled"</code>. |
| Default value (YAML) | `Disabled` |

### `HostSubnetNeighResponses` (config file) / `hostSubnetNeighResponses` (YAML)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casey Casey — Could we add a brief note that:

  • it only responds for IPs in no-encap pools (Never IPIP/VXLAN), and
  • for LoadBalancer VIPs, a single node is selected via hashing to answer ARP/NDP?
    That’ll set expectations about scope and who answers.

// ServiceUpdate just as it would in a production cluster.

const (
netshootImage = "docker.io/nicolaka/netshoot:v0.13"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casey Casey — Is there a reason to introduce a new external image (netshoot) in FV? We try to keep FV image surface small to reduce flakes. If we just need arping/curl, could we reuse an existing infra image or bake the minimal tools into one we already pull? Fine to leave as-is if not practical, though.

"github.com/projectcalico/calico/e2e/pkg/utils/images"
)

// proxy_neigh.go exercises the HostSubnetNeighResponses Felix feature

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casey Casey — nit: consistent naming would help find this later — would “proxy_neighbor.go” (and “ProxyNeighbor” in strings) read a bit clearer than “proxy_neigh”? You can take or leave it.

}

// Extract LoadBalancer ingress IPs from Status (the actual assigned IPs).
for _, ingress := range s.Status.LoadBalancer.Ingress {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casey Casey — Good call to include status.ingress IPs. Question: do we need to dedupe between LoadbalancerIp and LoadbalancerIngressIps when both are present? Probably rare, but easy to append duplicates. Fine to leave if upstream deduping makes it harmless.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants