├── rustfmt.toml ├── .gitignore ├── .github └── CODEOWNERS ├── Dockerfile ├── CONTRIBUTING.md ├── dockerize ├── GOVERNANCE.md ├── src ├── connection │ ├── ctx.rs │ ├── mod.rs │ ├── duplex.rs │ ├── socket.rs │ ├── half_duplex.rs │ └── secure.rs ├── lib.rs ├── path.rs ├── resolver │ ├── config.rs │ ├── mod.rs │ └── namerd.rs ├── balancer │ ├── factory.rs │ ├── endpoint.rs │ ├── mod.rs │ └── dispatcher.rs ├── admin.rs ├── router.rs ├── server │ ├── config.rs │ ├── sni.rs │ └── mod.rs ├── main.rs ├── connector │ ├── mod.rs │ └── config.rs └── app.rs ├── MAINTAINERS.md ├── example.yml ├── scripts └── rust-unmangle.sh ├── Cargo.toml ├── namerd.sh ├── PROFILING.md ├── .circleci └── config.yml ├── CHANGES.md ├── router.md ├── README.md ├── LICENSE └── Cargo.lock /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | tmp.discovery 3 | .idea 4 | *.iml 5 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @adleong @olix0r 2 | 3 | # William and Oliver should approve all changelog entries. 4 | CHANGES.md @wmorgan @olix0r 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:jessie-slim 2 | 3 | COPY ./target/x86_64-unknown-linux-gnu/release/linkerd-tcp /usr/local/bin/ 4 | 5 | ENTRYPOINT ["/usr/local/bin/linkerd-tcp"] 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing # 2 | 3 | :balloon: Thanks for your help improving the project! 4 | 5 | All Linkerd sub-projects follow the contribution guidelines described [here](https://github.com/linkerd/linkerd/blob/master/CONTRIBUTING.md). 6 | 7 | -------------------------------------------------------------------------------- /dockerize: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ -z "$1" ]; then 6 | echo "usage: dockerize " >&2 7 | exit 1 8 | fi 9 | 10 | docker run --rm -v "`pwd`":/rust/app -w /rust/app rust:1.23.0 cargo build --release --target=x86_64-unknown-linux-gnu 11 | docker build -t linkerd/linkerd-tcp:$1 . 12 | 13 | echo "Created linkerd/linkerd-tcp:$1" 14 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | # Linkerd-TCP Governance 2 | 3 | Governance of Linkerd-TCP follows the same rules as that of Linkerd itself. 4 | These rules are described in Linkerd's GOVERNANCE.md, e.g. at https://github.com/linkerd/linkerd/blob/master/GOVERNANCE.md. 5 | 6 | However, the set of maintainers is different, and is defined by the contents of 7 | the MAINTAINERS.md file in this repo. 8 | -------------------------------------------------------------------------------- /src/connection/ctx.rs: -------------------------------------------------------------------------------- 1 | /// A connection context 2 | pub trait Ctx: Drop { 3 | fn read(&mut self, sz: usize); 4 | fn wrote(&mut self, sz: usize); 5 | } 6 | 7 | #[allow(dead_code)] 8 | pub fn null() -> Null { 9 | Null() 10 | } 11 | #[allow(dead_code)] 12 | pub struct Null(); 13 | impl Ctx for Null { 14 | fn read(&mut self, _sz: usize) {} 15 | fn wrote(&mut self, _sz: usize) {} 16 | } 17 | impl Drop for Null { 18 | fn drop(&mut self) {} 19 | } 20 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | The Linkerd-tcp maintainers are: 2 | 3 | * Oliver Gould @olix0r (super-maintainer) 4 | * Alex Leong @adleong (super-maintainer) 5 | * Brian Smith @briansmith 6 | * Eliza Weisman @hawkw 7 | * William Morgan @wmorgan 8 | 9 | 10 | 19 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! linkerd-tcp: A load-balancing TCP/TLS stream routing proxy. 2 | //! 3 | //! 4 | //! 5 | //! Copyright 2017 Linkerd-TCP authors. 6 | 7 | #![deny(missing_docs)] 8 | #![deny(warnings)] 9 | 10 | extern crate bytes; 11 | #[macro_use] 12 | extern crate log; 13 | extern crate futures; 14 | extern crate hyper; 15 | extern crate ordermap; 16 | extern crate rand; 17 | extern crate rustls; 18 | extern crate serde; 19 | #[macro_use] 20 | extern crate serde_derive; 21 | extern crate serde_json; 22 | extern crate serde_yaml; 23 | extern crate tacho; 24 | extern crate tokio_core; 25 | #[macro_use] 26 | extern crate tokio_io; 27 | extern crate tokio_timer; 28 | extern crate url; 29 | 30 | mod admin; 31 | pub mod app; 32 | mod balancer; 33 | mod connection; 34 | mod connector; 35 | mod path; 36 | mod resolver; 37 | mod router; 38 | mod server; 39 | 40 | use balancer::WeightedAddr; 41 | use path::Path; 42 | -------------------------------------------------------------------------------- /example.yml: -------------------------------------------------------------------------------- 1 | admin: 2 | port: 9989 3 | metricsIntervalSecs: 10 4 | 5 | routers: 6 | 7 | - label: default 8 | interpreter: 9 | kind: io.l5d.namerd.http 10 | baseUrl: http://localhost:4180 11 | namespace: default 12 | periodSecs: 20 13 | 14 | servers: 15 | - port: 7474 16 | dstName: /svc/default 17 | connectTimeoutMs: 500 18 | connectionLifetimeSecs: 60 19 | 20 | # - port: 7473 21 | # dstName: /svc/default 22 | # connectTimeoutMs: 500 23 | # tls: 24 | # defaultIdentity: 25 | # privateKey: ../../eg-ca/foo.bird.tls/private.pem 26 | # certs: 27 | # - ../../eg-ca/foo.bird.tls/cert.pem 28 | # - ../../eg-ca/ca/intermediate/certs/ca-chain.cert.pem 29 | 30 | client: 31 | kind: io.l5d.global 32 | minConnections: 3 33 | connectTimeoutMs: 500 34 | -------------------------------------------------------------------------------- /scripts/rust-unmangle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sed -rf 2 | # Unmangle Rust symbols 3 | # Borrowed from https://github.com/Yamakaky/rust-unmangle 4 | # See https://git.kernel.org/cgit/linux/kernel/git/tip/tip.git/commit/?id=cae15db74999edb96dd9f5bbd4d55849391dd92b 5 | # Example, with [FlameGraph](https://github.com/brendangregg/FlameGraph): 6 | # perf record -g target/debug/bin 7 | # perf script | stackcollapse-perf | rust-unmangle | flamegraph > perf.svg 8 | 9 | # Remove hash and address offset 10 | s/::h[0-9a-f]{16}//g 11 | s/\+0x[0-9a-f]+//g 12 | 13 | # Convert special characters 14 | s/\$C\$/,/g 15 | s/\$SP\$/@/g 16 | s/\$BP\$/*/g 17 | s/\$RF\$/\&/g 18 | s/\$LT\$//g 20 | s/\$LP\$/(/g 21 | s/\$RP\$/)/g 22 | s/\$u20\$/ /g 23 | s/\$u27\$/'/g 24 | s/\$u5b\$/[/g 25 | s/\$u5d\$/]/g 26 | s/\$u7b\$/{/g 27 | s/\$u7d\$/}/g 28 | s/\$u7e\$/~/g 29 | 30 | # Fix . and _ 31 | s/\.\./::/g 32 | s/[^\.]\.[^\.]/./g 33 | s/([;:])_/\1/g 34 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linkerd-tcp" 3 | description = "A native TCP proxy for the linkerd service mesh" 4 | version = "0.1.1" 5 | authors = [ 6 | "Oliver Gould ", 7 | "Steve Jenson ", 8 | ] 9 | homepage = "https://linkerd.io" 10 | readme = "README.md" 11 | license = "Apache-2.0" 12 | 13 | [[bin]] 14 | name = "linkerd-tcp" 15 | doc = false 16 | 17 | [dependencies] 18 | bytes = "0.4" 19 | clap = "2.24" 20 | futures = "0.1" 21 | hyper = "0.11.15" 22 | log = "0.3" 23 | ordermap = "0.2" 24 | pretty_env_logger = "0.1" 25 | rand = "0.3" 26 | rustls = { git = "https://github.com/briansmith/rustls", branch = "make_server_sni_public" } 27 | serde = "1.0" 28 | serde_derive = "1.0" 29 | serde_json = "1.0" 30 | serde_yaml = "0.7" 31 | # tacho = { path = "../tacho" } 32 | tacho = "0.4.1" 33 | tokio-core = "0.1" 34 | tokio-io = "0.1" 35 | tokio-service = "0.1" 36 | tokio-timer = "0.1" 37 | url = "1.4" 38 | -------------------------------------------------------------------------------- /namerd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | version="1.0.2" 6 | bin="target/namerd-${version}-exec" 7 | sha="338428a49cbe5f395c01a62e06b23fa492a7a9f89a510ae227b46c915b07569e" 8 | url="https://github.com/linkerd/linkerd/releases/download/${version}/namerd-${version}-exec" 9 | 10 | validbin() { 11 | checksum=$(openssl dgst -sha256 $bin | awk '{ print $2 }') 12 | [ "$checksum" = $sha ] 13 | } 14 | 15 | if [ -f "$bin" ] && ! validbin ; then 16 | echo "bad $bin" >&2 17 | mv "$bin" "${bin}.bad" 18 | fi 19 | 20 | if [ ! -f "$bin" ]; then 21 | echo "downloading $bin" >&2 22 | curl -L --silent --fail -o "$bin" "$url" 23 | chmod 755 "$bin" 24 | fi 25 | 26 | if ! validbin ; then 27 | echo "bad $bin. delete $bin and run $0 again." >&2 28 | exit 1 29 | fi 30 | 31 | mkdir -p ./tmp.discovery 32 | if [ ! -f ./tmp.discovery/default ]; then 33 | echo "127.1 9991" > ./tmp.discovery/default 34 | fi 35 | 36 | "$bin" -- - < /#/io.l5d.fs; 48 | 49 | interfaces: 50 | - kind: io.l5d.httpController 51 | EOF 52 | -------------------------------------------------------------------------------- /src/path.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] 4 | pub struct Path(String); 5 | impl Path { 6 | pub fn as_str(&self) -> &str { 7 | &self.0 8 | } 9 | 10 | pub fn is_empty(&self) -> bool { 11 | self.len() == 1 12 | } 13 | pub fn len(&self) -> usize { 14 | self.0.len() 15 | } 16 | 17 | pub fn starts_with(&self, other: &Path) -> bool { 18 | let &Path(ref other) = other; 19 | if self.0.len() > other.len() { 20 | self.0.starts_with(other) && 21 | (self.0.ends_with('/') || other[self.0.len()..].starts_with('/')) 22 | } else if other.len() == self.0.len() { 23 | self.0 == *other 24 | } else { 25 | false 26 | } 27 | } 28 | } 29 | impl fmt::Display for Path { 30 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 31 | f.write_str(&self.0) 32 | } 33 | } 34 | impl From for Path { 35 | fn from(s: String) -> Path { 36 | assert!(s.starts_with('/')); 37 | Path(s) 38 | } 39 | } 40 | impl<'a> From<&'a str> for Path { 41 | fn from(s: &'a str) -> Path { 42 | assert!(s.starts_with('/')); 43 | Path(s.into()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/connection/mod.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::net; 3 | use std::rc::Rc; 4 | 5 | pub mod ctx; 6 | mod duplex; 7 | mod half_duplex; 8 | pub mod secure; 9 | pub mod socket; 10 | 11 | pub use self::ctx::Ctx; 12 | pub use self::duplex::Duplex; 13 | pub use self::socket::Socket; 14 | 15 | /// A src or dst connection with server or client context. 16 | pub struct Connection { 17 | /// Record infomation about the connection to be used by a load balance and/or to be 18 | /// exported as serve/client-scoped metrics. 19 | pub ctx: C, 20 | 21 | /// Does networked I/O, possibly with TLS. 22 | pub socket: Socket, 23 | } 24 | 25 | impl Connection { 26 | pub fn new(socket: Socket, ctx: C) -> Connection { 27 | Connection { socket, ctx } 28 | } 29 | 30 | pub fn peer_addr(&self) -> net::SocketAddr { 31 | self.socket.peer_addr() 32 | } 33 | 34 | pub fn local_addr(&self) -> net::SocketAddr { 35 | self.socket.local_addr() 36 | } 37 | 38 | /// Transfers data between connections bidirectionally. 39 | pub fn into_duplex( 40 | self, 41 | other: Connection, 42 | buf: Rc>>, 43 | ) -> Duplex { 44 | duplex::new(self, other, buf) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/resolver/config.rs: -------------------------------------------------------------------------------- 1 | use super::namerd::Namerd; 2 | use std::time::Duration; 3 | use tacho; 4 | use url::{self, Url}; 5 | 6 | pub type Result = ::std::result::Result; 7 | 8 | #[derive(Debug)] 9 | pub enum Error { 10 | InvalidPeriod(u64), 11 | InvalidBaseUrl(String, url::ParseError), 12 | } 13 | 14 | #[derive(Clone, Debug, Serialize, Deserialize)] 15 | #[serde(deny_unknown_fields, rename_all = "camelCase")] 16 | pub struct NamerdConfig { 17 | pub base_url: String, 18 | pub period_secs: u64, 19 | pub namespace: String, 20 | } 21 | 22 | impl NamerdConfig { 23 | pub fn into_namerd(self, metrics: &tacho::Scope) -> Result { 24 | if self.period_secs == 0 { 25 | return Err(Error::InvalidPeriod(self.period_secs)); 26 | } 27 | let period = Duration::from_secs(self.period_secs); 28 | 29 | if let Err(e) = Url::parse(&self.base_url) { 30 | return Err(Error::InvalidBaseUrl(self.base_url, e)); 31 | } 32 | 33 | let metrics = metrics.clone().prefixed("resolver").labeled( 34 | "namespace".into(), 35 | self.namespace.clone(), 36 | ); 37 | let namerd = Namerd::new(self.base_url, period, self.namespace, metrics); 38 | Ok(namerd) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/balancer/factory.rs: -------------------------------------------------------------------------------- 1 | use super::Balancer; 2 | use super::super::Path; 3 | use super::super::connector::{ConfigError, ConnectorFactory}; 4 | use super::super::resolver::Resolve; 5 | use std::cell::RefCell; 6 | use std::rc::Rc; 7 | use tacho; 8 | use tokio_core::reactor::Handle; 9 | use tokio_timer::Timer; 10 | 11 | #[derive(Clone)] 12 | pub struct BalancerFactory { 13 | connector_factory: Rc>, 14 | metrics: tacho::Scope, 15 | } 16 | 17 | impl BalancerFactory { 18 | pub fn new(cf: ConnectorFactory, metrics: &tacho::Scope) -> BalancerFactory { 19 | BalancerFactory { 20 | connector_factory: Rc::new(RefCell::new(cf)), 21 | metrics: metrics.clone(), 22 | } 23 | } 24 | 25 | pub fn mk_balancer( 26 | &self, 27 | reactor: &Handle, 28 | timer: &Timer, 29 | dst_name: &Path, 30 | resolve: Resolve, 31 | ) -> Result { 32 | let connector = self.connector_factory.borrow().mk_connector(dst_name)?; 33 | let metrics = self.metrics.clone().labeled("dst", dst_name); 34 | Ok(super::new( 35 | reactor, 36 | timer, 37 | dst_name, 38 | connector, 39 | resolve, 40 | &metrics, 41 | )) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PROFILING.md: -------------------------------------------------------------------------------- 1 | Profiling linkerd-tcp 2 | ===================== 3 | 4 | Building FlameGraphs for linkerd-tcp under Linux 5 | ------------------------------------------------ 6 | Ensure that `perf` is installed 7 | ``` 8 | sudo apt-get install linux-tools-common linux-tools-generic linux-tools-`uname -r` 9 | ``` 10 | 11 | Clone a copy of Brendan Gregg's awesome `FlameGraph` repository 12 | ``` 13 | git clone --depth=1 https://github.com/brendangregg/FlameGraph 14 | ``` 15 | 16 | Run `linkerd-tcp` under `linux perf` [samplerate](#1-setting-a-useful-sample-rate) 17 | ``` 18 | RUST_BACKTRACE=full perf record -g -F 999 target/release/linkerd-tcp example.yml 19 | ``` 20 | 21 | Send it traffic through your favorite load tester. 22 | ``` 23 | slow_cooker_linux_amd64 -H 'default' -totalRequests 1000000 -qps 10 -concurrency 100 http://proxy-test-4e:7474/ 24 | ``` 25 | 26 | Once your load test is complete, use the linkerd-tcp admin port to shutdown 27 | the process. 28 | ``` 29 | curl -X POST http://localhost:9989/shutdown 30 | ``` 31 | 32 | Build your flamegraph 33 | ``` 34 | perf script | stackcollapse-perf.pl | rust-unmangle.sh | flamegraph.pl > linkerd-tcp-flame.svg 35 | ``` 36 | 37 | Copy `linkerd-tcp-flame.svg` to your local machine and dig in with a 38 | SVG viewer like [Gapplin](https://itunes.apple.com/us/app/gapplin/id768053424?mt=12) 39 | for the Mac. 40 | 41 | Footnotes 42 | --------- 43 | 44 | #### [1] Setting a Useful Sample Rate 45 | When setting a sample rate (`-F`), remember that the Nyquist theorem tells us 46 | that to see events which happen at a rate of N you'll need to sample with a 47 | rate of 2N. -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: linkerd/rustup-nightly:v2 6 | 7 | working_directory: ~/tacho 8 | steps: 9 | - run: 10 | name: install rust 11 | command: /install-rust.sh 12 | 13 | - restore_cache: 14 | key: rust-{{ checksum "/rust/update-hashes/nightly-x86_64-unknown-linux-gnu" }}.0 15 | 16 | # XXX not today, satan... (fails on nighty currently) 17 | # - run: 18 | # name: install clippy 19 | # command: if [ ! -x /cargo/bin/cargo-clippy ]; then cargo install clippy ; fi 20 | 21 | # TODO when rustfmt is more stable. 22 | # - run: 23 | # name: install rustfmt 24 | # command: if [ ! -x /cargo/bin/cargo-fmt ]; then cargo install rustfmt ; fi 25 | 26 | - save_cache: 27 | key: rust-{{ checksum "/rust/update-hashes/nightly-x86_64-unknown-linux-gnu" }}.0 28 | paths: 29 | - /rust 30 | - /cargo 31 | 32 | - checkout 33 | 34 | - run: 35 | name: update 36 | command: cargo update 37 | 38 | - restore_cache: 39 | key: cargo.lock-{{ checksum "Cargo.lock" }} 40 | 41 | - run: 42 | name: test 43 | command: cargo test 44 | 45 | - save_cache: 46 | key: cargo.lock-{{ checksum "Cargo.lock" }} 47 | paths: 48 | - target 49 | 50 | # XXX not today, satan... (fails on nighty currently) 51 | # - run: 52 | # name: lint 53 | # command: cargo clippy 54 | 55 | # TODO when rustfmt is more stable. 56 | # - run: 57 | # name: format 58 | # command: cargo fmt -- --write-mode=diff 59 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## In the next release 2 | 3 | ## 0.1.1 4 | 5 | linkerd-tcp 0.1.1 focuses on improving TLS support, and on updating linkerd-tcp's 6 | dependencies. 7 | 8 | * Trace negotiated SNI & ALPN at the end of the server handshake. 9 | * Improve handling and reporting of TLS configuration errors. 10 | * Update to latest versions of dependencies. 11 | * Switch the official Rust base docker image. 12 | * Remove use of deprecated downstream API members. 13 | * Remove dependency on nightly Rust to build linkerd-tcp. 14 | 15 | ## 0.1.0 16 | linkerd-tcp 0.1.0 constitutes a major rewrite. 17 | 18 | Previously, linkerd-tcp did not properly utilize tokio's task model, which lead 19 | to a number of performance and correctness problems. Furthermore, linkerd-tcp's 20 | configuration interface was substantially different from linkerd's, which 21 | caused some confusion. 22 | 23 | * Significant performance and correctness improvements. 24 | * Add support for connection and stream timeouts. 25 | * Add several additional metrics. 26 | * **Breaking change**: Change configuration file syntax to be structured like a Linkerd router: 27 | * Rename `proxies` key to `routers` 28 | * Change `servers` to take separate `ip` and `port` keys, rather than `addr`. 29 | * Change `namerd` section in server configuration to Linkerd-style `interpreter` 30 | section on a router configuration. The `path` key is now specified per-server as `dstPath`. 31 | 32 | ## 0.0.3 33 | 34 | * New admin endpoint! POST to `/shutdown` to tell the process to exit. 35 | * Fix connection leak when one peer disconnected. 36 | * Fix stat reporting issue for the rx bytes metric. 37 | * Improve error message when config file cannot be read. 38 | * Update examples to bind to all available sockets by default. 39 | * Add guidelines for contributing to the linkerd-tcp repo. 40 | * Add script for building a slimmer docker image. 41 | * **Breaking change**: Convert namerd config to use `url` field instead of `addr`. 42 | * **Breaking change**: Convert server config attributes from camel to snake case. 43 | 44 | ## 0.0.2 45 | 46 | * Expose a configurable admin server with Prometheus metrics. 47 | 48 | ## 0.0.1 49 | 50 | Introducing linkerd-tcp. :balloon: 51 | 52 | * Run a TCP load balancer for the [Linkerd][https://linkerd.io] service mesh. 53 | * Support TLS and integrate with Namerd for service discovery. 54 | * Read a YAML or JSON configuration file at startup. -------------------------------------------------------------------------------- /src/admin.rs: -------------------------------------------------------------------------------- 1 | use super::app::Closer; 2 | use futures::{Future, future}; 3 | use hyper::{self, Get, Post, StatusCode}; 4 | use hyper::header::ContentLength; 5 | use hyper::server::{Service, Request, Response}; 6 | use std::boxed::Box; 7 | use std::cell::RefCell; 8 | use std::process; 9 | use std::rc::Rc; 10 | use std::time::{Duration, Instant}; 11 | use tokio_core::reactor::Handle; 12 | use tokio_timer::Timer; 13 | 14 | #[derive(Clone)] 15 | pub struct Admin { 16 | prometheus: Rc>, 17 | closer: Rc>>, 18 | grace: Duration, 19 | reactor: Handle, 20 | timer: Timer, 21 | } 22 | 23 | type RspFuture = Box>; 24 | 25 | impl Admin { 26 | pub fn new( 27 | prometheus: Rc>, 28 | closer: Closer, 29 | grace: Duration, 30 | reactor: Handle, 31 | timer: Timer, 32 | ) -> Admin { 33 | Admin { 34 | closer: Rc::new(RefCell::new(Some(closer))), 35 | prometheus, 36 | grace, 37 | reactor, 38 | timer, 39 | } 40 | } 41 | 42 | fn metrics(&self) -> RspFuture { 43 | let body = self.prometheus.borrow(); 44 | let rsp = Response::new() 45 | .with_status(StatusCode::Ok) 46 | .with_header(ContentLength(body.len() as u64)) 47 | .with_body(body.clone()); 48 | Box::new(future::ok(rsp)) 49 | } 50 | 51 | /// Tell the serving thread to stop what it's doing. 52 | // TODO offer a `force` param? 53 | fn shutdown(&self) -> RspFuture { 54 | let mut closer = self.closer.borrow_mut(); 55 | if let Some(c) = closer.take() { 56 | info!("shutting down via admin API"); 57 | let _ = c.send(Instant::now() + self.grace); 58 | } 59 | let rsp = Response::new().with_status(StatusCode::Ok); 60 | Box::new(future::ok(rsp)) 61 | } 62 | 63 | fn abort(&self) -> RspFuture { 64 | process::exit(1); 65 | } 66 | 67 | fn not_found(&self) -> RspFuture { 68 | let rsp = Response::new().with_status(StatusCode::NotFound); 69 | Box::new(future::ok(rsp)) 70 | } 71 | } 72 | 73 | impl Service for Admin { 74 | type Request = Request; 75 | type Response = Response; 76 | type Error = hyper::Error; 77 | type Future = RspFuture; 78 | fn call(&self, req: Request) -> RspFuture { 79 | match (req.method(), req.path()) { 80 | (&Get, "/metrics") => self.metrics(), 81 | (&Post, "/shutdown") => self.shutdown(), 82 | (&Post, "/abort") => self.abort(), 83 | _ => self.not_found(), 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/resolver/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{WeightedAddr, Path}; 2 | use futures::{Future, Stream, Poll}; 3 | use futures::sync::mpsc; 4 | use tokio_core::reactor::Handle; 5 | use tokio_timer::{Timer, TimerError}; 6 | 7 | mod config; 8 | mod namerd; 9 | pub use self::config::{Error as ConfigError, NamerdConfig}; 10 | pub use self::namerd::{Namerd, Addrs}; 11 | 12 | #[derive(Debug)] 13 | pub enum Error { 14 | Hyper(::hyper::Error), 15 | UnexpectedStatus(::hyper::StatusCode), 16 | Serde(::serde_json::Error), 17 | Timer(TimerError), 18 | Rejected, 19 | NotBound, 20 | } 21 | 22 | impl From> for Error { 23 | fn from(_e: mpsc::SendError) -> Error { 24 | Error::Rejected 25 | } 26 | } 27 | 28 | pub type Result = ::std::result::Result; 29 | 30 | /// Creates a multithreaded resolver. 31 | /// 32 | /// The `Resolver` side is a client of the `Executor`. Namerd work is performed on 33 | /// whatever thread the executor is spawned on. 34 | pub fn new(namerd: Namerd) -> (Resolver, Executor) { 35 | let (tx, rx) = mpsc::unbounded(); 36 | let res = Resolver { requests: tx }; 37 | let exe = Executor { 38 | requests: rx, 39 | namerd: namerd, 40 | }; 41 | (res, exe) 42 | } 43 | 44 | /// Requests resolutions from an `Executor`. 45 | /// 46 | /// Resolution requests are sent on an channel along with a response channel. The executor 47 | /// writes to the response channel as results are ready. 48 | #[derive(Clone)] 49 | pub struct Resolver { 50 | requests: mpsc::UnboundedSender<(Path, mpsc::UnboundedSender>>)>, 51 | } 52 | 53 | impl Resolver { 54 | pub fn resolve(&mut self, path: Path) -> Resolve { 55 | let addrs = { 56 | let reqs = &self.requests; 57 | let (tx, rx) = mpsc::unbounded(); 58 | reqs.unbounded_send((path, tx)).expect( 59 | "failed to send resolution request", 60 | ); 61 | rx 62 | }; 63 | Resolve(addrs) 64 | } 65 | } 66 | 67 | pub struct Resolve(mpsc::UnboundedReceiver>>); 68 | 69 | impl Stream for Resolve { 70 | type Item = Result>; 71 | type Error = (); 72 | fn poll(&mut self) -> Poll, Self::Error> { 73 | self.0.poll() 74 | } 75 | } 76 | 77 | /// Serves resolutions from `Resolver`s. 78 | pub struct Executor { 79 | requests: mpsc::UnboundedReceiver<(Path, mpsc::UnboundedSender>>)>, 80 | namerd: Namerd, 81 | } 82 | 83 | impl Executor { 84 | pub fn execute(self, handle: &Handle, timer: &Timer) -> Execute { 85 | let handle = handle.clone(); 86 | let namerd = self.namerd.with_client(&handle, timer); 87 | let f = self.requests.for_each(move |(path, rsp_tx)| { 88 | // Stream namerd resolutions to the response channel. 89 | let resolve = namerd.resolve(path.as_str()); 90 | let respond = resolve.forward(rsp_tx).map_err(|_| {}).map(|_| {}); 91 | // Do all of this work in another task so that we can receive 92 | // additional requests. 93 | handle.spawn(respond); 94 | Ok(()) 95 | }); 96 | Execute(Box::new(f)) 97 | } 98 | } 99 | 100 | // A stream of name resolutions. 101 | pub struct Execute(Box>); 102 | impl Future for Execute { 103 | type Item = (); 104 | type Error = (); 105 | fn poll(&mut self) -> Poll { 106 | self.0.poll() 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/connection/duplex.rs: -------------------------------------------------------------------------------- 1 | use super::Connection; 2 | use super::Ctx; 3 | use super::half_duplex::{self, HalfDuplex}; 4 | use futures::{Async, Future, Poll}; 5 | use std::cell::RefCell; 6 | use std::io; 7 | use std::net; 8 | use std::rc::Rc; 9 | 10 | pub struct Summary { 11 | pub to_dst_bytes: usize, 12 | pub to_src_bytes: usize, 13 | } 14 | 15 | pub fn new(src: Connection, dst: Connection, buf: Rc>>) -> Duplex 16 | where 17 | S: Ctx, 18 | D: Ctx, 19 | { 20 | let src_addr = src.peer_addr(); 21 | let dst_addr = dst.peer_addr(); 22 | let src = Rc::new(RefCell::new(src)); 23 | let dst = Rc::new(RefCell::new(dst)); 24 | Duplex { 25 | dst_addr, 26 | to_dst: Some(half_duplex::new(src.clone(), dst.clone(), buf.clone())), 27 | to_dst_bytes: 0, 28 | 29 | src_addr, 30 | to_src: Some(half_duplex::new(dst.clone(), src.clone(), buf)), 31 | to_src_bytes: 0, 32 | } 33 | } 34 | 35 | /// Joins src and dst transfers into a single Future. 36 | pub struct Duplex { 37 | dst_addr: net::SocketAddr, 38 | src_addr: net::SocketAddr, 39 | to_dst: Option>, 40 | to_src: Option>, 41 | to_dst_bytes: usize, 42 | to_src_bytes: usize, 43 | } 44 | 45 | impl Future for Duplex { 46 | type Item = Summary; 47 | type Error = io::Error; 48 | fn poll(&mut self) -> Poll { 49 | if let Some(mut to_dst) = self.to_dst.take() { 50 | trace!( 51 | "polling dstward from {} to {}", 52 | self.src_addr, 53 | self.dst_addr 54 | ); 55 | match to_dst.poll()? { 56 | Async::Ready(sz) => { 57 | trace!( 58 | "dstward complete from {} to {}", 59 | self.src_addr, 60 | self.dst_addr 61 | ); 62 | self.to_dst_bytes = sz; 63 | } 64 | Async::NotReady => { 65 | trace!("dstward not ready"); 66 | self.to_dst = Some(to_dst); 67 | } 68 | } 69 | } 70 | 71 | if let Some(mut to_src) = self.to_src.take() { 72 | trace!( 73 | "polling srcward from {} to {}", 74 | self.dst_addr, 75 | self.src_addr 76 | ); 77 | match to_src.poll()? { 78 | Async::Ready(sz) => { 79 | trace!( 80 | "srcward complete from {} to {}", 81 | self.dst_addr, 82 | self.src_addr 83 | ); 84 | self.to_src_bytes = sz; 85 | } 86 | Async::NotReady => { 87 | trace!("srcward not ready"); 88 | self.to_src = Some(to_src); 89 | } 90 | } 91 | } 92 | 93 | if self.to_dst.is_none() && self.to_src.is_none() { 94 | trace!("complete"); 95 | // self.tx_bytes_stat.add(self.tx_bytes); 96 | // self.rx_bytes_stat.add(self.rx_bytes) 97 | let summary = Summary { 98 | to_dst_bytes: self.to_dst_bytes, 99 | to_src_bytes: self.to_src_bytes, 100 | }; 101 | Ok(Async::Ready(summary)) 102 | } else { 103 | trace!("not ready"); 104 | Ok(Async::NotReady) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/router.rs: -------------------------------------------------------------------------------- 1 | use super::{Path, connector}; 2 | use super::balancer::{Balancer, BalancerFactory}; 3 | use super::resolver::Resolver; 4 | use futures::{Future, Poll, Async}; 5 | use std::cell::RefCell; 6 | use std::collections::HashMap; 7 | use std::io; 8 | use std::rc::Rc; 9 | use tacho::{self, Timing}; 10 | use tokio_core::reactor::Handle; 11 | use tokio_timer::Timer; 12 | 13 | static ROUTE_CREATE_KEY: &'static str = "route_create"; 14 | static ROUTE_ERROR_KEY: &'static str = "route_error"; 15 | static ROUTE_FOUND_KEY: &'static str = "route_found"; 16 | static ROUTE_TIME_US_KEY: &'static str = "route_time_us"; 17 | 18 | pub fn new(resolver: Resolver, factory: BalancerFactory, metrics: &tacho::Scope) -> Router { 19 | let inner = InnerRouter { 20 | resolver, 21 | factory, 22 | routes: HashMap::default(), 23 | route_create: metrics.counter(ROUTE_CREATE_KEY), 24 | route_error: metrics.counter(ROUTE_ERROR_KEY), 25 | route_found: metrics.counter(ROUTE_FOUND_KEY), 26 | route_time_us: metrics.stat(ROUTE_TIME_US_KEY), 27 | }; 28 | Router(Rc::new(RefCell::new(inner))) 29 | } 30 | 31 | 32 | /// Produces a `Balancer` for a 33 | /// 34 | /// The router maintains an internal cache of routes, by destination name. 35 | #[derive(Clone)] 36 | pub struct Router(Rc>); 37 | 38 | impl Router { 39 | /// Obtains a balancer for an inbound connection. 40 | pub fn route(&self, dst: &Path, rct: &Handle, tim: &Timer) -> Route { 41 | self.0.borrow_mut().route(dst, rct, tim) 42 | } 43 | } 44 | 45 | struct InnerRouter { 46 | routes: HashMap, 47 | resolver: Resolver, 48 | factory: BalancerFactory, 49 | route_create: tacho::Counter, 50 | route_error: tacho::Counter, 51 | route_found: tacho::Counter, 52 | route_time_us: tacho::Stat, 53 | } 54 | 55 | impl InnerRouter { 56 | fn route(&mut self, dst: &Path, reactor: &Handle, timer: &Timer) -> Route { 57 | let t = tacho::Timing::start(); 58 | let r = self.do_route(dst, reactor, timer); 59 | self.route_time_us.add(t.elapsed_us()); 60 | Route(Some(r)) 61 | } 62 | 63 | fn do_route( 64 | &mut self, 65 | dst: &Path, 66 | reactor: &Handle, 67 | timer: &Timer, 68 | ) -> Result { 69 | // Try to get a balancer from the cache. 70 | if let Some(route) = self.routes.get(dst) { 71 | self.route_found.incr(1); 72 | return Ok(route.clone()); 73 | } 74 | 75 | let resolve = self.resolver.resolve(dst.clone()); 76 | match self.factory.mk_balancer(reactor, timer, dst, resolve) { 77 | Err(e) => { 78 | self.route_error.incr(1); 79 | Err(e) 80 | } 81 | Ok(balancer) => { 82 | self.route_create.incr(1); 83 | self.routes.insert(dst.clone(), balancer.clone()); 84 | Ok(balancer) 85 | } 86 | } 87 | } 88 | } 89 | 90 | /// Materializes a `Balancer`. 91 | /// 92 | /// 93 | #[derive(Clone)] 94 | pub struct Route(Option>); 95 | impl Future for Route { 96 | type Item = Balancer; 97 | type Error = io::Error; 98 | 99 | fn poll(&mut self) -> Poll { 100 | match self.0.take().expect( 101 | "route must not be polled more than once", 102 | ) { 103 | Err(e) => Err(io::Error::new( 104 | io::ErrorKind::Other, 105 | format!("config error: {:?}", e), 106 | )), 107 | Ok(selector) => Ok(Async::Ready(selector)), 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/server/config.rs: -------------------------------------------------------------------------------- 1 | use super::{Unbound, sni}; 2 | use super::super::router::Router; 3 | use rustls; 4 | use std::cell::RefCell; 5 | use std::collections::HashMap; 6 | use std::net; 7 | use std::rc::Rc; 8 | use std::sync::Arc; 9 | use std::time::Duration; 10 | use tacho; 11 | 12 | pub type Result = ::std::result::Result; 13 | 14 | #[derive(Debug)] 15 | pub enum Error { 16 | NoDstName, 17 | Sni(sni::Error), 18 | } 19 | 20 | #[derive(Clone, Debug, Serialize, Deserialize)] 21 | #[serde(deny_unknown_fields, rename_all = "camelCase")] 22 | pub struct ServerConfig { 23 | port: u16, 24 | ip: Option, 25 | dst_name: Option, 26 | tls: Option, 27 | connect_timeout_ms: Option, 28 | connection_lifetime_secs: Option, 29 | max_concurrency: Option, 30 | // TODO idle time 31 | } 32 | 33 | impl ServerConfig { 34 | pub fn mk_server( 35 | &self, 36 | router: Router, 37 | buf: Rc>>, 38 | metrics: &tacho::Scope, 39 | ) -> Result { 40 | match *self { 41 | ServerConfig { 42 | port, 43 | ref ip, 44 | ref dst_name, 45 | ref tls, 46 | ref connect_timeout_ms, 47 | ref connection_lifetime_secs, 48 | ref max_concurrency, 49 | } => { 50 | if dst_name.is_none() { 51 | return Err(Error::NoDstName); 52 | } 53 | let dst_name = dst_name.as_ref().unwrap().clone(); 54 | let ip = ip.unwrap_or_else(|| net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1))); 55 | let addr = net::SocketAddr::new(ip, port); 56 | let tls = match tls.as_ref() { 57 | None => None, 58 | Some(&TlsServerConfig { 59 | ref alpn_protocols, 60 | ref default_identity, 61 | ref identities, 62 | }) => { 63 | let mut tls = rustls::ServerConfig::new(); 64 | if let Some(protos) = alpn_protocols.as_ref() { 65 | tls.set_protocols(protos); 66 | } 67 | let sni = sni::new(identities, default_identity).map_err(Error::Sni)?; 68 | tls.cert_resolver = Arc::new(sni); 69 | Some(super::UnboundTls { config: Arc::new(tls) }) 70 | } 71 | }; 72 | let timeout = connect_timeout_ms.map(Duration::from_millis); 73 | let lifetime = connection_lifetime_secs.map(Duration::from_secs); 74 | let max_concurrency = max_concurrency.unwrap_or(super::DEFAULT_MAX_CONCURRENCY); 75 | Ok(super::unbound( 76 | addr, 77 | dst_name.into(), 78 | router, 79 | buf, 80 | tls, 81 | timeout, 82 | lifetime, 83 | max_concurrency, 84 | metrics, 85 | )) 86 | } 87 | } 88 | } 89 | } 90 | 91 | // TODO support cypher suites 92 | // TODO support client validation 93 | // TODO supoprt persistence? 94 | #[derive(Clone, Debug, Serialize, Deserialize)] 95 | #[serde(deny_unknown_fields, rename_all = "camelCase")] 96 | pub struct TlsServerConfig { 97 | pub alpn_protocols: Option>, 98 | pub default_identity: Option, 99 | pub identities: Option>, 100 | } 101 | 102 | #[derive(Clone, Debug, Serialize, Deserialize)] 103 | #[serde(deny_unknown_fields, rename_all = "camelCase")] 104 | pub struct TlsServerIdentityConfig { 105 | pub certs: Vec, 106 | pub private_key: String, 107 | } 108 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | extern crate futures; 4 | extern crate linkerd_tcp; 5 | #[macro_use] 6 | extern crate log; 7 | extern crate pretty_env_logger; 8 | extern crate tokio_core; 9 | extern crate tokio_timer; 10 | 11 | use clap::{Arg, App as ClapApp}; 12 | use linkerd_tcp::app::{self, AppConfig, App, AdminRunner, RouterSpawner}; 13 | use std::collections::VecDeque; 14 | use std::fs; 15 | use std::io::Read; 16 | use std::thread; 17 | use tokio_core::reactor::{Core, Handle}; 18 | use tokio_timer::Timer; 19 | 20 | static CONFIG_PATH_ARG: &'static str = "PATH"; 21 | 22 | /// Runs linkerd-tcp. 23 | /// 24 | /// Accepts a configuration file 25 | fn main() { 26 | // Configure the logger from the RUST_LOG environment variable. 27 | drop(pretty_env_logger::init()); 28 | 29 | // Load command-line options. 30 | let opts = ClapApp::new(crate_name!()) 31 | .version(crate_version!()) 32 | .about(crate_description!()) 33 | .arg( 34 | Arg::with_name(CONFIG_PATH_ARG) 35 | .required(true) 36 | .index(1) 37 | .help("Config file path."), 38 | ) 39 | .get_matches(); 40 | 41 | // Parse configuration file. 42 | let config: AppConfig = { 43 | let path = opts.value_of(CONFIG_PATH_ARG).unwrap(); 44 | let mut txt = String::new(); 45 | let res = match path { 46 | "-" => ::std::io::stdin().read_to_string(&mut txt), 47 | path => fs::File::open(path).and_then(|mut f| f.read_to_string(&mut txt)), 48 | }; 49 | match res { 50 | Err(e) => panic!("error reading configuration from {}: {}", path, e), 51 | Ok(_) => txt.parse().expect("failed to parse configuration"), 52 | } 53 | }; 54 | debug!("parsed config: {:?}", config); 55 | 56 | // Process the configuration, splitting it into two threads. These threads are 57 | // connected by synchronization primitives as needed, but no work is being done yet. 58 | // Next, we'll attach each of these to a reactor in an independent thread, driving 59 | // both admin and serving work. 60 | let App { routers, admin } = config.into_app().expect("failed to load configuration"); 61 | debug!("loaded app"); 62 | 63 | let (closer, closed) = app::closer(); 64 | 65 | // A single timer for the whole process. The default hashwheel timer has a 66 | // granularity of 100ms. 67 | let timer = Timer::default(); 68 | 69 | // Create a background admin thread that runs an admin server and executes executes 70 | // namerd resolutions 71 | let admin_thread = spawn_admin(admin, closer, &timer); 72 | run_routers(routers, closed, &timer); 73 | admin_thread.join().expect("failed to join admin thread"); 74 | debug!("stopped") 75 | } 76 | 77 | fn spawn_admin(admin: AdminRunner, closer: app::Closer, timer: &Timer) -> thread::JoinHandle<()> { 78 | let timer = timer.clone(); 79 | thread::Builder::new() 80 | .name("admin".into()) 81 | .spawn(move || { 82 | debug!("running admin server"); 83 | let mut core = Core::new().expect("failed to initialize admin reactor"); 84 | admin.run(closer, &mut core, &timer).expect( 85 | "failed to run the admin server", 86 | ); 87 | }) 88 | .expect("failed to spawn admin thread") 89 | } 90 | 91 | fn run_routers(routers: VecDeque, closed: app::Closed, timer: &Timer) { 92 | // Schedule all routers on the main thread. 93 | let mut core = Core::new().expect("failed to initialize server reactor"); 94 | spawn_routers(routers, &core.handle(), timer); 95 | 96 | // Run until the admin thread closes the application. 97 | debug!("running until admin server closes"); 98 | core.run(closed).expect("failed to run"); 99 | } 100 | 101 | fn spawn_routers(mut routers: VecDeque, reactor: &Handle, timer: &Timer) { 102 | while let Some(r) = routers.pop_front() { 103 | debug!("spawning router"); 104 | r.spawn(reactor, timer).expect("failed to spawn router"); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/server/sni.rs: -------------------------------------------------------------------------------- 1 | use super::config::TlsServerIdentityConfig; 2 | use rustls::{Certificate, ResolvesServerCert, SignatureScheme, sign}; 3 | use rustls::internal::pemfile; 4 | use std::collections::HashMap; 5 | use std::fs::File; 6 | use std::io; 7 | use std::sync::Arc; 8 | 9 | pub fn new( 10 | identities: &Option>, 11 | default: &Option, 12 | ) -> Result { 13 | let n_identities = identities.as_ref().map(|ids| ids.len()).unwrap_or(0); 14 | let default = match default { 15 | &Some(ref c) => Some(ServerIdentity::load(c)?), 16 | &None if n_identities > 0 => { 17 | return Err(Error::NoIdentities) 18 | }, 19 | &None => None, 20 | }; 21 | let sni = Sni { 22 | default, 23 | identities: { 24 | let mut ids = HashMap::with_capacity(n_identities); 25 | if let Some(identities) = identities.as_ref() { 26 | for (k, c) in identities { 27 | let k: String = (*k).clone(); 28 | let v = ServerIdentity::load(c)?; 29 | ids.insert(k, v); 30 | } 31 | } 32 | Arc::new(ids) 33 | }, 34 | }; 35 | Ok(sni) 36 | } 37 | 38 | #[derive(Debug)] 39 | pub enum Error { 40 | NoIdentities, 41 | FailedToOpenCertificateFile(String, io::Error), 42 | FailedToReadCertificateFile(String), 43 | FailedToOpenPrivateKeyFile(String, io::Error), 44 | FailedToReadPrivateKeyFile(String), 45 | WrongNumberOfKeysInPrivateKeyFile(String, usize), 46 | FailedToConstructPrivateKey(String), 47 | } 48 | 49 | pub struct Sni { 50 | default: Option, 51 | identities: Arc>, 52 | } 53 | 54 | impl ResolvesServerCert for Sni { 55 | fn resolve( 56 | &self, 57 | server_name: Option<&str>, 58 | _sigschemes: &[SignatureScheme], 59 | ) -> Option { 60 | debug!("finding cert resolver for {:?}", server_name); 61 | server_name 62 | .and_then(|n| { 63 | debug!("found match for {}", n); 64 | self.identities.get(n) 65 | }) 66 | .or_else(|| { 67 | debug!("reverting to default"); 68 | self.default.as_ref() 69 | }) 70 | .map(|id| id.key.clone()) 71 | } 72 | } 73 | 74 | struct ServerIdentity { 75 | key: sign::CertifiedKey, 76 | } 77 | 78 | impl ServerIdentity { 79 | fn load(c: &TlsServerIdentityConfig) -> Result { 80 | let mut certs = vec![]; 81 | for p in &c.certs { 82 | certs.append(&mut load_certs(p)?); 83 | } 84 | let key = load_private_key(&c.private_key)?; 85 | Ok(ServerIdentity { 86 | key: sign::CertifiedKey::new(certs, Arc::new(key)), 87 | }) 88 | } 89 | } 90 | 91 | // from rustls example 92 | fn load_certs(cert_file_path: &String) -> Result, Error> { 93 | let file = File::open(&cert_file_path) 94 | .map_err(|e| Error::FailedToOpenCertificateFile(cert_file_path.clone(), e))?; 95 | let mut r = io::BufReader::new(file); 96 | pemfile::certs(&mut r) 97 | .map_err(|()| Error::FailedToReadCertificateFile(cert_file_path.clone())) 98 | } 99 | 100 | // from rustls example 101 | fn load_private_key(key_file_path: &String) 102 | -> Result, Error> { 103 | let file = File::open(&key_file_path) 104 | .map_err(|e| Error::FailedToOpenPrivateKeyFile(key_file_path.clone(), e))?; 105 | let mut r = io::BufReader::new(file); 106 | let keys = pemfile::rsa_private_keys(&mut r) 107 | .map_err(|()| Error::FailedToReadPrivateKeyFile(key_file_path.clone()))?; 108 | if keys.len() != 1 { 109 | return Err(Error::WrongNumberOfKeysInPrivateKeyFile(key_file_path.clone(), keys.len())); 110 | } 111 | let key = sign::RSASigningKey::new(&keys[0]) 112 | .map_err(|()| Error::FailedToConstructPrivateKey(key_file_path.clone()))?; 113 | Ok(Box::new(key)) 114 | } 115 | -------------------------------------------------------------------------------- /src/balancer/endpoint.rs: -------------------------------------------------------------------------------- 1 | use super::super::connection::{Connection as _Connection, ctx}; 2 | use super::super::connector; 3 | use futures::{Future, Poll}; 4 | use std::{io, net}; 5 | use std::cell::{Ref, RefCell}; 6 | use std::rc::Rc; 7 | use std::time::Instant; 8 | use tacho; 9 | 10 | pub type Connection = _Connection; 11 | 12 | pub fn new(peer_addr: net::SocketAddr, weight: f64) -> Endpoint { 13 | Endpoint { 14 | peer_addr, 15 | weight, 16 | state: Rc::new(RefCell::new(State::default())), 17 | } 18 | } 19 | 20 | #[derive(Default)] 21 | pub struct State { 22 | pub pending_conns: usize, 23 | pub open_conns: usize, 24 | pub consecutive_failures: usize, 25 | pub rx_bytes: usize, 26 | pub tx_bytes: usize, 27 | } 28 | 29 | impl State { 30 | pub fn load(&self) -> usize { 31 | self.open_conns + self.pending_conns 32 | } 33 | pub fn is_idle(&self) -> bool { 34 | self.open_conns == 0 35 | } 36 | } 37 | 38 | /// Represents a single concrete traffic destination 39 | pub struct Endpoint { 40 | peer_addr: net::SocketAddr, 41 | weight: f64, 42 | state: Rc>, 43 | } 44 | 45 | impl Endpoint { 46 | pub fn peer_addr(&self) -> net::SocketAddr { 47 | self.peer_addr 48 | } 49 | 50 | pub fn state(&self) -> Ref { 51 | self.state.borrow() 52 | } 53 | 54 | // TODO we should be able to use throughput/bandwidth as well. 55 | pub fn load(&self) -> usize { 56 | self.state.borrow().load() 57 | } 58 | 59 | pub fn set_weight(&mut self, w: f64) { 60 | assert!(0.0 <= w && w <= 1.0); 61 | self.weight = w; 62 | } 63 | 64 | pub fn weight(&self) -> f64 { 65 | self.weight 66 | } 67 | 68 | pub fn connect(&self, sock: connector::Connecting, duration: &tacho::Timer) -> Connecting { 69 | let conn = { 70 | let peer_addr = self.peer_addr; 71 | let state = self.state.clone(); 72 | let duration = duration.clone(); 73 | debug!("{}: connecting", peer_addr); 74 | sock.then(move |res| match res { 75 | Err(e) => { 76 | error!("{}: connection failed: {}", peer_addr, e); 77 | let mut s = state.borrow_mut(); 78 | s.consecutive_failures += 1; 79 | s.pending_conns -= 1; 80 | Err(e) 81 | } 82 | Ok(sock) => { 83 | debug!("{}: connected", peer_addr); 84 | { 85 | let mut s = state.borrow_mut(); 86 | s.consecutive_failures = 0; 87 | s.pending_conns -= 1; 88 | s.open_conns += 1; 89 | } 90 | let ctx = Ctx { 91 | state, 92 | duration, 93 | start: Instant::now(), 94 | }; 95 | Ok(Connection::new(sock, ctx)) 96 | } 97 | }) 98 | }; 99 | 100 | let mut state = self.state.borrow_mut(); 101 | state.pending_conns += 1; 102 | Connecting(Box::new(conn)) 103 | } 104 | 105 | pub fn is_idle(&self) -> bool { 106 | self.state.borrow().is_idle() 107 | } 108 | } 109 | 110 | pub struct Connecting(Box + 'static>); 111 | impl Future for Connecting { 112 | type Item = Connection; 113 | type Error = io::Error; 114 | fn poll(&mut self) -> Poll { 115 | self.0.poll() 116 | } 117 | } 118 | 119 | pub struct Ctx { 120 | state: Rc>, 121 | duration: tacho::Timer, 122 | start: Instant, 123 | } 124 | impl ctx::Ctx for Ctx { 125 | fn read(&mut self, sz: usize) { 126 | let mut state = self.state.borrow_mut(); 127 | state.rx_bytes += sz; 128 | } 129 | 130 | fn wrote(&mut self, sz: usize) { 131 | let mut state = self.state.borrow_mut(); 132 | state.tx_bytes += sz; 133 | } 134 | } 135 | impl Drop for Ctx { 136 | fn drop(&mut self) { 137 | let mut state = self.state.borrow_mut(); 138 | state.open_conns -= 1; 139 | self.duration.record_since(self.start) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /router.md: -------------------------------------------------------------------------------- 1 | # Rust Stream Balancer Design 2 | 3 | ## Prototype 4 | 5 | The initial implementation is basically a prototype. It proves the concept, but it has 6 | severe deficiencies that cause performance (and probably correctness) problems. 7 | Specifically, it implements its own polling... poorly. 8 | 9 | At startup, the configuration is parsed. For each **proxy**, the namerd and serving 10 | configurations are split and connectd by an async channel so that namerd updates are 11 | processed outside of the serving thread. All of the namerd watchers are collected to be 12 | run together with the admin server. Once all of the proxy configurations are processed, 13 | the application is run. 14 | 15 | The admin thread is started, initiating all namerd polling and starting the admin server. 16 | 17 | Simultaneously, all of the proxies are run in the main thread. For each of these, a 18 | **connector** is created to determine how all downstream connections are established for 19 | the proxy. A **balancer** is created with the connector and a stream of namerd updates. An 20 | **acceptor** is created for each listening interface, which manifests as a stream of 21 | connections, connections. The balancer is made shareable across servers by creating an 22 | async channel and each server's connections are streamed into a sink clone. The balancer 23 | is driven to process all of these connections. 24 | 25 | The balancer implements a Sink that manages _all_ I/O and connection management. Each 26 | time `Balancer::start_send` or `Balancer::poll_complete` is called, the following work is 27 | done: 28 | - _all_ conneciton streams are checked for I/O and data is transfered; 29 | - closed connections are reaped; 30 | - service discovery is checked for updates; 31 | - new connections are established; 32 | - stats are recorded; 33 | 34 | ## Lessons/Problems 35 | 36 | ### Inflexible 37 | 38 | This model doesn't really reflect that of linkerd. We have no mechanism to _route_ 39 | connections. All connections are simply forwarded. We cannot, for instance, route based on 40 | client credentials or SNI destination. 41 | 42 | ### Inefficient 43 | 44 | Currently, each balancer is effectively a scheduler, and a pretty poor one at that. I/O 45 | processing should be far more granular and we shouldn't update load balancer endpoints in 46 | the I/O path (unless absolutely necessary). 47 | 48 | ### Timeouts 49 | 50 | We need several types of timeouts that are not currently implemented: 51 | - Connection timeout: time from incoming connection to outbound established. 52 | - Stream lifetime: maximum time a stream may stay open. 53 | - Idle timeout: maximum time a connection may stay open without transmitting data. 54 | 55 | ## Proposal 56 | 57 | linkerd-tcp should become a _stream router_. In the same way that linkerd routes requests, 58 | linkerd-tcp should route connections. The following is a rough, evolving sketch of how 59 | linkerd-tcp should be refactored to accomodate this: 60 | 61 | The linkerd-tcp configuration should support one or more **routers**. Each router is 62 | configured with one or more **servers**. A server, which may or may not terminate TLS, 63 | produces a stream of incoming connections comprising an envelope--a source identity (an 64 | address, but maybe more) and a destination name--and a bidirectional data stream. The 65 | server may choose the destination by static configuration or as some function of the 66 | connection (e.g. client credentials, SNI, etc). Each connection envelope may be annotated 67 | with a standard set of metadata including, for example, an optional connect deadline, 68 | stream deadline, etc. 69 | 70 | The streams of all incoming connections for a router are merged into a single stream of 71 | enveloped connections. This stream is forwarded to a **binder**. A binder is responsible 72 | for maintaining a cache of balancers by destination name. When a balancer does not exist 73 | in the cache, a new namerd lookup is initiated and its result stream (and value) is cached 74 | so that future connections may resolve quickly. The binder obtains a **balancer** for each 75 | destination name that maintains a list of endpoints and their load (in terms of 76 | connections, throughput, etc). 77 | 78 | If the inbound connection has not expired (i.e. due to a timeout), it is dispatched to the 79 | balancer for processing. The balancer maintains a reactor handle and initiates I/O and 80 | balancer state management on the reactor. 81 | 82 | ``` 83 | ------ ------ 84 | | srv0 | ... | srvN | 85 | ------ | ------ 86 | | 87 | | (Envelope, IoStream) 88 | V 89 | ------------------- ------------- 90 | | binder |----| interpreter | 91 | ------------------- ------------- 92 | | 93 | V 94 | ---------- 95 | | balancer | 96 | ---------- 97 | | 98 | V 99 | ---------- 100 | | endpoint | 101 | ---------- 102 | | 103 | V 104 | -------- 105 | | duplex | 106 | -------- 107 | ``` 108 | -------------------------------------------------------------------------------- /src/connection/socket.rs: -------------------------------------------------------------------------------- 1 | use super::secure::SecureStream; 2 | use futures::Poll; 3 | use rustls::{ClientSession, ServerSession}; 4 | use std::fmt; 5 | use std::io::{self, Read, Write}; 6 | use std::net::{Shutdown, SocketAddr}; 7 | use tokio_core::net::TcpStream; 8 | use tokio_io::AsyncWrite; 9 | 10 | pub fn plain(tcp: TcpStream) -> Socket { 11 | Socket { 12 | local_addr: tcp.local_addr().expect("tcp stream has no local address"), 13 | peer_addr: tcp.peer_addr().expect("tcp stream has no peer address"), 14 | kind: Kind::Plain(tcp), 15 | } 16 | } 17 | 18 | pub fn secure_client(tls: SecureStream) -> Socket { 19 | Socket { 20 | local_addr: tls.local_addr(), 21 | peer_addr: tls.peer_addr(), 22 | kind: Kind::SecureClient(Box::new(tls)), 23 | } 24 | } 25 | 26 | pub fn secure_server(tls: SecureStream) -> Socket { 27 | Socket { 28 | local_addr: tls.local_addr(), 29 | peer_addr: tls.peer_addr(), 30 | kind: Kind::SecureServer(Box::new(tls)), 31 | } 32 | } 33 | 34 | /// Hides the implementation details of socket I/O. 35 | /// 36 | /// Plaintext and encrypted (client and server) streams have different type signatures. 37 | /// Exposing these types to the rest of the application is painful, so `Socket` provides 38 | /// an opaque container for the various types of sockets supported by this proxy. 39 | pub struct Socket { 40 | local_addr: SocketAddr, 41 | peer_addr: SocketAddr, 42 | kind: Kind, 43 | } 44 | 45 | // Since the rustls types are much larger than the plain type, they are boxed. Because 46 | // clippy says so. 47 | enum Kind { 48 | Plain(TcpStream), 49 | SecureClient(Box>), 50 | SecureServer(Box>), 51 | } 52 | 53 | impl fmt::Debug for Socket { 54 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 55 | match self.kind { 56 | Kind::Plain(_) => { 57 | f.debug_struct("Plain") 58 | .field("peer", &self.peer_addr) 59 | .field("local", &self.local_addr) 60 | .finish() 61 | } 62 | Kind::SecureClient(_) => { 63 | f.debug_struct("SecureClient") 64 | .field("peer", &self.peer_addr) 65 | .field("local", &self.local_addr) 66 | .finish() 67 | } 68 | Kind::SecureServer(_) => { 69 | f.debug_struct("SecureServer") 70 | .field("peer", &self.peer_addr) 71 | .field("local", &self.local_addr) 72 | .finish() 73 | } 74 | } 75 | } 76 | } 77 | 78 | impl Socket { 79 | pub fn tcp_shutdown(&mut self, how: Shutdown) -> io::Result<()> { 80 | trace!("{:?}.tcp_shutdown({:?})", self, how); 81 | match self.kind { 82 | Kind::Plain(ref mut stream) => TcpStream::shutdown(stream, how), 83 | Kind::SecureClient(ref mut stream) => stream.tcp_shutdown(how), 84 | Kind::SecureServer(ref mut stream) => stream.tcp_shutdown(how), 85 | } 86 | } 87 | 88 | pub fn local_addr(&self) -> SocketAddr { 89 | self.local_addr 90 | } 91 | 92 | pub fn peer_addr(&self) -> SocketAddr { 93 | self.peer_addr 94 | } 95 | } 96 | 97 | /// Reads the socket without blocking. 98 | impl Read for Socket { 99 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 100 | trace!("{:?}.read({})", self, buf.len()); 101 | match self.kind { 102 | Kind::Plain(ref mut stream) => stream.read(buf), 103 | Kind::SecureClient(ref mut stream) => stream.read(buf), 104 | Kind::SecureServer(ref mut stream) => stream.read(buf), 105 | } 106 | } 107 | } 108 | 109 | /// Writes to the socket without blocking. 110 | impl Write for Socket { 111 | fn write(&mut self, buf: &[u8]) -> io::Result { 112 | trace!("{:?}.write({})", self, buf.len()); 113 | match self.kind { 114 | Kind::Plain(ref mut stream) => stream.write(buf), 115 | Kind::SecureClient(ref mut stream) => stream.write(buf), 116 | Kind::SecureServer(ref mut stream) => stream.write(buf), 117 | } 118 | } 119 | 120 | fn flush(&mut self) -> io::Result<()> { 121 | trace!("{:?}.flush()", self); 122 | match self.kind { 123 | Kind::Plain(ref mut stream) => stream.flush(), 124 | Kind::SecureClient(ref mut stream) => stream.flush(), 125 | Kind::SecureServer(ref mut stream) => stream.flush(), 126 | } 127 | } 128 | } 129 | 130 | /// Closes the write-side of a stream. 131 | impl AsyncWrite for Socket { 132 | fn shutdown(&mut self) -> Poll<(), io::Error> { 133 | trace!("{:?}.shutdown()", self); 134 | match self.kind { 135 | Kind::Plain(ref mut stream) => AsyncWrite::shutdown(stream), 136 | Kind::SecureClient(ref mut stream) => stream.shutdown(), 137 | Kind::SecureServer(ref mut stream) => stream.shutdown(), 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/connector/mod.rs: -------------------------------------------------------------------------------- 1 | use super::Path; 2 | use super::connection::secure; 3 | use super::connection::socket::{self, Socket}; 4 | use futures::{Future, Poll}; 5 | use rustls::ClientConfig as RustlsClientConfig; 6 | use std::{io, net, time}; 7 | use std::sync::Arc; 8 | use tokio_core::net::TcpStream; 9 | use tokio_core::reactor::Handle; 10 | use tokio_timer::Timer; 11 | 12 | mod config; 13 | 14 | pub use self::config::{ConnectorFactoryConfig, ConnectorConfig, TlsConnectorFactoryConfig, 15 | Error as ConfigError}; 16 | 17 | /// Builds a connector for each name. 18 | pub struct ConnectorFactory(ConnectorFactoryInner); 19 | 20 | enum ConnectorFactoryInner { 21 | /// Uses a single connector for all names. 22 | StaticGlobal(Connector), 23 | /// Builds a new connector for each name by applying all configurations with a 24 | /// matching prefix. This is considered "static" because the set of configurations may 25 | /// not be updated dynamically. 26 | StaticPrefixed(StaticPrefixConnectorFactory), 27 | } 28 | 29 | impl ConnectorFactory { 30 | pub fn new_global(conn: Connector) -> ConnectorFactory { 31 | ConnectorFactory(ConnectorFactoryInner::StaticGlobal(conn)) 32 | } 33 | 34 | pub fn new_prefixed(prefixed_configs: Vec<(Path, ConnectorConfig)>) -> ConnectorFactory { 35 | let f = StaticPrefixConnectorFactory(prefixed_configs); 36 | ConnectorFactory(ConnectorFactoryInner::StaticPrefixed(f)) 37 | } 38 | 39 | pub fn mk_connector(&self, dst_name: &Path) -> config::Result { 40 | match self.0 { 41 | ConnectorFactoryInner::StaticGlobal(ref c) => Ok(c.clone()), 42 | ConnectorFactoryInner::StaticPrefixed(ref f) => f.mk_connector(dst_name), 43 | } 44 | } 45 | } 46 | 47 | struct StaticPrefixConnectorFactory(Vec<(Path, ConnectorConfig)>); 48 | impl StaticPrefixConnectorFactory { 49 | /// Builds a new connector by applying all configurations with a matching prefix. 50 | fn mk_connector(&self, dst_name: &Path) -> config::Result { 51 | let mut config = ConnectorConfig::default(); 52 | for &(ref pfx, ref c) in &self.0 { 53 | if pfx.starts_with(dst_name) { 54 | config.update(c); 55 | } 56 | } 57 | config.mk_connector() 58 | } 59 | } 60 | 61 | #[derive(Clone)] 62 | pub struct Tls { 63 | name: String, 64 | config: Arc, 65 | } 66 | 67 | impl Tls { 68 | fn handshake(&self, tcp: TcpStream) -> secure::ClientHandshake { 69 | secure::client_handshake(tcp, &self.config, &self.name) 70 | } 71 | } 72 | 73 | fn new( 74 | connect_timeout: Option, 75 | tls: Option, 76 | max_waiters: usize, 77 | min_connections: usize, 78 | fail_limit: usize, 79 | fail_penalty: time::Duration, 80 | ) -> Connector { 81 | Connector { 82 | connect_timeout, 83 | tls, 84 | max_waiters, 85 | min_connections, 86 | fail_limit, 87 | fail_penalty, 88 | } 89 | } 90 | 91 | #[derive(Clone)] 92 | pub struct Connector { 93 | connect_timeout: Option, 94 | tls: Option, 95 | max_waiters: usize, 96 | min_connections: usize, 97 | fail_limit: usize, 98 | fail_penalty: time::Duration, 99 | } 100 | 101 | impl Connector { 102 | pub fn max_waiters(&self) -> usize { 103 | self.max_waiters 104 | } 105 | 106 | pub fn min_connections(&self) -> usize { 107 | self.min_connections 108 | } 109 | 110 | pub fn failure_limit(&self) -> usize { 111 | self.fail_limit 112 | } 113 | pub fn failure_penalty(&self) -> time::Duration { 114 | self.fail_penalty 115 | } 116 | 117 | fn timeout(&self, fut: F, timer: &Timer) -> Box> 118 | where 119 | F: Future + 'static, 120 | { 121 | match self.connect_timeout { 122 | None => Box::new(fut), 123 | Some(t) => Box::new(timer.timeout(fut, t).map_err(|e| e.into())), 124 | } 125 | } 126 | 127 | pub fn connect(&self, addr: &net::SocketAddr, reactor: &Handle, timer: &Timer) -> Connecting { 128 | let tcp = TcpStream::connect(addr, reactor); 129 | let socket: Box> = match self.tls { 130 | None => { 131 | let f = tcp.map(socket::plain); 132 | Box::new(self.timeout(f, timer)) 133 | } 134 | Some(ref tls) => { 135 | let tls = tls.clone(); 136 | let f = tcp.and_then(move |tcp| tls.handshake(tcp)).map( 137 | socket::secure_client, 138 | ); 139 | Box::new(self.timeout(f, timer)) 140 | } 141 | }; 142 | Connecting(socket) 143 | } 144 | } 145 | 146 | pub struct Connecting(Box>); 147 | impl Future for Connecting { 148 | type Item = Socket; 149 | type Error = io::Error; 150 | fn poll(&mut self) -> Poll { 151 | self.0.poll() 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/connector/config.rs: -------------------------------------------------------------------------------- 1 | use super::{Connector, ConnectorFactory, Tls}; 2 | use rustls; 3 | use std::fs::File; 4 | use std::io::BufReader; 5 | use std::sync::Arc; 6 | use std::time; 7 | 8 | const DEFAULT_MAX_WAITERS: usize = 1_000_000; 9 | const DEFAULT_MAX_CONSECUTIVE_FAILURES: usize = 5; 10 | const DEFAULT_FAILURE_PENALTY_SECS: u64 = 60; 11 | 12 | pub type Result = ::std::result::Result; 13 | 14 | #[derive(Clone, Debug)] 15 | pub enum Error { 16 | GlobalWithPrefix, 17 | StaticWithoutPrefix, 18 | } 19 | 20 | #[derive(Clone, Debug, Serialize, Deserialize)] 21 | #[serde(deny_unknown_fields, tag = "kind")] 22 | pub enum ConnectorFactoryConfig { 23 | #[serde(rename = "io.l5d.global")] 24 | Global(ConnectorConfig), 25 | 26 | #[serde(rename = "io.l5d.static")] 27 | Static { configs: Vec }, 28 | } 29 | 30 | impl Default for ConnectorFactoryConfig { 31 | fn default() -> ConnectorFactoryConfig { 32 | ConnectorFactoryConfig::Global(ConnectorConfig::default()) 33 | } 34 | } 35 | 36 | impl ConnectorFactoryConfig { 37 | pub fn mk_connector_factory(&self) -> Result { 38 | match *self { 39 | ConnectorFactoryConfig::Global(ref cfg) => { 40 | if cfg.prefix.is_some() { 41 | return Err(Error::GlobalWithPrefix); 42 | } 43 | let conn = cfg.mk_connector()?; 44 | Ok(ConnectorFactory::new_global(conn)) 45 | } 46 | ConnectorFactoryConfig::Static { ref configs } => { 47 | let mut pfx_configs = Vec::with_capacity(configs.len()); 48 | for cfg in configs { 49 | match cfg.prefix { 50 | None => { 51 | return Err(Error::StaticWithoutPrefix); 52 | } 53 | Some(ref pfx) => { 54 | pfx_configs.push((pfx.clone().into(), cfg.clone())); 55 | } 56 | } 57 | } 58 | Ok(ConnectorFactory::new_prefixed(pfx_configs)) 59 | } 60 | } 61 | } 62 | } 63 | 64 | #[derive(Clone, Default, Debug, Serialize, Deserialize)] 65 | #[serde(deny_unknown_fields, rename_all = "camelCase")] 66 | pub struct ConnectorConfig { 67 | pub prefix: Option, 68 | pub tls: Option, 69 | pub connect_timeout_ms: Option, 70 | 71 | pub max_waiters: Option, 72 | pub min_connections: Option, 73 | 74 | pub fail_fast: Option, 75 | 76 | // TODO requeue_budget: Option 77 | } 78 | 79 | #[derive(Clone, Default, Debug, Serialize, Deserialize)] 80 | #[serde(deny_unknown_fields, rename_all = "camelCase")] 81 | pub struct FailFastConfig { 82 | pub max_consecutive_failures: Option, 83 | pub failure_penalty_secs: Option, 84 | } 85 | 86 | impl ConnectorConfig { 87 | pub fn mk_connector(&self) -> Result { 88 | let tls = match self.tls { 89 | None => None, 90 | Some(ref tls) => Some(tls.mk_tls()?), 91 | }; 92 | let connect_timeout = self.connect_timeout_ms.map(time::Duration::from_millis); 93 | let max_waiters = self.max_waiters.unwrap_or(DEFAULT_MAX_WAITERS); 94 | let min_conns = self.min_connections.unwrap_or(0); 95 | let max_fails = self.fail_fast 96 | .as_ref() 97 | .and_then(|c| c.max_consecutive_failures) 98 | .unwrap_or(DEFAULT_MAX_CONSECUTIVE_FAILURES); 99 | let fail_penalty = { 100 | let s = self.fail_fast 101 | .as_ref() 102 | .and_then(|c| c.failure_penalty_secs) 103 | .unwrap_or(DEFAULT_FAILURE_PENALTY_SECS); 104 | time::Duration::from_secs(s) 105 | }; 106 | Ok(super::new( 107 | connect_timeout, 108 | tls, 109 | max_waiters, 110 | min_conns, 111 | max_fails, 112 | fail_penalty, 113 | )) 114 | } 115 | 116 | pub fn update(&mut self, other: &ConnectorConfig) { 117 | if let Some(ref otls) = other.tls { 118 | self.tls = Some(otls.clone()); 119 | } 120 | if let Some(ct) = other.connect_timeout_ms { 121 | self.connect_timeout_ms = Some(ct); 122 | } 123 | } 124 | } 125 | 126 | #[derive(Clone, Default, Debug, Serialize, Deserialize)] 127 | #[serde(deny_unknown_fields, rename_all = "camelCase")] 128 | pub struct TlsConnectorFactoryConfig { 129 | pub dns_name: String, 130 | pub trust_certs: Option>, 131 | } 132 | 133 | impl TlsConnectorFactoryConfig { 134 | pub fn mk_tls(&self) -> Result { 135 | let mut config = rustls::ClientConfig::new(); 136 | if let Some(ref certs) = self.trust_certs { 137 | for p in certs { 138 | let f = File::open(p).expect("cannot open certificate file"); 139 | config 140 | .root_store 141 | .add_pem_file(&mut BufReader::new(f)) 142 | .expect("certificate error"); 143 | } 144 | }; 145 | let tls = Tls { 146 | name: self.dns_name.clone(), 147 | config: Arc::new(config), 148 | }; 149 | Ok(tls) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/connection/half_duplex.rs: -------------------------------------------------------------------------------- 1 | use super::Connection; 2 | use super::Ctx; 3 | use futures::{Async, Future, Poll}; 4 | use std::cell::RefCell; 5 | use std::io::{self, Read, Write}; 6 | use std::net::Shutdown; 7 | use std::rc::Rc; 8 | //use tacho; 9 | use tokio_io::AsyncWrite; 10 | 11 | pub fn new( 12 | reader: Rc>>, 13 | writer: Rc>>, 14 | buf: Rc>>, 15 | ) -> HalfDuplex 16 | where 17 | R: Ctx, 18 | W: Ctx, 19 | { 20 | HalfDuplex { 21 | reader, 22 | writer, 23 | buf, 24 | pending: None, 25 | bytes_total: 0, 26 | should_shutdown: false, 27 | // bytes_total_count: metrics.counter("bytes_total".into()), 28 | // allocs_count: metrics.counter("allocs_count".into()), 29 | } 30 | } 31 | 32 | /// A future representing reading all data from one side of a proxy connection and writing 33 | /// it to another. 34 | /// 35 | /// In the typical case, nothing allocations are required. If the write side exhibits 36 | /// backpressure, however, a buffer is allocated to 37 | pub struct HalfDuplex { 38 | reader: Rc>>, 39 | writer: Rc>>, 40 | 41 | // Holds transient data when copying between the reader and writer. 42 | buf: Rc>>, 43 | 44 | // Holds data that can't be fully written. 45 | pending: Option>, 46 | 47 | // The number of bytes we've written so far. 48 | bytes_total: usize, 49 | 50 | // Indicates that that the reader has returned 0 and the writer should be shut down. 51 | should_shutdown: bool, 52 | 53 | // bytes_total_count: tacho::Counter, 54 | // allocs_count: tacho::Counter, 55 | } 56 | 57 | impl Future for HalfDuplex 58 | where 59 | R: Ctx, 60 | W: Ctx, 61 | { 62 | type Item = usize; 63 | type Error = io::Error; 64 | 65 | /// Reads from from the `reader` into a shared buffer before writing to `writer`. 66 | /// 67 | /// If all data cannot be written, the unwritten data is stored in a newly-allocated 68 | /// buffer. This pending data is flushed before any more data is read. 69 | fn poll(&mut self) -> Poll { 70 | trace!("poll"); 71 | let mut writer = self.writer.borrow_mut(); 72 | let mut reader = self.reader.borrow_mut(); 73 | 74 | // Because writer.socket.shutdown may return WouldBlock, we may already be 75 | // shutting down and need to resume graceful shutdown. 76 | if self.should_shutdown { 77 | try_nb!(writer.socket.shutdown()); 78 | writer.socket.tcp_shutdown(Shutdown::Write)?; 79 | return Ok(Async::Ready(self.bytes_total)); 80 | } 81 | 82 | // If we've read more than we were able to write previously, then write all of it 83 | // until the write would block. 84 | if let Some(mut pending) = self.pending.take() { 85 | trace!("writing {} pending bytes", pending.len()); 86 | while !pending.is_empty() { 87 | match writer.socket.write(&pending) { 88 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { 89 | self.pending = Some(pending); 90 | return Ok(Async::NotReady); 91 | } 92 | Err(e) => return Err(e), 93 | Ok(wsz) => { 94 | // Drop the portion of the buffer that we've already written. 95 | // There may or may not be more pending data remaining. 96 | pending.drain(0..wsz); 97 | self.bytes_total += wsz; 98 | writer.ctx.wrote(wsz); 99 | } 100 | } 101 | } 102 | } 103 | 104 | // Read and write data until one of the endpoints is not ready. All data is read 105 | // into a thread-global transfer buffer and then written from this buffer. If all 106 | // data cannot be written, it is copied into a newly-allocated local buffer to be 107 | // flushed later. 108 | loop { 109 | assert!(self.pending.is_none()); 110 | 111 | let mut rbuf = self.buf.borrow_mut(); 112 | let rsz = try_nb!(reader.socket.read(&mut rbuf)); 113 | reader.ctx.read(rsz); 114 | if rsz == 0 { 115 | self.should_shutdown = true; 116 | try_nb!(writer.socket.shutdown()); 117 | writer.socket.tcp_shutdown(Shutdown::Write)?; 118 | return Ok(Async::Ready(self.bytes_total)); 119 | } 120 | 121 | let mut wbuf = &rbuf[..rsz]; 122 | while !wbuf.is_empty() { 123 | match writer.socket.write(wbuf) { 124 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { 125 | let mut p = vec![0; wbuf.len()]; 126 | p.copy_from_slice(wbuf); 127 | self.pending = Some(p); 128 | return Ok(Async::NotReady); 129 | } 130 | Err(e) => return Err(e), 131 | Ok(wsz) => { 132 | self.bytes_total += wsz; 133 | writer.ctx.wrote(wsz); 134 | wbuf = &wbuf[wsz..]; 135 | } 136 | } 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linkerd-tcp # 2 | 3 | A TCP load balancer for the [linkerd][linkerd] service mesh. 4 | 5 | Status: _beta_ 6 | 7 | [![CircleCI](https://circleci.com/gh/linkerd/linkerd-tcp/tree/master.svg?style=svg)](https://circleci.com/gh/linkerd/linkerd-tcp/tree/master) 8 | 9 | ## Features ## 10 | 11 | - Lightweight, native **TCP** and **TLS** load balancer built on [tokio]. 12 | - Weighted-least-loaded [P2C][p2c] load balancing. 13 | - Minimal resource utilization: typically <.5 cores with ~2MB RSS. 14 | - Tightly integrated with the [linkerd service mesh][namerd]. 15 | - Supports endpoint weighting (i.e. for "red line" testing). 16 | - Modern Transport Layer Security via [rustls][rustls]: 17 | - TLS1.2 and TLS1.3 (draft 18) only. 18 | - ECDSA or RSA server authentication by clients. 19 | - RSA server authentication by servers. 20 | - Forward secrecy using ECDHE; with curve25519, nistp256 or nistp384 curves. 21 | - AES128-GCM and AES256-GCM bulk encryption, with safe nonces. 22 | - Chacha20Poly1305 bulk encryption. 23 | - ALPN support. 24 | - SNI support. 25 | 26 | ## Quickstart ## 27 | 28 | 1. Install [Rust and Cargo][install-rust]. 29 | 2. Run [namerd][namerd]. `./namerd.sh` fetches, configures, and runs namerd using a local-fs-backed discovery (in ./tmp.discovery). 30 | 3. From this repository, run: `cargo run -- example.yml` 31 | 32 | We :heart: pull requests! See [CONTRIBUTING.md](CONTRIBUTING.md) for info on 33 | contributing changes. 34 | 35 | ## Usage ## 36 | 37 | ``` 38 | linkerd-tcp 0.1.0 39 | A native TCP proxy for the linkerd service mesh 40 | 41 | USAGE: 42 | linkerd-tcp 43 | 44 | FLAGS: 45 | -h, --help Prints help information 46 | -V, --version Prints version information 47 | 48 | ARGS: 49 | Config file path 50 | ``` 51 | 52 | ### Example configuration ### 53 | 54 | ```yaml 55 | 56 | # Administrative control endpoints are exposed on a dedicated HTTP server. Endpoints 57 | # include: 58 | # - /metrics -- produces a snapshot of metrics formatted for prometheus. 59 | # - /shutdown -- POSTing to this endpoint initiates graceful shutdown. 60 | # - /abort -- POSTing to this terminates the process immediately. 61 | admin: 62 | port: 9989 63 | 64 | # By default, the admin server listens only on localhost. We can force it to bind 65 | # on all interfaces by overriding the IP. 66 | ip: 0.0.0.0 67 | 68 | # Metrics are snapshot at a fixed interval of 10s. 69 | metricsIntervalSecs: 10 70 | 71 | # A process exposes one or more 'routers'. Routers connect server traffic to 72 | # load balancers. 73 | routers: 74 | 75 | # Each router has a 'label' for reporting purposes. 76 | - label: default 77 | 78 | # Each router is configured to resolve names. 79 | # Currently, only namerd's HTTP interface is supported: 80 | interpreter: 81 | kind: io.l5d.namerd.http 82 | baseUrl: http://localhost:4180 83 | namespace: default 84 | periodSecs: 20 85 | 86 | servers: 87 | 88 | # Each router has one or more 'servers' listening for incoming connections. 89 | # By default, routers listen on localhost. You need to specify a port. 90 | - port: 7474 91 | dstName: /svc/default 92 | # You can limit the amount of time that a server will wait to obtain a 93 | # connection from the router. 94 | connectTimeoutMs: 500 95 | 96 | # By default each server listens on 'localhost' to avoid exposing an open 97 | # relay by default. Servers may be configured to listen on a specific local 98 | # address or all local addresses (0.0.0.0). 99 | - port: 7575 100 | ip: 0.0.0.0 101 | # Note that each server may route to a different destination through a 102 | # single router: 103 | dstName: /svc/google 104 | # Servers may be configured to perform a TLS handshake. 105 | tls: 106 | defaultIdentity: 107 | privateKey: private.pem 108 | certs: 109 | - cert.pem 110 | - ../eg-ca/ca/intermediate/certs/ca-chain.cert.pem 111 | 112 | # Clients may also be configured to perform a TLS handshake. 113 | client: 114 | kind: io.l5d.static 115 | # We can also apply linkerd-style per-client configuration: 116 | configs: 117 | - prefix: /svc/google 118 | connectTimeoutMs: 400 119 | # Require that the downstream connection be TLS'd, with a 120 | # `subjectAltName` including the DNS name _www.google.com_ 121 | # using either our local CA or the host's default openssl 122 | # certificate. 123 | tls: 124 | dnsName: "www.google.com" 125 | trustCerts: 126 | - ../eg-ca/ca/intermediate/certs/ca-chain.cert.pem 127 | - /usr/local/etc/openssl/cert.pem 128 | ``` 129 | 130 | ### Logging ### 131 | 132 | Logging may be enabled by setting `RUST_LOG=linkerd_tcp=info` on the environment. When 133 | debugging, set `RUST_LOG=trace`. 134 | 135 | ## Docker ## 136 | 137 | To build the linkerd/linkerd-tcp docker image, run: 138 | 139 | ```bash 140 | ./dockerize latest 141 | ``` 142 | 143 | Replace `latest` with the version that you want to build. 144 | 145 | Try running the image with: 146 | 147 | ```bash 148 | docker run -v `pwd`/example.yml:/example.yml linkerd/linkerd-tcp:latest /example.yml 149 | ``` 150 | 151 | ## Code of Conduct ## 152 | 153 | This project is for everyone. We ask that our users and contributors take a few minutes to 154 | review our [code of conduct][coc]. 155 | 156 | ## License ## 157 | 158 | Copyright 2017-2018 Linkerd-TCP authors. All rights reserved. 159 | 160 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in compliance with the License. You may obtain a copy of the License at 161 | 162 | http://www.apache.org/licenses/LICENSE-2.0 163 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 164 | 165 | 166 | [coc]: https://github.com/linkerd/linkerd/wiki/Linkerd-code-of-conduct 167 | [install-rust]: https://www.rust-lang.org/en-US/install.html 168 | [linkerd]: https://github.com/linkerd/linkerd 169 | [namerd]: https://github.com/linkerd/linkerd/tree/master/namerd 170 | [p2c]: https://www.eecs.harvard.edu/~michaelm/postscripts/mythesis.pdf 171 | [rustls]: https://github.com/ctz/rustls 172 | [tokio]: https://tokio.rs 173 | -------------------------------------------------------------------------------- /src/balancer/mod.rs: -------------------------------------------------------------------------------- 1 | use super::Path; 2 | use super::connector::Connector; 3 | use super::resolver::Resolve; 4 | use futures::{Async, Future, Poll, unsync}; 5 | use ordermap::OrderMap; 6 | use std::{cmp, io, net}; 7 | use std::collections::VecDeque; 8 | use std::time::{Duration, Instant}; 9 | use tacho; 10 | use tokio_core::reactor::Handle; 11 | use tokio_timer::Timer; 12 | 13 | mod dispatcher; 14 | mod endpoint; 15 | mod factory; 16 | 17 | pub use self::endpoint::{Connection as EndpointConnection, Ctx as EndpointCtx}; 18 | use self::endpoint::Endpoint; 19 | pub use self::factory::BalancerFactory; 20 | 21 | type Waiter = unsync::oneshot::Sender; 22 | 23 | /// A weighted concrete destination address. 24 | #[derive(Clone, Debug)] 25 | pub struct WeightedAddr { 26 | pub addr: ::std::net::SocketAddr, 27 | pub weight: f64, 28 | } 29 | 30 | impl WeightedAddr { 31 | pub fn new(addr: net::SocketAddr, weight: f64) -> WeightedAddr { 32 | WeightedAddr { addr, weight } 33 | } 34 | } 35 | 36 | pub fn new( 37 | reactor: &Handle, 38 | timer: &Timer, 39 | dst: &Path, 40 | connector: Connector, 41 | resolve: Resolve, 42 | metrics: &tacho::Scope, 43 | ) -> Balancer { 44 | let (tx, rx) = unsync::mpsc::unbounded(); 45 | let dispatcher = dispatcher::new( 46 | reactor.clone(), 47 | timer.clone(), 48 | dst.clone(), 49 | connector, 50 | resolve, 51 | rx, 52 | Endpoints::default(), 53 | metrics, 54 | ); 55 | reactor.spawn(dispatcher.map_err(|_| {})); 56 | Balancer(tx) 57 | } 58 | 59 | #[derive(Clone)] 60 | pub struct Balancer(unsync::mpsc::UnboundedSender); 61 | impl Balancer { 62 | /// Obtains a connection to the destination. 63 | pub fn connect(&self) -> Connect { 64 | let (tx, rx) = unsync::oneshot::channel(); 65 | let result = unsync::mpsc::UnboundedSender::unbounded_send(&self.0, tx) 66 | .map_err(|_| io::Error::new(io::ErrorKind::Other, "lost dispatcher")) 67 | .map(|_| rx); 68 | Connect(Some(result)) 69 | } 70 | } 71 | 72 | pub struct Connect(Option>>); 73 | impl Future for Connect { 74 | type Item = endpoint::Connection; 75 | type Error = io::Error; 76 | fn poll(&mut self) -> Poll { 77 | let mut recv = self.0.take().expect( 78 | "connect must not be polled after completion", 79 | )?; 80 | match recv.poll() { 81 | Err(_) => Err(io::Error::new(io::ErrorKind::Interrupted, "canceled")), 82 | Ok(Async::Ready(conn)) => Ok(Async::Ready(conn)), 83 | Ok(Async::NotReady) => { 84 | self.0 = Some(Ok(recv)); 85 | Ok(Async::NotReady) 86 | } 87 | } 88 | } 89 | } 90 | 91 | pub type EndpointMap = OrderMap; 92 | pub type FailedMap = OrderMap; 93 | 94 | #[derive(Default)] 95 | pub struct Endpoints { 96 | //minimum_connections: usize, 97 | /// Endpoints considered available for new connections. 98 | available: EndpointMap, 99 | 100 | /// Endpoints that are still active but considered unavailable for new connections. 101 | retired: EndpointMap, 102 | 103 | failed: FailedMap, 104 | } 105 | 106 | impl Endpoints { 107 | pub fn available(&self) -> &EndpointMap { 108 | &self.available 109 | } 110 | 111 | pub fn failed(&self) -> &FailedMap { 112 | &self.failed 113 | } 114 | 115 | pub fn retired(&self) -> &EndpointMap { 116 | &self.retired 117 | } 118 | 119 | pub fn update_failed(&mut self, max_failures: usize, penalty: Duration) { 120 | let mut available = VecDeque::with_capacity(self.failed.len()); 121 | let mut failed = VecDeque::with_capacity(self.failed.len()); 122 | 123 | for (_, ep) in self.available.drain(..) { 124 | if ep.state().consecutive_failures < max_failures { 125 | available.push_back(ep); 126 | } else { 127 | failed.push_back((Instant::now(), ep)); 128 | } 129 | } 130 | 131 | for (_, (start, ep)) in self.failed.drain(..) { 132 | if start + penalty <= Instant::now() { 133 | available.push_back(ep); 134 | } else { 135 | failed.push_back((start, ep)); 136 | } 137 | } 138 | 139 | if available.is_empty() { 140 | while let Some((_, ep)) = failed.pop_front() { 141 | self.available.insert(ep.peer_addr(), ep); 142 | } 143 | } else { 144 | while let Some(ep) = available.pop_front() { 145 | self.available.insert(ep.peer_addr(), ep); 146 | } 147 | while let Some((since, ep)) = failed.pop_front() { 148 | self.failed.insert(ep.peer_addr(), (since, ep)); 149 | } 150 | } 151 | } 152 | 153 | // TODO: we need to do some sort of probation deal to manage endpoints that are 154 | // retired. 155 | pub fn update_resolved(&mut self, resolved: &[WeightedAddr]) { 156 | let mut temp = { 157 | let sz = cmp::max(self.available.len(), self.retired.len()); 158 | VecDeque::with_capacity(sz) 159 | }; 160 | let dsts = Endpoints::dsts_by_addr(resolved); 161 | self.check_retired(&dsts, &mut temp); 162 | self.check_available(&dsts, &mut temp); 163 | self.check_failed(&dsts); 164 | self.update_available_from_new(dsts); 165 | } 166 | 167 | /// Checks active endpoints. 168 | fn check_available( 169 | &mut self, 170 | dsts: &OrderMap, 171 | temp: &mut VecDeque, 172 | ) { 173 | for (addr, ep) in self.available.drain(..) { 174 | if dsts.contains_key(&addr) { 175 | temp.push_back(ep); 176 | } else if ep.is_idle() { 177 | drop(ep); 178 | } else { 179 | self.retired.insert(addr, ep); 180 | } 181 | } 182 | 183 | for _ in 0..temp.len() { 184 | let ep = temp.pop_front().unwrap(); 185 | self.available.insert(ep.peer_addr(), ep); 186 | } 187 | } 188 | 189 | /// Checks retired endpoints. 190 | /// 191 | /// Endpoints are either salvaged backed into the active pool, maintained as 192 | /// retired if still active, or dropped if inactive. 193 | fn check_retired( 194 | &mut self, 195 | dsts: &OrderMap, 196 | temp: &mut VecDeque, 197 | ) { 198 | for (addr, ep) in self.retired.drain(..) { 199 | if dsts.contains_key(&addr) { 200 | self.available.insert(addr, ep); 201 | } else if ep.is_idle() { 202 | drop(ep); 203 | } else { 204 | temp.push_back(ep); 205 | } 206 | } 207 | 208 | for _ in 0..temp.len() { 209 | let ep = temp.pop_front().unwrap(); 210 | self.retired.insert(ep.peer_addr(), ep); 211 | } 212 | } 213 | 214 | /// Checks failed endpoints. 215 | fn check_failed(&mut self, dsts: &OrderMap) { 216 | let mut temp = VecDeque::with_capacity(self.failed.len()); 217 | for (addr, (since, ep)) in self.failed.drain(..) { 218 | if dsts.contains_key(&addr) { 219 | temp.push_back((since, ep)); 220 | } else if ep.is_idle() { 221 | drop(ep); 222 | } else { 223 | self.retired.insert(addr, ep); 224 | } 225 | } 226 | 227 | for _ in 0..temp.len() { 228 | let (instant, ep) = temp.pop_front().unwrap(); 229 | self.failed.insert(ep.peer_addr(), (instant, ep)); 230 | } 231 | } 232 | 233 | fn update_available_from_new(&mut self, mut dsts: OrderMap) { 234 | // Add new endpoints or update the base weights of existing endpoints. 235 | //let metrics = self.endpoint_metrics.clone(); 236 | for (addr, weight) in dsts.drain(..) { 237 | if let Some(&mut (_, ref mut ep)) = self.failed.get_mut(&addr) { 238 | ep.set_weight(weight); 239 | continue; 240 | } 241 | 242 | if let Some(ep) = self.available.get_mut(&addr) { 243 | ep.set_weight(weight); 244 | continue; 245 | } 246 | 247 | self.available.insert(addr, endpoint::new(addr, weight)); 248 | } 249 | } 250 | 251 | fn dsts_by_addr(dsts: &[WeightedAddr]) -> OrderMap { 252 | let mut by_addr = OrderMap::with_capacity(dsts.len()); 253 | for &WeightedAddr { addr, weight } in dsts { 254 | by_addr.insert(addr, weight); 255 | } 256 | by_addr 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/resolver/namerd.rs: -------------------------------------------------------------------------------- 1 | //! Namerd Endpointer 2 | 3 | // TODO In the future, we likely want to change this to use the split bind & addr APIs so 4 | // balancers can be shared across logical names. In the meantime, it's sufficient to have 5 | // a balancer per logical name. 6 | 7 | use super::{WeightedAddr, Result, Error}; 8 | use bytes::{Buf, BufMut, IntoBuf, Bytes, BytesMut}; 9 | use futures::{Async, Future, IntoFuture, Poll, Stream}; 10 | use hyper::{Body, Chunk, Client, StatusCode, Uri}; 11 | use hyper::client::{Connect as HyperConnect, HttpConnector}; 12 | use serde_json as json; 13 | use std::{net, time}; 14 | use std::collections::HashMap; 15 | use std::rc::Rc; 16 | use tacho; 17 | use tokio_core::reactor::Handle; 18 | use tokio_timer::{Timer, Interval}; 19 | use url::Url; 20 | 21 | type HttpConnectorFactory = Client; 22 | 23 | type AddrsFuture = Box, Error = Error>>; 24 | 25 | // pub struct Addrs(Box>, Error = ()>>); 26 | // impl Stream for Addrs { 27 | // type Item = Result>; 28 | // type Error = (); 29 | // fn poll(&mut self) -> Poll, Self::Error> { 30 | // self.0.poll() 31 | // } 32 | // } 33 | 34 | #[derive(Clone)] 35 | pub struct Namerd { 36 | base_url: String, 37 | period: time::Duration, 38 | namespace: String, 39 | stats: Stats, 40 | } 41 | 42 | impl Namerd { 43 | pub fn new( 44 | base_url: String, 45 | period: time::Duration, 46 | namespace: String, 47 | metrics: tacho::Scope, 48 | ) -> Namerd { 49 | Namerd { 50 | base_url: format!("{}/api/1/resolve/{}", base_url, namespace), 51 | stats: Stats::new(metrics), 52 | namespace, 53 | period, 54 | } 55 | } 56 | } 57 | 58 | impl Namerd { 59 | pub fn with_client(self, handle: &Handle, timer: &Timer) -> WithClient { 60 | WithClient { 61 | namerd: self, 62 | client: Rc::new(Client::new(handle)), 63 | timer: timer.clone(), 64 | } 65 | } 66 | } 67 | 68 | /// A name 69 | pub struct WithClient { 70 | namerd: Namerd, 71 | client: Rc, 72 | timer: Timer, 73 | } 74 | impl WithClient { 75 | pub fn resolve(&self, target: &str) -> Addrs { 76 | let uri = Url::parse_with_params(&self.namerd.base_url, &[("path", &target)]) 77 | .expect("invalid namerd url") 78 | .as_str() 79 | .parse::() 80 | .expect("Could not parse namerd URI"); 81 | let init = request(self.client.clone(), uri.clone(), self.namerd.stats.clone()); 82 | let interval = self.timer.interval(self.namerd.period); 83 | Addrs { 84 | client: self.client.clone(), 85 | stats: self.namerd.stats.clone(), 86 | state: Some(State::Pending(init, interval)), 87 | uri, 88 | } 89 | } 90 | } 91 | 92 | /// Streams 93 | pub struct Addrs { 94 | state: Option, 95 | client: Rc, 96 | uri: Uri, 97 | stats: Stats, 98 | } 99 | 100 | enum State { 101 | Pending(AddrsFuture, Interval), 102 | Waiting(Interval), 103 | } 104 | 105 | impl Stream for Addrs { 106 | type Item = Result>; 107 | type Error = Error; 108 | 109 | fn poll(&mut self) -> Poll, Self::Error> { 110 | loop { 111 | match self.state.take().expect("polled after completion") { 112 | State::Waiting(mut int) => { 113 | match int.poll() { 114 | Err(e) => { 115 | self.state = Some(State::Waiting(int)); 116 | return Err(Error::Timer(e)); 117 | } 118 | Ok(Async::NotReady) => { 119 | self.state = Some(State::Waiting(int)); 120 | return Ok(Async::NotReady); 121 | } 122 | Ok(Async::Ready(_)) => { 123 | let fut = { 124 | let c = self.client.clone(); 125 | let u = self.uri.clone(); 126 | let s = self.stats.clone(); 127 | request(c, u, s) 128 | }; 129 | self.state = Some(State::Pending(fut, int)); 130 | } 131 | } 132 | } 133 | 134 | State::Pending(mut fut, int) => { 135 | match fut.poll() { 136 | Err(e) => { 137 | self.state = Some(State::Waiting(int)); 138 | return Ok(Async::Ready(Some(Err(e)))); 139 | } 140 | Ok(Async::Ready(addrs)) => { 141 | self.state = Some(State::Waiting(int)); 142 | return Ok(Async::Ready(Some(Ok(addrs)))); 143 | } 144 | Ok(Async::NotReady) => { 145 | self.state = Some(State::Pending(fut, int)); 146 | return Ok(Async::NotReady); 147 | } 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | 155 | fn request(client: Rc>, uri: Uri, stats: Stats) -> AddrsFuture { 156 | debug!("Polling namerd at {}", uri.to_string()); 157 | let rsp = stats 158 | .request_latency 159 | .time(client.get(uri).then(handle_response)) 160 | .then(move |rsp| { 161 | if rsp.is_ok() { 162 | stats.success_count.incr(1); 163 | } else { 164 | stats.failure_count.incr(1); 165 | } 166 | rsp 167 | }); 168 | Box::new(rsp) 169 | } 170 | 171 | fn handle_response(result: ::hyper::Result<::hyper::client::Response>) -> AddrsFuture { 172 | match result { 173 | Ok(rsp) => { 174 | match rsp.status() { 175 | StatusCode::Ok => parse_body(rsp.body()), 176 | status => { 177 | info!("error: bad response: {}", status); 178 | Box::new(Err(Error::UnexpectedStatus(status)).into_future()) 179 | } 180 | } 181 | } 182 | Err(e) => { 183 | error!("failed to read response: {:?}", e); 184 | Box::new(Err(Error::Hyper(e)).into_future()) 185 | } 186 | } 187 | } 188 | 189 | fn parse_body(body: Body) -> AddrsFuture { 190 | trace!("parsing namerd response"); 191 | let f = body.collect() 192 | .then(|res| match res { 193 | Ok(ref chunks) => parse_chunks(chunks), 194 | Err(e) => { 195 | info!("error: {}", e); 196 | Err(Error::Hyper(e)) 197 | } 198 | }); 199 | Box::new(f) 200 | } 201 | 202 | fn bytes_in(chunks: &[Chunk]) -> usize { 203 | let mut sz = 0; 204 | for c in chunks { 205 | sz += (*c).len(); 206 | } 207 | sz 208 | } 209 | 210 | fn to_buf(chunks: &[Chunk]) -> Bytes { 211 | let mut buf = BytesMut::with_capacity(bytes_in(chunks)); 212 | for c in chunks { 213 | buf.put_slice(&*c) 214 | } 215 | buf.freeze() 216 | } 217 | 218 | fn parse_chunks(chunks: &[Chunk]) -> Result> { 219 | let r = to_buf(chunks).into_buf().reader(); 220 | let result: json::Result = json::from_reader(r); 221 | match result { 222 | Ok(ref nrsp) if nrsp.kind == "bound" => Ok(to_weighted_addrs(&nrsp.addrs)), 223 | Ok(_) => Err(Error::NotBound), 224 | Err(e) => { 225 | info!("error parsing response: {}", e); 226 | Err(Error::Serde(e)) 227 | } 228 | } 229 | } 230 | 231 | fn to_weighted_addrs(namerd_addrs: &[NamerdAddr]) -> Vec { 232 | // We never intentionally clear the EndpointMap. 233 | let mut dsts: Vec = Vec::new(); 234 | let mut sum = 0.0; 235 | for na in namerd_addrs { 236 | let addr = net::SocketAddr::new(na.ip.parse().unwrap(), na.port); 237 | let w = na.meta.endpoint_addr_weight.unwrap_or(1.0); 238 | sum += w; 239 | dsts.push(WeightedAddr::new(addr, w)); 240 | } 241 | // Normalize weights on [0.0, 0.1]. 242 | for dst in &mut dsts { 243 | dst.weight /= sum; 244 | } 245 | dsts 246 | } 247 | 248 | #[derive(Debug, Deserialize)] 249 | struct NamerdResponse { 250 | #[serde(rename = "type")] 251 | kind: String, 252 | addrs: Vec, 253 | meta: HashMap, 254 | } 255 | 256 | #[derive(Debug, Deserialize)] 257 | struct NamerdAddr { 258 | ip: String, 259 | port: u16, 260 | meta: Meta, 261 | } 262 | 263 | #[derive(Debug, Deserialize)] 264 | struct Meta { 265 | authority: Option, 266 | 267 | #[serde(rename = "nodeName")] 268 | node_name: Option, 269 | 270 | endpoint_addr_weight: Option, 271 | } 272 | 273 | 274 | #[derive(Clone)] 275 | pub struct Stats { 276 | request_latency: tacho::Timer, 277 | success_count: tacho::Counter, 278 | failure_count: tacho::Counter, 279 | } 280 | impl Stats { 281 | fn new(metrics: tacho::Scope) -> Stats { 282 | Stats { 283 | request_latency: metrics.timer_ms("request_latency_ms".into()), 284 | success_count: metrics.counter("success_count".into()), 285 | failure_count: metrics.counter("failure_count".into()), 286 | } 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/connection/secure.rs: -------------------------------------------------------------------------------- 1 | use futures::{Async, Future, Poll}; 2 | use rustls::{Session, ClientConfig, ServerConfig, ClientSession, ServerSession}; 3 | use std::fmt; 4 | use std::io::{self, Read, Write}; 5 | use std::net::{Shutdown, SocketAddr}; 6 | use std::sync::Arc; 7 | use tokio_core::net::TcpStream; 8 | use tokio_io::AsyncWrite; 9 | 10 | pub fn client_handshake(tcp: TcpStream, config: &Arc, name: &str) -> ClientHandshake { 11 | let ss = SecureStream::new(tcp, ClientSession::new(config, name)); 12 | ClientHandshake(Some(ss)) 13 | } 14 | 15 | pub fn server_handshake(tcp: TcpStream, config: &Arc) -> ServerHandshake { 16 | let ss = SecureStream::new(tcp, ServerSession::new(config)); 17 | ServerHandshake(Some(ss)) 18 | } 19 | 20 | /// Securely transmits data. 21 | pub struct SecureStream { 22 | peer: SocketAddr, 23 | local: SocketAddr, 24 | /// The external encrypted side of the socket. 25 | tcp: TcpStream, 26 | /// The internal decrypted side of the socket. 27 | session: I, 28 | } 29 | 30 | impl fmt::Debug for SecureStream { 31 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 32 | f.debug_struct("SecureStream") 33 | .field("peer", &self.peer) 34 | .field("local", &self.local) 35 | .finish() 36 | } 37 | } 38 | 39 | impl SecureStream 40 | where 41 | S: Session, 42 | { 43 | fn new(tcp: TcpStream, session: S) -> SecureStream { 44 | SecureStream { 45 | peer: tcp.peer_addr().unwrap(), 46 | local: tcp.local_addr().unwrap(), 47 | tcp, 48 | session, 49 | } 50 | } 51 | 52 | pub fn peer_addr(&self) -> SocketAddr { 53 | self.peer 54 | } 55 | 56 | pub fn local_addr(&self) -> SocketAddr { 57 | self.local 58 | } 59 | 60 | pub fn tcp_shutdown(&mut self, how: Shutdown) -> io::Result<()> { 61 | trace!("tcp_shutdown: {:?}", self); 62 | self.tcp.shutdown(how) 63 | } 64 | 65 | fn read_tcp_to_session(&mut self) -> Option> { 66 | if !self.session.wants_read() { 67 | trace!("read_tcp_to_session: no read needed: {}", self.peer); 68 | return None; 69 | } 70 | 71 | trace!("read_tcp_to_session: read_tls: {}", self.peer); 72 | match self.session.read_tls(&mut self.tcp) { 73 | Err(e) => { 74 | if e.kind() == io::ErrorKind::WouldBlock { 75 | trace!("read_tcp_to_session: read_tls: {}: {}", self.peer, e); 76 | None 77 | } else { 78 | error!("read_tcp_to_session: read_tls: {}: {}", self.peer, e); 79 | Some(Err(e)) 80 | } 81 | } 82 | Ok(sz) => { 83 | trace!("read_tcp_to_session: read_tls: {} {}B", self.peer, sz); 84 | if sz == 0 { 85 | Some(Ok(sz)) 86 | } else { 87 | trace!("read_tcp_to_session: process_new_packets: {}", self.peer); 88 | match self.session.process_new_packets() { 89 | Ok(_) => Some(Ok(sz)), 90 | Err(e) => { 91 | trace!("read_tcp_to_session: process_new_packets error: {:?}", self); 92 | Some(Err(io::Error::new(io::ErrorKind::Other, e))) 93 | } 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | fn write_session_to_tcp(&mut self) -> io::Result { 101 | trace!("write_session_to_tcp: write_tls: {}", self.peer); 102 | let sz = self.session.write_tls(&mut self.tcp)?; 103 | trace!("write_session_to_tcp: write_tls: {}: {}B", self.peer, sz); 104 | Ok(sz) 105 | } 106 | } 107 | 108 | impl Read for SecureStream 109 | where 110 | S: Session, 111 | { 112 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 113 | trace!("read: {}", self.peer); 114 | let read_ok = match self.read_tcp_to_session() { 115 | None => false, 116 | Some(Ok(_)) => true, 117 | Some(Err(e)) => { 118 | trace!("read: {}: {:?}", self.peer, e.kind()); 119 | return Err(e); 120 | } 121 | }; 122 | 123 | let sz = self.session.read(buf)?; 124 | trace!("read: {}: {}B", self.peer, sz); 125 | if !read_ok && sz == 0 { 126 | Err(io::ErrorKind::WouldBlock.into()) 127 | } else { 128 | Ok(sz) 129 | } 130 | } 131 | } 132 | 133 | impl Write for SecureStream 134 | where 135 | S: Session, 136 | { 137 | fn write(&mut self, buf: &[u8]) -> io::Result { 138 | trace!("write: {}", self.peer); 139 | let sz = self.session.write(buf)?; 140 | trace!("write: {}: {}B", self.peer, sz); 141 | 142 | { 143 | let mut write_ok = true; 144 | while self.session.wants_write() && write_ok { 145 | write_ok = match self.write_session_to_tcp() { 146 | Ok(sz) => sz > 0, 147 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => false, 148 | e @ Err(_) => return e, 149 | }; 150 | } 151 | } 152 | 153 | Ok(sz) 154 | } 155 | 156 | fn flush(&mut self) -> io::Result<()> { 157 | trace!("flush: {:?}", self); 158 | self.session.flush()?; 159 | self.tcp.flush() 160 | } 161 | } 162 | 163 | impl AsyncWrite for SecureStream 164 | where 165 | S: Session, 166 | { 167 | fn shutdown(&mut self) -> Poll<(), io::Error> { 168 | self.session.send_close_notify(); 169 | self.session.write_tls(&mut self.tcp)?; 170 | self.tcp.flush()?; 171 | Ok(Async::Ready(())) 172 | } 173 | } 174 | 175 | /// A future that completes when a server's TLS handshake is complete. 176 | #[derive(Debug)] 177 | pub struct ServerHandshake(Option>); 178 | impl Future for ServerHandshake { 179 | type Item = SecureStream; 180 | type Error = io::Error; 181 | fn poll(&mut self) -> Poll { 182 | trace!("{:?}.poll()", self); 183 | let mut ss = self.0.take().expect( 184 | "poll must not be called after completion", 185 | ); 186 | 187 | // Read and write the handshake. 188 | { 189 | let mut wrote = true; 190 | while ss.session.is_handshaking() && wrote { 191 | if let Some(Err(e)) = ss.read_tcp_to_session() { 192 | trace!("server handshake: {}: error: {}", ss.peer, e); 193 | return Err(e); 194 | }; 195 | trace!("server handshake: write_session_to_tcp: {}", ss.peer); 196 | wrote = ss.session.wants_write() && 197 | match ss.write_session_to_tcp() { 198 | Ok(sz) => { 199 | trace!( 200 | "server handshake: write_session_to_tcp: {}: wrote {}", 201 | ss.peer, 202 | sz 203 | ); 204 | sz > 0 205 | } 206 | Err(e) => { 207 | trace!("server handshake: write_session_to_tcp: {}: {}", ss.peer, e); 208 | if e.kind() != io::ErrorKind::WouldBlock { 209 | return Err(e); 210 | } 211 | false 212 | } 213 | } 214 | } 215 | } 216 | 217 | // If the remote hasn't read everything yet, resume later. 218 | if ss.session.is_handshaking() { 219 | trace!("server handshake: {}: not complete", ss.peer); 220 | self.0 = Some(ss); 221 | return Ok(Async::NotReady); 222 | } 223 | 224 | trace!("server handshake completed: SNI={:?}, ALPN={:?}", 225 | ss.session.get_sni_hostname(), ss.session.get_alpn_protocol()); 226 | 227 | // Finally, acknowledge the handshake is complete. 228 | if ss.session.wants_write() { 229 | trace!("server handshake: write_session_to_tcp: {}: final", ss.peer); 230 | match ss.write_session_to_tcp() { 231 | Ok(sz) => { 232 | trace!( 233 | "server handshake: write_session_to_tcp: {}: final: wrote {}B", 234 | ss.peer, 235 | sz 236 | ); 237 | } 238 | Err(e) => { 239 | trace!( 240 | "server handshake: write_session_to_tcp: {}: final: {}", 241 | ss.peer, 242 | e 243 | ); 244 | if e.kind() != io::ErrorKind::WouldBlock { 245 | return Err(e); 246 | } 247 | } 248 | } 249 | } 250 | 251 | trace!("server handshake: {}: complete", ss.peer); 252 | Ok(Async::Ready(ss)) 253 | } 254 | } 255 | 256 | /// A future that completes when a client's TLS handshake is complete. 257 | #[derive(Debug)] 258 | pub struct ClientHandshake(Option>); 259 | 260 | impl Future for ClientHandshake { 261 | type Item = SecureStream; 262 | type Error = io::Error; 263 | fn poll(&mut self) -> Poll { 264 | trace!("{:?}.poll()", self); 265 | let mut ss = self.0.take().expect( 266 | "poll must not be called after completion", 267 | ); 268 | 269 | // Read and write the handshake. 270 | { 271 | let mut read_ok = true; 272 | let mut write_ok = true; 273 | while ss.session.is_handshaking() && (read_ok || write_ok) { 274 | trace!("client handshake: read_tcp_to_session: {}", ss.peer); 275 | read_ok = match ss.read_tcp_to_session() { 276 | None => { 277 | trace!( 278 | "client handshake: read_tcp_to_session: {}: not ready", 279 | ss.peer 280 | ); 281 | false 282 | } 283 | Some(Ok(sz)) => { 284 | trace!( 285 | "client handshake: read_tcp_to_session: {}: {}B", 286 | ss.peer, 287 | sz 288 | ); 289 | sz > 0 290 | } 291 | Some(Err(e)) => { 292 | trace!( 293 | "client handshake: read_tcp_to_session: {}: error: {}", 294 | ss.peer, 295 | e 296 | ); 297 | return Err(e); 298 | } 299 | }; 300 | 301 | trace!("client handshake: write_session_to_tcp: {}", ss.peer); 302 | write_ok = ss.session.wants_write() && 303 | match ss.write_session_to_tcp() { 304 | Ok(sz) => { 305 | trace!( 306 | "client handshake: write_session_to_tcp: {}: wrote {}", 307 | ss.peer_addr(), 308 | sz 309 | ); 310 | sz > 0 311 | } 312 | Err(e) => { 313 | trace!( 314 | "client handshake: write_session_to_tcp: {}: {}", 315 | ss.peer_addr(), 316 | e 317 | ); 318 | if e.kind() != io::ErrorKind::WouldBlock { 319 | return Err(e); 320 | } 321 | false 322 | } 323 | }; 324 | } 325 | } 326 | 327 | // If the remote hasn't read everything yet, resume later. 328 | if ss.session.is_handshaking() { 329 | trace!("handshake: {}: not complete", ss.peer_addr()); 330 | self.0 = Some(ss); 331 | return Ok(Async::NotReady); 332 | } 333 | 334 | trace!("handshake: {}: complete", ss.peer_addr()); 335 | Ok(Async::Ready(ss)) 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/server/mod.rs: -------------------------------------------------------------------------------- 1 | //! TODO `dst_name` should be chosen dynamically. 2 | 3 | use super::Path; 4 | use super::connection::{Connection, Socket, ctx, secure, socket}; 5 | use super::router::Router; 6 | use futures::{Async, Future, Poll, Stream, future}; 7 | use rustls; 8 | use std::{io, net}; 9 | use std::cell::RefCell; 10 | use std::rc::Rc; 11 | use std::sync::Arc; 12 | use std::time::Duration; 13 | use tacho; 14 | use tokio_core::net::{TcpListener, TcpStream}; 15 | use tokio_core::reactor::Handle; 16 | use tokio_timer::Timer; 17 | 18 | mod config; 19 | mod sni; 20 | pub use self::config::{Error as ConfigError, ServerConfig}; 21 | 22 | const DEFAULT_MAX_CONCURRENCY: usize = 100000; 23 | 24 | /// Builds a server that is not yet bound on a port. 25 | fn unbound( 26 | listen_addr: net::SocketAddr, 27 | dst_name: Path, 28 | router: Router, 29 | buf: Rc>>, 30 | tls: Option, 31 | connect_timeout: Option, 32 | connection_lifetime: Option, 33 | max_concurrency: usize, 34 | metrics: &tacho::Scope, 35 | ) -> Unbound { 36 | let metrics = metrics.clone().prefixed("srv"); 37 | Unbound { 38 | listen_addr, 39 | dst_name, 40 | router, 41 | buf, 42 | tls, 43 | connect_timeout, 44 | connection_lifetime, 45 | max_concurrency, 46 | metrics, 47 | } 48 | } 49 | 50 | pub struct Unbound { 51 | listen_addr: net::SocketAddr, 52 | dst_name: Path, 53 | router: Router, 54 | buf: Rc>>, 55 | tls: Option, 56 | metrics: tacho::Scope, 57 | connect_timeout: Option, 58 | connection_lifetime: Option, 59 | max_concurrency: usize, 60 | } 61 | impl Unbound { 62 | pub fn listen_addr(&self) -> net::SocketAddr { 63 | self.listen_addr 64 | } 65 | 66 | pub fn dst_name(&self) -> &Path { 67 | &self.dst_name 68 | } 69 | 70 | fn init_src_connection( 71 | src_tcp: TcpStream, 72 | metrics: &Metrics, 73 | tls: &Option, 74 | ) -> Box, Error = io::Error>> { 75 | 76 | let sock: Box> = match tls.as_ref() { 77 | None => Box::new(future::ok(socket::plain(src_tcp))), 78 | Some(tls) => { 79 | // TODO we should be able to get metadata from a TLS handshake but we can't! 80 | let sock = tls.handshake_latency 81 | .time(secure::server_handshake(src_tcp, &tls.config)) 82 | .map(socket::secure_server); 83 | Box::new(sock) 84 | } 85 | }; 86 | 87 | let metrics = metrics.per_conn.clone(); 88 | let conn = sock.map(move |sock| { 89 | let ctx = SrcCtx { 90 | rx_bytes_total: 0, 91 | tx_bytes_total: 0, 92 | metrics, 93 | }; 94 | Connection::new(sock, ctx) 95 | }); 96 | Box::new(conn) 97 | } 98 | 99 | pub fn bind(self, reactor: &Handle, timer: &Timer) -> io::Result { 100 | debug!("routing on {} to {}", self.listen_addr, self.dst_name); 101 | let listen = TcpListener::bind(&self.listen_addr, reactor)?; 102 | let bound_addr = listen.local_addr().unwrap(); 103 | 104 | let metrics = self.metrics.labeled("srv_addr", format!("{}", bound_addr)); 105 | let tls = self.tls.map(|tls| { 106 | BoundTls { 107 | config: tls.config, 108 | handshake_latency: metrics.clone().prefixed("tls").timer_us("handshake_us"), 109 | } 110 | }); 111 | 112 | let connect_metrics = metrics.clone().prefixed("connect"); 113 | let stream_metrics = metrics.clone().prefixed("stream"); 114 | let per_conn = ConnMetrics { 115 | rx_bytes: stream_metrics.counter("rx_bytes"), 116 | tx_bytes: stream_metrics.counter("tx_bytes"), 117 | rx_bytes_per_conn: stream_metrics.stat("connection_rx_bytes"), 118 | tx_bytes_per_conn: stream_metrics.stat("connection_tx_bytes"), 119 | latency: connect_metrics.timer_us("latency_us"), 120 | duration: stream_metrics.timer_ms("duration_ms"), 121 | }; 122 | let metrics = Metrics { 123 | accepts: metrics.counter("accepts"), 124 | closes: metrics.counter("closes"), 125 | failures: metrics.counter("failures"), 126 | active: metrics.gauge("active"), 127 | waiters: metrics.gauge("waiters"), 128 | connect_failures: FailureMetrics::new(&connect_metrics, "failure"), 129 | stream_failures: FailureMetrics::new(&stream_metrics, "failure"), 130 | per_conn, 131 | }; 132 | 133 | // TODO determine dst_addr dynamically. 134 | let dst_name = self.dst_name; 135 | let router = self.router; 136 | let connect_timeout = self.connect_timeout; 137 | let connection_lifetime = self.connection_lifetime; 138 | let buf = self.buf; 139 | 140 | let reactor = reactor.clone(); 141 | let timer = timer.clone(); 142 | let serving = listen 143 | .incoming() 144 | .map(move |(src_tcp, src_addr)| { 145 | trace!("received incoming connection from {}", src_addr); 146 | metrics.accepts.incr(1); 147 | let active = metrics.active.clone(); 148 | active.incr(1); 149 | let waiters = metrics.waiters.clone(); 150 | waiters.incr(1); 151 | 152 | // Finish accepting the connection from the server. 153 | // TODO determine dst_addr dynamically. 154 | let src = Unbound::init_src_connection(src_tcp, &metrics, &tls); 155 | 156 | // Obtain a balancing endpoint selector for the given destination. 157 | let balancer = router.route(&dst_name, &reactor, &timer); 158 | 159 | // Once the incoming connection is ready and we have a balancer ready, obtain an 160 | // outbound connection and begin streaming. We obtain an outbound connection after 161 | // the incoming handshake is complete so that we don't waste outbound connections 162 | // on failed inbound connections. 163 | let connect = src.join(balancer).and_then(move |(src, b)| { 164 | b.connect().map(move |dst| (src, dst)) 165 | }); 166 | 167 | // Enforce a connection timeout, measure successful connection 168 | // latencies and failure counts. 169 | let connect = { 170 | // Measure the time until the connection is established, if it completes. 171 | let c = timeout( 172 | metrics.per_conn.latency.time(connect), 173 | connect_timeout, 174 | &timer, 175 | ); 176 | let fails = metrics.connect_failures.clone(); 177 | c.then(move |res| match res { 178 | Ok((src, dst)) => { 179 | trace!("connection ready for {} to {}", src_addr, dst.peer_addr()); 180 | waiters.decr(1); 181 | Ok((src, dst)) 182 | } 183 | Err(e) => { 184 | trace!("connection failed for {}: {}", src_addr, e); 185 | waiters.decr(1); 186 | fails.record(&e); 187 | Err(e) 188 | } 189 | }) 190 | }; 191 | 192 | // Copy data between the endpoints. 193 | let stream = { 194 | let buf = buf.clone(); 195 | let stream_fails = metrics.stream_failures.clone(); 196 | let duration = metrics.per_conn.duration.clone(); 197 | let lifetime = connection_lifetime; 198 | let timer = timer.clone(); 199 | connect.and_then(move |(src, dst)| { 200 | // Enforce a timeout on total connection lifetime. 201 | let dst_addr = dst.peer_addr(); 202 | let duplex = src.into_duplex(dst, buf); 203 | duration.time(timeout(duplex, lifetime, &timer)).then( 204 | move |res| match res { 205 | Ok(_) => { 206 | trace!("stream succeeded for {} to {}", src_addr, dst_addr); 207 | Ok(()) 208 | } 209 | Err(e) => { 210 | trace!("stream failed for {} to {}: {}", src_addr, dst_addr, e); 211 | stream_fails.record(&e); 212 | Err(e) 213 | } 214 | }, 215 | ) 216 | }) 217 | }; 218 | 219 | let closes = metrics.closes.clone(); 220 | let failures = metrics.failures.clone(); 221 | stream.then(move |ret| { 222 | active.decr(1); 223 | if ret.is_ok() { 224 | closes.incr(1); 225 | } else { 226 | failures.incr(1); 227 | } 228 | Ok(()) 229 | }) 230 | }) 231 | .buffer_unordered(self.max_concurrency); 232 | 233 | Ok(Bound(Box::new(serving))) 234 | } 235 | } 236 | 237 | pub struct Bound(Box + 'static>); 238 | impl Future for Bound { 239 | type Item = (); 240 | type Error = io::Error; 241 | fn poll(&mut self) -> Poll { 242 | loop { 243 | match self.0.poll() { 244 | Ok(Async::NotReady) => return Ok(Async::NotReady), 245 | Ok(Async::Ready(None)) => return Ok(Async::Ready(())), 246 | Ok(Async::Ready(Some(_))) | 247 | Err(_) => {} 248 | } 249 | } 250 | } 251 | } 252 | 253 | struct Metrics { 254 | accepts: tacho::Counter, 255 | closes: tacho::Counter, 256 | failures: tacho::Counter, 257 | active: tacho::Gauge, 258 | waiters: tacho::Gauge, 259 | per_conn: ConnMetrics, 260 | connect_failures: FailureMetrics, 261 | stream_failures: FailureMetrics, 262 | } 263 | 264 | #[derive(Clone)] 265 | struct FailureMetrics { 266 | timeouts: tacho::Counter, 267 | other: tacho::Counter, 268 | } 269 | impl FailureMetrics { 270 | fn new(metrics: &tacho::Scope, key: &'static str) -> FailureMetrics { 271 | FailureMetrics { 272 | timeouts: metrics.clone().labeled("cause", "timeout").counter(key), 273 | other: metrics.clone().labeled("cause", "other").counter(key), 274 | } 275 | } 276 | 277 | fn record(&self, e: &io::Error) { 278 | if e.kind() == io::ErrorKind::TimedOut { 279 | self.timeouts.incr(1); 280 | } else { 281 | self.other.incr(1); 282 | } 283 | } 284 | } 285 | 286 | #[derive(Clone)] 287 | struct ConnMetrics { 288 | rx_bytes: tacho::Counter, 289 | tx_bytes: tacho::Counter, 290 | rx_bytes_per_conn: tacho::Stat, 291 | tx_bytes_per_conn: tacho::Stat, 292 | duration: tacho::Timer, 293 | latency: tacho::Timer, 294 | } 295 | 296 | fn timeout( 297 | fut: F, 298 | timeout: Option, 299 | timer: &Timer, 300 | ) -> Box> 301 | where 302 | F: Future + 'static, 303 | { 304 | match timeout { 305 | None => Box::new(fut), 306 | Some(duration) => { 307 | let timer = timer.clone(); 308 | let fut = future::lazy(move || timer.timeout(fut, duration)); 309 | Box::new(fut) 310 | } 311 | } 312 | } 313 | 314 | #[derive(Clone)] 315 | pub struct UnboundTls { 316 | config: Arc, 317 | } 318 | 319 | #[derive(Clone)] 320 | pub struct BoundTls { 321 | config: Arc, 322 | handshake_latency: tacho::Timer, 323 | } 324 | 325 | pub struct SrcCtx { 326 | rx_bytes_total: usize, 327 | tx_bytes_total: usize, 328 | metrics: ConnMetrics, 329 | } 330 | impl ctx::Ctx for SrcCtx { 331 | fn read(&mut self, sz: usize) { 332 | self.rx_bytes_total += sz; 333 | self.metrics.rx_bytes.incr(sz); 334 | } 335 | 336 | fn wrote(&mut self, sz: usize) { 337 | self.tx_bytes_total += sz; 338 | self.metrics.tx_bytes.incr(sz); 339 | } 340 | } 341 | impl Drop for SrcCtx { 342 | fn drop(&mut self) { 343 | self.metrics.rx_bytes_per_conn.add( 344 | self.rx_bytes_total as u64, 345 | ); 346 | self.metrics.tx_bytes_per_conn.add( 347 | self.tx_bytes_total as u64, 348 | ); 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | //! Provides all of the utilities needed to load a configuration and run a process. 2 | 3 | use super::{admin, resolver, router, server}; 4 | use super::balancer::BalancerFactory; 5 | use super::connector::{ConfigError as ConnectorConfigError, ConnectorFactoryConfig}; 6 | use super::resolver::{ConfigError as ResolverConfigError, NamerdConfig}; 7 | use super::server::ConfigError as ServerConfigError; 8 | use futures::{Future, Stream, sync}; 9 | use hyper; 10 | use hyper::server::Http; 11 | use serde_json; 12 | use serde_yaml; 13 | use std::cell::RefCell; 14 | use std::collections::VecDeque; 15 | use std::net; 16 | use std::rc::Rc; 17 | use std::time::{Duration, Instant}; 18 | use tacho; 19 | use tokio_core::net::TcpListener; 20 | use tokio_core::reactor::{Core, Handle}; 21 | use tokio_timer::Timer; 22 | 23 | const DEFAULT_ADMIN_PORT: u16 = 9989; 24 | const DEFAULT_BUFFER_SIZE_BYTES: usize = 16 * 1024; 25 | const DEFAULT_GRACE_SECS: u64 = 10; 26 | const DEFAULT_METRICS_INTERVAL_SECS: u64 = 60; 27 | 28 | /// An app-specific Result type. 29 | pub type Result = ::std::result::Result; 30 | 31 | /// Describes a configuration error. 32 | #[derive(Debug)] 33 | pub enum Error { 34 | /// A JSON syntax error. 35 | Json(serde_json::Error), 36 | 37 | /// A Yaml syntax error. 38 | Yaml(serde_yaml::Error), 39 | 40 | /// Indicates a a misconfigured client. 41 | Connector(ConnectorConfigError), 42 | 43 | /// Indicates a misconfigured interpreter. 44 | Interpreter(ResolverConfigError), 45 | 46 | /// Indicats a misconfigured server. 47 | Server(ServerConfigError), 48 | } 49 | 50 | /// Signals a receiver to shutdown by the provided deadline. 51 | pub type Closer = sync::oneshot::Sender; 52 | 53 | /// Signals that the receiver should release its resources by the provided deadline. 54 | pub type Closed = sync::oneshot::Receiver; 55 | 56 | /// Creates a thread-safe shutdown latch. 57 | pub fn closer() -> (Closer, Closed) { 58 | sync::oneshot::channel() 59 | } 60 | 61 | /// Holds the configuration for a linkerd-tcp instance. 62 | #[derive(Clone, Debug, Serialize, Deserialize)] 63 | #[serde(deny_unknown_fields, rename_all = "camelCase")] 64 | pub struct AppConfig { 65 | /// Configures the processes's admin server. 66 | pub admin: Option, 67 | 68 | /// Configures one or more routers. 69 | pub routers: Vec, 70 | 71 | /// Configures the shared buffer used for transferring data. 72 | pub buffer_size_bytes: Option, 73 | } 74 | 75 | impl ::std::str::FromStr for AppConfig { 76 | type Err = Error; 77 | 78 | /// Parses a JSON- or YAML-formatted configuration file. 79 | fn from_str(txt: &str) -> Result { 80 | let txt = txt.trim_left(); 81 | if txt.starts_with('{') { 82 | serde_json::from_str(txt).map_err(Error::Json) 83 | } else { 84 | serde_yaml::from_str(txt).map_err(Error::Yaml) 85 | } 86 | } 87 | } 88 | 89 | impl AppConfig { 90 | /// Build an App from a configuration. 91 | pub fn into_app(mut self) -> Result { 92 | // Create a shared transfer buffer to be used for all stream proxying. 93 | let buf = { 94 | let sz = self.buffer_size_bytes.unwrap_or(DEFAULT_BUFFER_SIZE_BYTES); 95 | Rc::new(RefCell::new(vec![0 as u8; sz])) 96 | }; 97 | 98 | let (metrics, reporter) = tacho::new(); 99 | let metrics = metrics.prefixed("l5d"); 100 | 101 | // Load all router configurations. 102 | // 103 | // Separate resolver tasks are created to be executed in the admin thread's 104 | // reactor so that service discovery lookups are performed out of the serving 105 | // thread. 106 | let mut routers = VecDeque::with_capacity(self.routers.len()); 107 | let mut resolvers = VecDeque::with_capacity(self.routers.len()); 108 | for config in self.routers.drain(..) { 109 | let mut r = config.into_router(buf.clone(), &metrics)?; 110 | let e = r.resolver_executor.take().expect( 111 | "router missing resolver executor", 112 | ); 113 | routers.push_back(r); 114 | resolvers.push_back(e); 115 | } 116 | 117 | // Read the admin server configuration and bundle it an AdminRunner. 118 | let admin = { 119 | let addr = { 120 | let ip = self.admin.as_ref().and_then(|a| a.ip).unwrap_or_else( 121 | localhost_addr, 122 | ); 123 | let port = self.admin.as_ref().and_then(|a| a.port).unwrap_or( 124 | DEFAULT_ADMIN_PORT, 125 | ); 126 | net::SocketAddr::new(ip, port) 127 | }; 128 | let grace = { 129 | let s = self.admin 130 | .as_ref() 131 | .and_then(|admin| admin.grace_secs) 132 | .unwrap_or(DEFAULT_GRACE_SECS); 133 | Duration::from_secs(s) 134 | }; 135 | let metrics_interval = { 136 | let s = self.admin 137 | .as_ref() 138 | .and_then(|admin| admin.metrics_interval_secs) 139 | .unwrap_or(DEFAULT_METRICS_INTERVAL_SECS); 140 | Duration::from_secs(s) 141 | }; 142 | AdminRunner { 143 | addr, 144 | reporter, 145 | resolvers, 146 | grace, 147 | metrics_interval, 148 | } 149 | }; 150 | 151 | Ok(App { 152 | routers: routers, 153 | admin: admin, 154 | }) 155 | } 156 | } 157 | 158 | 159 | fn localhost_addr() -> net::IpAddr { 160 | net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)) 161 | } 162 | 163 | /// Holds configuraed tasks to be spawned. 164 | pub struct App { 165 | /// Executes configured routers. 166 | pub routers: VecDeque, 167 | /// Executes the admin server. 168 | pub admin: AdminRunner, 169 | } 170 | 171 | /// Holds the configuration for a single stream router. 172 | #[derive(Clone, Debug, Serialize, Deserialize)] 173 | #[serde(deny_unknown_fields, rename_all = "camelCase")] 174 | pub struct RouterConfig { 175 | /// A descriptive name for this router. For stats reporting. 176 | pub label: String, 177 | 178 | /// The configuration for one or more servers. 179 | pub servers: Vec, 180 | 181 | /// Determines how outbound connections are initiated. 182 | /// 183 | /// By default, connections are clear TCP. 184 | pub client: Option, 185 | 186 | /// Interprets request destinations into a stream of address pool updates. 187 | pub interpreter: InterpreterConfig, 188 | } 189 | 190 | impl RouterConfig { 191 | /// Consumes and validates this configuration to produce a router initializer. 192 | fn into_router( 193 | mut self, 194 | buf: Rc>>, 195 | metrics: &tacho::Scope, 196 | ) -> Result { 197 | let metrics = metrics.clone().labeled("rt", self.label); 198 | 199 | // Each router has its own resolver/executor pair. The resolver is used by the 200 | // router. The resolver executor is used to drive execution in another thread. 201 | let (resolver, resolver_exec) = match self.interpreter { 202 | InterpreterConfig::NamerdHttp(config) => { 203 | let namerd = config.into_namerd(&metrics).map_err(Error::Interpreter)?; 204 | resolver::new(namerd) 205 | } 206 | }; 207 | 208 | let balancer = { 209 | let metrics = metrics.clone().prefixed("balancer"); 210 | let client = self.client 211 | .unwrap_or_default() 212 | .mk_connector_factory() 213 | .map_err(Error::Connector)?; 214 | BalancerFactory::new(client, &metrics) 215 | }; 216 | let router = router::new(resolver, balancer, &metrics); 217 | 218 | let mut servers = VecDeque::with_capacity(self.servers.len()); 219 | for config in self.servers.drain(..) { 220 | // The router and transfer buffer are shareable across servers. 221 | let server = config 222 | .mk_server(router.clone(), buf.clone(), &metrics) 223 | .map_err(Error::Server)?; 224 | servers.push_back(server); 225 | } 226 | 227 | Ok(RouterSpawner { 228 | servers: servers, 229 | resolver_executor: Some(resolver_exec), 230 | }) 231 | } 232 | } 233 | 234 | /// Spawns a router by spawning all of its serving interfaces. 235 | pub struct RouterSpawner { 236 | servers: VecDeque, 237 | resolver_executor: Option, 238 | } 239 | 240 | impl RouterSpawner { 241 | /// Spawns a router by spawning all of its serving interfaces. 242 | /// 243 | /// Returns successfully if all servers have been bound and spawned correctly. 244 | pub fn spawn(mut self, reactor: &Handle, timer: &Timer) -> Result<()> { 245 | while let Some(unbound) = self.servers.pop_front() { 246 | info!( 247 | "routing on {} to {}", 248 | unbound.listen_addr(), 249 | unbound.dst_name() 250 | ); 251 | let bound = unbound.bind(reactor, timer).expect("failed to bind server"); 252 | reactor.spawn(bound.map_err(|_| {})); 253 | } 254 | Ok(()) 255 | } 256 | } 257 | 258 | /// Configures an interpreter. 259 | /// 260 | /// Currently, only the io.l5d.namerd.http interpreter is supported. 261 | #[derive(Clone, Debug, Serialize, Deserialize)] 262 | #[serde(deny_unknown_fields, tag = "kind")] 263 | pub enum InterpreterConfig { 264 | /// Polls namerd for updates. 265 | #[serde(rename = "io.l5d.namerd.http")] 266 | NamerdHttp(NamerdConfig), 267 | } 268 | 269 | /// Configures the admin server. 270 | #[derive(Clone, Debug, Serialize, Deserialize)] 271 | #[serde(deny_unknown_fields, rename_all = "camelCase")] 272 | pub struct AdminConfig { 273 | /// The port on which the admin server listens. 274 | pub port: Option, 275 | 276 | /// The IP address on which the admin server listens. 277 | pub ip: Option, 278 | 279 | /// The interval at which metrics should be snapshot (and reset) for export. 280 | pub metrics_interval_secs: Option, 281 | 282 | /// The amount of time to wait for connections to complete between the /admin/shutdown 283 | /// endpoint being triggered and the process exiting. 284 | pub grace_secs: Option, 285 | } 286 | 287 | /// Spawns resolvers before running . 288 | pub struct AdminRunner { 289 | addr: net::SocketAddr, 290 | reporter: tacho::Reporter, 291 | resolvers: VecDeque, 292 | grace: Duration, 293 | metrics_interval: Duration, 294 | } 295 | 296 | impl AdminRunner { 297 | /// Runs the admin server on the provided reactor. 298 | /// 299 | /// When the _shutdown_ endpoint is triggered, a shutdown deadline is sent on 300 | /// `closer`. 301 | pub fn run(self, closer: Closer, reactor: &mut Core, timer: &Timer) -> Result<()> { 302 | let AdminRunner { 303 | addr, 304 | grace, 305 | metrics_interval, 306 | mut reporter, 307 | mut resolvers, 308 | } = self; 309 | 310 | let handle = reactor.handle(); 311 | while let Some(resolver) = resolvers.pop_front() { 312 | handle.spawn(resolver.execute(&handle, timer)); 313 | } 314 | 315 | let prom_export = Rc::new(RefCell::new(String::with_capacity(8 * 1024))); 316 | let reporting = { 317 | let prom_export = prom_export.clone(); 318 | timer.interval(metrics_interval).map_err(|_| {}).for_each( 319 | move |_| { 320 | let report = reporter.take(); 321 | let mut prom_export = prom_export.borrow_mut(); 322 | prom_export.clear(); 323 | tacho::prometheus::write(&mut *prom_export, &report) 324 | .expect("error foramtting metrics for prometheus"); 325 | Ok(()) 326 | }, 327 | ) 328 | }; 329 | 330 | handle.spawn(reporting); 331 | 332 | let serving = { 333 | let listener = { 334 | info!("admin listening on http://{}.", addr); 335 | TcpListener::bind(&addr, &handle).expect("unable to listen") 336 | }; 337 | 338 | let serve_handle = handle.clone(); 339 | let server = 340 | admin::Admin::new(prom_export, closer, grace, handle.clone(), timer.clone()); 341 | let http = Http::::new(); 342 | listener.incoming() 343 | .for_each(move |(tcp, _)| { 344 | let serve = http.serve_connection(tcp, server.clone()) 345 | .map_err(|err| { 346 | error!("error serving admin: {:?}", err); 347 | }) 348 | .map(|_| ()); 349 | serve_handle.spawn(serve); 350 | Ok(()) 351 | }) 352 | }; 353 | 354 | reactor.run(serving).unwrap(); 355 | 356 | Ok(()) 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/balancer/dispatcher.rs: -------------------------------------------------------------------------------- 1 | use super::{Endpoints, EndpointMap, Waiter, WeightedAddr}; 2 | use super::endpoint::{self, Endpoint}; 3 | use super::super::Path; 4 | use super::super::connection::Connection; 5 | use super::super::connector::Connector; 6 | use super::super::resolver::Resolve; 7 | use futures::{Future, Stream, Poll, Async}; 8 | use rand::{self, Rng}; 9 | use std::collections::VecDeque; 10 | use std::io; 11 | use std::time::{Duration, Instant}; 12 | use tacho; 13 | use tokio_core::reactor::Handle; 14 | use tokio_timer::Timer; 15 | 16 | pub fn new( 17 | reactor: Handle, 18 | timer: Timer, 19 | dst_name: Path, 20 | connector: Connector, 21 | resolve: Resolve, 22 | waiters_rx: S, 23 | endpoints: Endpoints, 24 | metrics: &tacho::Scope, 25 | ) -> Dispatcher 26 | where 27 | S: Stream, 28 | { 29 | Dispatcher { 30 | reactor, 31 | timer, 32 | dst_name, 33 | endpoints, 34 | resolve, 35 | waiters_rx, 36 | max_waiters: connector.max_waiters(), 37 | min_connections: connector.min_connections(), 38 | fail_limit: connector.failure_limit(), 39 | fail_penalty: connector.failure_penalty(), 40 | connector, 41 | connecting: VecDeque::default(), 42 | connected: VecDeque::default(), 43 | waiters: VecDeque::default(), 44 | metrics: Metrics::new(metrics), 45 | } 46 | } 47 | 48 | /// Initiates load balanced outbound connections. 49 | pub struct Dispatcher { 50 | reactor: Handle, 51 | timer: Timer, 52 | 53 | /// Names the destination replica set to which connections are being dispatched. 54 | dst_name: Path, 55 | 56 | /// Handles destination-specific connection policy. 57 | connector: Connector, 58 | 59 | /// Provides new service discovery resolutions as a Stream. 60 | resolve: Resolve, 61 | 62 | /// Holds the state of all available/failed/retired endpoints. 63 | endpoints: Endpoints, 64 | 65 | /// Limits the number of consecutive failures allowed before an endpoint is marked as 66 | /// failed. 67 | fail_limit: usize, 68 | 69 | /// Controls how long an endpoint will be marked as failed before being considered for 70 | /// new connections.s 71 | fail_penalty: Duration, 72 | 73 | /// Controls the minimum number of connecting/connected connections to be maintained 74 | /// at all times. 75 | min_connections: usize, 76 | 77 | /// A queue of pending connections. 78 | connecting: VecDeque>, 79 | 80 | /// A queue of ready connections to be dispatched ot waiters. 81 | connected: VecDeque>, 82 | 83 | /// Provides new connection requests as a Stream.. 84 | waiters_rx: W, 85 | 86 | /// A queue of waiters that have not yet received a connection. 87 | waiters: VecDeque, 88 | 89 | /// Limits the size of `waiters`. 90 | max_waiters: usize, 91 | 92 | metrics: Metrics, 93 | } 94 | 95 | impl Dispatcher 96 | where 97 | W: Stream, 98 | { 99 | /// Receives and attempts to dispatch new waiters. 100 | /// 101 | /// If there are no available connections to be dispatched, up to `max_waiters` are 102 | /// buffered. 103 | fn recv_waiters(&mut self) { 104 | while self.waiters.len() < self.max_waiters { 105 | match self.waiters_rx.poll() { 106 | Ok(Async::Ready(None)) | 107 | Ok(Async::NotReady) => return, 108 | Err(_) => { 109 | error!("{}: error from waiters channel", self.dst_name); 110 | } 111 | Ok(Async::Ready(Some(w))) => { 112 | match self.connected.pop_front() { 113 | None => self.waiters.push_back(w), 114 | Some(conn) => { 115 | if let Err(conn) = w.send(conn) { 116 | self.connected.push_front(conn); 117 | } 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | fn poll_connecting(&mut self) { 126 | debug!("polling {} pending connections", self.connecting.len()); 127 | for _ in 0..self.connecting.len() { 128 | let mut connecting = self.connecting.pop_front().unwrap(); 129 | match connecting.poll() { 130 | Err(e) => { 131 | debug!("connection failed: {}", e); 132 | self.metrics.pending.decr(1); 133 | self.metrics.failure(&e); 134 | } 135 | Ok(Async::NotReady) => { 136 | trace!("connection pending"); 137 | self.connecting.push_back(connecting); 138 | } 139 | Ok(Async::Ready(connected)) => { 140 | debug!("connected"); 141 | self.metrics.connects.incr(1); 142 | self.metrics.pending.decr(1); 143 | self.metrics.open.incr(1); 144 | self.connected.push_back(connected) 145 | } 146 | } 147 | } 148 | } 149 | 150 | fn update_endpoints(&mut self) { 151 | if let Some(addrs) = self.poll_resolve() { 152 | self.endpoints.update_resolved(&addrs); 153 | debug!( 154 | "balancer updated: available={} failed={}, retired={}", 155 | self.endpoints.available().len(), 156 | self.endpoints.failed().len(), 157 | self.endpoints.retired().len() 158 | ); 159 | } 160 | 161 | self.endpoints.update_failed( 162 | self.fail_limit, 163 | self.fail_penalty, 164 | ); 165 | 166 | } 167 | 168 | fn poll_resolve(&mut self) -> Option> { 169 | // Poll the resolution until it's 170 | let mut addrs = None; 171 | loop { 172 | match self.resolve.poll() { 173 | Ok(Async::NotReady) => { 174 | return addrs; 175 | } 176 | Ok(Async::Ready(None)) => { 177 | info!("resolution complete! no further updates will be received"); 178 | return addrs; 179 | } 180 | // 181 | Err(e) => { 182 | error!("{}: resolver error: {:?}", self.dst_name, e); 183 | } 184 | Ok(Async::Ready(Some(Err(e)))) => { 185 | error!("{}: resolver error: {:?}", self.dst_name, e); 186 | } 187 | Ok(Async::Ready(Some(Ok(a)))) => { 188 | addrs = Some(a); 189 | } 190 | } 191 | } 192 | } 193 | 194 | fn init_connecting(&mut self) { 195 | let available = self.endpoints.available(); 196 | if available.is_empty() { 197 | trace!("no available endpoints"); 198 | return; 199 | } 200 | 201 | let needed = { 202 | let needed = self.min_connections + self.waiters.len(); 203 | let pending = self.connecting.len() + self.connected.len(); 204 | if needed < pending { 205 | 0 206 | } else { 207 | needed - pending 208 | } 209 | }; 210 | debug!("initiating {} connections", needed); 211 | 212 | let mut rng = rand::thread_rng(); 213 | for _ in 0..needed { 214 | match select_endpoint(&mut rng, available) { 215 | None => { 216 | trace!("no endpoints ready"); 217 | self.metrics.unavailable.incr(1); 218 | return; 219 | } 220 | Some(ep) => { 221 | self.metrics.attempts.incr(1); 222 | let mut conn = { 223 | let sock = self.connector.connect( 224 | &ep.peer_addr(), 225 | &self.reactor, 226 | &self.timer, 227 | ); 228 | let c = ep.connect(sock, &self.metrics.connection_duration); 229 | self.metrics.connect_latency.time(c) 230 | }; 231 | match conn.poll() { 232 | Err(e) => { 233 | debug!("connection failed: {}", e); 234 | self.metrics.failure(&e); 235 | } 236 | Ok(Async::NotReady) => { 237 | trace!("connection pending"); 238 | self.metrics.pending.incr(1); 239 | self.connecting.push_back(conn); 240 | } 241 | Ok(Async::Ready(conn)) => { 242 | debug!("connected"); 243 | self.metrics.connects.incr(1); 244 | self.metrics.pending.decr(1); 245 | self.metrics.open.incr(1); 246 | self.connected.push_back(conn); 247 | } 248 | } 249 | } 250 | } 251 | } 252 | } 253 | 254 | fn dispatch_connected_to_waiters(&mut self) { 255 | debug!( 256 | "dispatching {} connections to {} waiters", 257 | self.connected.len(), 258 | self.waiters.len() 259 | ); 260 | while let Some(conn) = self.connected.pop_front() { 261 | if let Err(conn) = self.dispatch_to_next_waiter(conn) { 262 | self.connected.push_front(conn); 263 | return; 264 | } 265 | } 266 | } 267 | 268 | fn dispatch_to_next_waiter( 269 | &mut self, 270 | conn: endpoint::Connection, 271 | ) -> Result<(), endpoint::Connection> { 272 | match self.waiters.pop_front() { 273 | None => Err(conn), 274 | Some(waiter) => { 275 | match waiter.send(conn) { 276 | Ok(()) => Ok(()), 277 | Err(conn) => self.dispatch_to_next_waiter(conn), 278 | } 279 | } 280 | } 281 | } 282 | 283 | fn record(&self, t0: Instant) { 284 | { 285 | let mut open = 0; 286 | let mut pending = 0; 287 | { 288 | let available = self.endpoints.available(); 289 | self.metrics.available.set(available.len()); 290 | for ep in available.values() { 291 | let state = ep.state(); 292 | open += state.open_conns; 293 | pending += state.pending_conns; 294 | } 295 | } 296 | { 297 | let failed = self.endpoints.failed(); 298 | self.metrics.failed.set(failed.len()); 299 | for &(_, ref ep) in failed.values() { 300 | let state = ep.state(); 301 | open += state.open_conns; 302 | pending += state.pending_conns; 303 | } 304 | } 305 | { 306 | let retired = self.endpoints.retired(); 307 | self.metrics.retired.set(retired.len()); 308 | for ep in retired.values() { 309 | let state = ep.state(); 310 | open += state.open_conns; 311 | pending += state.pending_conns; 312 | } 313 | } 314 | self.metrics.open.set(open); 315 | self.metrics.pending.set(pending); 316 | } 317 | self.metrics.waiters.set(self.waiters.len()); 318 | self.metrics.poll_time.record_since(t0); 319 | } 320 | } 321 | 322 | /// Buffers up to `max_waiters` concurrent connection requests, along with corresponding 323 | /// connection attempts. 324 | impl Future for Dispatcher 325 | where 326 | S: Stream, 327 | { 328 | type Item = (); 329 | type Error = io::Error; 330 | 331 | fn poll(&mut self) -> Poll<(), io::Error> { 332 | let t0 = Instant::now(); 333 | 334 | // Poll all pending connections. Newly established connections are added to the 335 | // `connected` queue, to be dispatched. 336 | self.poll_connecting(); 337 | 338 | // Now that we may have new established connnections, dispatch them to waiters. 339 | self.dispatch_connected_to_waiters(); 340 | 341 | // Having dispatched, we're ready to refill the waiters queue from the channel. No 342 | // more than `max_waiters` items are retained at once. 343 | // 344 | // We may end up in a situation where we haven't received `Async::NotReady` from 345 | // `waiters_rx.poll()`. We rely on the fact that connection events will be 346 | // necessary to satisfy existing waiters, and another `recv_waiters()` call will 347 | // be triggered from those events. 348 | self.recv_waiters(); 349 | 350 | // Update our lists of endpoints from service discovery before initiating new 351 | // connections for pending waiters. 352 | self.update_endpoints(); 353 | self.init_connecting(); 354 | 355 | // Dispatch any remaining available connections to any remaining waiters. This is 356 | // necessary because `init_connecting()` can technically satisfy connections 357 | // immediately. If this were to happen, there would be gauranteed event to trigger 358 | // a subsequent dispatch. 359 | self.dispatch_connected_to_waiters(); 360 | 361 | // And because we've potentially drained the waiters queue again, we have to 362 | // refill it to ensure that this task is polled again. 363 | self.recv_waiters(); 364 | 365 | // Update gauges & record the time it took to poll. 366 | self.record(t0); 367 | 368 | // This Future never completes. 369 | // TODO graceful shutdown. 370 | Ok(Async::NotReady) 371 | } 372 | } 373 | 374 | /// Selects an endpoint using the power of two choices. 375 | /// 376 | /// Two endpoints are chosen randomly and return the lesser-loaded endpoint. 377 | /// If no endpoints are available, `None` is retruned. 378 | fn select_endpoint<'r, 'e, R: Rng>( 379 | rng: &'r mut R, 380 | available: &'e EndpointMap, 381 | ) -> Option<&'e Endpoint> { 382 | match available.len() { 383 | 0 => None, 384 | 1 => { 385 | // One endpoint, use it. 386 | available.get_index(0).map(|(_, ep)| ep) 387 | } 388 | sz => { 389 | // Pick 2 candidate indices. 390 | let (i0, i1) = if sz == 2 { 391 | if rng.gen::() { (0, 1) } else { (1, 0) } 392 | } else { 393 | // 3 or more endpoints: choose two distinct endpoints at random. 394 | let i0 = rng.gen_range(0, sz); 395 | let mut i1 = rng.gen_range(0, sz); 396 | while i0 == i1 { 397 | i1 = rng.gen_range(0, sz); 398 | } 399 | (i0, i1) 400 | }; 401 | 402 | // Determine the the scores of each endpoint 403 | let (addr0, ep0) = available.get_index(i0).unwrap(); 404 | let (load0, weight0) = (ep0.load(), ep0.weight()); 405 | let score0 = (load0 + 1) as f64 * (1.0 - weight0); 406 | 407 | let (addr1, ep1) = available.get_index(i1).unwrap(); 408 | let (load1, weight1) = (ep1.load(), ep1.weight()); 409 | let score1 = (load1 + 1) as f64 * (1.0 - weight1); 410 | 411 | if score0 <= score1 { 412 | trace!( 413 | "dst: {} {}*{} (not {} {}*{})", 414 | addr0, 415 | load0, 416 | weight0, 417 | addr1, 418 | load1, 419 | weight1 420 | ); 421 | Some(ep0) 422 | } else { 423 | trace!( 424 | "dst: {} {}*{} (not {} {}*{})", 425 | addr1, 426 | load1, 427 | weight1, 428 | addr0, 429 | load0, 430 | weight0 431 | ); 432 | Some(ep1) 433 | } 434 | } 435 | } 436 | } 437 | 438 | struct Metrics { 439 | available: tacho::Gauge, 440 | failed: tacho::Gauge, 441 | retired: tacho::Gauge, 442 | pending: tacho::Gauge, 443 | open: tacho::Gauge, 444 | waiters: tacho::Gauge, 445 | poll_time: tacho::Timer, 446 | attempts: tacho::Counter, 447 | unavailable: tacho::Counter, 448 | connects: tacho::Counter, 449 | timeouts: tacho::Counter, 450 | refused: tacho::Counter, 451 | failures: tacho::Counter, 452 | connect_latency: tacho::Timer, 453 | connection_duration: tacho::Timer, 454 | } 455 | 456 | impl Metrics { 457 | fn new(base: &tacho::Scope) -> Metrics { 458 | let ep = base.clone().prefixed("endpoint"); 459 | let conn = base.clone().prefixed("connection"); 460 | Metrics { 461 | available: ep.gauge("available"), 462 | failed: ep.gauge("failed"), 463 | retired: ep.gauge("retired"), 464 | pending: conn.gauge("pending"), 465 | open: conn.gauge("open"), 466 | waiters: base.gauge("waiters"), 467 | poll_time: base.timer_us("poll_time_us"), 468 | unavailable: base.counter("unavailable"), 469 | attempts: conn.counter("attempts"), 470 | connects: conn.counter("connects"), 471 | timeouts: conn.clone().labeled("cause", "timeout").counter("failure"), 472 | refused: conn.clone().labeled("cause", "refused").counter("failure"), 473 | failures: conn.clone().labeled("cause", "other").counter("failure"), 474 | connect_latency: conn.timer_us("latency_us"), 475 | connection_duration: conn.timer_ms("duration_ms"), 476 | } 477 | } 478 | 479 | fn failure(&self, err: &io::Error) { 480 | match err.kind() { 481 | io::ErrorKind::TimedOut => self.timeouts.incr(1), 482 | io::ErrorKind::ConnectionRefused => self.refused.incr(1), 483 | _ => self.failures.incr(1), 484 | } 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aho-corasick" 3 | version = "0.6.4" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "ansi_term" 11 | version = "0.9.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.10.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | 19 | [[package]] 20 | name = "atty" 21 | version = "0.2.6" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | dependencies = [ 24 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 25 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 26 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 27 | ] 28 | 29 | [[package]] 30 | name = "base64" 31 | version = "0.6.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | dependencies = [ 34 | "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "base64" 40 | version = "0.9.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | dependencies = [ 43 | "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 45 | ] 46 | 47 | [[package]] 48 | name = "bitflags" 49 | version = "1.0.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | 52 | [[package]] 53 | name = "byteorder" 54 | version = "1.2.1" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | 57 | [[package]] 58 | name = "bytes" 59 | version = "0.4.6" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | dependencies = [ 62 | "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 63 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 64 | ] 65 | 66 | [[package]] 67 | name = "cfg-if" 68 | version = "0.1.2" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | 71 | [[package]] 72 | name = "clap" 73 | version = "2.29.2" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | dependencies = [ 76 | "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", 77 | "atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 78 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 79 | "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 80 | "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 81 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 82 | "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 83 | ] 84 | 85 | [[package]] 86 | name = "coco" 87 | version = "0.1.1" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | dependencies = [ 90 | "either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 92 | ] 93 | 94 | [[package]] 95 | name = "dtoa" 96 | version = "0.4.2" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | 99 | [[package]] 100 | name = "either" 101 | version = "1.4.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | 104 | [[package]] 105 | name = "env_logger" 106 | version = "0.4.3" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | dependencies = [ 109 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 110 | "regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 111 | ] 112 | 113 | [[package]] 114 | name = "fuchsia-zircon" 115 | version = "0.3.3" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | dependencies = [ 118 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 120 | ] 121 | 122 | [[package]] 123 | name = "fuchsia-zircon-sys" 124 | version = "0.3.3" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | 127 | [[package]] 128 | name = "futures" 129 | version = "0.1.18" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | 132 | [[package]] 133 | name = "futures-cpupool" 134 | version = "0.1.8" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | dependencies = [ 137 | "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 138 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 139 | ] 140 | 141 | [[package]] 142 | name = "gcc" 143 | version = "0.3.54" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | 146 | [[package]] 147 | name = "hdrsample" 148 | version = "3.0.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | dependencies = [ 151 | "num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 152 | ] 153 | 154 | [[package]] 155 | name = "httparse" 156 | version = "1.2.4" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | 159 | [[package]] 160 | name = "hyper" 161 | version = "0.11.16" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | dependencies = [ 164 | "base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "bytes 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 166 | "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 167 | "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 168 | "httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 169 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 170 | "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 171 | "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 172 | "mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 173 | "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 174 | "relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 175 | "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 176 | "tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 177 | "tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 178 | "tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 179 | "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 180 | "unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 181 | ] 182 | 183 | [[package]] 184 | name = "idna" 185 | version = "0.1.4" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | dependencies = [ 188 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 189 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 190 | "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 191 | ] 192 | 193 | [[package]] 194 | name = "iovec" 195 | version = "0.1.2" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | dependencies = [ 198 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 199 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 200 | ] 201 | 202 | [[package]] 203 | name = "itoa" 204 | version = "0.3.4" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | 207 | [[package]] 208 | name = "kernel32-sys" 209 | version = "0.2.2" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | dependencies = [ 212 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 213 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 214 | ] 215 | 216 | [[package]] 217 | name = "language-tags" 218 | version = "0.2.2" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | 221 | [[package]] 222 | name = "lazy_static" 223 | version = "0.2.11" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | 226 | [[package]] 227 | name = "lazy_static" 228 | version = "1.0.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | 231 | [[package]] 232 | name = "lazycell" 233 | version = "0.6.0" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | 236 | [[package]] 237 | name = "libc" 238 | version = "0.2.36" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | 241 | [[package]] 242 | name = "linked-hash-map" 243 | version = "0.5.1" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | 246 | [[package]] 247 | name = "linkerd-tcp" 248 | version = "0.1.1" 249 | dependencies = [ 250 | "bytes 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 251 | "clap 2.29.2 (registry+https://github.com/rust-lang/crates.io-index)", 252 | "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 253 | "hyper 0.11.16 (registry+https://github.com/rust-lang/crates.io-index)", 254 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 255 | "ordermap 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 256 | "pretty_env_logger 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 257 | "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", 258 | "rustls 0.10.0 (git+https://github.com/briansmith/rustls?branch=make_server_sni_public)", 259 | "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 260 | "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 261 | "serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 262 | "serde_yaml 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 263 | "tacho 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 264 | "tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 265 | "tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 266 | "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 267 | "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 268 | "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 269 | ] 270 | 271 | [[package]] 272 | name = "log" 273 | version = "0.3.9" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | dependencies = [ 276 | "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 277 | ] 278 | 279 | [[package]] 280 | name = "log" 281 | version = "0.4.1" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | dependencies = [ 284 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 285 | ] 286 | 287 | [[package]] 288 | name = "matches" 289 | version = "0.1.6" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | 292 | [[package]] 293 | name = "memchr" 294 | version = "2.0.1" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | dependencies = [ 297 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 298 | ] 299 | 300 | [[package]] 301 | name = "mime" 302 | version = "0.3.5" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | dependencies = [ 305 | "unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 306 | ] 307 | 308 | [[package]] 309 | name = "mio" 310 | version = "0.6.12" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | dependencies = [ 313 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 314 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 315 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 316 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 317 | "lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 318 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 319 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 320 | "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 321 | "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 322 | "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 323 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 324 | ] 325 | 326 | [[package]] 327 | name = "miow" 328 | version = "0.2.1" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | dependencies = [ 331 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 332 | "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 333 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 334 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 335 | ] 336 | 337 | [[package]] 338 | name = "net2" 339 | version = "0.2.31" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | dependencies = [ 342 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 343 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 344 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 345 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 346 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 347 | ] 348 | 349 | [[package]] 350 | name = "num" 351 | version = "0.1.41" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | dependencies = [ 354 | "num-bigint 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 355 | "num-complex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 356 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 357 | "num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", 358 | "num-rational 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 359 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 360 | ] 361 | 362 | [[package]] 363 | name = "num-bigint" 364 | version = "0.1.41" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | dependencies = [ 367 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 368 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 369 | "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", 370 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 371 | ] 372 | 373 | [[package]] 374 | name = "num-complex" 375 | version = "0.1.41" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | dependencies = [ 378 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 379 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 380 | ] 381 | 382 | [[package]] 383 | name = "num-integer" 384 | version = "0.1.35" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | dependencies = [ 387 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 388 | ] 389 | 390 | [[package]] 391 | name = "num-iter" 392 | version = "0.1.34" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | dependencies = [ 395 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 396 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 397 | ] 398 | 399 | [[package]] 400 | name = "num-rational" 401 | version = "0.1.41" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | dependencies = [ 404 | "num-bigint 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 405 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 406 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 407 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 408 | ] 409 | 410 | [[package]] 411 | name = "num-traits" 412 | version = "0.1.42" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | 415 | [[package]] 416 | name = "num_cpus" 417 | version = "1.8.0" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | dependencies = [ 420 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 421 | ] 422 | 423 | [[package]] 424 | name = "ordermap" 425 | version = "0.2.13" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | 428 | [[package]] 429 | name = "percent-encoding" 430 | version = "1.0.1" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | 433 | [[package]] 434 | name = "pretty_env_logger" 435 | version = "0.1.1" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | dependencies = [ 438 | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 439 | "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 440 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 441 | ] 442 | 443 | [[package]] 444 | name = "quote" 445 | version = "0.3.15" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | 448 | [[package]] 449 | name = "rand" 450 | version = "0.3.20" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | dependencies = [ 453 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 454 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 455 | ] 456 | 457 | [[package]] 458 | name = "rayon" 459 | version = "0.7.1" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | dependencies = [ 462 | "rayon-core 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 463 | ] 464 | 465 | [[package]] 466 | name = "rayon-core" 467 | version = "1.3.0" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | dependencies = [ 470 | "coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 471 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 472 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 473 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 474 | "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", 475 | ] 476 | 477 | [[package]] 478 | name = "redox_syscall" 479 | version = "0.1.37" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | 482 | [[package]] 483 | name = "redox_termios" 484 | version = "0.1.1" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | dependencies = [ 487 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 488 | ] 489 | 490 | [[package]] 491 | name = "regex" 492 | version = "0.2.5" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | dependencies = [ 495 | "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 496 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 497 | "regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 498 | "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 499 | "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 500 | ] 501 | 502 | [[package]] 503 | name = "regex-syntax" 504 | version = "0.4.2" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | 507 | [[package]] 508 | name = "relay" 509 | version = "0.1.1" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | dependencies = [ 512 | "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 513 | ] 514 | 515 | [[package]] 516 | name = "ring" 517 | version = "0.11.0" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | dependencies = [ 520 | "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", 521 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 522 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 523 | "rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 524 | "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 525 | ] 526 | 527 | [[package]] 528 | name = "rustc-serialize" 529 | version = "0.3.24" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | 532 | [[package]] 533 | name = "rustls" 534 | version = "0.10.0" 535 | source = "git+https://github.com/briansmith/rustls?branch=make_server_sni_public#ba6c42c0e2583a7cd3d2044aca1bc9ad58ad5108" 536 | dependencies = [ 537 | "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 538 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 539 | "ring 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 540 | "sct 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 541 | "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 542 | "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 543 | "webpki 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", 544 | ] 545 | 546 | [[package]] 547 | name = "safemem" 548 | version = "0.2.0" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | 551 | [[package]] 552 | name = "scoped-tls" 553 | version = "0.1.0" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | 556 | [[package]] 557 | name = "scopeguard" 558 | version = "0.3.3" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | 561 | [[package]] 562 | name = "sct" 563 | version = "0.1.4" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | dependencies = [ 566 | "ring 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 567 | "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 568 | ] 569 | 570 | [[package]] 571 | name = "serde" 572 | version = "1.0.27" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | 575 | [[package]] 576 | name = "serde_derive" 577 | version = "1.0.27" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | dependencies = [ 580 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 581 | "serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", 582 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 583 | ] 584 | 585 | [[package]] 586 | name = "serde_derive_internals" 587 | version = "0.19.0" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | dependencies = [ 590 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 591 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 592 | ] 593 | 594 | [[package]] 595 | name = "serde_json" 596 | version = "1.0.9" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | dependencies = [ 599 | "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 600 | "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 601 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 602 | "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 603 | ] 604 | 605 | [[package]] 606 | name = "serde_yaml" 607 | version = "0.7.3" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | dependencies = [ 610 | "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 611 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 612 | "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 613 | "yaml-rust 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 614 | ] 615 | 616 | [[package]] 617 | name = "slab" 618 | version = "0.3.0" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | 621 | [[package]] 622 | name = "slab" 623 | version = "0.4.0" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | 626 | [[package]] 627 | name = "smallvec" 628 | version = "0.2.1" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | 631 | [[package]] 632 | name = "strsim" 633 | version = "0.6.0" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | 636 | [[package]] 637 | name = "syn" 638 | version = "0.11.11" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | dependencies = [ 641 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 642 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 643 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 644 | ] 645 | 646 | [[package]] 647 | name = "synom" 648 | version = "0.11.3" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | dependencies = [ 651 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 652 | ] 653 | 654 | [[package]] 655 | name = "tacho" 656 | version = "0.4.2" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | dependencies = [ 659 | "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 660 | "hdrsample 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 661 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 662 | "ordermap 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 663 | ] 664 | 665 | [[package]] 666 | name = "take" 667 | version = "0.1.0" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | 670 | [[package]] 671 | name = "termion" 672 | version = "1.5.1" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | dependencies = [ 675 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 676 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 677 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 678 | ] 679 | 680 | [[package]] 681 | name = "textwrap" 682 | version = "0.9.0" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | dependencies = [ 685 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 686 | ] 687 | 688 | [[package]] 689 | name = "thread_local" 690 | version = "0.3.5" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | dependencies = [ 693 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 694 | "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 695 | ] 696 | 697 | [[package]] 698 | name = "time" 699 | version = "0.1.39" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | dependencies = [ 702 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 703 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 704 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 705 | ] 706 | 707 | [[package]] 708 | name = "tokio-core" 709 | version = "0.1.12" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | dependencies = [ 712 | "bytes 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 713 | "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 714 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 715 | "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 716 | "mio 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 717 | "scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 718 | "slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 719 | "tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 720 | ] 721 | 722 | [[package]] 723 | name = "tokio-io" 724 | version = "0.1.4" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | dependencies = [ 727 | "bytes 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 728 | "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 729 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 730 | ] 731 | 732 | [[package]] 733 | name = "tokio-proto" 734 | version = "0.1.1" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | dependencies = [ 737 | "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 738 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 739 | "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 740 | "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", 741 | "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 742 | "smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 743 | "take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 744 | "tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 745 | "tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 746 | "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 747 | ] 748 | 749 | [[package]] 750 | name = "tokio-service" 751 | version = "0.1.0" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | dependencies = [ 754 | "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 755 | ] 756 | 757 | [[package]] 758 | name = "tokio-timer" 759 | version = "0.1.2" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | dependencies = [ 762 | "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 763 | "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 764 | ] 765 | 766 | [[package]] 767 | name = "unicase" 768 | version = "2.1.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | dependencies = [ 771 | "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 772 | ] 773 | 774 | [[package]] 775 | name = "unicode-bidi" 776 | version = "0.3.4" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | dependencies = [ 779 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 780 | ] 781 | 782 | [[package]] 783 | name = "unicode-normalization" 784 | version = "0.1.5" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | 787 | [[package]] 788 | name = "unicode-width" 789 | version = "0.1.4" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | 792 | [[package]] 793 | name = "unicode-xid" 794 | version = "0.0.4" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | 797 | [[package]] 798 | name = "unreachable" 799 | version = "1.0.0" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | dependencies = [ 802 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 803 | ] 804 | 805 | [[package]] 806 | name = "untrusted" 807 | version = "0.5.1" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | 810 | [[package]] 811 | name = "url" 812 | version = "1.6.0" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | dependencies = [ 815 | "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 816 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 817 | "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 818 | ] 819 | 820 | [[package]] 821 | name = "utf8-ranges" 822 | version = "1.0.0" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | 825 | [[package]] 826 | name = "vec_map" 827 | version = "0.8.0" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | 830 | [[package]] 831 | name = "version_check" 832 | version = "0.1.3" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | 835 | [[package]] 836 | name = "void" 837 | version = "1.0.2" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | 840 | [[package]] 841 | name = "webpki" 842 | version = "0.14.0" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | dependencies = [ 845 | "ring 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 846 | "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 847 | "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 848 | ] 849 | 850 | [[package]] 851 | name = "winapi" 852 | version = "0.2.8" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | 855 | [[package]] 856 | name = "winapi" 857 | version = "0.3.4" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | dependencies = [ 860 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 861 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 862 | ] 863 | 864 | [[package]] 865 | name = "winapi-build" 866 | version = "0.1.1" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | 869 | [[package]] 870 | name = "winapi-i686-pc-windows-gnu" 871 | version = "0.4.0" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | 874 | [[package]] 875 | name = "winapi-x86_64-pc-windows-gnu" 876 | version = "0.4.0" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | 879 | [[package]] 880 | name = "ws2_32-sys" 881 | version = "0.2.1" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | dependencies = [ 884 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 885 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 886 | ] 887 | 888 | [[package]] 889 | name = "yaml-rust" 890 | version = "0.4.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | dependencies = [ 893 | "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 894 | ] 895 | 896 | [metadata] 897 | "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" 898 | "checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455" 899 | "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" 900 | "checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859" 901 | "checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9" 902 | "checksum base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "229d032f1a99302697f10b27167ae6d03d49d032e6a8e2550e8d3fc13356d2b4" 903 | "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" 904 | "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" 905 | "checksum bytes 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1b7db437d718977f6dc9b2e3fd6fc343c02ac6b899b73fdd2179163447bd9ce9" 906 | "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" 907 | "checksum clap 2.29.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4151c5790817c7d21bbdc6c3530811f798172915f93258244948b93ba19604a6" 908 | "checksum coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c06169f5beb7e31c7c67ebf5540b8b472d23e3eade3b2ec7d1f5b504a85f91bd" 909 | "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" 910 | "checksum either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "740178ddf48b1a9e878e6d6509a1442a2d42fd2928aae8e7a6f8a36fb01981b3" 911 | "checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" 912 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 913 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 914 | "checksum futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0bab5b5e94f5c31fc764ba5dd9ad16568aae5d4825538c01d6bca680c9bf94a7" 915 | "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" 916 | "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" 917 | "checksum hdrsample 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f78c6b8a49cb8334c462af348efd6e8a92fdfb7b5cc4cb298d9b9e3ee9df719" 918 | "checksum httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f407128745b78abc95c0ffbe4e5d37427fdc0d45470710cfef8c44522a2e37" 919 | "checksum hyper 0.11.16 (registry+https://github.com/rust-lang/crates.io-index)" = "6a82c41828dd6f271f4d6ebc3f1db78239a4b2b3d355dfdb5f8bbf55f004463a" 920 | "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" 921 | "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" 922 | "checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" 923 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 924 | "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 925 | "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 926 | "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" 927 | "checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef" 928 | "checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" 929 | "checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" 930 | "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 931 | "checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" 932 | "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" 933 | "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" 934 | "checksum mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e00e17be181010a91dbfefb01660b17311059dc8c7f48b9017677721e732bd" 935 | "checksum mio 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "75f72a93f046f1517e3cfddc0a096eb756a2ba727d36edc8227dee769a50a9b0" 936 | "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 937 | "checksum net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "3a80f842784ef6c9a958b68b7516bc7e35883c614004dd94959a4dca1b716c09" 938 | "checksum num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cc4083e14b542ea3eb9b5f33ff48bd373a92d78687e74f4cc0a30caeb754f0ca" 939 | "checksum num-bigint 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "bdc1494b5912f088f260b775799468d9b9209ac60885d8186a547a0476289e23" 940 | "checksum num-complex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "58de7b4bf7cf5dbecb635a5797d489864eadd03b107930cbccf9e0fd7428b47c" 941 | "checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" 942 | "checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" 943 | "checksum num-rational 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "0b950f75e042fdd710460084d19c8efdcd72d65183ead8ecd04b90483f5a55d2" 944 | "checksum num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "9936036cc70fe4a8b2d338ab665900323290efb03983c86cbe235ae800ad8017" 945 | "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 946 | "checksum ordermap 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b81cf3b8cb96aa0e73bbedfcdc9708d09fec2854ba8d474be4e6f666d7379e8b" 947 | "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 948 | "checksum pretty_env_logger 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8a97d1fde8be5bdb2c315277042a39a89b8ca5640c9d8e1a900cc9d906ee5af2" 949 | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 950 | "checksum rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "512870020642bb8c221bf68baa1b2573da814f6ccfe5c9699b1c303047abe9b1" 951 | "checksum rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a77c51c07654ddd93f6cb543c7a849863b03abc7e82591afda6dc8ad4ac3ac4a" 952 | "checksum rayon-core 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e64b609139d83da75902f88fd6c01820046840a18471e4dfcd5ac7c0f46bea53" 953 | "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" 954 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 955 | "checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa" 956 | "checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" 957 | "checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a" 958 | "checksum ring 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2a6dc7fc06a05e6de183c5b97058582e9da2de0c136eafe49609769c507724" 959 | "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 960 | "checksum rustls 0.10.0 (git+https://github.com/briansmith/rustls?branch=make_server_sni_public)" = "" 961 | "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" 962 | "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" 963 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 964 | "checksum sct 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c9292b6def6a7b9983b2329f772fe83cab573a06e829464e540329b12cb6e5f1" 965 | "checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526" 966 | "checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0" 967 | "checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5" 968 | "checksum serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9db7266c7d63a4c4b7fe8719656ccdd51acf1bed6124b174f933b009fb10bcb" 969 | "checksum serde_yaml 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e0f868d400d9d13d00988da49f7f02aeac6ef00f11901a8c535bd59d777b9e19" 970 | "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" 971 | "checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d" 972 | "checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013" 973 | "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" 974 | "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" 975 | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" 976 | "checksum tacho 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "42a6c74d2b96a3eb435b59dd077a9e999b357cf45c34a71492cfb54d28f35e7d" 977 | "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" 978 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 979 | "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" 980 | "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" 981 | "checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098" 982 | "checksum tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "52b4e32d8edbf29501aabb3570f027c6ceb00ccef6538f4bddba0200503e74e8" 983 | "checksum tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "514aae203178929dbf03318ad7c683126672d4d96eccb77b29603d33c9e25743" 984 | "checksum tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fbb47ae81353c63c487030659494b295f6cb6576242f907f203473b191b0389" 985 | "checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" 986 | "checksum tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6131e780037787ff1b3f8aad9da83bca02438b72277850dd6ad0d455e0e20efc" 987 | "checksum unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284b6d3db520d67fbe88fd778c21510d1b0ba4a551e5d0fbb023d33405f6de8a" 988 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 989 | "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" 990 | "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" 991 | "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" 992 | "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 993 | "checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae" 994 | "checksum url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa35e768d4daf1d85733418a49fb42e10d7f633e394fccab4ab7aba897053fe2" 995 | "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" 996 | "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" 997 | "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" 998 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 999 | "checksum webpki 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e499345fc4c6b7c79a5b8756d4592c4305510a13512e79efafe00dfbd67bbac6" 1000 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1001 | "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" 1002 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1003 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1004 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1005 | "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1006 | "checksum yaml-rust 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57ab38ee1a4a266ed033496cf9af1828d8d6e6c1cfa5f643a2809effcae4d628" 1007 | --------------------------------------------------------------------------------