Skip to content

Decouple server config from OrigDstAddr #962

Merged
hawkw merged 6 commits intoeliza/orig-dst-layerfrom
ver/eliza/odl
Mar 31, 2021
Merged

Decouple server config from OrigDstAddr #962
hawkw merged 6 commits intoeliza/orig-dst-layerfrom
ver/eliza/odl

Conversation

@olix0r
Copy link
Member

@olix0r olix0r commented Mar 31, 2021

Making the config types generic over the orig-dst strategy promulgates
generics across all of the stacks. But the binding logic is only
necessary at the very "top" of the stack where the accept loop is
driven.

This change eliminates the GetAddrs trait and modifies the Bind
trait to take params that allow the binding implementation to be
configured (i.e., from the environment). OrigDstAddr binding is
provided as a Bind implementation that wraps the BindTcp strategy.

This lets us eliminate the mock-orig-dst feature by moving the
MockOrigDst helpers into the integration test suite.

olix0r added 4 commits March 30, 2021 08:36
7d2bdbb fixed an issue with TLS detection. This change adds a test for
this behavior.

In order to implement a test, this change modifies the `detect` helper
to not be responsible for SNI matching & TLS termination. The `detect`
helper is replaced with a `detect_sni` function that only reads the
ClientHello without terminating TLS. A test has been added for the
`detect_sni` helper.
The HTTP detect module may fail to detect an HTTP protocol if the first
read does not return at least 14 bytes. This has caused spurious
failures as described in linkerd/linkerd2#5672.

This change updates the admin server's protocol detection to handle
these connections as HTTP/1 so that the HTTP server can try to process
them and fail on its own if the connection really is not HTTP.
Making the config types generic over the orig-dst strategy promulgates
generics across all of the stacks. But the binding logic is only
necessary at the very "top" of the stack where the accept loop is
driven.

This change eliminates the `GetAddrs` trait and modifies the `Bind`
trait to take params that allow the binding implementation to be
configured (i.e., from the environment). `OrigDstAddr` binding is
provided as a `Bind` implementation that wraps the `BindTcp` strategy.

This lets us eliminate the `mock-orig-dst` feature by moving the
`MockOrigDst` helpers into the integration test suite.
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.

this looks great, I think this factoring is much better than what I ended up with. I had some minor non-blocking nits, and also noticed one accidental change that i think we definitely need to fix, but besides that, I'm happy to merge this into my branch.

Comment on lines +84 to +86
bind_in: BIn,
bind_out: BOut,
bind_adm: BAdm,
Copy link
Contributor

Choose a reason for hiding this comment

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

hmmm...this is not my favorite thing in the world, the big pile of positionals feels like it's a little hard to follow at the callsite? Maybe it would be better if there was an arguments struct like:

pub struct BindConfig<In, Out, Adm> {
    pub bind_inbound: In,
    pub bind_outbound: Out,
    pub admin: Adm,
}

to fake calling build with named params, like:

let config = ...;
let bind_inbound = ...;
let bind_outbound = ...;
let bind_admin = ...;
let app = config.build(BindConfig { bind_inbound, bind_outbound, bind_admin })?;

but...it's probably not really worth the complexity; it's not like this is a library API or even a function that we call all that often (i think it gets called...twice in the entire codebase?)

where
A: GetAddrs<TcpStream> + Send + Sync + 'static,
A::Addrs: Send + Sync + 'static,
T: Param<ListenAddr> + Param<Keepalive>,
Copy link
Contributor

Choose a reason for hiding this comment

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

nice...although OTTOH, as far as i can tell, we only ever call this with ServerConfig...maybe instead of making Bind generic over an arbitrary params type, we could just have a BindConfig with a Keepalive and a ListenAddr in this module (solving the problem of ServerConfig being defined in a higher-up crate), and have Bind::bind take a BindConfig? That might be simpler than having a more generic solution that we don't actually need.

but...this doesn't add so much complexity that i care all that strongly...

Comment on lines +11 to +20
#[derive(Copy, Clone, Debug, Default)]
pub struct BindWithOrigDst<B = listen::BindTcp> {
inner: B,
}

#[derive(Clone, Debug)]
pub struct Addrs<A = listen::Addrs> {
pub inner: A,
pub orig_dst: OrigDstAddr,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

very elegant, i approve of this factoring <3

Ok((addrs, tcp))
});

Ok((addr, Box::pin(incoming)))
Copy link
Contributor

Choose a reason for hiding this comment

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

this does mean we're Box::pinning the incoming stream twice, so every time we poll it we dereference two trait object pointers. Since this map doesn't capture anything, I don't think we need to box again; we could change Incoming to

type Incoming = futures::stream::Map<
    T::Incoming, 
    fn(io::Result<(B::Addrs, B::Io)>-> io::Result<(Self::Addrs, Self::Io)>
>;

which is not all that much worse of a type and avoids a second trait object pointer deref. Not sure if this matters though...

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 tried this and it doesn't work as you'd hope because the closure captures part of the type signature... if you can shave off the box, I'm open to it, though

Comment on lines +33 to +38
let app = match async move {
config
.build(bind, bind, BindTcp::default(), shutdown_tx, trace?)
.await
}
.await
Copy link
Contributor

Choose a reason for hiding this comment

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

oh, wow, huh, this sure is some formatting...

Comment on lines +30 to +46
impl<A> Param<Remote<ClientAddr>> for Addrs<A>
where
A: Param<Remote<ClientAddr>>,
{
fn param(&self) -> Remote<ClientAddr> {
self.inner.param()
}
}

impl<A> Param<Local<ServerAddr>> for Addrs<A>
where
A: Param<Local<ServerAddr>>,
{
fn param(&self) -> Local<ServerAddr> {
self.inner.param()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

sad that these can't just be a impl<A, T> Param<T> for Addrs<A> where A: Param<T>, but i guess there's no way to convince rustc that that wouldn't conflict with Param<OrigDstAddrs>...

let tcp = res?;
super::set_nodelay_or_warn(&tcp);
super::set_keepalive_or_warn(&tcp, keepalive);
let client = Remote(ClientAddr(tcp.peer_addr()?));
Copy link
Contributor

Choose a reason for hiding this comment

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

small nitpick: I put the "accepted" trace event here once we have the addrs, and we don't have that any more...nbd but it might be worth having

Comment on lines +700 to +706
let svc = server.new_service(orig_dst::Addrs {
orig_dst: OrigDstAddr(ep1),
inner: listen::Addrs {
server: Local(ServerAddr(([127, 0, 0, 1], 4140).into())),
client: Remote(ClientAddr(([127, 0, 0, 1], 666).into())),
},
});
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: maybe there should be an orig_dst::Addrs::new(OrigDstAddr, Local<ServerAddr>, Remote<ClientAddr>) constructor to make this slightly less wordy? not a big deal.

@hawkw hawkw merged commit b695009 into eliza/orig-dst-layer Mar 31, 2021
@hawkw hawkw deleted the ver/eliza/odl branch March 31, 2021 19:00
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.

2 participants