-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
proxy: Clarify Outbound::recognize #1144
Changes from 3 commits
5b86cb1
6fd4435
8429708
c090d73
77d36cd
af0d763
2093817
0f4f672
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,6 +54,74 @@ impl<B> Outbound<B> { | |
bind_timeout, | ||
} | ||
} | ||
|
||
|
||
/// TODO: Return error when `HostAndPort::normalize()` fails. | ||
/// TODO: Use scheme-appropriate default port. | ||
fn normalize(authority: &http::uri::Authority) -> Option<HostAndPort> { | ||
const DEFAULT_PORT: Option<u16> = Some(80); | ||
HostAndPort::normalize(authority, DEFAULT_PORT) | ||
.inspect_err(|e| error!("invalid authority=\"{}\": {:?}", authority, e)) | ||
} | ||
|
||
/// Determines the logical host:port of the request. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT: I think the preferred convention for rustdoc comments is for the summary of a function to be a present-tense description of the action that the function does, like "Determine the logical host:port..." rather than "Determines..." There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (also I think the bikeshed should be blue...) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hawkw From everything I've seen, the idiomatic style is to conjugate them as I did here. https://doc.rust-lang.org/src/core/convert.rs.html#309 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @olix0r Yeah, it looks like TRPL also uses that conjugation, color me wrong then! I'm not sure where that got into my head from... |
||
/// | ||
/// If the parsed URI includes an authority, use that. Otherwise, try to load the | ||
/// authority from the `Host` header. | ||
/// | ||
/// The port is either parsed from the authority or a default of 80 is used. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Splitting these out is great, and I love the added documentation. |
||
fn host_port(req: &http::Request<B>) -> Option<HostAndPort> { | ||
// Note: Calls to `normalize` cannot be deduped without cloning `authority`. | ||
req.uri() | ||
.authority_part() | ||
.and_then(Self::normalize) | ||
.or_else(|| { | ||
h1::authority_from_host(req) | ||
.and_then(|h| Self::normalize(&h)) | ||
}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like we could do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To do this, we have to clone the authority. (because authority_part returns a ref, and we can't make the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, sounds good! |
||
} | ||
|
||
/// Determines the destination to use in the router. | ||
/// | ||
/// A Destination is determined for each request as follows: | ||
/// | ||
/// 1. If the request includes a logical authority, it is routed by the hostname and | ||
/// port. If an explicit port is not provided, a default is assumed. | ||
/// | ||
/// 2. If the request authority contains a concrete IP address, it is routed directly | ||
/// to that IP address. | ||
/// | ||
/// 3. If no authority is present on the request, the client's original destination, | ||
/// as determined by the SO_ORIGINAL_DST socket option, is used as the request's | ||
/// destination. | ||
/// | ||
/// The SO_ORIGINAL_DST socket option is typically set by iptables(8) in | ||
/// containerized environments like Kubernetes (as configured by the proxy-init | ||
/// program). | ||
/// | ||
/// 4. If the request has no authority and the SO_ORIGINAL_DST socket option has not | ||
/// been set (or is unavailable on the current platform), no destination is | ||
/// determined. | ||
fn destination(req: &http::Request<B>) -> Option<Destination> { | ||
match Self::host_port(req) { | ||
Some(HostAndPort { host: Host::DnsName(host), port }) => { | ||
let dst = DnsNameAndPort { host, port }; | ||
Some(Destination::Hostname(dst)) | ||
} | ||
|
||
Some(HostAndPort { host: Host::Ip(ip), port }) => { | ||
let dst = SocketAddr::from((ip, port)); | ||
Some(Destination::ImplicitOriginalDst(dst)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
None => { | ||
req.extensions() | ||
.get::<Arc<ctx::transport::Server>>() | ||
.and_then(|ctx| ctx.orig_dst_if_not_local()) | ||
.map(Destination::ImplicitOriginalDst) | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl<B> Clone for Outbound<B> | ||
|
@@ -88,45 +156,11 @@ where | |
choose::PowerOfTwoChoices, | ||
>>>>; | ||
|
||
// Route the request by its destination AND PROTOCOL. This prevents HTTP/1 | ||
// requests from being routed to HTTP/2 servers, and vice versa. | ||
fn recognize(&self, req: &Self::Request) -> Option<Self::Key> { | ||
let dest = Self::destination(req)?; | ||
let proto = bind::Protocol::detect(req); | ||
|
||
// The request URI and Host: header have not yet been normalized | ||
// by `NormalizeUri`, as we need to know whether the request will | ||
// be routed by Host/authority or by SO_ORIGINAL_DST, in order to | ||
// determine whether the service is reusable. | ||
let authority = req.uri().authority_part() | ||
.cloned() | ||
// Therefore, we need to check the host header as well as the URI | ||
// for a valid authority, before we fall back to SO_ORIGINAL_DST. | ||
.or_else(|| h1::authority_from_host(req)); | ||
|
||
// TODO: Return error when `HostAndPort::normalize()` fails. | ||
let mut dest = match authority.as_ref() | ||
.and_then(|auth| HostAndPort::normalize(auth, Some(80)).ok()) { | ||
Some(HostAndPort { host: Host::DnsName(dns_name), port }) => | ||
Some(Destination::Hostname(DnsNameAndPort { host: dns_name, port })), | ||
Some(HostAndPort { host: Host::Ip(_), .. }) | | ||
None => None, | ||
}; | ||
|
||
if dest.is_none() { | ||
dest = req.extensions() | ||
.get::<Arc<ctx::transport::Server>>() | ||
.and_then(|ctx| { | ||
ctx.orig_dst_if_not_local() | ||
}) | ||
.map(Destination::ImplicitOriginalDst) | ||
}; | ||
|
||
// If there is no authority in the request URI or in the Host header, | ||
// and there is no original dst, then we have nothing! In that case, | ||
// return `None`, which results an "unrecognized" error. In practice, | ||
// this shouldn't ever happen, since we expect the proxy to be run on | ||
// Linux servers, with iptables setup, so there should always be an | ||
// original destination. | ||
let dest = dest?; | ||
|
||
Some((dest, proto)) | ||
} | ||
|
||
|
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.
If we're discarding the error here, maybe we should at least
.inspect_err(|e| error!("normalizing {:?} failed: {:?}", authority, e))
first (or something like that)?I know the original code you're refactoring didn't do that, but it seems simple enough that we could add it in this PR...
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.
What does that do?
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.
Log the error prior to discarding it.
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.
Is this part of the std lib or is it an extension? I don't see this defined on
Result
. In any case, let's address this separately.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, nevermind, that's a method on
Stream
, I think, that I mistakenly assumed was also onResult
s;map_err
would do more or less the same thing here though, since the error value gets discarded anyway.