From ee08706eb936bbf191ffd9953d884ccead75bec1 Mon Sep 17 00:00:00 2001 From: Ulyssa Date: Wed, 3 Jul 2024 10:56:10 -0700 Subject: [PATCH] Add `downstream_server_ip_addr` hostcall --- Cargo.lock | 25 ++++++++++---- cli/tests/integration/common.rs | 4 ++- cli/tests/trap-test/Cargo.lock | 27 ++++++++++----- cli/tests/trap-test/src/main.rs | 4 ++- lib/Cargo.toml | 2 +- lib/compute-at-edge-abi/compute-at-edge.witx | 6 ++++ lib/src/component/http_req.rs | 15 ++++++-- lib/src/execute.rs | 24 ++++++++----- lib/src/service.rs | 27 +++++++++++---- lib/src/session.rs | 36 ++++++++++++-------- lib/src/wiggle_abi/req_impl.rs | 26 +++++++++++++- test-fixtures/Cargo.lock | 20 +++++------ test-fixtures/Cargo.toml | 8 ++--- test-fixtures/src/bin/downstream-req.rs | 3 ++ 14 files changed, 163 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8fed5afe..ef533716 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -681,12 +681,12 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastly-shared" -version = "0.9.12" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276812acf6f1b34029a5eb6ff3add7fe11452e1513762d02263a031097e68761" +checksum = "68b32d7c7a252c40a66621410fdf1519a8ff50b0f77ec81c83d69db7ec2377f5" dependencies = [ "bitflags 1.3.2", - "http", + "http 1.1.0", ] [[package]] @@ -902,7 +902,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -958,6 +958,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -965,7 +976,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", "pin-project-lite", ] @@ -998,7 +1009,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "httparse", "httpdate", @@ -2407,7 +2418,7 @@ dependencies = [ "fastly-shared", "flate2", "futures", - "http", + "http 0.2.12", "http-body", "hyper", "itertools 0.10.5", diff --git a/cli/tests/integration/common.rs b/cli/tests/integration/common.rs index d7c4e8d0..f2c5239f 100644 --- a/cli/tests/integration/common.rs +++ b/cli/tests/integration/common.rs @@ -398,9 +398,11 @@ impl Test { .build() .unwrap(); *req.uri_mut() = new_uri; + let local = (Ipv4Addr::LOCALHOST, 80).into(); + let remote = (Ipv4Addr::LOCALHOST, 0).into(); let resp = ctx .clone() - .handle_request(req.map(Into::into), Ipv4Addr::LOCALHOST.into()) + .handle_request(req.map(Into::into), local, remote) .await .map(|result| { match result { diff --git a/cli/tests/trap-test/Cargo.lock b/cli/tests/trap-test/Cargo.lock index f74ea7b5..774c91c4 100644 --- a/cli/tests/trap-test/Cargo.lock +++ b/cli/tests/trap-test/Cargo.lock @@ -691,12 +691,12 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastly-shared" -version = "0.9.12" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276812acf6f1b34029a5eb6ff3add7fe11452e1513762d02263a031097e68761" +checksum = "68b32d7c7a252c40a66621410fdf1519a8ff50b0f77ec81c83d69db7ec2377f5" dependencies = [ "bitflags 1.3.2", - "http", + "http 1.1.0", ] [[package]] @@ -906,7 +906,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -962,6 +962,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -969,7 +980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", "pin-project-lite", ] @@ -1002,7 +1013,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "httparse", "httpdate", @@ -2224,7 +2235,7 @@ version = "0.1.0" dependencies = [ "anyhow", "futures", - "http", + "http 0.2.12", "hyper", "tokio", "tracing-subscriber", @@ -2331,7 +2342,7 @@ dependencies = [ "fastly-shared", "flate2", "futures", - "http", + "http 0.2.12", "http-body", "hyper", "itertools 0.10.5", diff --git a/cli/tests/trap-test/src/main.rs b/cli/tests/trap-test/src/main.rs index 566aac4f..4cdc0318 100644 --- a/cli/tests/trap-test/src/main.rs +++ b/cli/tests/trap-test/src/main.rs @@ -24,8 +24,10 @@ async fn fatal_error_traps_impl(adapt_core_wasm: bool) -> TestResult { adapt_core_wasm, )?; let req = Request::get("http://127.0.0.1:7676/").body(Body::from(""))?; + let local = "127.0.0.1:80".parse().unwrap(); + let remote = "127.0.0.1:0".parse().unwrap(); let resp = ctx - .handle_request_with_runtime_error(req, "127.0.0.1".parse().unwrap()) + .handle_request_with_runtime_error(req, local, remote) .await?; // The Guest was terminated and so should return a 500. diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 7188c886..32edc64c 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -32,7 +32,7 @@ bytesize = "^1.1.0" cfg-if = "^1.0" clap = { workspace = true } cranelift-entity = "^0.88.1" -fastly-shared = "^0.9.11" +fastly-shared = "^0.10.1" flate2 = "^1.0.24" futures = { workspace = true } http = "^0.2.8" diff --git a/lib/compute-at-edge-abi/compute-at-edge.witx b/lib/compute-at-edge-abi/compute-at-edge.witx index fdb462e2..bd339e14 100644 --- a/lib/compute-at-edge-abi/compute-at-edge.witx +++ b/lib/compute-at-edge-abi/compute-at-edge.witx @@ -172,6 +172,12 @@ (result $err (expected $num_bytes (error $fastly_status))) ) + (@interface func (export "downstream_server_ip_addr") + ;; must be a 16-byte array + (param $addr_octets_out (@witx pointer (@witx char8))) + (result $err (expected $num_bytes (error $fastly_status))) + ) + (@interface func (export "downstream_client_h2_fingerprint") (param $h2fp_out (@witx pointer (@witx char8))) (param $h2fp_max_len (@witx usize)) diff --git a/lib/src/component/http_req.rs b/lib/src/component/http_req.rs index a477428b..9e64fa47 100644 --- a/lib/src/component/http_req.rs +++ b/lib/src/component/http_req.rs @@ -18,6 +18,7 @@ use { request::Request, Method, Uri, }, + std::net::IpAddr, std::str::FromStr, }; @@ -107,7 +108,6 @@ impl http_req::Host for Session { } async fn downstream_client_ip_addr(&mut self) -> Result, types::Error> { - use std::net::IpAddr; match self.downstream_client_ip() { IpAddr::V4(addr) => { let octets = addr.octets(); @@ -123,7 +123,18 @@ impl http_req::Host for Session { } async fn downstream_server_ip_addr(&mut self) -> Result, types::Error> { - Err(Error::NotAvailable("Downstream server ip address").into()) + match self.downstream_server_ip() { + IpAddr::V4(addr) => { + let octets = addr.octets(); + debug_assert_eq!(octets.len(), 4); + Ok(Vec::from(octets)) + } + IpAddr::V6(addr) => { + let octets = addr.octets(); + debug_assert_eq!(octets.len(), 16); + Ok(Vec::from(octets)) + } + } } async fn downstream_tls_cipher_openssl_name( diff --git a/lib/src/execute.rs b/lib/src/execute.rs index 60836dbe..9cd982ba 100644 --- a/lib/src/execute.rs +++ b/lib/src/execute.rs @@ -23,7 +23,7 @@ use { collections::HashSet, fs, io::Write, - net::{IpAddr, Ipv4Addr}, + net::{Ipv4Addr, SocketAddr}, path::{Path, PathBuf}, sync::atomic::{AtomicBool, AtomicU64, Ordering}, sync::{Arc, Mutex}, @@ -347,14 +347,17 @@ impl ExecuteCtx { /// # let req = Request::new(Body::from("")); /// let adapt_core_wasm = false; /// let ctx = ExecuteCtx::new("path/to/a/file.wasm", ProfilingStrategy::None, HashSet::new(), None, Default::default(), adapt_core_wasm)?; - /// let resp = ctx.handle_request(req, "127.0.0.1".parse().unwrap()).await?; + /// let local = "127.0.0.1:80".parse().unwrap(); + /// let remote = "127.0.0.1:0".parse().unwrap(); + /// let resp = ctx.handle_request(req, local, remote).await?; /// # Ok(()) /// # } /// ``` pub async fn handle_request( self, incoming_req: Request, - remote: IpAddr, + local: SocketAddr, + remote: SocketAddr, ) -> Result<(Response, Option), Error> { let req = prepare_request(incoming_req)?; let (sender, receiver) = oneshot::channel(); @@ -366,7 +369,7 @@ impl ExecuteCtx { // Spawn a separate task to run the guest code. That allows _this_ method to return a response early // if the guest sends one, while the guest continues to run afterward within its task. let guest_handle = tokio::task::spawn( - self.run_guest(req, req_id, sender, remote) + self.run_guest(req, req_id, sender, local, remote) .instrument(info_span!("request", id = req_id)), ); @@ -400,9 +403,10 @@ impl ExecuteCtx { pub async fn handle_request_with_runtime_error( self, incoming_req: Request, - remote: IpAddr, + local: SocketAddr, + remote: SocketAddr, ) -> Result, Error> { - let result = self.handle_request(incoming_req, remote).await?; + let result = self.handle_request(incoming_req, local, remote).await?; let resp = match result.1 { None => result.0, Some(err) => { @@ -422,7 +426,8 @@ impl ExecuteCtx { req: Request, req_id: u64, sender: Sender>, - remote: IpAddr, + local: SocketAddr, + remote: SocketAddr, ) -> Result<(), ExecutionError> { info!("handling request {} {}", req.method(), req.uri()); let start_timestamp = Instant::now(); @@ -430,6 +435,7 @@ impl ExecuteCtx { req_id, req, sender, + local, remote, &self, self.backends.clone(), @@ -578,12 +584,14 @@ impl ExecuteCtx { let req = Request::get("http://example.com/").body(Body::empty())?; let req_id = 0; let (sender, receiver) = oneshot::channel(); - let remote = Ipv4Addr::LOCALHOST.into(); + let local = (Ipv4Addr::LOCALHOST, 80).into(); + let remote = (Ipv4Addr::LOCALHOST, 0).into(); let session = Session::new( req_id, req, sender, + local, remote, &self, self.backends.clone(), diff --git a/lib/src/service.rs b/lib/src/service.rs index 60411f33..1f33a4e4 100644 --- a/lib/src/service.rs +++ b/lib/src/service.rs @@ -11,7 +11,7 @@ use { std::{ convert::Infallible, future::Future, - net::{IpAddr, SocketAddr}, + net::SocketAddr, pin::Pin, task::{self, Poll}, }, @@ -54,7 +54,7 @@ impl ViceroyService { } /// An internal helper, create a [`RequestService`](struct.RequestService.html). - fn make_service(&self, remote: IpAddr) -> RequestService { + fn make_service(&self, remote: &AddrStream) -> RequestService { RequestService::new(self.ctx.clone(), remote) } @@ -81,7 +81,7 @@ impl<'addr> Service<&'addr AddrStream> for ViceroyService { } fn call(&mut self, addr: &'addr AddrStream) -> Self::Future { - future::ok(self.make_service(addr.remote_addr().ip())) + future::ok(self.make_service(addr)) } } @@ -102,13 +102,21 @@ impl<'addr> Service<&'addr AddrStream> for ViceroyService { #[derive(Clone)] pub struct RequestService { ctx: ExecuteCtx, - remote_addr: IpAddr, + local_addr: SocketAddr, + remote_addr: SocketAddr, } impl RequestService { /// Create a new request service. - fn new(ctx: ExecuteCtx, remote_addr: IpAddr) -> Self { - Self { ctx, remote_addr } + fn new(ctx: ExecuteCtx, addr: &AddrStream) -> Self { + let local_addr = addr.local_addr(); + let remote_addr = addr.remote_addr(); + + Self { + ctx, + local_addr, + remote_addr, + } } } @@ -126,9 +134,14 @@ impl Service> for RequestService { fn call(&mut self, req: Request) -> Self::Future { // Request handling currently takes ownership of the context, which is cheaply cloneable. let ctx = self.ctx.clone(); + let local = self.local_addr; let remote = self.remote_addr; // Now, use the execution context to handle the request. - Box::pin(async move { ctx.handle_request(req, remote).await.map(|result| result.0) }) + Box::pin(async move { + ctx.handle_request(req, local, remote) + .await + .map(|result| result.0) + }) } } diff --git a/lib/src/session.rs b/lib/src/session.rs index adc03d88..754d8f2e 100644 --- a/lib/src/session.rs +++ b/lib/src/session.rs @@ -7,6 +7,13 @@ pub use async_item::{ AsyncItem, PeekableTask, PendingKvDeleteTask, PendingKvInsertTask, PendingKvLookupTask, }; +use std::collections::HashMap; +use std::future::Future; +use std::io::Write; +use std::net::{IpAddr, SocketAddr}; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; + use { self::downstream::DownstreamResponse, crate::{ @@ -28,22 +35,16 @@ use { cranelift_entity::{entity_impl, PrimaryMap}, futures::future::{self, FutureExt}, http::{request, response, HeaderMap, Request, Response}, - std::{ - collections::HashMap, - future::Future, - io::Write, - net::IpAddr, - path::PathBuf, - sync::{Arc, Mutex}, - }, tokio::sync::oneshot::Sender, }; /// Data specific to an individual request, including any host-side /// allocations on behalf of the guest processing the request. pub struct Session { - /// The downstream IP address for this session. - downstream_client_ip: IpAddr, + /// The downstream IP address and port for this session. + downstream_client_addr: SocketAddr, + /// The IP address and port that received this session. + downstream_server_addr: SocketAddr, /// Handle for the downstream request "parts". NB the backing parts data can be mutated /// or even removed from the relevant map. downstream_req_handle: RequestHandle, @@ -140,7 +141,8 @@ impl Session { req_id: u64, req: Request, resp_sender: Sender>, - client_ip: IpAddr, + server_addr: SocketAddr, + client_addr: SocketAddr, ctx: &ExecuteCtx, backends: Arc, device_detection: Arc, @@ -161,7 +163,8 @@ impl Session { let downstream_req_body_handle = async_items.push(Some(AsyncItem::Body(body))).into(); Session { - downstream_client_ip: client_ip, + downstream_server_addr: server_addr, + downstream_client_addr: client_addr, downstream_req_handle, downstream_req_body_handle, downstream_req_original_headers, @@ -192,8 +195,13 @@ impl Session { // ----- Downstream Request API ----- /// Retrieve the downstream client IP address associated with this session. - pub fn downstream_client_ip(&self) -> &IpAddr { - &self.downstream_client_ip + pub fn downstream_client_ip(&self) -> IpAddr { + self.downstream_client_addr.ip() + } + + /// Retrieve the IP address the downstream client connected to for this session. + pub fn downstream_server_ip(&self) -> IpAddr { + self.downstream_server_addr.ip() } /// Retrieve the handle corresponding to the downstream request. diff --git a/lib/src/wiggle_abi/req_impl.rs b/lib/src/wiggle_abi/req_impl.rs index cd449553..2a6ae71b 100644 --- a/lib/src/wiggle_abi/req_impl.rs +++ b/lib/src/wiggle_abi/req_impl.rs @@ -25,6 +25,7 @@ use { fastly_shared::{INVALID_BODY_HANDLE, INVALID_REQUEST_HANDLE, INVALID_RESPONSE_HANDLE}, http::{HeaderValue, Method, Uri}, hyper::http::request::Request, + std::net::IpAddr, wiggle::{GuestMemory, GuestPtr}, }; @@ -66,13 +67,36 @@ impl FastlyHttpReq for Session { Ok(()) } + fn downstream_server_ip_addr( + &mut self, + memory: &mut GuestMemory<'_>, + // Must be a 16-byte array: + addr_octets_ptr: GuestPtr, + ) -> Result { + match self.downstream_server_ip() { + IpAddr::V4(addr) => { + let octets = addr.octets(); + let octets_bytes = octets.len() as u32; + debug_assert_eq!(octets_bytes, 4); + memory.copy_from_slice(&octets, addr_octets_ptr.as_array(octets_bytes))?; + Ok(octets_bytes) + } + IpAddr::V6(addr) => { + let octets = addr.octets(); + let octets_bytes = octets.len() as u32; + debug_assert_eq!(octets_bytes, 16); + memory.copy_from_slice(&octets, addr_octets_ptr.as_array(octets_bytes))?; + Ok(octets_bytes) + } + } + } + fn downstream_client_ip_addr( &mut self, memory: &mut GuestMemory<'_>, // Must be a 16-byte array: addr_octets_ptr: GuestPtr, ) -> Result { - use std::net::IpAddr; match self.downstream_client_ip() { IpAddr::V4(addr) => { let octets = addr.octets(); diff --git a/test-fixtures/Cargo.lock b/test-fixtures/Cargo.lock index 4848ed8c..a4d91000 100644 --- a/test-fixtures/Cargo.lock +++ b/test-fixtures/Cargo.lock @@ -61,9 +61,9 @@ dependencies = [ [[package]] name = "fastly" -version = "0.9.12" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce91a71c1576ca56ff2de349550d175e70004515499c64c845b1b40a017b075" +checksum = "a5a264a5d96c95d4417ec84325d9da58a6790fa856365f87bb66bd27ee3c65da" dependencies = [ "anyhow", "bytes", @@ -84,9 +84,9 @@ dependencies = [ [[package]] name = "fastly-macros" -version = "0.9.12" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d5de12e9bc451138b281cee3cc57e8b1f3b46b7afb8d0d201e6b50d1f6b574" +checksum = "9bc9655039ed2895c60f644fb7c68abf29f0b0a5b01dd76f2a9d2745665773ab" dependencies = [ "proc-macro2", "quote", @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "fastly-shared" -version = "0.9.12" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276812acf6f1b34029a5eb6ff3add7fe11452e1513762d02263a031097e68761" +checksum = "68b32d7c7a252c40a66621410fdf1519a8ff50b0f77ec81c83d69db7ec2377f5" dependencies = [ "bitflags", "http", @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "fastly-sys" -version = "0.9.12" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b71f4531944113114b69571608a2d9a98bca6a656ff6d7b87cac726ff70e00d" +checksum = "da26ca0ff94d3bd29e38e86be8c57495fe21ae0cf1203cc2f9106255478e6df2" dependencies = [ "bitflags", "fastly-shared", @@ -140,9 +140,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", diff --git a/test-fixtures/Cargo.toml b/test-fixtures/Cargo.toml index c67250e4..226b9546 100644 --- a/test-fixtures/Cargo.toml +++ b/test-fixtures/Cargo.toml @@ -9,10 +9,10 @@ publish = false [dependencies] base64 = "0.21.2" -fastly = "0.9.11" -fastly-shared = "0.9.11" -fastly-sys = "0.9.11" +fastly = "0.10.1" +fastly-shared = "0.10.1" +fastly-sys = "0.10.1" bytes = "1.0.0" -http = "0.2.9" +http = "1.1.0" rustls-pemfile = "1.0.3" serde = "1.0.114" diff --git a/test-fixtures/src/bin/downstream-req.rs b/test-fixtures/src/bin/downstream-req.rs index 8515d1fc..b555a0b1 100644 --- a/test-fixtures/src/bin/downstream-req.rs +++ b/test-fixtures/src/bin/downstream-req.rs @@ -25,6 +25,9 @@ fn main() { let localhost: IpAddr = "127.0.0.1".parse().unwrap(); assert_eq!(client_req.get_client_ip_addr().unwrap(), localhost); + let localhost: IpAddr = "127.0.0.1".parse().unwrap(); + assert_eq!(client_req.get_server_ip_addr().unwrap(), localhost); + assert_eq!(client_req.get_tls_cipher_openssl_name(), None); assert_eq!(client_req.get_tls_cipher_openssl_name_bytes(), None); assert_eq!(client_req.get_tls_client_hello(), None);