A purpose-built proxy for the Linkerd service mesh. Written in Rust.
Go to file
katelyn martin 6426c38906
fix(http/prom): record bodies when eos reached (#3856)
* chore(app/outbound): `linkerd-mock-http-body` test dependency

this adds a development dependency, so we can use this mock body type in
the outbound proxy's unit tests.

Signed-off-by: katelyn martin <kate@buoyant.io>

* chore(app/outbound): additional http route metrics tests

Signed-off-by: katelyn martin <kate@buoyant.io>

* chore(app/outbound): additional grpc route metrics tests

Signed-off-by: katelyn martin <kate@buoyant.io>

* fix(http/prom): record bodies when eos reached

this commit fixes a bug discovered by @alpeb, which was introduced in
proxy v2.288.0.

> The associated metric is `outbound_http_route_request_statuses_total`:
>
> ```
> $ linkerd dg proxy-metrics -n booksapp deploy/webapp|rg outbound_http_route_request_statuses_total.*authors
> outbound_http_route_request_statuses_total{parent_group="core",parent_kind="Service",parent_namespace="booksapp",parent_name="authors",parent_port="7001",parent_section_name="",route_group="",route_kind="default",route_namespace="",route_name="http",hostname="",http_status="204",error=""} 5
> outbound_http_route_request_statuses_total{parent_group="core",parent_kind="Service",parent_namespace="booksapp",parent_name="authors",parent_port="7001",parent_section_name="",route_group="",route_kind="default",route_namespace="",route_name="http",hostname="",http_status="201",error="UNKNOWN"} 5
> outbound_http_route_request_statuses_total{parent_group="core",parent_kind="Service",parent_namespace="booksapp",parent_name="authors",parent_port="7001",parent_section_name="",route_group="",route_kind="default",route_namespace="",route_name="http",hostname="",http_status="200",error="UNKNOWN"} 10
> ```
>
> The problem was introduced in `edge-25.3.4`, with the proxy `v2.288.0`.
> Before that the metrics looked like:
>
> ```
> $ linkerd dg proxy-metrics -n booksapp deploy/webapp|rg outbound_http_route_request_statuses_total.*authors
> outbound_http_route_request_statuses_total{parent_group="core",parent_kind="Service",parent_namespace="booksapp",parent_name="authors",parent_port="7001",parent_section_name="",route_group="",route_kind="default",route_namespace="",route_name="http",hostname="",http_status="200",error=""} 193
> outbound_http_route_request_statuses_total{parent_group="core",parent_kind="Service",parent_namespace="booksapp",parent_name="authors",parent_port="7001",parent_section_name="",route_group="",route_kind="default",route_namespace="",route_name="http",hostname="",http_status="204",error=""} 96
> outbound_http_route_request_statuses_total{parent_group="core",parent_kind="Service",parent_namespace="booksapp",parent_name="authors",parent_port="7001",parent_section_name="",route_group="",route_kind="default",route_namespace="",route_name="http",hostname="",http_status="201",error=""} 96
> ```
>
> So the difference is the non-empty value for `error=UNKNOWN` even
> when `https_status` is 2xx, which `linkerd viz stat-outbound`
> interprets as failed requests.

in #3086 we introduced a suite of route- and backend-level metrics. that
subsystem contains a body middleware that will report itself as having
reached the end-of-stream by delegating directly down to its inner
body's `is_end_stream()` hint.

this is roughly correct, but is slightly distinct from the actual
invariant: a `linkerd_http_prom::record_response::ResponseBody<B>` must
call its `end_stream` helper to classify the outcome and increment the
corresponding time series in the
`outbound_http_route_request_statuses_total` metric family.

in #3504 we upgraded our hyper dependency. while doing so, we neglected
to include a call to `end_stream` if a data frame is yielded and the
inner body reports itself as having reached the end-of-stream.

this meant that instrumented bodies would be polled until the end is
reached, but were being dropped before a `None` was encountered.

this commit fixes this issue in two ways, to be defensive:

* invoke `end_stream()` if a non-trailers frame is yielded, and the
  inner body now reports itself as having ended. this restores the
  behavior in place prior to #3504. see the relevant component of that
  diff, here:
  <https://github.com/linkerd/linkerd2-proxy/pull/3504/files#diff-45d0bc344f76c111551a8eaf5d3f0e0c22ee6e6836a626e46402a6ae3cbc0035L262-R274>

* rather than delegating to the inner `<B as Body>::is_end_stream()`
  method, report the end-of-stream being reached by inspecting whether
  or not the inner response state has been taken. this is the state that
  directly indicates whether or not the `ResponseBody<B>` middleware is
  finished.

X-ref: #3504
X-ref: #3086
X-ref: linkerd/linkerd2#8733
Signed-off-by: katelyn martin <kate@buoyant.io>

---------

Signed-off-by: katelyn martin <kate@buoyant.io>
2025-04-09 15:30:55 -04:00
.checksec build(deps): Update rustls and ring (#2735) 2024-02-21 16:03:35 -08:00
.devcontainer chore: bump dev from v44 to v45 (#3496) 2025-01-06 13:27:49 -05:00
.github build(deps): bump tj-actions/changed-files from 46.0.4 to 46.0.5 (#3851) 2025-04-09 10:59:09 -04:00
docs ci: Lint markdown files (#1707) 2022-05-25 11:46:19 -07:00
hyper-balance chore(deps)!: upgrade to hyper 1.x (#3504) 2025-03-21 12:53:11 -04:00
linkerd fix(http/prom): record bodies when eos reached (#3856) 2025-04-09 15:30:55 -04:00
linkerd2-proxy chore: use workspace-level package metadata (#3761) 2025-03-15 12:54:50 -04:00
opencensus-proto chore(deps)!: upgrade to hyper 1.x (#3504) 2025-03-21 12:53:11 -04:00
opentelemetry-proto build(deps): bump the opentelemetry group with 2 updates (#3801) 2025-03-24 06:47:06 -07:00
spiffe-proto chore(deps)!: upgrade to hyper 1.x (#3504) 2025-03-21 12:53:11 -04:00
tools chore(deps)!: upgrade to hyper 1.x (#3504) 2025-03-21 12:53:11 -04:00
.clippy.toml clippy: Disallow lock and instant types from `std` (#1458) 2022-02-02 11:59:03 -08:00
.codecov.yml ci: Fixup codecov config (#2545) 2023-12-03 11:53:02 -08:00
.dockerignore
.gitattributes Update to linkerd2-proxy-api v0.5 and tonic v0.7 (#1596) 2022-04-11 11:29:33 -07:00
.gitignore chore(gitignore): ignore `.cargo` directory (#3451) 2024-12-11 11:35:07 -05:00
CONTRIBUTING.md dev: Update markdowlint-cli2 to 5.0.1 (#1892) 2022-08-15 13:42:18 -07:00
Cargo.lock fix(http/prom): record bodies when eos reached (#3856) 2025-04-09 15:30:55 -04:00
Cargo.toml chore(proxy/http): use `hyper_util::rt::tokio::TokioExecutor` (#3833) 2025-04-01 13:19:53 -04:00
DCO
Dockerfile chore: bump dev from v44 to v45 (#3496) 2025-01-06 13:27:49 -05:00
GOVERNANCE.md ci: Lint markdown files (#1707) 2022-05-25 11:46:19 -07:00
LICENSE
MAINTAINERS.md ci: Lint markdown files (#1707) 2022-05-25 11:46:19 -07:00
README.md readme: mTLS has been prod for years (#2782) 2024-03-11 20:52:23 -07:00
deny.toml chore(deny): allow Zlib (#3829) 2025-03-31 14:43:45 -04:00
justfile fix(ci): work around broken `linkerd install --crds` (#3828) 2025-03-31 14:55:09 +00:00
rust-toolchain.toml build(deps): bump linkerd/dev from v43 to v44 (#3420) 2024-12-06 10:50:41 -05:00

README.md

The Linkerd Proxy

linkerd2

GitHub license Slack Status

This repo contains the transparent proxy component of Linkerd2. While the Linkerd2 proxy is heavily influenced by the Linkerd 1.X proxy, it comprises an entirely new codebase implemented in the Rust programming language.

This proxy's features include:

  • Transparent, zero-config proxying for HTTP, HTTP/2, and arbitrary TCP protocols.
  • Automatic Prometheus metrics export for HTTP and TCP traffic;
  • Transparent, zero-config WebSocket proxying;
  • Automatic, latency-aware, layer-7 load balancing;
  • Automatic layer-4 load balancing for non-HTTP traffic;
  • Automatic Mutual TLS;
  • An on-demand diagnostic tap API.

This proxy is primarily intended to run on Linux in containerized environments like Kubernetes, though it may also work on other Unix-like systems (like macOS).

The proxy supports service discovery via DNS and the linkerd2 Destination gRPC API.

The Linkerd project is hosted by the Cloud Native Computing Foundation (CNCF).

Building the project

We use just-cargo which provide a thin wrapper around just and cargo.

We recommend that you use the included Dev Container to avoid setting up the complex development environment by hand.

Just

A justfile is provided to automate most build tasks. It provides the following recipes:

  • just build -- Compiles the proxy on your local system using cargo
  • just test -- Runs unit and integration tests on your local system using cargo
  • just docker -- Builds a Docker container image that can be used for testing.

Cargo

Usually, Cargo, Rust's package manager, is used to build and test this project. If you don't have Cargo installed, we suggest getting it via https://rustup.rs/.

Devcontainer

A Devcontainer is provided for use with Visual Studio Code. It includes all of the tooling needed to build and test the proxy.

Repository Structure

This project is broken into many small libraries, or crates, so that components may be compiled & tested independently. The following crate targets are especially important:

Code of conduct

This project is for everyone. We ask that our users and contributors take a few minutes to review our code of conduct.

Security

We test our code by way of fuzzing and this is described in FUZZING.md.

A third party security audit focused on fuzzing Linkerd2-proxy was performed by Ada Logics in 2021. The full report is available here.

License

linkerd2-proxy is copyright 2018 the linkerd2-proxy authors. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.