More connectivity matrix fixes - #1933
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughChangesThe PR centralizes connectivity expectation ownership, updates TCP and UDP probe handling, expands ACL coverage, improves reachability diagnostics, and adjusts ESLAG endpoint and multihomed leaf selection. Connectivity matrix validation and probing
ACL protocol and NAT coverage
ESLAG and multihoming support
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR continues tightening the connectivity-matrix-driven test harness by making matrix validation stricter (rejecting expectations that no probe phase can assert), improving probe error attribution, and expanding ACL test coverage to cover additional dataplane behaviors.
Changes:
- Adjust multihomed server leaf candidate selection to prefer orphan leaves and only “top up” with ESLAG leaves when needed.
- Refactor connectivity matrix validation/probing by introducing a single “entry owner” (phase) classifier, adding explicit validation for unprobeable expectations, and improving TCP/UDP probe error context via
Reachability. - Extend gateway ACL test suite/docs with numeric-protocol ICMP coverage and pre-NAT destination matching coverage; add unit tests for
getServerHostBGPCmd.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pkg/hhfab/vlabbuilder.go | Ensures orphan leaves are preferred for multihomed server placement, with ESLAG leaves appended only when necessary. |
| pkg/hhfab/testing.go | Updates TCP/UDP port probes to accept Reachability and improves IperfError context (Why). |
| pkg/hhfab/testing_test.go | Adds unit tests for host BGP command generation from server connections. |
| pkg/hhfab/rt_acl_tests.md | Documents new ACL test cases/coverage and clarifies limitations. |
| pkg/hhfab/rt_acl_tests.go | Adds new ACL test cases (numeric ICMP protocol; pre-NAT destination match) and reuses the persistent iperf3 port constant. |
| pkg/hhfab/matrix.go | Adds phase ownership classification, validates unprobeable entries, anchors listener teardown pkill, and aligns phases with entryOwner. |
| pkg/hhfab/endpoints_test.go | Updates validation tests and adds coverage for “no phase can read this entry” failures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
8023e3d to
ca75c69
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
pkg/hhfab/endpoints_test.go (1)
319-326: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the required unprobed classification.
The endpoint label also appears in the unknown-entry error. A regression that returns
could not be evaluatedinstead ofno probe phase can readstill passes this case. Store the result and assert the required classification.Proposed fix
- require.ErrorContains(t, m.Validate(), "server-1(vpc-1/default) → server-2(vpc-2/default)") + err = m.Validate() + require.ErrorContains(t, err, "no probe phase can read") + require.ErrorContains(t, err, "server-1(vpc-1/default) → server-2(vpc-2/default)")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/hhfab/endpoints_test.go` around lines 319 - 326, Update the final m.Validate assertion in the TranslatedAddress case to store the returned error and assert that it contains both the endpoint label and the required “no probe phase can read” unprobed classification, rather than relying only on the endpoint text.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/hhfab/matrix.go`:
- Around line 319-321: Add an ownership check to the condition that applies the
external Allow shortcut. The existing condition checks e.Pair.Destination,
e.Pair.Destination.External, and extAllowBySource[e.Pair.Source], but it should
also verify that the entry owner equals matrixPhaseCurl before continuing. This
ensures the shortcut only suppresses validation for curl-owned entries with
VerdictUnknown, not for unrelated external DNAT entries that happen to share a
source with an unscoped external Allow.
- Around line 373-376: Update Validate() to reject entries that simultaneously
define ProtoPort and a nonzero NAT.DestinationPort, including the inherited NAT
overlay case; return the existing validation error format for invalid
combinations. Preserve entryOwner() precedence only for valid, non-overlapping
entries.
---
Nitpick comments:
In `@pkg/hhfab/endpoints_test.go`:
- Around line 319-326: Update the final m.Validate assertion in the
TranslatedAddress case to store the returned error and assert that it contains
both the endpoint label and the required “no probe phase can read” unprobed
classification, rather than relying only on the endpoint text.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b7b6827a-1c23-4575-b4fe-33ebe46b8b66
📒 Files selected for processing (8)
pkg/hhfab/endpoints_test.gopkg/hhfab/matrix.gopkg/hhfab/rt_acl_tests.gopkg/hhfab/rt_acl_tests.mdpkg/hhfab/rt_eslag_fallback.gopkg/hhfab/testing.gopkg/hhfab/testing_test.gopkg/hhfab/vlabbuilder.go
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
pkg/hhfab/matrix.go:410
- hasEntriesOwnedBy only scans explicitly added expectations in m.entries, but the probe phases also act on implicit default expectations returned by Lookup (e.g., default-deny server→server and server→external pairs). As a result, TestConnectivityWithMatrix can allow pings/iperfs/curls to be disabled even though those phases would still have work (and would otherwise pass those implicit expectations unasserted).
func (m *ConnectivityMatrix) hasEntriesOwnedBy(p matrixPhase) bool {
for _, byPP := range m.entries {
for _, e := range byPP {
if m.entryOwner(e) == p {
return true
6628df4 to
b9c51d5
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/hhfab/endpoints.go`:
- Around line 201-202: Update the HostBGP attachment deduplication flow around
the attachment collection and metadata construction so duplicate attachments
preserve metadata from every raw VPCAttachment: merge l3vni with logical AND
(and retain the ESLAG exemption only when appropriate) before applying the no-IP
suppression at the line-250 path. Ensure the resulting behavior is independent
of VPCAttachmentList.Items order, and add regression coverage for both
attachment orders.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7733d4ca-a1a5-41e8-b774-869a6a44266c
📒 Files selected for processing (2)
pkg/hhfab/endpoints.gopkg/hhfab/endpoints_test.go
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (3)
pkg/hhfab/matrix.go:537
- probesServerPair() claims to match what the server-to-server phases will probe, but it doesn’t exclude self-pairs or same-node pairs. Those are skipped by entryOwner(), so this helper can return true for pairs that will never be probed (and may lead to extra iteration / future inconsistencies).
// probesServerPair reports whether the server-to-server phases will probe this
// pair, so listener setup covers exactly what gets probed.
func (d *matrixTestDeps) probesServerPair(src, dst *Endpoint) bool {
return src.Server != nil && dst.Server != nil &&
d.inSources(src.Server.Name) && d.inDestinations(dst.Server.Name)
pkg/hhfab/matrix.go:855
- The “phase has no enabled probe” guards use matrix.hasEntriesOwnedBy(...), which doesn’t consider opts.Sources/opts.Destinations. This can incorrectly fail runs where the caller filters out all server-to-server (or curl) entries, even though nothing in that phase would actually be probed.
// checkPing/checkIPerf/checkCurl no-op when their count is off, so a phase
// left with no enabled probe would pass its entries unasserted
if opts.PingsCount <= 0 && opts.IPerfsSeconds <= 0 && matrix.hasEntriesOwnedBy(matrixPhaseServerServer) {
return fmt.Errorf("matrix has server-to-server entries but both pings and iperfs are disabled") //nolint:goerr113
}
pkg/hhfab/endpoints.go:110
- The new naming is misleading: serverAttachment.l3vni is set via
vpc.Spec.Mode != vpcapi.VPCModeL2VNI, which includes bothl3vniandl3flatmodes, and eslagL3VNIOnly() similarly tests “non-L2VNI” rather than L3VNI specifically. Consider renaming to reflect the actual semantics (e.g., l3Mode / eslagNonL2VNIOnly) to avoid confusion as more VPC modes exist.
eslag bool
l3vni bool
attachName string // for diagnostics
}
// eslagL3VNIOnly reports whether every one of the server's attachments is an
// ESLAG connection into a non-L2VNI VPC. Those servers never run hhnet, so
// discovering no addresses on them is expected rather than a failure.
func eslagL3VNIOnly(atts []serverAttachment) bool {
if len(atts) == 0 {
return false
}
return !slices.ContainsFunc(atts, func(a serverAttachment) bool {
return !a.eslag || !a.l3vni
})
}
pau-hedgehog
left a comment
There was a problem hiding this comment.
Nice work, this closes almost everything from the last round plus the two issues that came out of reviewing it.
One defect worth fixing: the new disabled-probe guard counts stored entries while the server-to-server phase probes synthesized ones, so it can pass while asserting nothing. One question on the UDP connect budget depending on the expectation. The rest are comments and one-liners.
I ran two mutations on env-1 to check the ACL suite fails when it should: breaking the ACL config fails Default Deny through all three probe types both ways, and flipping one tcp verdict fails exactly one probe in one direction. Worth knowing it has teeth, and maybe worth making permanent.
The red v-mesh-gw job looks like the #1929/#1937/#1924 single-packet-loss family: one lost ping on an expected-allow switch-peering pair, nothing to do with the ACL or matrix paths. Worth rebasing onto master before merge so CI re-runs and we get a clean board rather than merging with a red check.
Nits, batched here since several sit on lines this PR doesn't touch:
- overlayExternalNoEgress still writes a bare {Pair, Verdict: VerdictDeny} with no Reason, Peering or Detail, so a curl failure on one of those pairs prints "because: no reason recorded" in the branch whose parent added the because clause. One Detail line.
- The --multihomed-servers usage string still says "2 connections to 2 different orphan leaves" while 94fe6f2 makes ESLAG leaves the documented fallback. The struct comment was fixed in #1910, this one was missed.
- TestValidate is still in endpoints_test.go and this PR adds 40 more lines to it. Cheaper to move to matrix_test.go now than later.
- checkUDPPort's dead band: "UDP traffic was not blocked" is a real improvement over claiming delivery, but it still doesn't say the result was inconclusive or print the two thresholds.
- The UDP retry doubles the pathological worst case to 2 x outer, ~96s for a deny probe at release defaults, serialized behind the weight-1 semaphore. The expected case is far better and the retry is the right call; just noting the tail.
e6df485 to
78e59df
Compare
|
@pau-hedgehog I rebased on master and pushed two more commits on top of the old ones; the first addresses your comments (except for the UDP connection timeout, until we decide what to do), the second improves the because clause for ACLs. I want to avoid a fourth round of matrix fixes if we can, so let's try and fix any remaining issue here |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (2)
pkg/hhfab/matrix.go:351
- The doc comment on entryOwner is misleading: Validate calls entryOwner before it reports VerdictUnknown entries, and entryOwner itself has VerdictUnknown-specific logic (e.g. external Curl allow short-circuit). The comment currently states Validate has already rejected VerdictUnknown, which isn’t true and may confuse future changes (e.g. someone removing the VerdictUnknown branch as “dead”).
// entryOwner assumes Validate has already rejected VerdictUnknown: the phases
// carry no verdict guard of their own, and reachabilityFromExpectation maps
// Unknown to Reachable:false, so a phase reached without Validate would assert
// deny on an entry explicitly marked unevaluable.
pkg/hhfab/endpoints.go:110
- Function/field naming doesn’t match the behavior described in the comment: eslagL3VNIOnly returns true for any attachment where VPC mode is “non-L2VNI” (endpoints.go:202 sets l3vni as
Mode != VPCModeL2VNI), which includes L3Flat as well as L3VNI (see other code paths checkingVPCModeL3VNI || VPCModeL3Flat). Consider renaming the boolean (e.g. l3Mode) and the helper (e.g. eslagL3ModeOnly) to avoid implying it only applies to L3VNI.
// eslagL3VNIOnly reports whether every one of the server's attachments is an
// ESLAG connection into a non-L2VNI VPC. Those servers never run hhnet, so
// discovering no addresses on them is expected rather than a failure.
func eslagL3VNIOnly(atts []serverAttachment) bool {
if len(atts) == 0 {
return false
}
return !slices.ContainsFunc(atts, func(a serverAttachment) bool {
return !a.eslag || !a.l3vni
})
}
pau-hedgehog
left a comment
There was a problem hiding this comment.
Rebase looks good, and thanks for turning these around so fast.
checkProbesEnabled closes the guard properly: it walks the same pairs the phases do, including the Deny that Lookup synthesizes where populate stored nothing, and honouring the source/destination filters covers the ownership-is-not-probed gap as well. TestCheckProbesEnabled asserting the guard rather than the walk is the right shape.
Also reviewed 78e59df on top. expectationWhy keeps the allow path byte-identical and only changes the deny phrasing, TestExpectationWhy covers every branch of it, and each current caller of setVPCToVPCProtoVerdict is an ACL context so labelling a proto-scoped deny "ACL" holds for all of them. "ACL on gateway-peering ..." is a real improvement on a failure line, that phrasing bothered me in the mutation runs too.
UDP connect budget settled in-thread: keeping 5s, revisit if we ever see a deny probe time out on a path we expect to be open.
Nothing else from me.
Three places decided which phase reads an entry: runMatrixServerServerPhase skipped a pair carrying proto-port entries or a DNAT'd port, runMatrixPortForwardPhase claimed an entry with a valid NAT (ip, port), and defaultEntryShadowed re-derived the first of those for Validate. They agreed, but any edit to one gate could silently desynchronize Validate from the phase it certifies — which is how the shadowed-entry bug arose in the first place. Replace all of it with entryOwner(), consulted by the four phases and by Validate. Two gaps close as a consequence: - a proto-scoped entry towards an external is read by nothing. runMatrixProtoPortPhase probes server destinations only, and the external oracle is one untargeted curl per source server. Validate now rejects such an entry instead of letting it report green while asserting nothing, which is what an ACL case on an external peering would have done. - an entry with a translated port but no valid destination IP was claimed by no phase, so unless its verdict was Unknown it went untested. It is now unprobeable by construction and Validate says so, before any SSH runs. The proto-port phase and its listener setup also go through entryOwner, so a multi-attached server no longer gets an on-demand iperf3 listener started and torn down on a same-node pair that no probe visits. Separately, on probe reporting: - checkTCPPort and checkUDPPort took a bool, so neither could set IperfError.Why and the ACL cases were the only failures in the suite without a "because: ..." suffix. They now take a Reachability like checkPing and checkCurl do, which also drops the workaround in runMatrixIperfPortForward that patched Why after the call. - UDP loss between the allow and deny thresholds is neither delivered nor blocked, so an expected deny fails there — correctly, but the message claimed datagrams were delivered, pointing at a dataplane bug when the path is merely lossy. - an icmp expectation is unassertable when pings are disabled, since checkPing no-ops at count 0. Report it rather than passing silently. - anchor the listener teardown's pkill pattern so port 5301 cannot match a listener on 53010. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Emanuele Di Pascale <emanuele@githedgehog.com>
MultiHomedServers is documented as taking two connections to two different leaves, "preferably orphans", and ESLAG leaves are only added to the candidate list when there are fewer than two orphans. But they were appended before the orphan loop ran, so with exactly one orphan the first multihomed server took two ESLAG leaves and the orphan went unused. Collect them separately and append after the orphan loop, so the round-robin reaches an ESLAG leaf only once the orphans are exhausted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Emanuele Di Pascale <emanuele@githedgehog.com>
Two dimensions the ACL suite asserted nothing about. Every ICMP expectation so far rested on ICMP falling through to the default action, since it matches neither a tcp nor a udp rule. The dataplane has no icmp keyword yet, but a numeric protocol is accepted, so a rule with proto "1" is the first case asserting that a rule can match ICMP at all. ACL rules are evaluated before NAT, so match.dst is compared against the address the initiator dialed — the peer's advertised expose "as" pool — not the destination's native IP. No case exercised that: nine configure no NAT and the masquerade one has no match block. Pair bidirectional static NAT with rules matching the peer's pool; an implementation matching post-NAT would deny everything, so the all-allow result is the assertion. Also documents in rt_acl_tests.md that ACLs on external peerings cannot be asserted with the probes available, and that the flow-scope-needs-NAT restriction is temporary per docs#338 — once flow works without NAT it becomes the CRD default, and every case here uses explicit packet scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Emanuele Di Pascale <emanuele@githedgehog.com>
The helper is pure and had no test, and its multi-VPC loop has no caller until the multi-VPC hostBGP work lands, so nothing pinned the emitted string when it moved from a literal "vpc" token to the VPC name. Cover the single-param form, the ServerOffset walk, the two-param concatenation, and the error paths. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Emanuele Di Pascale <emanuele@githedgehog.com>
runMatrixProtoPortPhase honors the Sources/Destinations filters, but startMatrixProtoPortListeners only skipped non-server sources, so it computed its listener set over a wider pair set than the phase probes. With either filter set, that starts and tears down an on-demand iperf3 listener on destinations no probe visits. Dormant today — every matrix caller passes tcOpts, which never sets either field, and the CLI flags feed the legacy path — but the fields exist for this one to use. State the pair condition once on matrixTestDeps and have both loops use it, so the listener set cannot drift from the probe set again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Emanuele Di Pascale <emanuele@githedgehog.com>
similarly to what happens in the static DHCP test, we are getting a new lease via hhnet bond on the eslag server, which might return a different IP. Update the matrix to make sure we have the correct information Signed-off-by: Emanuele Di Pascale <emanuele@githedgehog.com>
Validate was re-deriving what the curl phase can assert, and stated it for every external destination rather than for curl-owned entries only. An Unknown external port-forward entry therefore escaped Validate and was then skipped by its own phase, asserting nothing. Fold all three judgments into entryOwner so Validate becomes a plain three-way split on the owner, and reject two more entries no probe can read: ProtoPort combined with a translated port, which the proto-port probes would aim past, and an external Allow behind a NAT with no source pool, which the curl oracle discards while the entry claims reachability. Neither is produced today, but setVPCToVPCProtoVerdict inherits NAT from the pair's default entry, so the first is one overlay away. Since Validate aborts the run, no Unknown entry can reach a phase any more; drop the port-forward phase's now-dead verdict guard. Also reject runs where a phase has entries but all of its probes are knob-disabled no-ops: only checkUDPPort defaults its duration, so --pings=0 --iperfs=0 left every stated server-to-server expectation unasserted while Validate reported the oracle sound. Signed-off-by: Emanuele Di Pascale <emanuele@githedgehog.com>
A denied path neither completes nor refuses the iperf3 -u control connect, so the probe ran until its own `timeout` backstop killed it, leaving 5s for the report to come back over SSH. Overrun that and the unparseable report was returned as a hard error before the deny expectation was ever consulted, failing a correctly denied path. Bound the connect with iperf3's --connect-timeout and size the deadlines off it: 5s for deny probes (an open path handshakes in milliseconds), 15s for allow probes, `timeout -k 5` as a pure backstop, and 30s of SSH headroom. Retry once when no report comes back, since that is a stall on the SSH/docker path rather than a verdict on the path under test. Deny probes drop from ~28.6s to ~5.5s at release defaults. Fix #1944 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Emanuele Di Pascale <emanuele@githedgehog.com>
CollectServerEndpoints skipped any server whose address discovery came back empty without recording a drop, so the matrix lost that server and all of its attachments while Validate still called the oracle sound. The comment named the exempt case, ESLAG servers in L3VNI mode which never run hhnet, but the condition tested only that no addresses were found: a server whose DHCP or hhnet failed took the same silent path. That is the wrong way round, since a server with one address matching nothing does stop the run. Carry the two facts the comment describes on each candidate attachment, taken from the attachment's own connection and VPC, and exempt only a server whose every attachment is ESLAG into a non-L2VNI VPC. Any other server with no addresses now falls through to the existing per- attachment loop and is recorded as dropped, which Validate refuses to run against. Fix #1945 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Emanuele Di Pascale <emanuele@githedgehog.com>
hasEntriesOwnedBy iterated m.entries, but no phase works from stored entries: they walk endpoint pairs and call Lookup, which synthesizes a Deny where nothing is stored. populate only stores reachable or unevaluable pairs, so an all-deny topology had zero stored server-to-server entries while the phase still probed every pair. With --pings 0 --iperfs 0 the guard passed, checkPing returned nil and every deny expectation passed unasserted, which is what the guard was for. Replace it with checkProbesEnabled, which walks the same pairs the phases do and honors the --source/--destination filters they also apply. It owns all three thresholds, so the icmp check moves out of runMatrixProtoPortPhase and into the same pre-flight pass: it is answerable from the matrix alone, and it no longer fires after setup and listener start, once per entry. TestCheckProbesEnabled asserts the guard rather than the walk, so reintroducing the stored-entry scan fails it. Also from review: - state on matrixPhase that ownership is not the same as being probed, since Validate does not model the source/destination filters, and on entryOwner that the phases depend on Validate having rejected VerdictUnknown first - give deny expectations a real explanation instead of "no reason recorded"; ReachabilityReason only ever explains an allow, so the absence of an allowing peering is itself the reason - distinguish the UDP dead band from genuine delivery and name both thresholds - fix the --multihomed-servers usage string, which still promised orphan leaves after ESLAG leaves became the documented fallback - move TestValidate to matrix_test.go Signed-off-by: Emanuele Di Pascale <emanuele@githedgehog.com>
An ACL deny printed "because: gateway-peering <name>", which reads as if the peering explained the block when it is the thing being blocked. The reason alone cannot say more: ReachabilityReason only ever names the peering that would allow a pair, so a deny has to be phrased against it. Reuse Detail, which already carries free-form context for Reason, to say what withholds the pair, and render it as "<detail> on <reason> <peering>". A proto-scoped deny is exactly an ACL decision — that is what makes a verdict protocol/port-dependent, as setVPCToVPCProtoVerdict's own doc notes — so it records "ACL" and the clause becomes "because: ACL on gateway-peering <name>". Denies that carry a peering but no detail (overrideVPCToVPCVerdict) now read "<reason> <peering> does not allow it" rather than borrowing the allow phrasing, and a pair no peering covers still reports "no peering allows it". Signed-off-by: Emanuele Di Pascale <emanuele@githedgehog.com>
78e59df to
48c69e9
Compare
No description provided.