diff --git a/ci.sh b/ci.sh
index db5f4d800..af98d6ed2 100755
--- a/ci.sh
+++ b/ci.sh
@@ -183,6 +183,7 @@ cargo batch  \
     --- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \
     $BUILD_EXTRA
 
+rm out/tests/nrf52840-dk/wifi_esp_hosted_perf
 
 if [[ -z "${TELEPROBE_TOKEN-}" ]]; then
     echo No teleprobe token found, skipping running HIL tests
diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs
index f48d3372e..0d7ac47a2 100644
--- a/embassy-net/src/lib.rs
+++ b/embassy-net/src/lib.rs
@@ -258,7 +258,7 @@ fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> HardwareAddress
     }
 }
 
-impl<D: Driver + 'static> Stack<D> {
+impl<D: Driver> Stack<D> {
     /// Create a new network stack.
     pub fn new<const SOCK: usize>(
         mut device: D,
@@ -555,7 +555,7 @@ impl<D: Driver + 'static> Stack<D> {
 }
 
 #[cfg(feature = "igmp")]
-impl<D: Driver + 'static> Stack<D> {
+impl<D: Driver> Stack<D> {
     /// Join a multicast group.
     pub async fn join_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
     where
@@ -645,7 +645,7 @@ impl SocketStack {
     }
 }
 
-impl<D: Driver + 'static> Inner<D> {
+impl<D: Driver> Inner<D> {
     #[cfg(feature = "proto-ipv4")]
     pub fn set_config_v4(&mut self, _s: &mut SocketStack, config: ConfigV4) {
         // Handle static config.
diff --git a/embassy-stm32/src/eth/v2/mod.rs b/embassy-stm32/src/eth/v2/mod.rs
index f03ea2e31..b7fe4766c 100644
--- a/embassy-stm32/src/eth/v2/mod.rs
+++ b/embassy-stm32/src/eth/v2/mod.rs
@@ -34,8 +34,6 @@ impl interrupt::typelevel::Handler<interrupt::typelevel::ETH> for InterruptHandl
     }
 }
 
-const MTU: usize = 1514; // 14 Ethernet header + 1500 IP packet
-
 pub struct Ethernet<'d, T: Instance, P: PHY> {
     _peri: PeripheralRef<'d, T>,
     pub(crate) tx: TDesRing<'d>,
@@ -163,7 +161,7 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
         dma.dmactx_cr().modify(|w| w.set_txpbl(1)); // 32 ?
         dma.dmacrx_cr().modify(|w| {
             w.set_rxpbl(1); // 32 ?
-            w.set_rbsz(MTU as u16);
+            w.set_rbsz(RX_BUFFER_SIZE as u16);
         });
 
         // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called
diff --git a/tests/nrf/.cargo/config.toml b/tests/nrf/.cargo/config.toml
index 03995f963..9d6b0313a 100644
--- a/tests/nrf/.cargo/config.toml
+++ b/tests/nrf/.cargo/config.toml
@@ -6,4 +6,4 @@ runner = "teleprobe client run"
 target = "thumbv7em-none-eabi"
 
 [env]
-DEFMT_LOG = "trace"
+DEFMT_LOG = "trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,smoltcp=info"
diff --git a/tests/nrf/Cargo.toml b/tests/nrf/Cargo.toml
index b3d214684..08fe1a4b5 100644
--- a/tests/nrf/Cargo.toml
+++ b/tests/nrf/Cargo.toml
@@ -19,6 +19,7 @@ embassy-net-enc28j60 = { version = "0.1.0", path = "../../embassy-net-enc28j60",
 embedded-hal-async = { version = "1.0.0-rc.1" }
 embedded-hal-bus = { version = "0.1.0-rc.1", features = ["async"] }
 static_cell = { version = "1.1", features = [ "nightly" ] }
+perf-client = { path = "../perf-client" }
 
 defmt = "0.3"
 defmt-rtt = "0.4"
diff --git a/tests/nrf/src/bin/ethernet_enc28j60_perf.rs b/tests/nrf/src/bin/ethernet_enc28j60_perf.rs
index 0446d39ac..60d30a2ff 100644
--- a/tests/nrf/src/bin/ethernet_enc28j60_perf.rs
+++ b/tests/nrf/src/bin/ethernet_enc28j60_perf.rs
@@ -4,17 +4,15 @@
 teleprobe_meta::target!(b"ak-gwe-r7");
 teleprobe_meta::timeout!(120);
 
-use defmt::{error, info, unwrap};
+use defmt::{info, unwrap};
 use embassy_executor::Spawner;
-use embassy_futures::join::join;
-use embassy_net::tcp::TcpSocket;
-use embassy_net::{Ipv4Address, Stack, StackResources};
+use embassy_net::{Stack, StackResources};
 use embassy_net_enc28j60::Enc28j60;
 use embassy_nrf::gpio::{Level, Output, OutputDrive};
 use embassy_nrf::rng::Rng;
 use embassy_nrf::spim::{self, Spim};
 use embassy_nrf::{bind_interrupts, peripherals};
-use embassy_time::{with_timeout, Delay, Duration, Timer};
+use embassy_time::Delay;
 use embedded_hal_bus::spi::ExclusiveDevice;
 use static_cell::make_static;
 use {defmt_rtt as _, panic_probe as _};
@@ -79,172 +77,16 @@ async fn main(spawner: Spawner) {
 
     unwrap!(spawner.spawn(net_task(stack)));
 
-    info!("Waiting for DHCP up...");
-    while stack.config_v4().is_none() {
-        Timer::after(Duration::from_millis(100)).await;
-    }
-    info!("IP addressing up!");
-
-    let down = test_download(stack).await;
-    let up = test_upload(stack).await;
-    let updown = test_upload_download(stack).await;
-
-    assert!(down > TEST_EXPECTED_DOWNLOAD_KBPS);
-    assert!(up > TEST_EXPECTED_UPLOAD_KBPS);
-    assert!(updown > TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS);
+    perf_client::run(
+        stack,
+        perf_client::Expected {
+            down_kbps: 200,
+            up_kbps: 200,
+            updown_kbps: 150,
+        },
+    )
+    .await;
 
     info!("Test OK");
     cortex_m::asm::bkpt();
 }
-
-const TEST_DURATION: usize = 10;
-const TEST_EXPECTED_DOWNLOAD_KBPS: usize = 200;
-const TEST_EXPECTED_UPLOAD_KBPS: usize = 200;
-const TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS: usize = 150;
-const RX_BUFFER_SIZE: usize = 4096;
-const TX_BUFFER_SIZE: usize = 4096;
-const SERVER_ADDRESS: Ipv4Address = Ipv4Address::new(192, 168, 2, 2);
-const DOWNLOAD_PORT: u16 = 4321;
-const UPLOAD_PORT: u16 = 4322;
-const UPLOAD_DOWNLOAD_PORT: u16 = 4323;
-
-async fn test_download(stack: &'static Stack<MyDriver>) -> usize {
-    info!("Testing download...");
-
-    let mut rx_buffer = [0; RX_BUFFER_SIZE];
-    let mut tx_buffer = [0; TX_BUFFER_SIZE];
-    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-    socket.set_timeout(Some(Duration::from_secs(10)));
-
-    info!("connecting to {:?}:{}...", SERVER_ADDRESS, DOWNLOAD_PORT);
-    if let Err(e) = socket.connect((SERVER_ADDRESS, DOWNLOAD_PORT)).await {
-        error!("connect error: {:?}", e);
-        return 0;
-    }
-    info!("connected, testing...");
-
-    let mut rx_buf = [0; 4096];
-    let mut total: usize = 0;
-    with_timeout(Duration::from_secs(TEST_DURATION as _), async {
-        loop {
-            match socket.read(&mut rx_buf).await {
-                Ok(0) => {
-                    error!("read EOF");
-                    return 0;
-                }
-                Ok(n) => total += n,
-                Err(e) => {
-                    error!("read error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    })
-    .await
-    .ok();
-
-    let kbps = (total + 512) / 1024 / TEST_DURATION;
-    info!("download: {} kB/s", kbps);
-    kbps
-}
-
-async fn test_upload(stack: &'static Stack<MyDriver>) -> usize {
-    info!("Testing upload...");
-
-    let mut rx_buffer = [0; RX_BUFFER_SIZE];
-    let mut tx_buffer = [0; TX_BUFFER_SIZE];
-    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-    socket.set_timeout(Some(Duration::from_secs(10)));
-
-    info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_PORT);
-    if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_PORT)).await {
-        error!("connect error: {:?}", e);
-        return 0;
-    }
-    info!("connected, testing...");
-
-    let buf = [0; 4096];
-    let mut total: usize = 0;
-    with_timeout(Duration::from_secs(TEST_DURATION as _), async {
-        loop {
-            match socket.write(&buf).await {
-                Ok(0) => {
-                    error!("write zero?!??!?!");
-                    return 0;
-                }
-                Ok(n) => total += n,
-                Err(e) => {
-                    error!("write error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    })
-    .await
-    .ok();
-
-    let kbps = (total + 512) / 1024 / TEST_DURATION;
-    info!("upload: {} kB/s", kbps);
-    kbps
-}
-
-async fn test_upload_download(stack: &'static Stack<MyDriver>) -> usize {
-    info!("Testing upload+download...");
-
-    let mut rx_buffer = [0; RX_BUFFER_SIZE];
-    let mut tx_buffer = [0; TX_BUFFER_SIZE];
-    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-    socket.set_timeout(Some(Duration::from_secs(10)));
-
-    info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT);
-    if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT)).await {
-        error!("connect error: {:?}", e);
-        return 0;
-    }
-    info!("connected, testing...");
-
-    let (mut reader, mut writer) = socket.split();
-
-    let tx_buf = [0; 4096];
-    let mut rx_buf = [0; 4096];
-    let mut total: usize = 0;
-    let tx_fut = async {
-        loop {
-            match writer.write(&tx_buf).await {
-                Ok(0) => {
-                    error!("write zero?!??!?!");
-                    return 0;
-                }
-                Ok(_) => {}
-                Err(e) => {
-                    error!("write error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    };
-
-    let rx_fut = async {
-        loop {
-            match reader.read(&mut rx_buf).await {
-                Ok(0) => {
-                    error!("read EOF");
-                    return 0;
-                }
-                Ok(n) => total += n,
-                Err(e) => {
-                    error!("read error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    };
-
-    with_timeout(Duration::from_secs(TEST_DURATION as _), join(tx_fut, rx_fut))
-        .await
-        .ok();
-
-    let kbps = (total + 512) / 1024 / TEST_DURATION;
-    info!("upload+download: {} kB/s", kbps);
-    kbps
-}
diff --git a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs
index 97ebafec8..9eee39ccf 100644
--- a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs
+++ b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs
@@ -4,16 +4,14 @@
 teleprobe_meta::target!(b"nrf52840-dk");
 teleprobe_meta::timeout!(120);
 
-use defmt::{error, info, unwrap};
+use defmt::{info, unwrap};
 use embassy_executor::Spawner;
-use embassy_futures::join::join;
-use embassy_net::tcp::TcpSocket;
-use embassy_net::{Config, Ipv4Address, Stack, StackResources};
+use embassy_net::{Config, Stack, StackResources};
 use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull};
 use embassy_nrf::rng::Rng;
 use embassy_nrf::spim::{self, Spim};
 use embassy_nrf::{bind_interrupts, peripherals};
-use embassy_time::{with_timeout, Delay, Duration, Timer};
+use embassy_time::Delay;
 use embedded_hal_bus::spi::ExclusiveDevice;
 use static_cell::make_static;
 use {defmt_rtt as _, embassy_net_esp_hosted as hosted, panic_probe as _};
@@ -23,6 +21,10 @@ bind_interrupts!(struct Irqs {
     RNG => embassy_nrf::rng::InterruptHandler<peripherals::RNG>;
 });
 
+// Test-only wifi network, no internet access!
+const WIFI_NETWORK: &str = "EmbassyTest";
+const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud";
+
 #[embassy_executor::task]
 async fn wifi_task(
     runner: hosted::Runner<
@@ -92,176 +94,16 @@ async fn main(spawner: Spawner) {
 
     unwrap!(spawner.spawn(net_task(stack)));
 
-    info!("Waiting for DHCP up...");
-    while stack.config_v4().is_none() {
-        Timer::after(Duration::from_millis(100)).await;
-    }
-    info!("IP addressing up!");
-
-    let down = test_download(stack).await;
-    let up = test_upload(stack).await;
-    let updown = test_upload_download(stack).await;
-
-    assert!(down > TEST_EXPECTED_DOWNLOAD_KBPS);
-    assert!(up > TEST_EXPECTED_UPLOAD_KBPS);
-    assert!(updown > TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS);
+    perf_client::run(
+        stack,
+        perf_client::Expected {
+            down_kbps: 50,
+            up_kbps: 50,
+            updown_kbps: 50,
+        },
+    )
+    .await;
 
     info!("Test OK");
     cortex_m::asm::bkpt();
 }
-
-// Test-only wifi network, no internet access!
-const WIFI_NETWORK: &str = "EmbassyTest";
-const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud";
-
-const TEST_DURATION: usize = 10;
-const TEST_EXPECTED_DOWNLOAD_KBPS: usize = 50;
-const TEST_EXPECTED_UPLOAD_KBPS: usize = 50;
-const TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS: usize = 50;
-const RX_BUFFER_SIZE: usize = 4096;
-const TX_BUFFER_SIZE: usize = 4096;
-const SERVER_ADDRESS: Ipv4Address = Ipv4Address::new(192, 168, 2, 2);
-const DOWNLOAD_PORT: u16 = 4321;
-const UPLOAD_PORT: u16 = 4322;
-const UPLOAD_DOWNLOAD_PORT: u16 = 4323;
-
-async fn test_download(stack: &'static Stack<MyDriver>) -> usize {
-    info!("Testing download...");
-
-    let mut rx_buffer = [0; RX_BUFFER_SIZE];
-    let mut tx_buffer = [0; TX_BUFFER_SIZE];
-    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-    socket.set_timeout(Some(Duration::from_secs(10)));
-
-    info!("connecting to {:?}:{}...", SERVER_ADDRESS, DOWNLOAD_PORT);
-    if let Err(e) = socket.connect((SERVER_ADDRESS, DOWNLOAD_PORT)).await {
-        error!("connect error: {:?}", e);
-        return 0;
-    }
-    info!("connected, testing...");
-
-    let mut rx_buf = [0; 4096];
-    let mut total: usize = 0;
-    with_timeout(Duration::from_secs(TEST_DURATION as _), async {
-        loop {
-            match socket.read(&mut rx_buf).await {
-                Ok(0) => {
-                    error!("read EOF");
-                    return 0;
-                }
-                Ok(n) => total += n,
-                Err(e) => {
-                    error!("read error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    })
-    .await
-    .ok();
-
-    let kbps = (total + 512) / 1024 / TEST_DURATION;
-    info!("download: {} kB/s", kbps);
-    kbps
-}
-
-async fn test_upload(stack: &'static Stack<MyDriver>) -> usize {
-    info!("Testing upload...");
-
-    let mut rx_buffer = [0; RX_BUFFER_SIZE];
-    let mut tx_buffer = [0; TX_BUFFER_SIZE];
-    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-    socket.set_timeout(Some(Duration::from_secs(10)));
-
-    info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_PORT);
-    if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_PORT)).await {
-        error!("connect error: {:?}", e);
-        return 0;
-    }
-    info!("connected, testing...");
-
-    let buf = [0; 4096];
-    let mut total: usize = 0;
-    with_timeout(Duration::from_secs(TEST_DURATION as _), async {
-        loop {
-            match socket.write(&buf).await {
-                Ok(0) => {
-                    error!("write zero?!??!?!");
-                    return 0;
-                }
-                Ok(n) => total += n,
-                Err(e) => {
-                    error!("write error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    })
-    .await
-    .ok();
-
-    let kbps = (total + 512) / 1024 / TEST_DURATION;
-    info!("upload: {} kB/s", kbps);
-    kbps
-}
-
-async fn test_upload_download(stack: &'static Stack<MyDriver>) -> usize {
-    info!("Testing upload+download...");
-
-    let mut rx_buffer = [0; RX_BUFFER_SIZE];
-    let mut tx_buffer = [0; TX_BUFFER_SIZE];
-    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-    socket.set_timeout(Some(Duration::from_secs(10)));
-
-    info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT);
-    if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT)).await {
-        error!("connect error: {:?}", e);
-        return 0;
-    }
-    info!("connected, testing...");
-
-    let (mut reader, mut writer) = socket.split();
-
-    let tx_buf = [0; 4096];
-    let mut rx_buf = [0; 4096];
-    let mut total: usize = 0;
-    let tx_fut = async {
-        loop {
-            match writer.write(&tx_buf).await {
-                Ok(0) => {
-                    error!("write zero?!??!?!");
-                    return 0;
-                }
-                Ok(_) => {}
-                Err(e) => {
-                    error!("write error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    };
-
-    let rx_fut = async {
-        loop {
-            match reader.read(&mut rx_buf).await {
-                Ok(0) => {
-                    error!("read EOF");
-                    return 0;
-                }
-                Ok(n) => total += n,
-                Err(e) => {
-                    error!("read error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    };
-
-    with_timeout(Duration::from_secs(TEST_DURATION as _), join(tx_fut, rx_fut))
-        .await
-        .ok();
-
-    let kbps = (total + 512) / 1024 / TEST_DURATION;
-    info!("upload+download: {} kB/s", kbps);
-    kbps
-}
diff --git a/tests/perf-client/Cargo.toml b/tests/perf-client/Cargo.toml
new file mode 100644
index 000000000..3284664d9
--- /dev/null
+++ b/tests/perf-client/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "perf-client"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4"] }
+embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "nightly", "unstable-traits"] }
+embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
+defmt = "0.3.0"
diff --git a/tests/perf-client/src/lib.rs b/tests/perf-client/src/lib.rs
new file mode 100644
index 000000000..a36147dbb
--- /dev/null
+++ b/tests/perf-client/src/lib.rs
@@ -0,0 +1,179 @@
+#![no_std]
+
+use defmt::{assert, *};
+use embassy_futures::join::join;
+use embassy_net::driver::Driver;
+use embassy_net::tcp::TcpSocket;
+use embassy_net::{Ipv4Address, Stack};
+use embassy_time::{with_timeout, Duration, Timer};
+
+pub struct Expected {
+    pub down_kbps: usize,
+    pub up_kbps: usize,
+    pub updown_kbps: usize,
+}
+
+pub async fn run<D: Driver>(stack: &Stack<D>, expected: Expected) {
+    info!("Waiting for DHCP up...");
+    while stack.config_v4().is_none() {
+        Timer::after(Duration::from_millis(100)).await;
+    }
+    info!("IP addressing up!");
+
+    let down = test_download(stack).await;
+    let up = test_upload(stack).await;
+    let updown = test_upload_download(stack).await;
+
+    assert!(down > expected.down_kbps);
+    assert!(up > expected.up_kbps);
+    assert!(updown > expected.updown_kbps);
+}
+
+const TEST_DURATION: usize = 10;
+const RX_BUFFER_SIZE: usize = 4096;
+const TX_BUFFER_SIZE: usize = 4096;
+const SERVER_ADDRESS: Ipv4Address = Ipv4Address::new(192, 168, 2, 2);
+const DOWNLOAD_PORT: u16 = 4321;
+const UPLOAD_PORT: u16 = 4322;
+const UPLOAD_DOWNLOAD_PORT: u16 = 4323;
+
+async fn test_download<D: Driver>(stack: &Stack<D>) -> usize {
+    info!("Testing download...");
+
+    let mut rx_buffer = [0; RX_BUFFER_SIZE];
+    let mut tx_buffer = [0; TX_BUFFER_SIZE];
+    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
+    socket.set_timeout(Some(Duration::from_secs(10)));
+
+    info!("connecting to {:?}:{}...", SERVER_ADDRESS, DOWNLOAD_PORT);
+    if let Err(e) = socket.connect((SERVER_ADDRESS, DOWNLOAD_PORT)).await {
+        error!("connect error: {:?}", e);
+        return 0;
+    }
+    info!("connected, testing...");
+
+    let mut rx_buf = [0; 4096];
+    let mut total: usize = 0;
+    with_timeout(Duration::from_secs(TEST_DURATION as _), async {
+        loop {
+            match socket.read(&mut rx_buf).await {
+                Ok(0) => {
+                    error!("read EOF");
+                    return 0;
+                }
+                Ok(n) => total += n,
+                Err(e) => {
+                    error!("read error: {:?}", e);
+                    return 0;
+                }
+            }
+        }
+    })
+    .await
+    .ok();
+
+    let kbps = (total + 512) / 1024 / TEST_DURATION;
+    info!("download: {} kB/s", kbps);
+    kbps
+}
+
+async fn test_upload<D: Driver>(stack: &Stack<D>) -> usize {
+    info!("Testing upload...");
+
+    let mut rx_buffer = [0; RX_BUFFER_SIZE];
+    let mut tx_buffer = [0; TX_BUFFER_SIZE];
+    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
+    socket.set_timeout(Some(Duration::from_secs(10)));
+
+    info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_PORT);
+    if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_PORT)).await {
+        error!("connect error: {:?}", e);
+        return 0;
+    }
+    info!("connected, testing...");
+
+    let buf = [0; 4096];
+    let mut total: usize = 0;
+    with_timeout(Duration::from_secs(TEST_DURATION as _), async {
+        loop {
+            match socket.write(&buf).await {
+                Ok(0) => {
+                    error!("write zero?!??!?!");
+                    return 0;
+                }
+                Ok(n) => total += n,
+                Err(e) => {
+                    error!("write error: {:?}", e);
+                    return 0;
+                }
+            }
+        }
+    })
+    .await
+    .ok();
+
+    let kbps = (total + 512) / 1024 / TEST_DURATION;
+    info!("upload: {} kB/s", kbps);
+    kbps
+}
+
+async fn test_upload_download<D: Driver>(stack: &Stack<D>) -> usize {
+    info!("Testing upload+download...");
+
+    let mut rx_buffer = [0; RX_BUFFER_SIZE];
+    let mut tx_buffer = [0; TX_BUFFER_SIZE];
+    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
+    socket.set_timeout(Some(Duration::from_secs(10)));
+
+    info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT);
+    if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT)).await {
+        error!("connect error: {:?}", e);
+        return 0;
+    }
+    info!("connected, testing...");
+
+    let (mut reader, mut writer) = socket.split();
+
+    let tx_buf = [0; 4096];
+    let mut rx_buf = [0; 4096];
+    let mut total: usize = 0;
+    let tx_fut = async {
+        loop {
+            match writer.write(&tx_buf).await {
+                Ok(0) => {
+                    error!("write zero?!??!?!");
+                    return 0;
+                }
+                Ok(_) => {}
+                Err(e) => {
+                    error!("write error: {:?}", e);
+                    return 0;
+                }
+            }
+        }
+    };
+
+    let rx_fut = async {
+        loop {
+            match reader.read(&mut rx_buf).await {
+                Ok(0) => {
+                    error!("read EOF");
+                    return 0;
+                }
+                Ok(n) => total += n,
+                Err(e) => {
+                    error!("read error: {:?}", e);
+                    return 0;
+                }
+            }
+        }
+    };
+
+    with_timeout(Duration::from_secs(TEST_DURATION as _), join(tx_fut, rx_fut))
+        .await
+        .ok();
+
+    let kbps = (total + 512) / 1024 / TEST_DURATION;
+    info!("upload+download: {} kB/s", kbps);
+    kbps
+}
diff --git a/tests/rp/.cargo/config.toml b/tests/rp/.cargo/config.toml
index bc92e788b..40b5d7000 100644
--- a/tests/rp/.cargo/config.toml
+++ b/tests/rp/.cargo/config.toml
@@ -19,4 +19,4 @@ rustflags = [
 target = "thumbv6m-none-eabi"
 
 [env]
-DEFMT_LOG = "trace"
+DEFMT_LOG = "trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info"
diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml
index 26e2473c7..8bb0de6c6 100644
--- a/tests/rp/Cargo.toml
+++ b/tests/rp/Cargo.toml
@@ -16,6 +16,7 @@ embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defm
 embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] }
 cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] }
 cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] }
+perf-client = { path = "../perf-client" }
 
 defmt = "0.3.0"
 defmt-rtt = "0.4"
diff --git a/tests/rp/src/bin/cyw43-perf.rs b/tests/rp/src/bin/cyw43-perf.rs
index 1c665f95d..de29c06dd 100644
--- a/tests/rp/src/bin/cyw43-perf.rs
+++ b/tests/rp/src/bin/cyw43-perf.rs
@@ -4,16 +4,13 @@
 teleprobe_meta::target!(b"rpi-pico");
 
 use cyw43_pio::PioSpi;
-use defmt::{assert, panic, *};
+use defmt::{panic, *};
 use embassy_executor::Spawner;
-use embassy_futures::join::join;
-use embassy_net::tcp::TcpSocket;
-use embassy_net::{Config, Ipv4Address, Stack, StackResources};
+use embassy_net::{Config, Stack, StackResources};
 use embassy_rp::gpio::{Level, Output};
 use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0};
 use embassy_rp::pio::{InterruptHandler, Pio};
 use embassy_rp::{bind_interrupts, rom_data};
-use embassy_time::{with_timeout, Duration, Timer};
 use static_cell::make_static;
 use {defmt_rtt as _, panic_probe as _};
 
@@ -23,6 +20,10 @@ bind_interrupts!(struct Irqs {
 
 teleprobe_meta::timeout!(120);
 
+// Test-only wifi network, no internet access!
+const WIFI_NETWORK: &str = "EmbassyTest";
+const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud";
+
 #[embassy_executor::task]
 async fn wifi_task(
     runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>,
@@ -88,176 +89,16 @@ async fn main(spawner: Spawner) {
         }
     }
 
-    info!("Waiting for DHCP up...");
-    while stack.config_v4().is_none() {
-        Timer::after(Duration::from_millis(100)).await;
-    }
-    info!("IP addressing up!");
-
-    let down = test_download(stack).await;
-    let up = test_upload(stack).await;
-    let updown = test_upload_download(stack).await;
-
-    assert!(down > TEST_EXPECTED_DOWNLOAD_KBPS);
-    assert!(up > TEST_EXPECTED_UPLOAD_KBPS);
-    assert!(updown > TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS);
+    perf_client::run(
+        stack,
+        perf_client::Expected {
+            down_kbps: 300,
+            up_kbps: 300,
+            updown_kbps: 300,
+        },
+    )
+    .await;
 
     info!("Test OK");
     cortex_m::asm::bkpt();
 }
-
-// Test-only wifi network, no internet access!
-const WIFI_NETWORK: &str = "EmbassyTest";
-const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud";
-
-const TEST_DURATION: usize = 10;
-const TEST_EXPECTED_DOWNLOAD_KBPS: usize = 300;
-const TEST_EXPECTED_UPLOAD_KBPS: usize = 300;
-const TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS: usize = 300;
-const RX_BUFFER_SIZE: usize = 4096;
-const TX_BUFFER_SIZE: usize = 4096;
-const SERVER_ADDRESS: Ipv4Address = Ipv4Address::new(192, 168, 2, 2);
-const DOWNLOAD_PORT: u16 = 4321;
-const UPLOAD_PORT: u16 = 4322;
-const UPLOAD_DOWNLOAD_PORT: u16 = 4323;
-
-async fn test_download(stack: &'static Stack<cyw43::NetDriver<'static>>) -> usize {
-    info!("Testing download...");
-
-    let mut rx_buffer = [0; RX_BUFFER_SIZE];
-    let mut tx_buffer = [0; TX_BUFFER_SIZE];
-    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-    socket.set_timeout(Some(Duration::from_secs(10)));
-
-    info!("connecting to {:?}:{}...", SERVER_ADDRESS, DOWNLOAD_PORT);
-    if let Err(e) = socket.connect((SERVER_ADDRESS, DOWNLOAD_PORT)).await {
-        error!("connect error: {:?}", e);
-        return 0;
-    }
-    info!("connected, testing...");
-
-    let mut rx_buf = [0; 4096];
-    let mut total: usize = 0;
-    with_timeout(Duration::from_secs(TEST_DURATION as _), async {
-        loop {
-            match socket.read(&mut rx_buf).await {
-                Ok(0) => {
-                    error!("read EOF");
-                    return 0;
-                }
-                Ok(n) => total += n,
-                Err(e) => {
-                    error!("read error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    })
-    .await
-    .ok();
-
-    let kbps = (total + 512) / 1024 / TEST_DURATION;
-    info!("download: {} kB/s", kbps);
-    kbps
-}
-
-async fn test_upload(stack: &'static Stack<cyw43::NetDriver<'static>>) -> usize {
-    info!("Testing upload...");
-
-    let mut rx_buffer = [0; RX_BUFFER_SIZE];
-    let mut tx_buffer = [0; TX_BUFFER_SIZE];
-    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-    socket.set_timeout(Some(Duration::from_secs(10)));
-
-    info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_PORT);
-    if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_PORT)).await {
-        error!("connect error: {:?}", e);
-        return 0;
-    }
-    info!("connected, testing...");
-
-    let buf = [0; 4096];
-    let mut total: usize = 0;
-    with_timeout(Duration::from_secs(TEST_DURATION as _), async {
-        loop {
-            match socket.write(&buf).await {
-                Ok(0) => {
-                    error!("write zero?!??!?!");
-                    return 0;
-                }
-                Ok(n) => total += n,
-                Err(e) => {
-                    error!("write error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    })
-    .await
-    .ok();
-
-    let kbps = (total + 512) / 1024 / TEST_DURATION;
-    info!("upload: {} kB/s", kbps);
-    kbps
-}
-
-async fn test_upload_download(stack: &'static Stack<cyw43::NetDriver<'static>>) -> usize {
-    info!("Testing upload+download...");
-
-    let mut rx_buffer = [0; RX_BUFFER_SIZE];
-    let mut tx_buffer = [0; TX_BUFFER_SIZE];
-    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-    socket.set_timeout(Some(Duration::from_secs(10)));
-
-    info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT);
-    if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT)).await {
-        error!("connect error: {:?}", e);
-        return 0;
-    }
-    info!("connected, testing...");
-
-    let (mut reader, mut writer) = socket.split();
-
-    let tx_buf = [0; 4096];
-    let mut rx_buf = [0; 4096];
-    let mut total: usize = 0;
-    let tx_fut = async {
-        loop {
-            match writer.write(&tx_buf).await {
-                Ok(0) => {
-                    error!("write zero?!??!?!");
-                    return 0;
-                }
-                Ok(_) => {}
-                Err(e) => {
-                    error!("write error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    };
-
-    let rx_fut = async {
-        loop {
-            match reader.read(&mut rx_buf).await {
-                Ok(0) => {
-                    error!("read EOF");
-                    return 0;
-                }
-                Ok(n) => total += n,
-                Err(e) => {
-                    error!("read error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    };
-
-    with_timeout(Duration::from_secs(TEST_DURATION as _), join(tx_fut, rx_fut))
-        .await
-        .ok();
-
-    let kbps = (total + 512) / 1024 / TEST_DURATION;
-    info!("upload+download: {} kB/s", kbps);
-    kbps
-}
diff --git a/tests/rp/src/bin/ethernet_w5100s_perf.rs b/tests/rp/src/bin/ethernet_w5100s_perf.rs
index faa8638c0..a4d253b3c 100644
--- a/tests/rp/src/bin/ethernet_w5100s_perf.rs
+++ b/tests/rp/src/bin/ethernet_w5100s_perf.rs
@@ -4,18 +4,16 @@
 teleprobe_meta::target!(b"w5100s-evb-pico");
 teleprobe_meta::timeout!(120);
 
-use defmt::{assert, *};
+use defmt::*;
 use embassy_executor::Spawner;
-use embassy_futures::join::join;
-use embassy_net::tcp::TcpSocket;
-use embassy_net::{Ipv4Address, Stack, StackResources};
+use embassy_net::{Stack, StackResources};
 use embassy_net_wiznet::chip::W5100S;
 use embassy_net_wiznet::*;
 use embassy_rp::clocks::RoscRng;
 use embassy_rp::gpio::{Input, Level, Output, Pull};
 use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0};
 use embassy_rp::spi::{Async, Config as SpiConfig, Spi};
-use embassy_time::{with_timeout, Delay, Duration, Timer};
+use embassy_time::Delay;
 use embedded_hal_bus::spi::ExclusiveDevice;
 use rand::RngCore;
 use static_cell::make_static;
@@ -78,172 +76,16 @@ async fn main(spawner: Spawner) {
     // Launch network task
     unwrap!(spawner.spawn(net_task(&stack)));
 
-    info!("Waiting for DHCP up...");
-    while stack.config_v4().is_none() {
-        Timer::after(Duration::from_millis(100)).await;
-    }
-    info!("IP addressing up!");
-
-    let down = test_download(stack).await;
-    let up = test_upload(stack).await;
-    let updown = test_upload_download(stack).await;
-
-    assert!(down > TEST_EXPECTED_DOWNLOAD_KBPS);
-    assert!(up > TEST_EXPECTED_UPLOAD_KBPS);
-    assert!(updown > TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS);
+    perf_client::run(
+        stack,
+        perf_client::Expected {
+            down_kbps: 500,
+            up_kbps: 500,
+            updown_kbps: 300,
+        },
+    )
+    .await;
 
     info!("Test OK");
     cortex_m::asm::bkpt();
 }
-
-const TEST_DURATION: usize = 10;
-const TEST_EXPECTED_DOWNLOAD_KBPS: usize = 500;
-const TEST_EXPECTED_UPLOAD_KBPS: usize = 500;
-const TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS: usize = 300;
-const RX_BUFFER_SIZE: usize = 4096;
-const TX_BUFFER_SIZE: usize = 4096;
-const SERVER_ADDRESS: Ipv4Address = Ipv4Address::new(192, 168, 2, 2);
-const DOWNLOAD_PORT: u16 = 4321;
-const UPLOAD_PORT: u16 = 4322;
-const UPLOAD_DOWNLOAD_PORT: u16 = 4323;
-
-async fn test_download(stack: &'static Stack<cyw43::NetDriver<'static>>) -> usize {
-    info!("Testing download...");
-
-    let mut rx_buffer = [0; RX_BUFFER_SIZE];
-    let mut tx_buffer = [0; TX_BUFFER_SIZE];
-    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-    socket.set_timeout(Some(Duration::from_secs(10)));
-
-    info!("connecting to {:?}:{}...", SERVER_ADDRESS, DOWNLOAD_PORT);
-    if let Err(e) = socket.connect((SERVER_ADDRESS, DOWNLOAD_PORT)).await {
-        error!("connect error: {:?}", e);
-        return 0;
-    }
-    info!("connected, testing...");
-
-    let mut rx_buf = [0; 4096];
-    let mut total: usize = 0;
-    with_timeout(Duration::from_secs(TEST_DURATION as _), async {
-        loop {
-            match socket.read(&mut rx_buf).await {
-                Ok(0) => {
-                    error!("read EOF");
-                    return 0;
-                }
-                Ok(n) => total += n,
-                Err(e) => {
-                    error!("read error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    })
-    .await
-    .ok();
-
-    let kbps = (total + 512) / 1024 / TEST_DURATION;
-    info!("download: {} kB/s", kbps);
-    kbps
-}
-
-async fn test_upload(stack: &'static Stack<cyw43::NetDriver<'static>>) -> usize {
-    info!("Testing upload...");
-
-    let mut rx_buffer = [0; RX_BUFFER_SIZE];
-    let mut tx_buffer = [0; TX_BUFFER_SIZE];
-    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-    socket.set_timeout(Some(Duration::from_secs(10)));
-
-    info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_PORT);
-    if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_PORT)).await {
-        error!("connect error: {:?}", e);
-        return 0;
-    }
-    info!("connected, testing...");
-
-    let buf = [0; 4096];
-    let mut total: usize = 0;
-    with_timeout(Duration::from_secs(TEST_DURATION as _), async {
-        loop {
-            match socket.write(&buf).await {
-                Ok(0) => {
-                    error!("write zero?!??!?!");
-                    return 0;
-                }
-                Ok(n) => total += n,
-                Err(e) => {
-                    error!("write error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    })
-    .await
-    .ok();
-
-    let kbps = (total + 512) / 1024 / TEST_DURATION;
-    info!("upload: {} kB/s", kbps);
-    kbps
-}
-
-async fn test_upload_download(stack: &'static Stack<cyw43::NetDriver<'static>>) -> usize {
-    info!("Testing upload+download...");
-
-    let mut rx_buffer = [0; RX_BUFFER_SIZE];
-    let mut tx_buffer = [0; TX_BUFFER_SIZE];
-    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-    socket.set_timeout(Some(Duration::from_secs(10)));
-
-    info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT);
-    if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT)).await {
-        error!("connect error: {:?}", e);
-        return 0;
-    }
-    info!("connected, testing...");
-
-    let (mut reader, mut writer) = socket.split();
-
-    let tx_buf = [0; 4096];
-    let mut rx_buf = [0; 4096];
-    let mut total: usize = 0;
-    let tx_fut = async {
-        loop {
-            match writer.write(&tx_buf).await {
-                Ok(0) => {
-                    error!("write zero?!??!?!");
-                    return 0;
-                }
-                Ok(_) => {}
-                Err(e) => {
-                    error!("write error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    };
-
-    let rx_fut = async {
-        loop {
-            match reader.read(&mut rx_buf).await {
-                Ok(0) => {
-                    error!("read EOF");
-                    return 0;
-                }
-                Ok(n) => total += n,
-                Err(e) => {
-                    error!("read error: {:?}", e);
-                    return 0;
-                }
-            }
-        }
-    };
-
-    with_timeout(Duration::from_secs(TEST_DURATION as _), join(tx_fut, rx_fut))
-        .await
-        .ok();
-
-    let kbps = (total + 512) / 1024 / TEST_DURATION;
-    info!("upload+download: {} kB/s", kbps);
-    kbps
-}
diff --git a/tests/stm32/.cargo/config.toml b/tests/stm32/.cargo/config.toml
index 07761b01c..2e3f055d4 100644
--- a/tests/stm32/.cargo/config.toml
+++ b/tests/stm32/.cargo/config.toml
@@ -14,7 +14,10 @@ rustflags = [
 ]
 
 [build]
-target = "thumbv7m-none-eabi"
+target = "thumbv6m-none-eabi"
+#target = "thumbv7m-none-eabi"
+#target = "thumbv7em-none-eabi"
+#target = "thumbv8m.main-none-eabihf"
 
 [env]
-DEFMT_LOG = "trace"
\ No newline at end of file
+DEFMT_LOG = "trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,smoltcp=info"
\ No newline at end of file