-
Notifications
You must be signed in to change notification settings - Fork 271
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
Conversation
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.
@@ -150,13 +150,6 @@ impl<M> Admin<M> { | |||
} | |||
} | |||
|
|||
impl<M: FmtMetrics + Clone, T> svc::NewService<T> for Admin<M> { |
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
impl Target { | ||
const HTTP1: Self = Self(http::Version::Http1); | ||
const H2: Self = Self(http::Version::H2); |
There was a problem hiding this comment.
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>( |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very nice!
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 }, | ||
) | ||
})), |
There was a problem hiding this comment.
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>( |
There was a problem hiding this comment.
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
// 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())) |
There was a problem hiding this comment.
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
// 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()); |
There was a problem hiding this comment.
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
There was a problem hiding this 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!
/// 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>> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it also detects TLS
There was a problem hiding this comment.
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!
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very nice!
There was a problem hiding this 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.
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.
Couple of tests appear to be failing? |
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)
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)
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>
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)
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>
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>
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:
push_detect
helper that only deals with protocol detection and dispatching connections to either the TCP forwarding stack or the HTTP handling stack.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.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