Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split the inbound server into multiple modules #1179

Merged
merged 48 commits into from
Aug 3, 2021
Merged

Conversation

olix0r
Copy link
Member

@olix0r olix0r commented Aug 2, 2021

The Inbound::push_server stack builder is responsible for building all of the inbound stacks: the HTTP stack, the TCP forwarding stack, and the direct/gateway stack. Furthermore, most of these stacks rely on fixed target types (which makes them inflexible).

This change refactors the inbound stack to setup for upcoming policy changes:

  1. A 'detect' stack is split out into a push_detect helper that only deals with protocol detection and dispatching connections to either the TCP forwarding stack or the HTTP handling stack.
  2. An 'accept' stack is split out into a push_accept helper that is responsible for extracting relevant connection metadata (client and orig dst addrs) and determining the policy to use for the connection.
  3. Each stack/module defines its own target types and other modules rely on param constraints instead of fixed target types.
  4. A port_policies module is introduced to model the existing per-port policies: whether identity is required and whether protocol detection should be skipped. Various auxiliary modules have been eliminated in favor of inlined target filters. This helps to make these stacks easier to reason about, as relevant logic isn't spread over multiple files.

Depends on #1174

olix0r added 30 commits July 30, 2021 18:46
Following #1169, this change modifies the `detect` middleware to use
`ExtractParam`. In doing this, it makes sense to modify the default
implementations of `ExtractParam` so that we can provide a static
configuration to the module that is cloned for all targets without
modification.

This change does not alter any functionality, but sets up to make the
HTTP detection layer configured per-target.
We have BoxHttp and BoxNewHttp aliases that help reduce type
boilerplate. This change adds similar aliases for TCP-forwarding stacks.
@olix0r olix0r marked this pull request as ready for review August 2, 2021 20:53
@olix0r olix0r requested a review from a team August 2, 2021 20:53
@@ -150,13 +150,6 @@ impl<M> Admin<M> {
}
}

impl<M: FmtMetrics + Clone, T> svc::NewService<T> for Admin<M> {
Copy link
Member Author

Choose a reason for hiding this comment

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

This can just be in-lined.. no need for a dedicated impl

@@ -66,31 +78,29 @@ impl Config {

let (ready, latch) = crate::server::Readiness::new();
let admin = crate::server::Admin::new(report, ready, shutdown, trace);
let admin = svc::stack(admin)
.push(metrics.http_endpoint.to_layer::<classify::Response, _, Target>())
let admin = svc::stack(move |_| admin.clone())
Copy link
Member Author

Choose a reason for hiding this comment

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

i.e. here

@@ -86,7 +87,7 @@ pub struct StackLabels {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RouteLabels {
direction: Direction,
target: Addr,
addr: profiles::LogicalAddr,
Copy link
Member Author

Choose a reason for hiding this comment

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

there's no need for the more general Addr type here -- we'll always have a LogicalAddr when we're dealing with routes.

/// Because ports are single `u16` values, we don't have to hash them; we can just use
/// the integer values as hashes directly.
#[derive(Default)]
pub struct PortHasher(u16);
Copy link
Member Author

Choose a reason for hiding this comment

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

Not really a part of config in general -- this has been moved the port_policies module as an impl detail.

use std::time::Duration;

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Route {
pub target: Addr,
pub addr: profiles::LogicalAddr,
Copy link
Member Author

Choose a reason for hiding this comment

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

routes always have a logical addr. no need to be more general

Comment on lines +373 to +375
impl Target {
const HTTP1: Self = Self(http::Version::Http1);
const H2: Self = Self(http::Version::H2);
Copy link
Member Author

Choose a reason for hiding this comment

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

Tests now use their own target type.


pub fn serve<B, G, GSvc, P>(
Copy link
Member Author

Choose a reason for hiding this comment

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

moved into its own module

use thiserror::Error;

#[derive(Clone, Debug)]
pub struct PortPolicies {
Copy link
Member Author

Choose a reason for hiding this comment

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

replaces require_identity and prevent_loop

Comment on lines +43 to +80
let serve = async move {
let shutdown = self.runtime.drain.clone().signaled();

// Handles connections to ports that can't be determined to be HTTP.
let forward = self
.clone()
.into_tcp_connect(la.port())
.push_tcp_forward()
.into_stack()
.push_map_target(TcpEndpoint::from_param)
.instrument(|_: &_| debug_span!("tcp"))
.into_inner();

// Handles connections that target the inbound proxy port.
let direct = self
.clone()
.into_tcp_connect(la.port())
.push_tcp_forward()
.map_stack(|_, _, s| s.push_map_target(TcpEndpoint::from_param))
.push_direct(gateway)
.into_stack()
.instrument(|_: &_| debug_span!("direct"))
.into_inner();

// Handles HTTP connections.
let http = self
.into_tcp_connect(la.port())
.push_http_router(profiles)
.push_http_server();

// Determines how to handle an inbound connection, dispatching it to the appropriate
// stack.
let server = http
.push_detect(forward)
.push_accept(la.port(), direct)
.into_inner();

serve::serve(listen, server, shutdown).await
Copy link
Member Author

Choose a reason for hiding this comment

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

now we build all of the stacks with the server so that each stack can be tested independently.

Copy link
Contributor

Choose a reason for hiding this comment

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

very nice!

Comment on lines +527 to +535
require_identity_for_inbound_ports
.into_iter()
.map(|p| (p, inbound::AllowPolicy::Authenticated))
.chain(inbound_opaque_ports.into_iter().map(|p| {
(
p,
inbound::AllowPolicy::Unauthenticated { skip_detect: true },
)
})),
Copy link
Member Author

Choose a reason for hiding this comment

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

require_id and opaque_ports configs are unified into port policies

// === impl Inbound ===

impl<C> Inbound<C> {
pub fn push_http_router<T, P>(
Copy link
Member Author

Choose a reason for hiding this comment

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

moved from http/lib.rs

Comment on lines +163 to +177
// If the target includes a logical named address and it exists in the set of
// allowed discovery suffixes, use that address for discovery. Otherwise, fail
// discovery (so that we skip the profile stack above).
let addr = t.logical.ok_or_else(|| {
DiscoveryRejected::new("inbound profile discovery requires DNS names")
})?;
if !allow_profile.matches(addr.name()) {
tracing::debug!(
%addr,
suffixes = %allow_profile,
"Rejecting discovery, address not in configured DNS suffixes",
);
return Err(DiscoveryRejected::new("address not in search DNS suffixes"));
}
Ok(profiles::LookupAddr(addr.into()))
Copy link
Member Author

Choose a reason for hiding this comment

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

replaces the allow_discovery module

Comment on lines 247 to 264
// Try to read a logical named address from the request. First check the canonical-dst
// header as set by the client proxy; otherwise fallback to the request's `:authority` or
// `host` headers. If these values include a numeric address, no logical name will be used.
// This value is used for profile discovery.
let logical = req
.headers()
.get(CANONICAL_DST_HEADER)
.and_then(|dst| {
dst.to_str().ok().and_then(|d| {
Addr::from_str(d).ok().map(|a| {
debug!("using {}", CANONICAL_DST_HEADER);
a
})
})
})
.or_else(|| http_request_authority_addr(req).ok())
.or_else(|| http_request_host_addr(req).ok())
.and_then(|a| a.into_name_addr());
Copy link
Member Author

Choose a reason for hiding this comment

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

now always builds a named address

Copy link
Contributor

@hawkw hawkw left a comment

Choose a reason for hiding this comment

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

left a preliminary review, would like to take a closer look at a few things. overall, this feels good!

linkerd/app/inbound/src/accept.rs Show resolved Hide resolved
/// Builds a stack that handles protocol detection according to the port's policy. If the
/// connection is determined to be HTTP, the inner stack is used; otherwise the connection is
/// passed to the provided 'forward' stack.
pub fn push_detect<T, I, NSvc, F, FSvc>(self, forward: F) -> Inbound<svc::BoxNewTcp<T, I>>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit/tioli: might have called this push_detect_http so it's completely obvious what we're detecting here...not a blocker

Copy link
Member Author

Choose a reason for hiding this comment

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

it also detects TLS

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, i missed that, sorry — can't read!

linkerd/app/inbound/src/detect.rs Outdated Show resolved Hide resolved
linkerd/app/inbound/src/detect.rs Outdated Show resolved Hide resolved
linkerd/app/inbound/src/detect.rs Outdated Show resolved Hide resolved
linkerd/app/inbound/src/http/router.rs Outdated Show resolved Hide resolved
linkerd/app/inbound/src/http/router.rs Outdated Show resolved Hide resolved
linkerd/app/inbound/src/http/router.rs Outdated Show resolved Hide resolved
linkerd/app/inbound/src/http/tests.rs Outdated Show resolved Hide resolved
Comment on lines +43 to +80
let serve = async move {
let shutdown = self.runtime.drain.clone().signaled();

// Handles connections to ports that can't be determined to be HTTP.
let forward = self
.clone()
.into_tcp_connect(la.port())
.push_tcp_forward()
.into_stack()
.push_map_target(TcpEndpoint::from_param)
.instrument(|_: &_| debug_span!("tcp"))
.into_inner();

// Handles connections that target the inbound proxy port.
let direct = self
.clone()
.into_tcp_connect(la.port())
.push_tcp_forward()
.map_stack(|_, _, s| s.push_map_target(TcpEndpoint::from_param))
.push_direct(gateway)
.into_stack()
.instrument(|_: &_| debug_span!("direct"))
.into_inner();

// Handles HTTP connections.
let http = self
.into_tcp_connect(la.port())
.push_http_router(profiles)
.push_http_server();

// Determines how to handle an inbound connection, dispatching it to the appropriate
// stack.
let server = http
.push_detect(forward)
.push_accept(la.port(), direct)
.into_inner();

serve::serve(listen, server, shutdown).await
Copy link
Contributor

Choose a reason for hiding this comment

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

very nice!

Copy link
Contributor

@hawkw hawkw left a comment

Choose a reason for hiding this comment

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

okay, i feel good about this — commented on the changes that i think are necessary to fix the fuzz build.

linkerd/app/inbound/src/http/router.rs Outdated Show resolved Hide resolved
linkerd/app/inbound/src/detect.rs Show resolved Hide resolved
linkerd/app/inbound/src/http/mod.rs Show resolved Hide resolved
linkerd/app/inbound/src/http/mod.rs Outdated Show resolved Hide resolved
linkerd/app/inbound/src/http/mod.rs Outdated Show resolved Hide resolved
olix0r added 3 commits August 3, 2021 17:02
The `Inbound::push_server` stack builder is responsible for building all
of the inbound stacks: the HTTP stack, the TCP forwarding stack, and the
direct/gateway stack. Furthermore, most of these stacks rely on fixed
target types (which makes them inflexible).

This change refactors the inbound stack to setup for upcoming policy
changes:

1. A 'detect' stack is split out into a `push_detect` helper that only
   deals with protocol detection and dispatching connections to either
   the TCP forwarding stack or the HTTP handling stack.
2. An 'accept' stack is split out into a `push_accept` helper that is
   responsible for extracting relevant connection metadata (client and
   orig dst addrs) and determining the policy to use for the connection.
3. Each stack/module defines its own target types and other modules rely
   on param constraints instead of fixed target types.
4. A `port_policies` module is introduced to model the existing per-port
   policies: whether identity is required and whether protocol detection
   should be skipped. Various auxiliary modules have been eliminated in
   favor of inlined target filters. This helps to make these stacks
   easier to reason about, as relevant logic isn't spread over multiple
   files.
@hawkw
Copy link
Contributor

hawkw commented Aug 3, 2021

Couple of tests appear to be failing?

@olix0r olix0r merged commit 2eb1671 into main Aug 3, 2021
@olix0r olix0r deleted the ver/inbound-port-policy branch August 3, 2021 18:56
olix0r added a commit to linkerd/linkerd2 that referenced this pull request Aug 5, 2021
This release includes only dependency updates an internal changes to
support upcoming policy features. No user-facing changes are expected.

---

* build(deps): bump tokio from 1.8.2 to 1.9.0 (linkerd/linkerd2-proxy#1162)
* build(deps): bump hyper from 0.14.10 to 0.14.11 (linkerd/linkerd2-proxy#1163)
* build(deps): bump codecov/codecov-action from 2.0.1 to 2.0.2 (linkerd/linkerd2-proxy#1165)
* build(deps): bump futures from 0.3.15 to 0.3.16 (linkerd/linkerd2-proxy#1166)
* build(deps): bump softprops/action-gh-release (linkerd/linkerd2-proxy#1167)
* build(deps): bump softprops/action-gh-release from 0.1.6 to 1 (linkerd/linkerd2-proxy#1168)
* stack: Introduce ExtractParam and InsertParam (linkerd/linkerd2-proxy#1169)
* inbound: Consolidate port-based switching (linkerd/linkerd2-proxy#1170)
* build(deps): bump socket2 from 0.4.0 to 0.4.1 (linkerd/linkerd2-proxy#1171)
* build(deps): bump serde_json from 1.0.64 to 1.0.65 (linkerd/linkerd2-proxy#1172)
* build(deps): bump async-trait from 0.1.50 to 0.1.51 (linkerd/linkerd2-proxy#1173)
* Update to Rust v1.54.0 (linkerd/linkerd2-proxy#1175)
* build(deps): bump serde_json from 1.0.65 to 1.0.66 (linkerd/linkerd2-proxy#1176)
* Add BoxTcp and BoxNewTcp service type aliases (linkerd/linkerd2-proxy#1177)
* http: Use ExtractParam to support dynamic detect configuration (linkerd/linkerd2-proxy#1174)
* Split the inbound server into multiple modules (linkerd/linkerd2-proxy#1179)
olix0r added a commit to linkerd/linkerd2 that referenced this pull request Aug 5, 2021
This release includes only dependency updates an internal changes to
support upcoming policy features. No user-facing changes are expected.

---

* build(deps): bump tokio from 1.8.2 to 1.9.0 (linkerd/linkerd2-proxy#1162)
* build(deps): bump hyper from 0.14.10 to 0.14.11 (linkerd/linkerd2-proxy#1163)
* build(deps): bump codecov/codecov-action from 2.0.1 to 2.0.2 (linkerd/linkerd2-proxy#1165)
* build(deps): bump futures from 0.3.15 to 0.3.16 (linkerd/linkerd2-proxy#1166)
* build(deps): bump softprops/action-gh-release (linkerd/linkerd2-proxy#1167)
* build(deps): bump softprops/action-gh-release from 0.1.6 to 1 (linkerd/linkerd2-proxy#1168)
* stack: Introduce ExtractParam and InsertParam (linkerd/linkerd2-proxy#1169)
* inbound: Consolidate port-based switching (linkerd/linkerd2-proxy#1170)
* build(deps): bump socket2 from 0.4.0 to 0.4.1 (linkerd/linkerd2-proxy#1171)
* build(deps): bump serde_json from 1.0.64 to 1.0.65 (linkerd/linkerd2-proxy#1172)
* build(deps): bump async-trait from 0.1.50 to 0.1.51 (linkerd/linkerd2-proxy#1173)
* Update to Rust v1.54.0 (linkerd/linkerd2-proxy#1175)
* build(deps): bump serde_json from 1.0.65 to 1.0.66 (linkerd/linkerd2-proxy#1176)
* Add BoxTcp and BoxNewTcp service type aliases (linkerd/linkerd2-proxy#1177)
* http: Use ExtractParam to support dynamic detect configuration (linkerd/linkerd2-proxy#1174)
* Split the inbound server into multiple modules (linkerd/linkerd2-proxy#1179)
hawkw added a commit that referenced this pull request Aug 10, 2021
PR #1179 changed the inbound HTTP fuzz logic, and added a mocked
`Target` type similar to other stack tests. However, this introduced a
bug, because the `Target::addr()` function, which returns the socket
address that the mock TCP connector should expect the proxy to try to
connect to, returns the (mocked) original destination address. Since the
stack we build for the fuzz test maps the target IP address to localhost
before calling into the connect stack (as the real proxy currently
does), this means that we attempt to connect to an address the mock
connector doesn't expect. Then, it panics and the fuzz test fails.

This branch fixes this issue by changing `Target::addr` to return
127.0.0.1:80 instead of 192.0.2.2:80. This should match what the proxy
will actually attempt to connect to.

I also updated the fuzz logic to use exactly the same connect stack as
the HTTP stack tests (including a timeout). Although this shouldn't be
exercised, it makes the fuzz test more closely match the stack tests and
thus the real life proxy. I wonder if we can eventually share more code
between the tests and the fuzzers, but that seems like a follow-up.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
sannimichaelse pushed a commit to sannimichaelse/linkerd2 that referenced this pull request Aug 10, 2021
This release includes only dependency updates an internal changes to
support upcoming policy features. No user-facing changes are expected.

---

* build(deps): bump tokio from 1.8.2 to 1.9.0 (linkerd/linkerd2-proxy#1162)
* build(deps): bump hyper from 0.14.10 to 0.14.11 (linkerd/linkerd2-proxy#1163)
* build(deps): bump codecov/codecov-action from 2.0.1 to 2.0.2 (linkerd/linkerd2-proxy#1165)
* build(deps): bump futures from 0.3.15 to 0.3.16 (linkerd/linkerd2-proxy#1166)
* build(deps): bump softprops/action-gh-release (linkerd/linkerd2-proxy#1167)
* build(deps): bump softprops/action-gh-release from 0.1.6 to 1 (linkerd/linkerd2-proxy#1168)
* stack: Introduce ExtractParam and InsertParam (linkerd/linkerd2-proxy#1169)
* inbound: Consolidate port-based switching (linkerd/linkerd2-proxy#1170)
* build(deps): bump socket2 from 0.4.0 to 0.4.1 (linkerd/linkerd2-proxy#1171)
* build(deps): bump serde_json from 1.0.64 to 1.0.65 (linkerd/linkerd2-proxy#1172)
* build(deps): bump async-trait from 0.1.50 to 0.1.51 (linkerd/linkerd2-proxy#1173)
* Update to Rust v1.54.0 (linkerd/linkerd2-proxy#1175)
* build(deps): bump serde_json from 1.0.65 to 1.0.66 (linkerd/linkerd2-proxy#1176)
* Add BoxTcp and BoxNewTcp service type aliases (linkerd/linkerd2-proxy#1177)
* http: Use ExtractParam to support dynamic detect configuration (linkerd/linkerd2-proxy#1174)
* Split the inbound server into multiple modules (linkerd/linkerd2-proxy#1179)
hawkw added a commit that referenced this pull request Aug 10, 2021
PR #1179 changed the inbound HTTP fuzz logic, and added a mocked
`Target` type similar to other stack tests. However, this introduced a
bug, because the `Target::addr()` function, which returns the socket
address that the mock TCP connector should expect the proxy to try to
connect to, returns the (mocked) original destination address. Since the
stack we build for the fuzz test maps the target IP address to localhost
before calling into the connect stack (as the real proxy currently
does), this means that we attempt to connect to an address the mock
connector doesn't expect. Then, it panics and the fuzz test fails.

This branch fixes this issue by changing `Target::addr` to return
127.0.0.1:80 instead of 192.0.2.2:80. This should match what the proxy
will actually attempt to connect to.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
sannimichaelse pushed a commit to sannimichaelse/linkerd2 that referenced this pull request Aug 13, 2021
This release includes only dependency updates an internal changes to
support upcoming policy features. No user-facing changes are expected.

---

* build(deps): bump tokio from 1.8.2 to 1.9.0 (linkerd/linkerd2-proxy#1162)
* build(deps): bump hyper from 0.14.10 to 0.14.11 (linkerd/linkerd2-proxy#1163)
* build(deps): bump codecov/codecov-action from 2.0.1 to 2.0.2 (linkerd/linkerd2-proxy#1165)
* build(deps): bump futures from 0.3.15 to 0.3.16 (linkerd/linkerd2-proxy#1166)
* build(deps): bump softprops/action-gh-release (linkerd/linkerd2-proxy#1167)
* build(deps): bump softprops/action-gh-release from 0.1.6 to 1 (linkerd/linkerd2-proxy#1168)
* stack: Introduce ExtractParam and InsertParam (linkerd/linkerd2-proxy#1169)
* inbound: Consolidate port-based switching (linkerd/linkerd2-proxy#1170)
* build(deps): bump socket2 from 0.4.0 to 0.4.1 (linkerd/linkerd2-proxy#1171)
* build(deps): bump serde_json from 1.0.64 to 1.0.65 (linkerd/linkerd2-proxy#1172)
* build(deps): bump async-trait from 0.1.50 to 0.1.51 (linkerd/linkerd2-proxy#1173)
* Update to Rust v1.54.0 (linkerd/linkerd2-proxy#1175)
* build(deps): bump serde_json from 1.0.65 to 1.0.66 (linkerd/linkerd2-proxy#1176)
* Add BoxTcp and BoxNewTcp service type aliases (linkerd/linkerd2-proxy#1177)
* http: Use ExtractParam to support dynamic detect configuration (linkerd/linkerd2-proxy#1174)
* Split the inbound server into multiple modules (linkerd/linkerd2-proxy#1179)

Signed-off-by: Sanni Michael <sannimichaelse@gmail.com>
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.

3 participants