From ca80e49aa071d714c8cd82935b2ea195d1e3478e Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Tue, 21 Jul 2026 09:25:33 +0200 Subject: [PATCH 1/2] [client] Refresh WireGuard stats in mobile debug bundles (#6814) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The iOS and Android DebugBundle paths built GeneratorDependencies without setting RefreshStatus, so the bundle's status.txt read the cached peer state instead of live WireGuard interface stats. When the periodic health probe had not run yet, connected relayed peers showed "handshake: -" and "0 B/0 B" even though the interface was passing traffic. Wire RefreshStatus to RunHealthProbes on both platforms, matching the desktop daemon path in client/server/debug.go. The engine reference is already available in the cc.Engine() block used for client metrics. ## Describe your changes ## Issue ticket number and link ## Stack ### Checklist - [x] Is it a bug fix - [ ] Is a typo/documentation fix - [ ] Is a feature enhancement - [ ] It is a refactor - [ ] Created tests that fail without the change (if possible) - [ ] This change does **not** modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — **OR** I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See [CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#discuss-changes-with-the-netbird-team-first). > By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md). ## Documentation Select exactly one: - [ ] I added/updated documentation for this change - [x] Documentation is **not needed** for this change (explain why) ### Docs PR URL (required if "docs added" is checked) Paste the PR link from https://github.com/netbirdio/docs here: https://github.com/netbirdio/docs/pull/__ --- View with Codesmith Autofix with Codesmith Need help on this PR? Tag /codesmith with what you need. Autofix is disabled. ## Summary by CodeRabbit * **Bug Fixes** * Improved debug bundle generation on Android and iOS by refreshing connection health status before collecting diagnostic information. * Ensured debug bundles include more current health-related data for troubleshooting. --- client/android/client.go | 3 +++ client/ios/NetBirdSDK/client.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/client/android/client.go b/client/android/client.go index 99ccdf393..2266ff53d 100644 --- a/client/android/client.go +++ b/client/android/client.go @@ -247,6 +247,9 @@ func (c *Client) DebugBundle(platformFiles PlatformFiles, anonymize bool) (strin deps.SyncResponse = resp if e := cc.Engine(); e != nil { + deps.RefreshStatus = func() { + e.RunHealthProbes(context.Background(), true) + } if cm := e.GetClientMetrics(); cm != nil { deps.ClientMetrics = cm } diff --git a/client/ios/NetBirdSDK/client.go b/client/ios/NetBirdSDK/client.go index 359a83556..a2f123900 100644 --- a/client/ios/NetBirdSDK/client.go +++ b/client/ios/NetBirdSDK/client.go @@ -233,6 +233,9 @@ func (c *Client) DebugBundle(anonymize bool) (string, error) { deps.SyncResponse = resp if e := cc.Engine(); e != nil { + deps.RefreshStatus = func() { + e.RunHealthProbes(context.Background(), true) + } if cm := e.GetClientMetrics(); cm != nil { deps.ClientMetrics = cm } From 82fdfa84b8bfb563ab93fc0cdfdd35f9a920f711 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Tue, 21 Jul 2026 10:10:12 +0200 Subject: [PATCH 2/2] [proxy] match Bedrock provider models against the normalized request model (#6773) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Describe your changes Native AWS Bedrock requests carry the model in the URL path as a cross-region inference-profile id (e.g. `us.anthropic.claude-haiku-4-5`). The request parser normalizes that to the catalog key (`anthropic.claude-haiku-4-5`) before the router runs, but the router matched it against the operator's registered provider models with exact string equality. So a Bedrock provider registered with the id Bedrock actually uses (`us.anthropic…`) never matched a normalized request → the request denied with `llm_policy.model_not_routable` ("no provider configured for model …"). Only a provider registered with the already-stripped catalog id worked, which is not how Bedrock ids appear. Fix: introduce a single shared `llm.NormalizeBedrockModel` (the same ARN/region-prefix/version-suffix stripping the parser already does) and, in the router's `routeClaimsModel`, normalize a **Bedrock** route's candidate models before comparing. Now a Bedrock provider registered with either the raw inference-profile id or the normalized catalog id matches the request. Non-Bedrock routes keep exact matching. Surfaced by the new native-Bedrock e2e (`WireBedrock`, `/model/{id}/invoke`); the old e2e used the Anthropic body shape, which never normalized either side and so hid this. The request parser keeps its own identical normalizer for now; de-duplicating it onto `llm.NormalizeBedrockModel` is a trivial follow-up. ## Issue ticket number and link N/A — follow-up to the Agent Network Bedrock support / model-allowlist work. ## Stack ### Checklist - [x] Is it a bug fix - [ ] Is a typo/documentation fix - [ ] Is a feature enhancement - [ ] It is a refactor - [x] Created tests that fail without the change (if possible) - [x] This change does **not** modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — **OR** I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See [CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#discuss-changes-with-the-netbird-team-first). > By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md). ## Documentation Select exactly one: - [ ] I added/updated documentation for this change - [x] Documentation is **not needed** for this change (explain why) Internal routing correctness fix; no user-facing surface change. ### Docs PR URL (required if "docs added" is checked) Paste the PR link from https://github.com/netbirdio/docs here: https://github.com/netbirdio/docs/pull/__ ## Tests - `proxy/internal/llm`: `NormalizeBedrockModel` unit cases (region prefixes, version suffixes, ARN). - `proxy/internal/middleware/builtin/llm_router`: `routeClaimsModel` matches a Bedrock route registered with the raw `us.anthropic…` id against a normalized request model; non-Bedrock routes still match exactly. Note: full through-tunnel e2e verification of this (the native-Bedrock `TestProvidersMatrix/bedrock`) also needs the DNS lazy-connection warm-up (separate PR) to get the client past the proxy-peer gate; they converge once both land. ## Summary by CodeRabbit * **Bug Fixes** * Improved Amazon Bedrock model matching across ARN formats, regional prefixes, and version or throughput suffixes. * Bedrock routes now correctly match equivalent model identifiers even when requests and route configurations use different formats. * Non-Bedrock model matching remains exact. --- proxy/internal/llm/bedrock_model.go | 38 +++++++++++++++++++ proxy/internal/llm/bedrock_model_test.go | 23 +++++++++++ .../builtin/llm_router/bedrock_route_test.go | 30 +++++++++++++++ .../builtin/llm_router/middleware.go | 9 +++++ 4 files changed, 100 insertions(+) create mode 100644 proxy/internal/llm/bedrock_model.go create mode 100644 proxy/internal/llm/bedrock_model_test.go create mode 100644 proxy/internal/middleware/builtin/llm_router/bedrock_route_test.go diff --git a/proxy/internal/llm/bedrock_model.go b/proxy/internal/llm/bedrock_model.go new file mode 100644 index 000000000..a4c4704f7 --- /dev/null +++ b/proxy/internal/llm/bedrock_model.go @@ -0,0 +1,38 @@ +package llm + +import ( + "regexp" + "strings" +) + +// bedrockRegionPrefixes are the cross-region inference-profile prefixes that +// front a Bedrock model id (e.g. "eu.anthropic.claude-..."). +var bedrockRegionPrefixes = []string{"us.", "eu.", "apac.", "global."} + +// bedrockVersionSuffix matches the trailing "-vN[:N]" or "-YYYYMMDD-vN[:N]" +// version/throughput suffix of a Bedrock model id. +var bedrockVersionSuffix = regexp.MustCompile(`-(\d{8}-)?v\d+(:\d+)?$`) + +// NormalizeBedrockModel strips an ARN wrapper, a cross-region inference-profile +// prefix, and the version/throughput suffix from a Bedrock model id so it +// matches the catalog/pricing key, e.g. +// "eu.anthropic.claude-sonnet-4-5-20250929-v1:0" -> "anthropic.claude-sonnet-4-5" +// and the inference-profile ARN's last segment likewise. It is the single +// source of truth shared by the request parser (which normalizes the request +// model from the URL path) and the router (which normalizes the operator's +// registered Bedrock model ids so both sides compare equal). +func NormalizeBedrockModel(modelID string) string { + m := modelID + if strings.HasPrefix(m, "arn:") { + if i := strings.LastIndex(m, "/"); i >= 0 { + m = m[i+1:] + } + } + for _, p := range bedrockRegionPrefixes { + if strings.HasPrefix(m, p) { + m = m[len(p):] + break + } + } + return bedrockVersionSuffix.ReplaceAllString(m, "") +} diff --git a/proxy/internal/llm/bedrock_model_test.go b/proxy/internal/llm/bedrock_model_test.go new file mode 100644 index 000000000..3bd9662b7 --- /dev/null +++ b/proxy/internal/llm/bedrock_model_test.go @@ -0,0 +1,23 @@ +package llm + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNormalizeBedrockModel(t *testing.T) { + cases := map[string]string{ + "eu.anthropic.claude-sonnet-4-5-20250929-v1:0": "anthropic.claude-sonnet-4-5", + "us.anthropic.claude-haiku-4-5": "anthropic.claude-haiku-4-5", + "us.anthropic.claude-opus-4-8-20250101-v1:0": "anthropic.claude-opus-4-8", + "anthropic.claude-sonnet-4-5-20250929-v1:0": "anthropic.claude-sonnet-4-5", + "meta.llama3-3-70b-instruct-v1:0": "meta.llama3-3-70b-instruct", + "amazon.nova-pro-v1:0": "amazon.nova-pro", + // Inference-profile ARN — model id lives in the last path segment. + "arn:aws:bedrock:eu-central-1:123456789012:inference-profile/eu.anthropic.claude-sonnet-4-5-20250929-v1:0": "anthropic.claude-sonnet-4-5", + } + for in, want := range cases { + require.Equal(t, want, NormalizeBedrockModel(in), "normalize %q", in) + } +} diff --git a/proxy/internal/middleware/builtin/llm_router/bedrock_route_test.go b/proxy/internal/middleware/builtin/llm_router/bedrock_route_test.go new file mode 100644 index 000000000..40cbcb6bd --- /dev/null +++ b/proxy/internal/middleware/builtin/llm_router/bedrock_route_test.go @@ -0,0 +1,30 @@ +package llm_router + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestRouteClaimsModel_BedrockNormalizesCandidate guards the fix for the native +// Bedrock routing gap: the request model reaches the router already normalized +// (the parser strips the region/inference-profile prefix and version suffix), +// so a provider registered with the raw inference-profile id must still match. +func TestRouteClaimsModel_BedrockNormalizesCandidate(t *testing.T) { + route := ProviderRoute{Bedrock: true, Models: []string{"us.anthropic.claude-haiku-4-5"}} + assert.True(t, routeClaimsModel(route, "anthropic.claude-haiku-4-5"), + "raw region-prefixed Bedrock model must match the normalized request model") + assert.False(t, routeClaimsModel(route, "anthropic.claude-opus-4-8"), + "a model outside the provider's list must not match") + + // A provider registered with the already-normalized id also matches. + normalized := ProviderRoute{Bedrock: true, Models: []string{"anthropic.claude-haiku-4-5"}} + assert.True(t, routeClaimsModel(normalized, "anthropic.claude-haiku-4-5"), + "normalized Bedrock model must match") + + // Non-Bedrock routes keep exact matching (no prefix stripping). + openai := ProviderRoute{Models: []string{"gpt-4o"}} + assert.True(t, routeClaimsModel(openai, "gpt-4o"), "exact model must match") + assert.False(t, routeClaimsModel(openai, "us.gpt-4o"), + "non-Bedrock routes must not strip a us. prefix") +} diff --git a/proxy/internal/middleware/builtin/llm_router/middleware.go b/proxy/internal/middleware/builtin/llm_router/middleware.go index 2aaeb1089..2d987eef6 100644 --- a/proxy/internal/middleware/builtin/llm_router/middleware.go +++ b/proxy/internal/middleware/builtin/llm_router/middleware.go @@ -23,6 +23,7 @@ import ( "golang.org/x/oauth2" "golang.org/x/oauth2/google" + "github.com/netbirdio/netbird/proxy/internal/llm" "github.com/netbirdio/netbird/proxy/internal/middleware" ) @@ -555,6 +556,14 @@ func routeClaimsModel(route ProviderRoute, model string) bool { if candidate == model { return true } + // Bedrock request models reach the router already normalized (the parser + // strips the region / inference-profile prefix and version suffix), but + // the operator may register the raw inference-profile id (e.g. + // "us.anthropic.claude-haiku-4-5"). Normalize the candidate so both sides + // compare equal; otherwise a native Bedrock request denies as not-routable. + if route.Bedrock && llm.NormalizeBedrockModel(candidate) == model { + return true + } } return false }