From 1a04e3b899a4bd6dfe75e8b43988b70f79051c67 Mon Sep 17 00:00:00 2001 From: Johan Anderholm Date: Fri, 23 Mar 2018 16:52:24 +0100 Subject: [PATCH] Resolve AP through proxy as well --- core/Cargo.toml | 1 + core/src/apresolve.rs | 65 ++++++++++++++++++++++++++------------ core/src/connection/mod.rs | 10 +++--- core/src/lib.rs | 1 + core/src/proxytunnel.rs | 21 ++++++------ core/src/session.rs | 2 +- 6 files changed, 63 insertions(+), 37 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 28d85a6e..c0bafbe1 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -16,6 +16,7 @@ extprim = "1.5.1" futures = "0.1.8" httparse = "1.2.4" hyper = "0.11.2" +hyper-proxy = { version = "0.4.1", default_features = false } lazy_static = "0.2.0" log = "0.3.5" num-bigint = "0.1.35" diff --git a/core/src/apresolve.rs b/core/src/apresolve.rs index 37cbfed0..db3b8d7a 100644 --- a/core/src/apresolve.rs +++ b/core/src/apresolve.rs @@ -1,8 +1,10 @@ const AP_FALLBACK: &'static str = "ap.spotify.com:443"; const APRESOLVE_ENDPOINT: &'static str = "http://apresolve.spotify.com/"; -use futures::{future, Future, Stream}; -use hyper::{self, Client, Uri}; +use futures::{Future, Stream}; +use hyper::client::HttpConnector; +use hyper::{self, Client, Method, Request, Uri}; +use hyper_proxy::{Intercept, Proxy, ProxyConnector}; use serde_json; use std::str::FromStr; use tokio_core::reactor::Handle; @@ -14,11 +16,29 @@ pub struct APResolveData { ap_list: Vec, } -fn apresolve(handle: &Handle) -> Box> { +fn apresolve(handle: &Handle, proxy: &Option) -> Box> { let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL"); + let use_proxy = proxy.is_some(); - let client = Client::new(handle); - let response = client.get(url); + let mut req = Request::new(Method::Get, url.clone()); + let response = match proxy { + &Some(ref val) => { + let proxy_url = Uri::from_str(&val).expect("invalid http proxy"); + let proxy = Proxy::new(Intercept::All, proxy_url); + let connector = HttpConnector::new(4, handle); + let proxy_connector = ProxyConnector::from_proxy_unsecured(connector, proxy); + if let Some(headers) = proxy_connector.http_headers(&url) { + req.headers_mut().extend(headers.iter()); + req.set_proxy(true); + } + let client = Client::configure().connector(proxy_connector).build(handle); + client.request(req) + } + _ => { + let client = Client::new(handle); + client.request(req) + } + }; let body = response.and_then(|response| { response.body().fold(Vec::new(), |mut acc, chunk| { @@ -32,8 +52,19 @@ fn apresolve(handle: &Handle) -> Box> { let data = body.and_then(|body| serde_json::from_str::(&body).chain_err(|| "invalid JSON")); - let ap = data.and_then(|data| { - let ap = data.ap_list.first().ok_or("empty AP List")?; + let ap = data.and_then(move |data| { + let mut aps = data.ap_list.iter().filter(|ap| { + if use_proxy { + // It is unlikely that the proxy will accept CONNECT on anything other than 443. + Uri::from_str(ap) + .ok() + .map_or(false, |uri| uri.port().map_or(false, |port| port == 443)) + } else { + true + } + }); + + let ap = aps.next().ok_or("empty AP List")?; Ok(ap.clone()) }); @@ -47,17 +78,11 @@ pub(crate) fn apresolve_or_fallback( where E: 'static, { - if proxy.is_some() { - // TODO: Use a proper proxy library and filter out a 443 proxy instead of relying on fallback. - // The problem with current libraries (hyper-proxy, reqwest) is that they depend on TLS - // and this is a dependency we might not want. - Box::new(future::result(Ok(AP_FALLBACK.into()))) - } else { - let ap = apresolve(handle).or_else(|e| { - warn!("Failed to resolve Access Point: {}", e.description()); - warn!("Using fallback \"{}\"", AP_FALLBACK); - Ok(AP_FALLBACK.into()) - }); - Box::new(ap) - } + let ap = apresolve(handle, proxy).or_else(|e| { + warn!("Failed to resolve Access Point: {}", e.description()); + warn!("Using fallback \"{}\"", AP_FALLBACK); + Ok(AP_FALLBACK.into()) + }); + + Box::new(ap) } diff --git a/core/src/connection/mod.rs b/core/src/connection/mod.rs index f9b4567b..dec4daf6 100644 --- a/core/src/connection/mod.rs +++ b/core/src/connection/mod.rs @@ -21,8 +21,8 @@ use proxytunnel; pub type Transport = Framed; -pub fn connect( - addr: A, +pub fn connect( + addr: String, handle: &Handle, proxy: &Option, ) -> Box> { @@ -38,7 +38,7 @@ pub fn connect( .unwrap() .next() .unwrap(), - Some(addr.to_socket_addrs().unwrap().next().unwrap()), + Some(addr.clone()), ) } None => (addr.to_socket_addrs().unwrap().next().unwrap(), None), @@ -46,8 +46,8 @@ pub fn connect( let socket = TcpStream::connect(&addr, handle); if let Some(connect_url) = connect_url { - let connection = - socket.and_then(move |socket| proxytunnel::connect(socket, connect_url).and_then(handshake)); + let connection = socket + .and_then(move |socket| proxytunnel::connect(socket, &connect_url).and_then(handshake)); Box::new(connection) } else { let connection = socket.and_then(handshake); diff --git a/core/src/lib.rs b/core/src/lib.rs index b1f13581..e7b4b3c7 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -18,6 +18,7 @@ extern crate crypto; extern crate extprim; extern crate httparse; extern crate hyper; +extern crate hyper_proxy; extern crate num_bigint; extern crate num_integer; extern crate num_traits; diff --git a/core/src/proxytunnel.rs b/core/src/proxytunnel.rs index b7d2e5bb..3f1db053 100644 --- a/core/src/proxytunnel.rs +++ b/core/src/proxytunnel.rs @@ -1,13 +1,13 @@ +use std::error::Error; +use std::io; +use std::str::FromStr; + use futures::{Async, Future, Poll}; use httparse; - -use std::io; -use std::net::SocketAddr; +use hyper::Uri; use tokio_io::io::{read, write_all, Read, Window, WriteAll}; use tokio_io::{AsyncRead, AsyncWrite}; -use std::error::Error; - pub struct ProxyTunnel { state: ProxyState, } @@ -17,7 +17,7 @@ enum ProxyState { ProxyResponse(Read>>), } -pub fn connect(connection: T, connect_url: SocketAddr) -> ProxyTunnel { +pub fn connect(connection: T, connect_url: &str) -> ProxyTunnel { let proxy = proxy_connect(connection, connect_url); ProxyTunnel { state: ProxyState::ProxyConnect(proxy), @@ -95,14 +95,13 @@ impl Future for ProxyTunnel { } } -fn proxy_connect(connection: T, connect_url: SocketAddr) -> WriteAll> { - // TODO: It would be better to use a non-resolved url here. This usually works, - // but it may fail in some environments and it will leak DNS requests. +fn proxy_connect(connection: T, connect_url: &str) -> WriteAll> { + let uri = Uri::from_str(&connect_url).unwrap(); let buffer = format!( "CONNECT {0}:{1} HTTP/1.1\r\n\ \r\n", - connect_url.ip(), - connect_url.port() + uri.host().expect(&format!("No host in {}", uri)), + uri.port().expect(&format!("No port in {}", uri)) ).into_bytes(); write_all(connection, buffer) diff --git a/core/src/session.rs b/core/src/session.rs index 4bdcbde9..ad0bf27a 100644 --- a/core/src/session.rs +++ b/core/src/session.rs @@ -56,7 +56,7 @@ impl Session { let proxy = config.proxy.clone(); let connection = access_point.and_then(move |addr| { info!("Connecting to AP \"{}\"", addr); - connection::connect::<&str>(&addr, &handle_, &proxy) + connection::connect(addr, &handle_, &proxy) }); let device_id = config.device_id.clone();