transport: Add values to the grpc.disconnect_error label for grpc.subchannel.disconnections metric (A94) - #8973
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #8973 +/- ##
==========================================
+ Coverage 83.04% 83.06% +0.01%
==========================================
Files 411 411
Lines 32892 32988 +96
==========================================
+ Hits 27316 27402 +86
- Misses 4181 4191 +10
Partials 1395 1395
🚀 New features to boost your workflow:
|
dedb4b2 to
06fb986
Compare
215dc6c to
de97023
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request successfully implements gRFC A94 by adding more granular grpc.disconnect_error labels to the grpc.subchannel.disconnections metric. The changes are well-implemented, introducing a disconnectError field in addrConn and a disconnectErrorString helper to map various error conditions to the new labels. The transport layer modifications to propagate the necessary error details are correct. The addition of comprehensive end-to-end tests is a great way to ensure the new labels are correctly reported in different disconnection scenarios. I have one minor suggestion to clean up a duplicated comment.
1200625 to
5584fb4
Compare
f219fe9 to
88cd619
Compare
|
fixed the comments, one test flaked once due to timing of how the connection was closed, so changed the test to be more deterministic. Master branch had new tests which were failing now, so couple of minor changes for that as well. |
|
I realize the tests are not very idiomatic - I will structure them into a table and push one more commit. |
|
|
||
| func (s) TestDisconnectLabel(t *testing.T) { | ||
| // 1. Valid GOAWAY | ||
| // Server GracefulStop sends GOAWAY with active streams = 0. |
There was a problem hiding this comment.
What does active streams have to do with anything that is happening with regards to this test?
There was a problem hiding this comment.
This is just to explain that we straightaway go to close the stream and expect GOAWAY. It does not have specific bearing on the value of the label itself.
There was a problem hiding this comment.
Can we please have descriptive comments for each of the three subtests.
And there are no active streams because the runDisconnectLabelTest only performs a unary RPC before invoking the triggerFunc. Yeah, some of things would be nice to be clearly explained in the comments.
My philosophy with tests is that they have to be as readable as possible, so that when someone lands on it (either because they are debugging a failed test or because they are trying to understand how the code being tested works), they should be able to very quickly understand what the test is doing and what it is expecting. The "how" part is usually less important, and as long as the "what"s are clearly documented, the reader will have a much easier time.
| return gotMetrics | ||
| } | ||
|
|
||
| func (s) TestDisconnectLabel(t *testing.T) { |
There was a problem hiding this comment.
There seem to be more disconnect reason labels than what are being tested here. Are looks like they are covered in otel e2e tests? Why do we cover only a subset here?
There was a problem hiding this comment.
The goal here is to just check that the plumbing works, e2e tests verify all scenarios.
There was a problem hiding this comment.
Can we please add a docstring that mentions this.
| default: | ||
| var sysErr syscall.Errno | ||
| if errors.As(err, &sysErr) { | ||
| return "socket error" | ||
| } | ||
| return "unknown" |
There was a problem hiding this comment.
Nit: If you defined the sysErr variable at the top of the switch, you could add a case for it, instead of folding it into the default case
switch {
// Existing cases
case errors.As(err, &sysErr):
return "socket error"
default:
return "unknown"
}There was a problem hiding this comment.
Not sure if you missed this or decided not to implement it.
There was a problem hiding this comment.
missed pushing that change, done now.
easwars
left a comment
There was a problem hiding this comment.
LGTM, modulo some minor comments
| return gotMetrics | ||
| } | ||
|
|
||
| func (s) TestDisconnectLabel(t *testing.T) { |
There was a problem hiding this comment.
Can we please add a docstring that mentions this.
|
|
||
| func (s) TestDisconnectLabel(t *testing.T) { | ||
| // 1. Valid GOAWAY | ||
| // Server GracefulStop sends GOAWAY with active streams = 0. |
There was a problem hiding this comment.
Can we please have descriptive comments for each of the three subtests.
And there are no active streams because the runDisconnectLabelTest only performs a unary RPC before invoking the triggerFunc. Yeah, some of things would be nice to be clearly explained in the comments.
My philosophy with tests is that they have to be as readable as possible, so that when someone lands on it (either because they are debugging a failed test or because they are trying to understand how the code being tested works), they should be able to very quickly understand what the test is doing and what it is expecting. The "how" part is usually less important, and as long as the "what"s are clearly documented, the reader will have a much easier time.
| default: | ||
| var sysErr syscall.Errno | ||
| if errors.As(err, &sysErr) { | ||
| return "socket error" | ||
| } | ||
| return "unknown" |
There was a problem hiding this comment.
Not sure if you missed this or decided not to implement it.
| // If the connection was closed by a GOAWAY frame, this will usually be a | ||
| // connection error that describes the connection closing. | ||
| Err error |
There was a problem hiding this comment.
I don't completely understand this last sentence. If the transport is being closed becasue of the receipt of a GOAWAY, I see that we usually don't set this field. Is that not true? Can this comment be made more easily understandable. Thanks.
There was a problem hiding this comment.
correct, I have fixed the comment.
| if ac.disconnectErrorLabel == "" { | ||
| ac.disconnectErrorLabel = "subchannel shutdown" | ||
| } |
There was a problem hiding this comment.
The above comment was supposed to be on top the line that sets the conenctivity state to Shutdown. Can we continue to retain it that way.
…channel.disconnections metric (A94) (grpc#8973) This PR implements granular grpc.disconnect_error labels for the grpc.subchannel.disconnections metric, as defined in [gRFC A94](https://github.com/grpc/proposal/blob/master/A94-subchannel-otel-metrics.md). RELEASE NOTES: * transport: Add disconnection reason to the grpc.disconnect_error label for grpc.subchannel.disconnections metric as defined in [gRFC A94](https://github.com/grpc/proposal/blob/master/A94-subchannel-otel-metrics.md).
Fixes #9253 `disconnectErrorString` (added for gRFC A94 in #8973) references `syscall.Errno`, `syscall.ECONNRESET` and `syscall.ECONNABORTED`, none of which exist on plan9, so `GOOS=plan9 go build` fails on every release since v1.81.0. This moves the error-to-label classification into `disconnectErrorLabel` with two implementations: a `!plan9` file with the existing errno matching, unchanged in behavior, and a plan9 file that keeps the portable classifications (`subchannel shutdown`, `connection timed out`, `unknown`). Only plan9 is excluded so the errno granularity on js/wasm and wasip1, where `syscall.Errno` exists, stays as it is today. Verified `GOOS=plan9 GOARCH=amd64 go build ./...` fails on master and passes with this change, and `go build` plus the root package tests still pass on linux and darwin. RELEASE NOTES: * grpc: fix compilation on plan9, broken since v1.81.0.
…/forgejo) (#13580) This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [google.golang.org/grpc](https://github.com/grpc/grpc-go) | `v1.79.3` → `v1.82.1` |  |  | --- ### gRPC-Go: xDS RBAC and HTTP/2 Vulnerabilities [GHSA-hrxh-6v49-42gf](GHSA-hrxh-6v49-42gf) <details> <summary>More information</summary> #### Details Multiple security vulnerabilities have been identified and addressed in grpc-go affecting the xDS RBAC authorization engine (internal/xds/rbac) and the HTTP/2 transport server implementation (internal/transport). These vulnerabilities could result in: - Authorization Bypass (Fail-Open) when translating xDS RBAC policies containing `Metadata` or `RequestedServerName` fields. - Denial of Service (High CPU Consumption) due to an HTTP/2 Rapid Reset mitigation bypass during client-initiated stream resets. - Denial of Service (Server Panic) when parsing crafted xDS RBAC policies containing `NOT` rules around unsupported fields. ##### Impact _What kind of vulnerability is it? Who is impacted?_ ##### xDS RBAC Authorization Bypass via `Metadata` & `RequestedServerName` matchers - Affected Component: xDS RBAC - Impact: When building policy matchers for gRPC RBAC from xDS configurations, unsupported `permission` and `principal` rules (specifically `Metadata` and `RequestedServerName`) were silently ignored and treated as no-ops. - If an authorization policy relied purely on these matchers for access control, treating those rules as no-ops effectively removed the restrictions. - If these unsupported rules were nested inside logical `NOT` rules (`Permission_NotRule` / `Principal_NotId`) or multi-condition `OR/AND` rules, silently dropping them changed the boolean logic flow of the authorization engine. As a result, policy evaluation decisions could fail open, allowing unauthorized clients to access protected gRPC services or resources. ##### HTTP/2 Rapid Reset Mitigation Bypass / Denial of Service via Stream Aborts - Affected Component: HTTP/2 transport - Impact: Earlier mitigations in grpc-go for HTTP/2 Rapid Reset only applied threshold checks to items that directly resulted in control frames being written back to the wire, such as `SETTINGS` ACKs or server-initiated `RST_STREAM`s. When a client initiated a rapid flood of stream creation (`HEADERS`) immediately followed by stream termination `RST_STREAM`, items queued up in the control buffer without counting against the transport response frame threshold. An attacker can repeatedly trigger this flood sequence to bypass reader blocking, resulting in high CPU usage, and Denial of Service (DoS). ##### Denial of Service (Panic) in xDS RBAC Engine via Unsupported Fields inside NOT Rules - Affected Component: xDS RBAC - Impact: The xDS RBAC policy translators recursively generate matchers for nested rules. When a `NOT` rule wrapped an unsupported or unhandled field (such as `SourcedMetadata`), the recursive step returned an empty matcher. This could result in a runtime panic when the RBAC engine attempts to authorize an incoming request. An attacker or misconfigured/malicious xDS management server delivering an LDS/RDS update containing a `NOT` rule around an unhandled field causes the gRPC server process to crash immediately (CWE-248 / Denial of Service). ##### Patches _Has the problem been patched? What versions should users upgrade to?_ All three issues have been fixed in `master` and will be released in 1.82.1 shortly. ##### Workarounds _Is there a way for users to fix or remediate the vulnerability without upgrading?_ If upgrading grpc-go immediately is not possible, apply the following workarounds based on your deployment architecture: * For xDS RBAC Vulnerabilities & Panics: Ensure that upstream xDS management servers do not push RBAC policies containing `Metadata`, `RequestedServerName`, or `NOT` rules wrapping unsupported fields (such as `SourcedMetadata`) to grpc-go servers. * For HTTP/2 Rapid Reset DOS: Configure upstream reverse proxies or load balancers (such as Envoy) with strict HTTP/2 `max_concurrent_streams` limits and active rate limiting on `RST_STREAM` frequency per connection. ##### Severity | Vulnerability | Qualitative Severity | Approximate CVSS v3.1 Score | Primary Impact | | :--- | :--- | :--- | :--- | | **xDS RBAC Authorization Bypass** | **High** | `8.2` | Unauthorized Access / Fail-Open | | **HTTP/2 Rapid Reset DOS Bypass** | **High** | `7.5` | High CPU Consumption / Denial of Service | | **xDS RBAC Engine Server Panic** | **Medium** | `5.9` | Process Crash / Denial of Service | #### Severity - CVSS Score: 8.8 / 10 (High) - Vector String: `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N` #### References - [https://github.com/grpc/grpc-go/security/advisories/GHSA-hrxh-6v49-42gf](https://github.com/grpc/grpc-go/security/advisories/GHSA-hrxh-6v49-42gf) - [https://github.com/grpc/grpc-go/pull/9236](https://github.com/grpc/grpc-go/pull/9236) - [https://github.com/grpc/grpc-go/commit/4ea465d4ab98013f72a142fe0fc89c19770b2935](https://github.com/grpc/grpc-go/commit/4ea465d4ab98013f72a142fe0fc89c19770b2935) - [https://github.com/grpc/grpc-go](https://github.com/grpc/grpc-go) - [https://github.com/grpc/grpc-go/releases/tag/v1.82.1](https://github.com/grpc/grpc-go/releases/tag/v1.82.1) This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-hrxh-6v49-42gf) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)). </details> --- ### Release Notes <details> <summary>grpc/grpc-go (google.golang.org/grpc)</summary> ### [`v1.82.1`](https://github.com/grpc/grpc-go/releases/tag/v1.82.1): Release 1.82.1 [Compare Source](grpc/grpc-go@v1.82.0...v1.82.1) ### Security - server: Stop reading from the connection when flooded by HTTP/2 frames. The default value for this limit is 100 frames, excluding DATA and HEADERS, and may be changed by setting environment variable `GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT`. - xds/rbac: Support `Metadata` and `RequestedServerName` permissions matcher fields. If present in a DENY rule, previously these would be ignored and fail-open. - xds/rbac: Fix panic when parsing unsupported fields in `NotRule`/`NotId` permissions. - xds/rbac: Support the deprecated `source_ip` principal identifier by treating it as equivalent to `direct_remote_ip`. ### [`v1.82.0`](https://github.com/grpc/grpc-go/releases/tag/v1.82.0): Release 1.82.0 [Compare Source](grpc/grpc-go@v1.81.1...v1.82.0) ### Behavior Changes - server: Remove support for `GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING` environment varibale. Strict incoming RPC path validation (which has been the default since `v1.79.3`) can no longer be disabled. ([#​9112](grpc/grpc-go#9112)) - transport: Add environment variable to change the default max header list size from `16MB` to `8KB`. This may be enabled by setting `GRPC_GO_EXPERIMENTAL_ENABLE_8KB_DEFAULT_HEADER_LIST_SIZE=true`. This will be enabled by default in a subsequent release. ([#​9019](grpc/grpc-go#9019)) - balancer: Load Balancing policy registry is now case-sensitive. Set `GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES=false` (and file an issue) to revert to case-insensitive behavior. ([#​9017](grpc/grpc-go#9017)) ### New Features - experimental/stats: Expose a new API, `NewContextWithLabelCallback`, to register a callback that is invoked when telemetry labels are added. ([#​8877](grpc/grpc-go#8877)) - Special Thanks: [@​seth-epps](https://github.com/seth-epps) - client: Return a portion of the response body in the error message, when the client receives an unexpected non-gRPC HTTP response, to make debugging easier. ([#​8929](grpc/grpc-go#8929)) - Special Thanks: [@​chengxilo](https://github.com/chengxilo) - server: Add environment variable `GRPC_GO_SERVER_GOROUTINE_LABELS` that controls setting `runtime/pprof.Labels` on goroutines spawned by the server. Set `GRPC_GO_SERVER_GOROUTINE_LABELS=grpc.method=true` to add the `grpc.method` label on goroutines spawned to handle incoming requests. ([#​9082](grpc/grpc-go#9082)) - Special Thanks: [@​dfinkel](https://github.com/dfinkel) ### Bug Fixes - xds/server: Fix a memory leak of HTTP filter instances occurring when route configurations are updated in-place during a Route Discovery Service (RDS) update. ([#​9138](grpc/grpc-go#9138)) - grpc: In the deprecated `gzip` Compressor (used via the deprecated `WithCompressor` dial option), enforce the `MaxRecvMsgSize` limit on the decompressed message buffer, preventing excessive memory allocation from highly compressed payloads. ([#​9114](grpc/grpc-go#9114)) - Special Thanks: [@​evilgensec](https://github.com/evilgensec) - stats/opentelemetry: Record retry attempts, `grpc.previous-rpc-attempts`, at the call level and not the attempt level. ([#​8923](grpc/grpc-go#8923)) - encoding: Ensure `Close()` is always called on readers returned from `Compressor.Decompress` if possible. ([#​9135](grpc/grpc-go#9135)) - channelz: Fix the `LastMessageSentTimestamp` and `LastMessageReceivedTimestamp` fields in `SocketMetrics` to ensure they contain correct timestamp values. ([#​9109](grpc/grpc-go#9109)) ### [`v1.81.1`](https://github.com/grpc/grpc-go/releases/tag/v1.81.1): Release 1.81.1 [Compare Source](grpc/grpc-go@v1.81.0...v1.81.1) ### Security - xds/rbac: Fix a potential authorization bypass caused by incorrectly falling through URI/DNS SANs to Subject Distinguished Name (DN) when matching the authenticated principal name. With this fix, only the first non-empty identity source will be used, as per [gRFC A41](https://github.com/grpc/proposal/blob/master/A41-xds-rbac.md). ([#​9111](grpc/grpc-go#9111)) - Special Thanks: [@​al4an444](https://github.com/al4an444) ### Bug Fixes - otel: Segregate client and server RPC information used for metrics and traces, to avoid one overwriting the other. ([#​9081](grpc/grpc-go#9081)) ### [`v1.81.0`](https://github.com/grpc/grpc-go/releases/tag/v1.81.0): Release 1.81.0 [Compare Source](grpc/grpc-go@v1.80.0...v1.81.0) ### Behavior Changes - balancer/rls: Switch gauge metrics to asynchronous emission (once per collection cycle) to reduce telemetry noise and align with other gRPC language implementations. ([#​8808](grpc/grpc-go#8808)) ### Dependencies - Minimum supported Go version is now 1.25. ([#​8969](grpc/grpc-go#8969)) ### Bug Fixes - xds: Use the leaf cluster's security config for the TLS handshake instead of the aggregate cluster's config. ([#​8956](grpc/grpc-go#8956)) - transport: Send a `RST_STREAM` when receiving an `END_STREAM` when the stream is not already half-closed. ([#​8832](grpc/grpc-go#8832)) - xds: Fix ADS resource name validation to prevent a panic. ([#​8970](grpc/grpc-go#8970)) ### New Features - grpc/stats: Add support for custom labels in per-call metrics ([gRFC A108](https://github.com/grpc/proposal/blob/master/A108-otel-custom-per-call-label.md)). ([#​9008](grpc/grpc-go#9008)) - xds: Add support for Server Name Indication (SNI) and SAN validation ([gRFC A101](https://github.com/grpc/proposal/blob/master/A101-SNI-setting-and-SNI-SAN-validation.md)). Disabled by default. To enable, set `GRPC_EXPERIMENTAL_XDS_SNI=true` environment variable. ([#​9016](grpc/grpc-go#9016)) - xds: Add support to control which fields get propagated from ORCA backend metric reports to LRS load reports ([gRFC A85](https://github.com/grpc/proposal/blob/master/A85-lrs-custom-metrics-changes.md)). Disabled by default. To enable, set `GRPC_EXPERIMENTAL_XDS_ORCA_LRS_PROPAGATION=true`. ([#​9005](grpc/grpc-go#9005)) - xds: Add metrics to track xDS client connectivity and cached resource state ([gRFC A78](https://github.com/grpc/proposal/blob/master/A78-grpc-metrics-wrr-pf-xds.md)). ([#​8807](grpc/grpc-go#8807)) - stats/otel: Enhance `grpc.subchannel.disconnections` metric by adding disconnection reason to the `grpc.disconnect_error` label ([gRFC A94](https://github.com/grpc/proposal/blob/master/A94-subchannel-otel-metrics.md)). This provides granular insights into why subchannels are closing. ([#​8973](grpc/grpc-go#8973)) - mem: Add `mem.Buffer.Slice()` API to slice the buffer like a slice. ([#​8977](grpc/grpc-go#8977)) - Special Thanks: [@​ash2k](https://github.com/ash2k) ### Performance Improvements - alts: Pool read buffers to lower memory utilization when sockets are unreadable. ([#​8964](grpc/grpc-go#8964)) - transport: Pool HTTP/2 framer read buffers to reduce idle memory consumption. Currently limited to Linux for ALTS and non-encrypted transports (TCP, Unix). To disable, set `GRPC_GO_EXPERIMENTAL_HTTP_FRAMER_READ_BUFFER_POOLING=false` and report any issues. ([#​9032](grpc/grpc-go#9032)) ### [`v1.80.0`](https://github.com/grpc/grpc-go/releases/tag/v1.80.0): Release 1.80.0 [Compare Source](grpc/grpc-go@v1.79.3...v1.80.0) ### Behavior Changes - balancer: log a warning if a balancer is registered with uppercase letters, as balancer names should be lowercase. In a future release, balancer names will be treated as case-insensitive; see [#​5288](grpc/grpc-go#5288) for details. ([#​8837](grpc/grpc-go#8837)) - xds: update resource error handling and re-resolution logic ([#​8907](grpc/grpc-go#8907)) - Re-resolve all `LOGICAL_DNS` clusters simultaneously when re-resolution is requested. - Fail all in-flight RPCs immediately upon receipt of listener or route resource errors, instead of allowing them to complete. ### Bug Fixes - xds: support the LB policy configured in `LOGICAL_DNS` cluster resources instead of defaulting to `pick_first`. ([#​8733](grpc/grpc-go#8733)) - credentials/tls: perform per-RPC authority validation against the leaf certificate instead of the entire peer certificate chain. ([#​8831](grpc/grpc-go#8831)) - xds: enabling A76 ring hash endpoint keys no longer causes EDS resources with invalid proxy metadata to be NACKed when HTTP CONNECT (gRFC A86) is disabled. ([#​8875](grpc/grpc-go#8875)) - xds: validate that the sum of endpoint weights in a locality does not exceed the maximum `uint32` value. ([#​8899](grpc/grpc-go#8899)) - Special Thanks: [@​RAVEYUS](https://github.com/RAVEYUS) - xds: fix incorrect proto field access in the weighted round robin (WRR) configuration where `blackout_period` was used instead of `weight_expiration_period`. ([#​8915](grpc/grpc-go#8915)) - Special Thanks: [@​gregbarasch](https://github.com/gregbarasch) - xds/rbac: handle addresses with ports in IP matchers. ([#​8990](grpc/grpc-go#8990)) ### New Features - ringhash: enable gRFC A76 (endpoint hash keys and request hash headers) by default. ([#​8922](grpc/grpc-go#8922)) ### Performance Improvements - credentials/alts: pool write buffers to reduce memory allocations and usage. ([#​8919](grpc/grpc-go#8919)) - grpc: enable the use of pooled write buffers for buffering HTTP/2 frame writes by default. This reduces memory usage when connections are idle. Use the [WithSharedWriteBuffer](https://pkg.go.dev/google.golang.org/grpc#WithSharedWriteBuffer) dial option or the [SharedWriteBuffer](https://pkg.go.dev/google.golang.org/grpc#SharedWriteBuffer) server option to disable this feature. ([#​8957](grpc/grpc-go#8957)) - xds/priority: stop caching child LB policies removed from the configuration. This will help reduce memory and cpu usage when localities are constantly switching between priorities. ([#​8997](grpc/grpc-go#8997)) - mem: add a faster tiered buffer pool; use the experimental [mem.NewBinaryTieredBufferPool](https://pkg.go.dev/google.golang.org/grpc/mem@master#NewBinaryTieredBufferPool) function to create such pools. ([#​8775](grpc/grpc-go#8775)) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - Between 12:00 AM and 03:59 AM (`* 0-3 * * *`) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNzIuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI3Mi4wIiwidGFyZ2V0QnJhbmNoIjoidjE2LjAvZm9yZ2VqbyIsImxhYmVscyI6WyJkZXBlbmRlbmN5LXVwZ3JhZGUiLCJ0ZXN0L25vdC1uZWVkZWQiXX0=--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/13580 Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
…#9255) Fixes grpc#9253 `disconnectErrorString` (added for gRFC A94 in grpc#8973) references `syscall.Errno`, `syscall.ECONNRESET` and `syscall.ECONNABORTED`, none of which exist on plan9, so `GOOS=plan9 go build` fails on every release since v1.81.0. This moves the error-to-label classification into `disconnectErrorLabel` with two implementations: a `!plan9` file with the existing errno matching, unchanged in behavior, and a plan9 file that keeps the portable classifications (`subchannel shutdown`, `connection timed out`, `unknown`). Only plan9 is excluded so the errno granularity on js/wasm and wasip1, where `syscall.Errno` exists, stays as it is today. Verified `GOOS=plan9 GOARCH=amd64 go build ./...` fails on master and passes with this change, and `go build` plus the root package tests still pass on linux and darwin. RELEASE NOTES: * grpc: fix compilation on plan9, broken since v1.81.0.
This PR implements granular grpc.disconnect_error labels for the grpc.subchannel.disconnections metric, as defined in gRFC A94.
RELEASE NOTES: