Unify Name/Host/Addr types under Addr (#120)

Currently, the proxy uses a variety of types to represent the logical
destination of a request. Outbound destinations use a `NameAddr` type
which may be either a `DnsNameAndPort` or a `SocketAddr`. Other parts of
the code used a `HostAndPort` enum that always contained a port and also
contained a `Host` which could either be a `dns::Name` or a `IpAddr`.
Furthermore, we coerce these types into a `http::uri::Authority` in many
cases.

All of these types represent the same thing; and it's not clear when/why
it's appropriate to use a given variant.

In order to simplify the situtation, a new `addr` module has been
introduced with `Addr` and `NameAddr` types. A `Addr` may
contain either a `NameAddr` or a `SocketAddr`.

The `Host` value has been removed from the `Settings::Http1` type,
replaced by a boolean, as it's redundant information stored elsewhere in
the route key.

There is one small change in behavior: The `authority` metrics label is
now omitted only for requests that include an `:authority` or `Host`
with a _name_ (i.e. and not an IP address).
This commit is contained in:
Oliver Gould 2018-11-08 14:49:42 -08:00 committed by GitHub
parent 5e0a15b8a7
commit c4b3765574
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 379 additions and 511 deletions

177
src/addr.rs Normal file
View File

@ -0,0 +1,177 @@
use http;
use std::fmt;
use std::net::{IpAddr, SocketAddr};
use std::str::FromStr;
use convert::TryFrom;
pub use dns::Name;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum Addr {
Name(NameAddr),
Socket(SocketAddr),
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct NameAddr {
name: Name,
port: u16,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Error {
/// The host is not a valid DNS name or IP address.
InvalidHost,
/// The port is missing.
MissingPort,
}
// === impl Addr ===
impl Addr {
pub fn new(host: &str, port: u16) -> Result<Self, Error> {
IpAddr::from_str(host)
.map(|ip| Addr::Socket((ip, port).into()))
.or_else(|_| NameAddr::new(host, port).map(Addr::Name))
}
pub fn from_authority_and_default_port(
a: &http::uri::Authority,
default_port: u16,
) -> Result<Self, Error> {
Self::new(a.host(), a.port().unwrap_or(default_port))
}
pub fn from_authority_with_port(a: &http::uri::Authority) -> Result<Self, Error> {
a.port()
.ok_or(Error::MissingPort)
.and_then(|p| Self::new(a.host(), p))
}
pub fn port(&self) -> u16 {
match self {
Addr::Name(n) => n.port(),
Addr::Socket(a) => a.port(),
}
}
pub fn is_loopback(&self) -> bool {
match self {
Addr::Name(n) => n.is_localhost(),
Addr::Socket(a) => a.ip().is_loopback(),
}
}
pub fn as_authority(&self) -> http::uri::Authority {
match self {
Addr::Name(n) => n.as_authority(),
Addr::Socket(a) => http::uri::Authority::from_str(&format!("{}", a))
.expect("SocketAddr must be valid authority"),
}
}
pub fn socket_addr(&self) -> Option<SocketAddr> {
match self {
Addr::Socket(a) => Some(*a),
Addr::Name(_) => None,
}
}
pub fn name_addr(&self) -> Option<&NameAddr> {
match self {
Addr::Name(ref n) => Some(n),
Addr::Socket(_) => None,
}
}
pub fn into_name_addr(self) -> Option<NameAddr> {
match self {
Addr::Name(n) => Some(n),
Addr::Socket(_) => None,
}
}
}
impl fmt::Display for Addr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Addr::Name(NameAddr { ref name, port }) => write!(f, "{}:{}", name, port),
Addr::Socket(addr) => write!(f, "{}", addr),
}
}
}
// === impl NameAddr ===
impl NameAddr {
pub fn new(host: &str, port: u16) -> Result<Self, Error> {
if host.is_empty() {
return Err(Error::InvalidHost);
}
Name::try_from(host.as_bytes())
.map(|name| NameAddr { name, port })
.map_err(|_| Error::InvalidHost)
}
pub fn from_authority_with_default_port(
a: &http::uri::Authority,
default_port: u16,
) -> Result<Self, Error> {
Self::new(a.host(), a.port().unwrap_or(default_port))
}
pub fn from_authority_with_port(a: &http::uri::Authority) -> Result<Self, Error> {
a.port()
.ok_or(Error::MissingPort)
.and_then(|p| Self::new(a.host(), p))
}
pub fn name(&self) -> &Name {
&self.name
}
pub fn port(&self) -> u16 {
self.port
}
pub fn is_localhost(&self) -> bool {
self.name.is_localhost()
}
pub fn as_authority(&self) -> http::uri::Authority {
http::uri::Authority::from_str(self.name.as_ref())
.expect("NameAddr must be valid authority")
}
}
impl fmt::Display for NameAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.name, self.port)
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::uri::Authority;
#[test]
fn test_is_loopback() {
let cases = &[
("localhost", false), // Not absolute
("localhost.", true),
("LocalhOsT.", true), // Case-insensitive
("mlocalhost.", false), // prefixed
("localhost1.", false), // suffixed
("127.0.0.1", true), // IPv4
("[::1]", true), // IPv6
];
for (host, expected_result) in cases {
let authority = Authority::from_static(host);
let hp = Addr::from_authority_and_default_port(&authority, 80).unwrap();
assert_eq!(hp.is_loopback(), *expected_result, "{:?}", host)
}
}
}

View File

@ -10,9 +10,10 @@ use http;
use indexmap::IndexSet; use indexmap::IndexSet;
use trust_dns_resolver::config::ResolverOpts; use trust_dns_resolver::config::ResolverOpts;
use transport::{Host, HostAndPort, HostAndPortError, tls}; use addr;
use convert::TryFrom; use convert::TryFrom;
use Conditional; use transport::tls;
use {Conditional, Addr};
// TODO: // TODO:
// //
@ -34,7 +35,7 @@ pub struct Config {
pub metrics_listener: Listener, pub metrics_listener: Listener,
/// Where to forward externally received connections. /// Where to forward externally received connections.
pub inbound_forward: Option<Addr>, pub inbound_forward: Option<SocketAddr>,
/// The maximum amount of time to wait for a connection to a local peer. /// The maximum amount of time to wait for a connection to a local peer.
pub inbound_connect_timeout: Duration, pub inbound_connect_timeout: Duration,
@ -71,7 +72,7 @@ pub struct Config {
/// ///
/// This is optional to allow the proxy to work without the controller for /// This is optional to allow the proxy to work without the controller for
/// experimental & testing purposes. /// experimental & testing purposes.
pub control_host_and_port: Option<HostAndPort>, pub control_host_and_port: Option<Addr>,
/// Time to wait when encountering errors talking to control plane before /// Time to wait when encountering errors talking to control plane before
/// a new connection. /// a new connection.
@ -109,14 +110,9 @@ pub struct Namespaces {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Listener { pub struct Listener {
/// The address to which the listener should bind. /// The address to which the listener should bind.
pub addr: Addr, pub addr: SocketAddr,
} }
/// A logical address. This abstracts over the various strategies for cross
/// process communication.
#[derive(Clone, Copy, Debug)]
pub struct Addr(SocketAddr);
/// Errors produced when loading a `Config` struct. /// Errors produced when loading a `Config` struct.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Error { pub enum Error {
@ -145,7 +141,7 @@ pub enum UrlError {
MissingAuthority, MissingAuthority,
/// The URL is missing the authority part. /// The URL is missing the authority part.
AuthorityError(HostAndPortError), AuthorityError(addr::Error),
/// The URL contains a path component that isn't "/", which isn't allowed. /// The URL contains a path component that isn't "/", which isn't allowed.
PathNotAllowed, PathNotAllowed,
@ -285,13 +281,13 @@ impl<'a> TryFrom<&'a Strings> for Config {
// will log any errors so defer returning any errors until all of them // will log any errors so defer returning any errors until all of them
// have been parsed. // have been parsed.
let outbound_listener_addr = parse_deprecated( let outbound_listener_addr = parse_deprecated(
strings, ENV_OUTBOUND_LISTENER, DEPRECATED_ENV_PRIVATE_LISTENER, str::parse); strings, ENV_OUTBOUND_LISTENER, DEPRECATED_ENV_PRIVATE_LISTENER, parse_addr);
let inbound_listener_addr = parse_deprecated( let inbound_listener_addr = parse_deprecated(
strings, ENV_INBOUND_LISTENER, DEPRECATED_ENV_PUBLIC_LISTENER, str::parse); strings, ENV_INBOUND_LISTENER, DEPRECATED_ENV_PUBLIC_LISTENER, parse_addr);
let control_listener_addr = parse(strings, ENV_CONTROL_LISTENER, str::parse); let control_listener_addr = parse(strings, ENV_CONTROL_LISTENER, parse_addr);
let metrics_listener_addr = parse(strings, ENV_METRICS_LISTENER, str::parse); let metrics_listener_addr = parse(strings, ENV_METRICS_LISTENER, parse_addr);
let inbound_forward = parse_deprecated( let inbound_forward = parse_deprecated(
strings, ENV_INBOUND_FORWARD, DEPRECATED_ENV_PRIVATE_FORWARD, str::parse); strings, ENV_INBOUND_FORWARD, DEPRECATED_ENV_PRIVATE_FORWARD, parse_addr);
let inbound_connect_timeout = parse_deprecated( let inbound_connect_timeout = parse_deprecated(
strings, ENV_INBOUND_CONNECT_TIMEOUT, DEPRECATED_ENV_PRIVATE_CONNECT_TIMEOUT, parse_duration); strings, ENV_INBOUND_CONNECT_TIMEOUT, DEPRECATED_ENV_PRIVATE_CONNECT_TIMEOUT, parse_duration);
let outbound_connect_timeout = parse_deprecated( let outbound_connect_timeout = parse_deprecated(
@ -405,19 +401,19 @@ impl<'a> TryFrom<&'a Strings> for Config {
Ok(Config { Ok(Config {
outbound_listener: Listener { outbound_listener: Listener {
addr: outbound_listener_addr? addr: outbound_listener_addr?
.unwrap_or_else(|| Addr::from_str(DEFAULT_OUTBOUND_LISTENER).unwrap()), .unwrap_or_else(|| parse_addr(DEFAULT_OUTBOUND_LISTENER).unwrap()),
}, },
inbound_listener: Listener { inbound_listener: Listener {
addr: inbound_listener_addr? addr: inbound_listener_addr?
.unwrap_or_else(|| Addr::from_str(DEFAULT_INBOUND_LISTENER).unwrap()), .unwrap_or_else(|| parse_addr(DEFAULT_INBOUND_LISTENER).unwrap()),
}, },
control_listener: Listener { control_listener: Listener {
addr: control_listener_addr? addr: control_listener_addr?
.unwrap_or_else(|| Addr::from_str(DEFAULT_CONTROL_LISTENER).unwrap()), .unwrap_or_else(|| parse_addr(DEFAULT_CONTROL_LISTENER).unwrap()),
}, },
metrics_listener: Listener { metrics_listener: Listener {
addr: metrics_listener_addr? addr: metrics_listener_addr?
.unwrap_or_else(|| Addr::from_str(DEFAULT_METRICS_LISTENER).unwrap()), .unwrap_or_else(|| parse_addr(DEFAULT_METRICS_LISTENER).unwrap()),
}, },
inbound_forward: inbound_forward?, inbound_forward: inbound_forward?,
@ -472,27 +468,10 @@ fn default_disable_ports_protocol_detection() -> IndexSet<u16> {
// ===== impl Addr ===== // ===== impl Addr =====
impl FromStr for Addr { fn parse_addr(s: &str) -> Result<SocketAddr, ParseError> {
type Err = ParseError; match parse_url(s)? {
Addr::Socket(a) => Ok(a),
fn from_str(s: &str) -> Result<Self, Self::Err> { _ => Err(ParseError::HostIsNotAnIpAddress)
let a = parse_url(s)?;
if let Host::Ip(ip) = a.host {
return Ok(Addr(SocketAddr::from((ip, a.port))));
}
Err(ParseError::HostIsNotAnIpAddress)
}
}
impl From<Addr> for SocketAddr {
fn from(addr: Addr) -> SocketAddr {
addr.0
}
}
impl From<SocketAddr> for Addr {
fn from(addr: SocketAddr) -> Self {
Addr(addr)
} }
} }
@ -562,7 +541,7 @@ fn parse_path(s: &str) -> Result<PathBuf, ParseError> {
Ok(PathBuf::from(s)) Ok(PathBuf::from(s))
} }
fn parse_url(s: &str) -> Result<HostAndPort, ParseError> { fn parse_url(s: &str) -> Result<Addr, ParseError> {
let url = s.parse::<http::Uri>().map_err(|_| ParseError::UrlError(UrlError::SyntaxError))?; let url = s.parse::<http::Uri>().map_err(|_| ParseError::UrlError(UrlError::SyntaxError))?;
if url.scheme_part().map(|s| s.as_str()) != Some("tcp") { if url.scheme_part().map(|s| s.as_str()) != Some("tcp") {
return Err(ParseError::UrlError(UrlError::UnsupportedScheme)); return Err(ParseError::UrlError(UrlError::UnsupportedScheme));
@ -577,7 +556,7 @@ fn parse_url(s: &str) -> Result<HostAndPort, ParseError> {
// https://github.com/hyperium/http/issues/127. For now just ignore any // https://github.com/hyperium/http/issues/127. For now just ignore any
// fragment that is there. // fragment that is there.
HostAndPort::normalize(authority, None) Addr::from_authority_with_port(authority)
.map_err(|e| ParseError::UrlError(UrlError::AuthorityError(e))) .map_err(|e| ParseError::UrlError(UrlError::AuthorityError(e)))
} }

View File

@ -3,12 +3,12 @@ use std::fmt;
use std::time::Duration; use std::time::Duration;
use svc; use svc;
use transport::{tls, HostAndPort}; use transport::tls;
use Conditional; use {Conditional, Addr};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Config { pub struct Config {
host_and_port: HostAndPort, host_and_port: Addr,
tls_server_identity: Conditional<tls::Identity, tls::ReasonForNoTls>, tls_server_identity: Conditional<tls::Identity, tls::ReasonForNoTls>,
tls_config: tls::ConditionalClientConfig, tls_config: tls::ConditionalClientConfig,
backoff: Duration, backoff: Duration,
@ -18,7 +18,7 @@ pub struct Config {
impl Config { impl Config {
pub fn new( pub fn new(
host_and_port: HostAndPort, host_and_port: Addr,
tls_server_identity: Conditional<tls::Identity, tls::ReasonForNoTls>, tls_server_identity: Conditional<tls::Identity, tls::ReasonForNoTls>,
backoff: Duration, backoff: Duration,
connect_timeout: Duration, connect_timeout: Duration,
@ -116,7 +116,7 @@ pub mod add_origin {
fn make(&self, config: &super::Config) -> Result<Self::Value, Self::Error> { fn make(&self, config: &super::Config) -> Result<Self::Value, Self::Error> {
let inner = self.inner.make(config)?; let inner = self.inner.make(config)?;
let scheme = uri::Scheme::from_shared(Bytes::from_static(b"http")).unwrap(); let scheme = uri::Scheme::from_shared(Bytes::from_static(b"http")).unwrap();
let authority = uri::Authority::from(&config.host_and_port); let authority = config.host_and_port.as_authority();
Ok(AddOrigin::new(inner, scheme, authority)) Ok(AddOrigin::new(inner, scheme, authority))
} }
} }
@ -254,7 +254,7 @@ pub mod resolve {
fn new_service(&self) -> Self::Future { fn new_service(&self) -> Self::Future {
Init { Init {
state: State::Resolve { state: State::Resolve {
future: self.dns.resolve_one_ip(&self.config.host_and_port.host), future: self.dns.resolve_one_ip(&self.config.host_and_port),
stack: self.stack.clone(), stack: self.stack.clone(),
config: self.config.clone(), config: self.config.clone(),
}, },
@ -284,7 +284,7 @@ pub mod resolve {
ref stack, ref stack,
} => { } => {
let ip = try_ready!(future.poll().map_err(Error::Dns)); let ip = try_ready!(future.poll().map_err(Error::Dns));
let sa = SocketAddr::from((ip, config.host_and_port.port)); let sa = SocketAddr::from((ip, config.host_and_port.port()));
let tls = config.tls_server_identity.as_ref().and_then(|id| { let tls = config.tls_server_identity.as_ref().and_then(|id| {
config config
@ -333,13 +333,14 @@ pub mod client {
use tower_h2::{client, BoxBody}; use tower_h2::{client, BoxBody};
use svc; use svc;
use transport::{connect, HostAndPort}; use transport::connect;
use Addr;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Target { pub struct Target {
pub(super) connect: connect::Target, pub(super) connect: connect::Target,
pub(super) builder: h2::client::Builder, pub(super) builder: h2::client::Builder,
pub(super) log_ctx: ::logging::Client<&'static str, HostAndPort>, pub(super) log_ctx: ::logging::Client<&'static str, Addr>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -401,7 +402,7 @@ pub mod client {
{ {
type Value = client::Connect< type Value = client::Connect<
C::Value, C::Value,
::logging::ClientExecutor<&'static str, HostAndPort>, ::logging::ClientExecutor<&'static str, Addr>,
BoxBody, BoxBody,
>; >;
type Error = C::Error; type Error = C::Error;

View File

@ -2,20 +2,18 @@ use http;
use std::fmt; use std::fmt;
use std::net::SocketAddr; use std::net::SocketAddr;
use proxy::http::{ use super::classify;
client, h1, normalize_uri::ShouldNormalizeUri, router, Settings, use proxy::http::{client, normalize_uri::ShouldNormalizeUri, router, Settings};
};
use proxy::server::Source; use proxy::server::Source;
use svc::stack_per_request::ShouldStackPerRequest; use svc::stack_per_request::ShouldStackPerRequest;
use tap; use tap;
use super::classify;
use transport::{connect, tls}; use transport::{connect, tls};
use Conditional; use {Conditional, NameAddr};
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Endpoint { pub struct Endpoint {
pub addr: SocketAddr, pub addr: SocketAddr,
pub authority: http::uri::Authority, pub dst_name: Option<NameAddr>,
pub settings: Settings, pub settings: Settings,
pub source_tls_status: tls::Status, pub source_tls_status: tls::Status,
} }
@ -93,20 +91,14 @@ impl<A> router::Recognize<http::Request<A>> for Recognize {
.and_then(|s| s.orig_dst_if_not_local()) .and_then(|s| s.orig_dst_if_not_local())
.or(self.default_addr)?; .or(self.default_addr)?;
let authority = req let dst_name = super::http_request_addr(req)
.uri() .ok()
.authority_part() .and_then(|h| h.into_name_addr());
.cloned() let settings = Settings::from_request(req);
.or_else(|| h1::authority_from_host(req))
.or_else(|| {
let a = format!("{}", addr);
http::uri::Authority::from_shared(a.into()).ok()
})?;
let settings = Settings::detect(req);
let ep = Endpoint { let ep = Endpoint {
addr, addr,
authority, dst_name,
settings, settings,
source_tls_status, source_tls_status,
}; };
@ -184,22 +176,21 @@ mod tests {
use super::{Endpoint, Recognize}; use super::{Endpoint, Recognize};
use proxy::http::router::Recognize as _Recognize; use proxy::http::router::Recognize as _Recognize;
use proxy::http::settings::{Host, Settings}; use proxy::http::settings::Settings;
use proxy::server::Source; use proxy::server::Source;
use transport::tls; use transport::tls;
use Conditional; use Conditional;
fn make_h1_endpoint(addr: net::SocketAddr) -> Endpoint { fn make_h1_endpoint(addr: net::SocketAddr) -> Endpoint {
let settings = Settings::Http1 { let settings = Settings::Http1 {
host: Host::NoAuthority,
is_h1_upgrade: false, is_h1_upgrade: false,
was_absolute_form: false, was_absolute_form: false,
stack_per_request: true,
}; };
let authority = http::uri::Authority::from_shared(format!("{}", addr).into()).unwrap();
let source_tls_status = TLS_DISABLED; let source_tls_status = TLS_DISABLED;
Endpoint { Endpoint {
addr, addr,
authority, dst_name: None,
settings, settings,
source_tls_status, source_tls_status,
} }

View File

@ -1,4 +1,3 @@
use http::uri;
use std::{ use std::{
fmt::{self, Write}, fmt::{self, Write},
net, net,
@ -7,7 +6,7 @@ use std::{
use metrics::FmtLabels; use metrics::FmtLabels;
use transport::tls; use transport::tls;
use Conditional; use {Conditional, NameAddr};
use super::{classify, inbound, outbound}; use super::{classify, inbound, outbound};
@ -16,7 +15,7 @@ pub struct EndpointLabels {
addr: net::SocketAddr, addr: net::SocketAddr,
direction: Direction, direction: Direction,
tls_status: tls::Status, tls_status: tls::Status,
authority: Authority, dst_name: Option<NameAddr>,
labels: Option<String>, labels: Option<String>,
} }
@ -34,7 +33,7 @@ enum Direction {
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct Authority(Option<uri::Authority>); struct Authority<'a>(&'a NameAddr);
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct Dst(outbound::Destination); struct Dst(outbound::Destination);
@ -69,7 +68,7 @@ impl From<inbound::Endpoint> for EndpointLabels {
fn from(ep: inbound::Endpoint) -> Self { fn from(ep: inbound::Endpoint) -> Self {
Self { Self {
addr: ep.addr, addr: ep.addr,
authority: Authority(Some(ep.authority)), dst_name: ep.dst_name,
direction: Direction::In, direction: Direction::In,
tls_status: ep.source_tls_status, tls_status: ep.source_tls_status,
labels: None, labels: None,
@ -92,26 +91,9 @@ where
impl From<outbound::Endpoint> for EndpointLabels { impl From<outbound::Endpoint> for EndpointLabels {
fn from(ep: outbound::Endpoint) -> Self { fn from(ep: outbound::Endpoint) -> Self {
use self::outbound::NameOrAddr;
use transport::DnsNameAndPort;
let authority = {
let a = match ep.dst.name_or_addr {
NameOrAddr::Name(DnsNameAndPort { ref host, ref port }) => {
if *port == 80 {
format!("{}", host)
} else {
format!("{}:{}", host, port)
}
}
NameOrAddr::Addr(addr) => format!("{}", addr),
};
Authority(uri::Authority::from_shared(a.into()).ok())
};
Self { Self {
addr: ep.connect.addr, addr: ep.connect.addr,
authority, dst_name: ep.dst.addr.into_name_addr(),
direction: Direction::Out, direction: Direction::Out,
tls_status: ep.connect.tls_status(), tls_status: ep.connect.tls_status(),
labels: prefix_labels("dst", ep.metadata.labels().into_iter()), labels: prefix_labels("dst", ep.metadata.labels().into_iter()),
@ -121,7 +103,8 @@ impl From<outbound::Endpoint> for EndpointLabels {
impl FmtLabels for EndpointLabels { impl FmtLabels for EndpointLabels {
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
(&self.authority, &self.direction).fmt_labels(f)?; let authority = self.dst_name.as_ref().map(Authority);
(authority, &self.direction).fmt_labels(f)?;
if let Some(labels) = self.labels.as_ref() { if let Some(labels) = self.labels.as_ref() {
write!(f, ",{}", labels)?; write!(f, ",{}", labels)?;
@ -143,11 +126,11 @@ impl FmtLabels for Direction {
} }
} }
impl FmtLabels for Authority { impl<'a> FmtLabels for Authority<'a> {
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 { match self.0.port() {
Some(ref a) => write!(f, "authority=\"{}\"", a), 80 => write!(f, "authority=\"{}\"", self.0.name()),
None => write!(f, "authority=\"\""), _ => write!(f, "authority=\"{}\"", self.0),
} }
} }
} }
@ -159,11 +142,7 @@ impl FmtLabels for Dst {
} else { } else {
"h1" "h1"
}; };
write!( write!(f, "dst=\"{}\",dst_protocol=\"{}\"", self.0.addr, proto)?;
f,
"dst=\"{}\",dst_protocol=\"{}\"",
self.0.name_or_addr, proto
)?;
Ok(()) Ok(())
} }

View File

@ -1,7 +1,6 @@
//! Configures and runs the linkerd2 service sidecar proxy //! Configures and runs the linkerd2 service sidecar proxy
use convert::TryFrom; use http;
use logging;
mod classify; mod classify;
pub mod config; pub mod config;
@ -12,11 +11,35 @@ mod metric_labels;
mod outbound; mod outbound;
mod profiles; mod profiles;
use self::config::{Config, Env};
pub use self::main::Main; pub use self::main::Main;
use addr::{self, Addr};
pub fn init() -> Result<config::Config, config::Error> {
use convert::TryFrom;
use logging;
pub fn init() -> Result<Config, config::Error> {
logging::init(); logging::init();
let config_strings = Env; config::Config::try_from(&config::Env)
Config::try_from(&config_strings) }
fn http_request_addr<B>(req: &http::Request<B>) -> Result<Addr, addr::Error> {
use proxy::{http::h1, Source};
const DEFAULT_PORT: u16 = 80;
req.uri()
.authority_part()
.ok_or(addr::Error::InvalidHost)
.and_then(|a| Addr::from_authority_and_default_port(a, DEFAULT_PORT))
.or_else(|_| {
h1::authority_from_host(req)
.ok_or(addr::Error::InvalidHost)
.and_then(|a| Addr::from_authority_and_default_port(&a, DEFAULT_PORT))
})
.or_else(|e| {
req.extensions()
.get::<Source>()
.and_then(|src| src.orig_dst_if_not_local())
.map(Addr::Socket)
.ok_or(e)
})
} }

View File

@ -1,46 +1,31 @@
use http; use http;
use std::fmt; use std::fmt;
use std::net::SocketAddr;
use app::classify; use app::classify;
use control::destination::{Metadata, ProtocolHint}; use control::destination::{Metadata, ProtocolHint};
use proxy::{ use proxy::http::{
http::{ classify::CanClassify,
classify::CanClassify, client,
client, h1, normalize_uri::ShouldNormalizeUri,
normalize_uri::ShouldNormalizeUri, profiles::{self, CanGetDestination},
profiles::{self, CanGetDestination}, router, Settings,
router, Settings,
},
Source,
}; };
use svc::{self, stack_per_request::ShouldStackPerRequest}; use svc::{self, stack_per_request::ShouldStackPerRequest};
use tap; use tap;
use transport::{connect, tls, DnsNameAndPort, Host, HostAndPort}; use transport::{connect, tls};
use {Addr, NameAddr};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Endpoint { pub struct Endpoint {
pub dst: Destination, pub dst: Destination,
pub connect: connect::Target, pub connect: connect::Target,
pub metadata: Metadata, pub metadata: Metadata,
_p: (),
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Destination { pub struct Destination {
pub name_or_addr: NameOrAddr, pub addr: Addr,
pub settings: Settings, pub settings: Settings,
_p: (),
}
/// Describes a destination for HTTP requests.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum NameOrAddr {
/// A logical, lazily-bound endpoint.
Name(DnsNameAndPort),
/// A single, bound endpoint.
Addr(SocketAddr),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -50,7 +35,7 @@ pub struct Route {
} }
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Recognize {} pub struct Recognize;
// === impl Endpoint === // === impl Endpoint ===
@ -136,34 +121,26 @@ impl<B> router::Recognize<http::Request<B>> for Recognize {
type Target = Destination; type Target = Destination;
fn recognize(&self, req: &http::Request<B>) -> Option<Self::Target> { fn recognize(&self, req: &http::Request<B>) -> Option<Self::Target> {
let dst = Destination::from_request(req); let addr = super::http_request_addr(req).ok()?;
let settings = Settings::from_request(req);
let dst = Destination::new(addr, settings);
debug!("recognize: dst={:?}", dst); debug!("recognize: dst={:?}", dst);
dst Some(dst)
} }
} }
// === impl Destination === // === impl Destination ===
impl Destination { impl Destination {
pub fn new(name_or_addr: NameOrAddr, settings: Settings) -> Self { pub fn new(addr: Addr, settings: Settings) -> Self {
Self { Self { addr, settings }
name_or_addr,
settings,
_p: (),
}
}
pub fn from_request<A>(req: &http::Request<A>) -> Option<Self> {
let name_or_addr = NameOrAddr::from_request(req)?;
let settings = Settings::detect(req);
Some(Self::new(name_or_addr, settings))
} }
} }
impl CanGetDestination for Destination { impl CanGetDestination for Destination {
fn get_destination(&self) -> Option<&DnsNameAndPort> { fn get_destination(&self) -> Option<&NameAddr> {
match self.name_or_addr { match self.addr {
NameOrAddr::Name(ref dst) => Some(dst), Addr::Name(ref name) => Some(name),
_ => None, _ => None,
} }
} }
@ -171,78 +148,7 @@ impl CanGetDestination for Destination {
impl fmt::Display for Destination { impl fmt::Display for Destination {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.name_or_addr.fmt(f) self.addr.fmt(f)
}
}
impl fmt::Display for NameOrAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
NameOrAddr::Name(ref name) => write!(f, "{}:{}", name.host, name.port),
NameOrAddr::Addr(ref addr) => addr.fmt(f),
}
}
}
impl NameOrAddr {
/// Determines the destination for a request.
///
/// Typically, a request's authority is used to produce a `NameOrAddr`. If the
/// authority addresses a DNS name, a `NameOrAddr::Name` is returned; and, otherwise,
/// it addresses a fixed IP address and a `NameOrAddr::Addr` is returned. The port is
/// inferred if not specified in the authority.
///
/// If no authority is available, the `SO_ORIGINAL_DST` socket option is checked. If
/// it's available, it is used to return a `NameOrAddr::Addr`. This socket option is
/// typically set by `iptables(8)` in containerized environments like Kubernetes (as
/// configured by the `proxy-init` program).
///
/// If none of this information is available, no `NameOrAddr` is returned.
pub fn from_request<B>(req: &http::Request<B>) -> Option<NameOrAddr> {
match Self::host_port(req) {
Some(HostAndPort {
host: Host::DnsName(host),
port,
}) => {
let name_or_addr = DnsNameAndPort { host, port };
Some(NameOrAddr::Name(name_or_addr))
}
Some(HostAndPort {
host: Host::Ip(ip),
port,
}) => {
let name_or_addr = SocketAddr::from((ip, port));
Some(NameOrAddr::Addr(name_or_addr))
}
None => req
.extensions()
.get::<Source>()
.and_then(|src| src.orig_dst_if_not_local())
.map(NameOrAddr::Addr),
}
}
/// Determines the logical host:port of the request.
///
/// 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.
fn host_port<B>(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)))
}
/// 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).ok()
} }
} }
@ -258,14 +164,14 @@ pub mod discovery {
use futures::{Async, Poll}; use futures::{Async, Poll};
use std::net::SocketAddr; use std::net::SocketAddr;
use super::{Destination, Endpoint, NameOrAddr}; use super::{Destination, Endpoint};
use control::destination::Metadata; use control::destination::Metadata;
use proxy::resolve; use proxy::resolve;
use transport::{connect, tls, DnsNameAndPort}; use transport::{connect, tls};
use Conditional; use {Addr, Conditional, NameAddr};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Resolve<R: resolve::Resolve<DnsNameAndPort>>(R); pub struct Resolve<R: resolve::Resolve<NameAddr>>(R);
#[derive(Debug)] #[derive(Debug)]
pub enum Resolution<R: resolve::Resolution> { pub enum Resolution<R: resolve::Resolution> {
@ -277,7 +183,7 @@ pub mod discovery {
impl<R> Resolve<R> impl<R> Resolve<R>
where where
R: resolve::Resolve<DnsNameAndPort, Endpoint = Metadata>, R: resolve::Resolve<NameAddr, Endpoint = Metadata>,
{ {
pub fn new(resolve: R) -> Self { pub fn new(resolve: R) -> Self {
Resolve(resolve) Resolve(resolve)
@ -286,15 +192,15 @@ pub mod discovery {
impl<R> resolve::Resolve<Destination> for Resolve<R> impl<R> resolve::Resolve<Destination> for Resolve<R>
where where
R: resolve::Resolve<DnsNameAndPort, Endpoint = Metadata>, R: resolve::Resolve<NameAddr, Endpoint = Metadata>,
{ {
type Endpoint = Endpoint; type Endpoint = Endpoint;
type Resolution = Resolution<R::Resolution>; type Resolution = Resolution<R::Resolution>;
fn resolve(&self, dst: &Destination) -> Self::Resolution { fn resolve(&self, dst: &Destination) -> Self::Resolution {
match dst.name_or_addr { match dst.addr {
NameOrAddr::Name(ref name) => Resolution::Name(dst.clone(), self.0.resolve(&name)), Addr::Name(ref name) => Resolution::Name(dst.clone(), self.0.resolve(&name)),
NameOrAddr::Addr(ref addr) => Resolution::Addr(dst.clone(), Some(*addr)), Addr::Socket(ref addr) => Resolution::Addr(dst.clone(), Some(*addr)),
} }
} }
} }
@ -327,7 +233,6 @@ pub mod discovery {
dst: dst.clone(), dst: dst.clone(),
connect: connect::Target::new(addr, Conditional::None(tls)), connect: connect::Target::new(addr, Conditional::None(tls)),
metadata, metadata,
_p: (),
}; };
Ok(Async::Ready(resolve::Update::Add(addr, ep))) Ok(Async::Ready(resolve::Update::Add(addr, ep)))
} }
@ -339,7 +244,6 @@ pub mod discovery {
dst: dst.clone(), dst: dst.clone(),
connect: connect::Target::new(addr, Conditional::None(tls.into())), connect: connect::Target::new(addr, Conditional::None(tls.into())),
metadata: Metadata::none(tls), metadata: Metadata::none(tls),
_p: (),
}; };
let up = resolve::Update::Add(addr, ep); let up = resolve::Update::Add(addr, ep);
Ok(Async::Ready(up)) Ok(Async::Ready(up))

View File

@ -11,7 +11,7 @@ use api::destination as api;
use control; use control;
use proxy::http::profiles; use proxy::http::profiles;
use transport::DnsNameAndPort; use NameAddr;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Client<T, N> { pub struct Client<T, N> {
@ -61,7 +61,7 @@ where
{ {
type Stream = Rx<T>; type Stream = Rx<T>;
fn get_routes(&self, dst: &DnsNameAndPort) -> Option<Self::Stream> { fn get_routes(&self, dst: &NameAddr) -> Option<Self::Stream> {
let fqa = self.normalize_name.normalize(dst)?; let fqa = self.normalize_name.normalize(dst)?;
Some(Rx { Some(Rx {
dst: fqa.without_trailing_dot().to_owned(), dst: fqa.without_trailing_dot().to_owned(),

View File

@ -1,72 +1,11 @@
use std;
/// Like `std::option::Option<C>` but `None` carries a reason why the value /// Like `std::option::Option<C>` but `None` carries a reason why the value
/// isn't available. /// isn't available.
#[derive(Clone)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Conditional<C, R> pub enum Conditional<C, R> {
where
C: Clone,
R: Clone,
{
Some(C), Some(C),
None(R), None(R),
} }
impl<C, R> Copy for Conditional<C, R>
where
C: Copy + Clone + std::fmt::Debug,
R: Copy + Clone + std::fmt::Debug,
{
}
impl<C, R> std::fmt::Debug for Conditional<C, R>
where
C: Clone + std::fmt::Debug,
R: Clone + std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Conditional::Some(s) => f.debug_tuple("Some").field(s).finish(),
Conditional::None(r) => f.debug_tuple("None").field(r).finish(),
}
}
}
impl<C, R> Eq for Conditional<C, R>
where
C: Eq + Clone,
R: Eq + Clone,
{
}
impl<C, R> PartialEq for Conditional<C, R>
where
C: PartialEq + Clone,
R: PartialEq + Clone,
{
fn eq(&self, other: &Conditional<C, R>) -> bool {
use self::Conditional::*;
match (self, other) {
(Some(a), Some(b)) => a.eq(b),
(None(a), None(b)) => a.eq(b),
_ => false,
}
}
}
impl<C, R> std::hash::Hash for Conditional<C, R>
where
C: std::hash::Hash + Clone,
R: std::hash::Hash + Clone,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Conditional::Some(c) => c.hash(state),
Conditional::None(r) => r.hash(state),
}
}
}
impl<C, R> Conditional<C, R> impl<C, R> Conditional<C, R>
where where
C: Clone, C: Clone,

View File

@ -25,8 +25,8 @@ use control::{
remote_stream::Remote, remote_stream::Remote,
}; };
use dns::{self, IpAddrListFuture}; use dns::{self, IpAddrListFuture};
use transport::{tls, DnsNameAndPort}; use transport::tls;
use Conditional; use {Conditional, NameAddr};
use super::{ActiveQuery, DestinationServiceQuery, UpdateRx}; use super::{ActiveQuery, DestinationServiceQuery, UpdateRx};
@ -50,15 +50,15 @@ where
&mut self, &mut self,
dns_resolver: &dns::Resolver, dns_resolver: &dns::Resolver,
deadline: Instant, deadline: Instant,
authority: &DnsNameAndPort, authority: &NameAddr,
) { ) {
trace!( trace!(
"resetting DNS query for {} at {:?}", "resetting DNS query for {} at {:?}",
authority.host, authority.name(),
deadline deadline
); );
self.reset_on_next_modification(); self.reset_on_next_modification();
self.dns_query = Some(dns_resolver.resolve_all_ips(deadline, &authority.host)); self.dns_query = Some(dns_resolver.resolve_all_ips(deadline, authority.name()));
} }
// Processes Destination service updates from `request_rx`, returning the new query // Processes Destination service updates from `request_rx`, returning the new query
@ -67,7 +67,7 @@ where
// "no change in existence" instead of "unknown". // "no change in existence" instead of "unknown".
pub(super) fn poll_destination_service( pub(super) fn poll_destination_service(
&mut self, &mut self,
auth: &DnsNameAndPort, auth: &NameAddr,
mut rx: UpdateRx<T>, mut rx: UpdateRx<T>,
tls_controller_namespace: Option<&str>, tls_controller_namespace: Option<&str>,
) -> (ActiveQuery<T>, Exists<()>) { ) -> (ActiveQuery<T>, Exists<()>) {
@ -123,7 +123,7 @@ where
} }
} }
pub(super) fn poll_dns(&mut self, dns_resolver: &dns::Resolver, authority: &DnsNameAndPort) { pub(super) fn poll_dns(&mut self, dns_resolver: &dns::Resolver, authority: &NameAddr) {
// Duration to wait before polling DNS again after an error // Duration to wait before polling DNS again after an error
// (or a NXDOMAIN response with no TTL). // (or a NXDOMAIN response with no TTL).
const DNS_ERROR_TTL: Duration = Duration::from_secs(5); const DNS_ERROR_TTL: Duration = Duration::from_secs(5);
@ -147,7 +147,7 @@ where
authority, authority,
ips.iter().map(|ip| { ips.iter().map(|ip| {
( (
SocketAddr::from((ip, authority.port)), SocketAddr::from((ip, authority.port())),
Metadata::none(tls::ReasonForNoIdentity::NotProvidedByServiceDiscovery), Metadata::none(tls::ReasonForNoIdentity::NotProvidedByServiceDiscovery),
) )
}), }),
@ -169,7 +169,7 @@ where
Err(e) => { Err(e) => {
// Do nothing so that the most recent non-error response is used until a // Do nothing so that the most recent non-error response is used until a
// non-error response is received // non-error response is received
trace!("DNS resolution failed for {}: {}", &authority.host, e); trace!("DNS resolution failed for {}: {}", authority.name(), e);
// Poll again after the default wait time. // Poll again after the default wait time.
Instant::now() + DNS_ERROR_TTL Instant::now() + DNS_ERROR_TTL
@ -200,7 +200,7 @@ where
} }
} }
fn add<A>(&mut self, authority_for_logging: &DnsNameAndPort, addrs_to_add: A) fn add<A>(&mut self, authority_for_logging: &NameAddr, addrs_to_add: A)
where where
A: Iterator<Item = (SocketAddr, Metadata)>, A: Iterator<Item = (SocketAddr, Metadata)>,
{ {
@ -214,7 +214,7 @@ where
self.addrs = Exists::Yes(cache); self.addrs = Exists::Yes(cache);
} }
fn remove<A>(&mut self, authority_for_logging: &DnsNameAndPort, addrs_to_remove: A) fn remove<A>(&mut self, authority_for_logging: &NameAddr, addrs_to_remove: A)
where where
A: Iterator<Item = SocketAddr>, A: Iterator<Item = SocketAddr>,
{ {
@ -230,7 +230,7 @@ where
self.addrs = Exists::Yes(cache); self.addrs = Exists::Yes(cache);
} }
fn no_endpoints(&mut self, authority_for_logging: &DnsNameAndPort, exists: bool) { fn no_endpoints(&mut self, authority_for_logging: &NameAddr, exists: bool) {
trace!( trace!(
"no endpoints for {:?} that is known to {}", "no endpoints for {:?} that is known to {}",
authority_for_logging, authority_for_logging,
@ -253,7 +253,7 @@ where
fn on_change( fn on_change(
responders: &mut Vec<Responder>, responders: &mut Vec<Responder>,
authority_for_logging: &DnsNameAndPort, authority_for_logging: &NameAddr,
change: CacheChange<SocketAddr, Metadata>, change: CacheChange<SocketAddr, Metadata>,
) { ) {
let (update_str, update, addr) = match change { let (update_str, update, addr) = match change {

View File

@ -29,7 +29,7 @@ use control::{
remote_stream::{Receiver, Remote}, remote_stream::{Receiver, Remote},
}; };
use dns; use dns;
use transport::DnsNameAndPort; use NameAddr;
mod destination_set; mod destination_set;
@ -59,9 +59,9 @@ pub(super) struct Background<T: HttpService> {
/// which require reconnects. /// which require reconnects.
#[derive(Default)] #[derive(Default)]
struct DestinationCache<T: HttpService> { struct DestinationCache<T: HttpService> {
destinations: HashMap<DnsNameAndPort, DestinationSet<T>>, destinations: HashMap<NameAddr, DestinationSet<T>>,
/// A queue of authorities that need to be reconnected. /// A queue of authorities that need to be reconnected.
reconnects: VecDeque<DnsNameAndPort>, reconnects: VecDeque<NameAddr>,
} }
/// The configurationn necessary to create a new Destination service /// The configurationn necessary to create a new Destination service
@ -338,7 +338,7 @@ impl NewQuery {
fn query_destination_service_if_relevant<T>( fn query_destination_service_if_relevant<T>(
&self, &self,
client: Option<&mut T>, client: Option<&mut T>,
auth: &DnsNameAndPort, auth: &NameAddr,
connect_or_reconnect: &str, connect_or_reconnect: &str,
) -> DestinationServiceQuery<T> ) -> DestinationServiceQuery<T>
where where
@ -415,7 +415,7 @@ where
/// Returns true if `auth` is currently known to need a Destination /// Returns true if `auth` is currently known to need a Destination
/// service query, but was unable to query previously due to the query /// service query, but was unable to query previously due to the query
/// limit being reached. /// limit being reached.
fn needs_query_for(&self, auth: &DnsNameAndPort) -> bool { fn needs_query_for(&self, auth: &NameAddr) -> bool {
self.destinations self.destinations
.get(auth) .get(auth)
.map(|dst| dst.needs_query_capacity()) .map(|dst| dst.needs_query_capacity())

View File

@ -40,13 +40,12 @@ use tower_h2::{Body, BoxBody, Data, HttpService};
use dns; use dns;
use transport::tls; use transport::tls;
use proxy::resolve::{self, Resolve, Update}; use proxy::resolve::{self, Resolve, Update};
use transport::DnsNameAndPort;
pub mod background; pub mod background;
use app::config::Namespaces; use app::config::Namespaces;
use self::background::Background; use self::background::Background;
use Conditional; use {Conditional, NameAddr};
/// A handle to request resolutions from the background discovery task. /// A handle to request resolutions from the background discovery task.
#[derive(Clone)] #[derive(Clone)]
@ -57,7 +56,7 @@ pub struct Resolver {
/// Requests that resolution updaes for `authority` be sent on `responder`. /// Requests that resolution updaes for `authority` be sent on `responder`.
#[derive(Debug)] #[derive(Debug)]
struct ResolveRequest { struct ResolveRequest {
authority: DnsNameAndPort, authority: NameAddr,
responder: Responder, responder: Responder,
} }
@ -136,12 +135,12 @@ where
// ==== impl Resolver ===== // ==== impl Resolver =====
impl Resolve<DnsNameAndPort> for Resolver { impl Resolve<NameAddr> for Resolver {
type Endpoint = Metadata; type Endpoint = Metadata;
type Resolution = Resolution; type Resolution = Resolution;
/// Start watching for address changes for a certain authority. /// Start watching for address changes for a certain authority.
fn resolve(&self, authority: &DnsNameAndPort) -> Resolution { fn resolve(&self, authority: &NameAddr) -> Resolution {
trace!("resolve; authority={:?}", authority); trace!("resolve; authority={:?}", authority);
let (update_tx, update_rx) = mpsc::unbounded(); let (update_tx, update_rx) = mpsc::unbounded();
let active = Arc::new(()); let active = Arc::new(());

View File

@ -1,9 +1,9 @@
use bytes::{BytesMut}; use bytes::{BytesMut};
use transport::DnsNameAndPort; use NameAddr;
pub trait Normalize { pub trait Normalize {
fn normalize(&self, authority: &DnsNameAndPort) -> Option<FullyQualifiedAuthority>; fn normalize(&self, authority: &NameAddr) -> Option<FullyQualifiedAuthority>;
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -24,8 +24,8 @@ impl KubernetesNormalize {
impl Normalize for KubernetesNormalize { impl Normalize for KubernetesNormalize {
/// Normalizes the name according to Kubernetes service naming conventions. /// Normalizes the name according to Kubernetes service naming conventions.
/// Case folding is not done; that is done internally inside `Authority`. /// Case folding is not done; that is done internally inside `Authority`.
fn normalize(&self, authority: &DnsNameAndPort) -> Option<FullyQualifiedAuthority> { fn normalize(&self, authority: &NameAddr) -> Option<FullyQualifiedAuthority> {
let name: &str = authority.host.as_ref(); let name: &str = authority.name().as_ref();
// parts should have a maximum 4 of pieces (name, namespace, svc, zone) // parts should have a maximum 4 of pieces (name, namespace, svc, zone)
let mut parts = name.splitn(4, '.'); let mut parts = name.splitn(4, '.');
@ -95,7 +95,7 @@ impl Normalize for KubernetesNormalize {
additional_len += 1 + zone.len(); // "." + zone additional_len += 1 + zone.len(); // "." + zone
} }
let port_str_len = match authority.port { let port_str_len = match authority.port() {
80 => 0, // XXX: Assumes http://, which is all we support right now. 80 => 0, // XXX: Assumes http://, which is all we support right now.
p if p >= 10000 => 1 + 5, p if p >= 10000 => 1 + 5,
p if p >= 1000 => 1 + 4, p if p >= 1000 => 1 + 4,
@ -126,7 +126,7 @@ impl Normalize for KubernetesNormalize {
// Append the port // Append the port
if port_str_len > 0 { if port_str_len > 0 {
normalized.extend_from_slice(b":"); normalized.extend_from_slice(b":");
let port = authority.port.to_string(); let port = authority.port().to_string();
normalized.extend_from_slice(port.as_ref()); normalized.extend_from_slice(port.as_ref());
} }
@ -142,19 +142,18 @@ impl FullyQualifiedAuthority {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use transport::{DnsNameAndPort, Host, HostAndPort};
use http::uri::Authority; use http::uri::Authority;
use std::str::FromStr; use std::str::FromStr;
use {Addr, NameAddr};
use super::Normalize; use super::Normalize;
#[test] #[test]
fn test_normalized_authority() { fn test_normalized_authority() {
fn dns_name_and_port_from_str(input: &str) -> DnsNameAndPort { fn dns_name_and_port_from_str(input: &str) -> NameAddr {
let authority = Authority::from_str(input).unwrap(); let authority = Authority::from_str(input).unwrap();
match HostAndPort::normalize(&authority, Some(80)) { match Addr::from_authority_and_default_port(&authority, 80) {
Ok(HostAndPort { host: Host::DnsName(host), port }) => Ok(Addr::Name(name)) => name,
DnsNameAndPort { host, port },
Err(e) => { Err(e) => {
unreachable!("{:?} when parsing {:?}", e, input) unreachable!("{:?} when parsing {:?}", e, input)
}, },

View File

@ -12,7 +12,8 @@ use trust_dns_resolver::{
}; };
use app::config::Config; use app::config::Config;
use transport::{self, tls}; use transport::tls;
use Addr;
#[derive(Clone)] #[derive(Clone)]
pub struct Resolver { pub struct Resolver {
@ -98,14 +99,15 @@ impl Resolver {
(resolver, background) (resolver, background)
} }
pub fn resolve_one_ip(&self, host: &transport::Host) -> IpAddrFuture { pub fn resolve_one_ip(&self, host: &Addr) -> IpAddrFuture {
match *host { match host {
transport::Host::DnsName(ref name) => { Addr::Name(n) => {
let name = n.name();
let ctx = ResolveOneCtx(name.clone()); let ctx = ResolveOneCtx(name.clone());
let f = ::logging::context_future(ctx, self.lookup_ip(name)); let f = ::logging::context_future(ctx, self.lookup_ip(name));
IpAddrFuture::DNS(Box::new(f)) IpAddrFuture::DNS(Box::new(f))
} }
transport::Host::Ip(addr) => IpAddrFuture::Fixed(addr), Addr::Socket(addr) => IpAddrFuture::Fixed(addr.ip()),
} }
} }

View File

@ -51,6 +51,7 @@ extern crate linkerd2_timeout as timeout;
// `linkerd2_metrics` is needed to satisfy the macro, but this is nicer to use internally. // `linkerd2_metrics` is needed to satisfy the macro, but this is nicer to use internally.
use self::linkerd2_metrics as metrics; use self::linkerd2_metrics as metrics;
mod addr;
pub mod app; pub mod app;
mod conditional; mod conditional;
pub mod control; pub mod control;
@ -65,5 +66,6 @@ mod tap;
pub mod telemetry; pub mod telemetry;
pub mod transport; pub mod transport;
use self::addr::{Addr, NameAddr};
use self::conditional::Conditional; use self::conditional::Conditional;
pub use self::transport::SoOriginalDst; pub use self::transport::SoOriginalDst;

View File

@ -10,7 +10,7 @@ use std::iter::FromIterator;
use std::sync::Arc; use std::sync::Arc;
use std::{error, fmt}; use std::{error, fmt};
use transport::DnsNameAndPort; use NameAddr;
pub type Routes = Vec<(RequestMatch, Route)>; pub type Routes = Vec<(RequestMatch, Route)>;
@ -21,7 +21,7 @@ pub type Routes = Vec<(RequestMatch, Route)>;
pub trait GetRoutes { pub trait GetRoutes {
type Stream: Stream<Item = Routes, Error = Error>; type Stream: Stream<Item = Routes, Error = Error>;
fn get_routes(&self, dst: &DnsNameAndPort) -> Option<Self::Stream>; fn get_routes(&self, dst: &NameAddr) -> Option<Self::Stream>;
} }
/// Implemented by target types that may be combined with a Route. /// Implemented by target types that may be combined with a Route.
@ -31,10 +31,10 @@ pub trait WithRoute {
fn with_route(self, route: Route) -> Self::Output; fn with_route(self, route: Route) -> Self::Output;
} }
/// Implemented by target types that may have a `DnsNameAndPort` destination that /// Implemented by target types that may have a `NameAddr` destination that
/// can be discovered via `GetRoutes`. /// can be discovered via `GetRoutes`.
pub trait CanGetDestination { pub trait CanGetDestination {
fn get_destination(&self) -> Option<&DnsNameAndPort>; fn get_destination(&self) -> Option<&NameAddr>;
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -1,6 +1,4 @@
use http::{self, uri}; use http::{self, header::HOST};
use super::h1;
/// Settings portion of the `Recognize` key for a request. /// Settings portion of the `Recognize` key for a request.
/// ///
@ -10,7 +8,8 @@ use super::h1;
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Settings { pub enum Settings {
Http1 { Http1 {
host: Host, /// Indicates whether a new service must be created for each request.
stack_per_request: bool,
/// Whether the request wants to use HTTP/1.1's Upgrade mechanism. /// Whether the request wants to use HTTP/1.1's Upgrade mechanism.
/// ///
/// Since these cannot be translated into orig-proto, it must be /// Since these cannot be translated into orig-proto, it must be
@ -26,79 +25,66 @@ pub enum Settings {
/// used to determine what URI normalization will be necessary. /// used to determine what URI normalization will be necessary.
was_absolute_form: bool, was_absolute_form: bool,
}, },
Http2 Http2,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Host {
Authority(uri::Authority),
NoAuthority,
} }
// ===== impl Settings ===== // ===== impl Settings =====
impl Settings { impl Settings {
pub fn detect<B>(req: &http::Request<B>) -> Self { pub fn from_request<B>(req: &http::Request<B>) -> Self {
if req.version() == http::Version::HTTP_2 { if req.version() == http::Version::HTTP_2 {
return Settings::Http2; return Settings::Http2;
} }
let was_absolute_form = super::h1::is_absolute_form(req.uri()); let is_missing_authority = req
trace!( .uri()
"Settings::detect(); req.uri='{:?}'; was_absolute_form={:?};", .authority_part()
req.uri(), was_absolute_form .map(|_| false)
); .or_else(|| {
// If the request has an authority part, use that as the host part of req.headers()
// the key for an HTTP/1.x request. .get(HOST)
let host = Host::detect(req); .and_then(|h| h.to_str().ok())
.map(|h| h.is_empty())
let is_h1_upgrade = super::h1::wants_upgrade(req); })
.unwrap_or(true);
Settings::Http1 { Settings::Http1 {
host, was_absolute_form: super::h1::is_absolute_form(req.uri()),
is_h1_upgrade, is_h1_upgrade: super::h1::wants_upgrade(req),
was_absolute_form, stack_per_request: is_missing_authority,
} }
} }
/// Returns true if the request was originally received in absolute form. /// Returns true if the request was originally received in absolute form.
pub fn was_absolute_form(&self) -> bool { pub fn was_absolute_form(&self) -> bool {
match self { match self {
&Settings::Http1 { was_absolute_form, .. } => was_absolute_form, Settings::Http1 {
_ => false, was_absolute_form, ..
} => *was_absolute_form,
Settings::Http2 => false,
} }
} }
pub fn can_reuse_clients(&self) -> bool { pub fn can_reuse_clients(&self) -> bool {
match *self { match self {
Settings::Http2 | Settings::Http1 { host: Host::Authority(_), .. } => true, Settings::Http1 {
_ => false, stack_per_request, ..
} => !stack_per_request,
Settings::Http2 => true,
} }
} }
pub fn is_h1_upgrade(&self) -> bool { pub fn is_h1_upgrade(&self) -> bool {
match *self { match self {
Settings::Http1 { is_h1_upgrade: true, .. } => true, Settings::Http1 { is_h1_upgrade, .. } => *is_h1_upgrade,
_ => false, Settings::Http2 => false,
} }
} }
pub fn is_http2(&self) -> bool { pub fn is_http2(&self) -> bool {
match *self { match self {
Settings::Http1 { .. } => false,
Settings::Http2 => true, Settings::Http2 => true,
_ => false,
} }
} }
} }
impl Host {
pub fn detect<B>(req: &http::Request<B>) -> Host {
req
.uri()
.authority_part()
.cloned()
.or_else(|| h1::authority_from_host(req))
.map(Host::Authority)
.unwrap_or_else(|| Host::NoAuthority)
}
}

View File

@ -2,13 +2,9 @@ extern crate tokio_connect;
pub use self::tokio_connect::Connect; pub use self::tokio_connect::Connect;
use http;
use std::{error, fmt, io}; use std::{error, fmt, io};
use std::net::{IpAddr, SocketAddr}; use std::net::SocketAddr;
use std::str::FromStr;
use convert::TryFrom;
use dns;
use svc; use svc;
use transport::{connection, tls}; use transport::{connection, tls};
@ -26,86 +22,6 @@ pub struct Target {
#[derive(Debug)] #[derive(Debug)]
pub struct InvalidTarget; pub struct InvalidTarget;
#[derive(Clone, Debug)]
pub struct HostAndPort {
pub host: Host,
pub port: u16,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct DnsNameAndPort {
pub host: dns::Name,
pub port: u16,
}
#[derive(Clone, Debug)]
pub enum Host {
DnsName(dns::Name),
Ip(IpAddr),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum HostAndPortError {
/// The host is not a valid DNS name or IP address.
InvalidHost,
/// The port is missing.
MissingPort,
}
// ===== impl HostAndPort =====
impl HostAndPort {
pub fn normalize(a: &http::uri::Authority, default_port: Option<u16>)
-> Result<Self, HostAndPortError>
{
let host = IpAddr::from_str(a.host())
.map(Host::Ip)
.or_else(|_|
dns::Name::try_from(a.host().as_bytes())
.map(Host::DnsName)
.map_err(|_| HostAndPortError::InvalidHost))?;
let port = a.port()
.or(default_port)
.ok_or_else(|| HostAndPortError::MissingPort)?;
Ok(HostAndPort {
host,
port
})
}
pub fn is_loopback(&self) -> bool {
match &self.host {
Host::DnsName(dns_name) => dns_name.is_localhost(),
Host::Ip(ip) => ip.is_loopback(),
}
}
}
impl<'a> From<&'a HostAndPort> for http::uri::Authority {
fn from(a: &HostAndPort) -> Self {
let s = match a.host {
Host::DnsName(ref n) => format!("{}:{}", n, a.port),
Host::Ip(ref ip) => format!("{}:{}", ip, a.port),
};
http::uri::Authority::from_str(&s).unwrap()
}
}
impl fmt::Display for HostAndPort {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.host {
Host::DnsName(ref dns) => {
write!(f, "{}:{}", dns, self.port)
}
Host::Ip(ref ip) => {
write!(f, "{}:{}", ip, self.port)
}
}
}
}
// ===== impl Target ===== // ===== impl Target =====
impl Target { impl Target {
@ -159,27 +75,3 @@ impl fmt::Display for InvalidTarget {
} }
impl error::Error for InvalidTarget {} impl error::Error for InvalidTarget {}
#[cfg(test)]
mod tests {
use http::uri::Authority;
use super::*;
#[test]
fn test_is_loopback() {
let cases = &[
("localhost", false), // Not absolute
("localhost.", true),
("LocalhOsT.", true), // Case-insensitive
("mlocalhost.", false), // prefixed
("localhost1.", false), // suffixed
("127.0.0.1", true), // IPv4
("[::1]", true), // IPv6
];
for (host, expected_result) in cases {
let authority = Authority::from_static(host);
let hp = HostAndPort::normalize(&authority, Some(80)).unwrap();
assert_eq!(hp.is_loopback(), *expected_result, "{:?}", host)
}
}
}

View File

@ -12,7 +12,6 @@ use tokio::{
reactor::Handle, reactor::Handle,
}; };
use app::config::Addr;
use Conditional; use Conditional;
use transport::{AddrInfo, BoxedIo, GetOriginalDst, tls}; use transport::{AddrInfo, BoxedIo, GetOriginalDst, tls};
@ -114,10 +113,10 @@ pub struct PeekFuture<T> {
// ===== impl BoundPort ===== // ===== impl BoundPort =====
impl BoundPort { impl BoundPort {
pub fn new(addr: Addr, tls: tls::ConditionalConnectionConfig<tls::ServerConfigWatch>) pub fn new(addr: SocketAddr, tls: tls::ConditionalConnectionConfig<tls::ServerConfigWatch>)
-> Result<Self, io::Error> -> Result<Self, io::Error>
{ {
let inner = std::net::TcpListener::bind(SocketAddr::from(addr))?; let inner = std::net::TcpListener::bind(addr)?;
let local_addr = inner.local_addr()?; let local_addr = inner.local_addr()?;
Ok(BoundPort { Ok(BoundPort {
inner, inner,

View File

@ -14,7 +14,6 @@ use tokio::{
prelude::*, prelude::*,
}; };
use app::config::Addr;
use Conditional; use Conditional;
use super::{ use super::{
@ -119,7 +118,7 @@ fn run_test<C, CF, CR, S, SF, SR>(
// tests to run at once, which wouldn't work if they all were bound on // tests to run at once, which wouldn't work if they all were bound on
// a fixed port. // a fixed port.
let addr = "127.0.0.1:0".parse::<SocketAddr>().unwrap(); let addr = "127.0.0.1:0".parse::<SocketAddr>().unwrap();
let server_bound = connection::BoundPort::new(Addr::from(addr), server_tls) let server_bound = connection::BoundPort::new(addr, server_tls)
.unwrap(); .unwrap();
let server_addr = server_bound.local_addr(); let server_addr = server_bound.local_addr();

View File

@ -15,10 +15,7 @@ pub use self::{
GetOriginalDst, GetOriginalDst,
SoOriginalDst SoOriginalDst
}, },
connect::{ connect::Connect,
Connect,
DnsNameAndPort, Host, HostAndPort, HostAndPortError,
},
connection::{ connection::{
BoundPort, BoundPort,
Connection, Connection,