diff --git a/Makefile b/Makefile index b437810a9..2082a7099 100644 --- a/Makefile +++ b/Makefile @@ -160,7 +160,7 @@ results/$(REGISTRY_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-reg tunnel-deb: results/$(TUNNEL_BASENAME).deb results/$(TUNNEL_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-tunnel) $(TUNNEL_TARGETS) - PROJECT=start-tunnel PLATFORM=$(ARCH) REQUIRES=debian DEPENDS=wireguard-tools,iptables ./build/os-compat/run-compat.sh ./dpkg-build.sh + PROJECT=start-tunnel PLATFORM=$(ARCH) REQUIRES=debian DEPENDS=wireguard-tools,iptables,conntrack ./build/os-compat/run-compat.sh ./dpkg-build.sh $(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE) diff --git a/build/dpkg-deps/depends b/build/dpkg-deps/depends index 9703cc99e..46f1dfe86 100644 --- a/build/dpkg-deps/depends +++ b/build/dpkg-deps/depends @@ -7,6 +7,7 @@ bmon btrfs-progs ca-certificates cifs-utils +conntrack cryptsetup curl dmidecode diff --git a/build/lib/scripts/forward-port b/build/lib/scripts/forward-port index 8152c6b7a..5d1e0ba45 100755 --- a/build/lib/scripts/forward-port +++ b/build/lib/scripts/forward-port @@ -5,34 +5,25 @@ if [ -z "$sip" ] || [ -z "$dip" ] || [ -z "$sport" ] || [ -z "$dport" ]; then exit 1 fi -# Helper function to check if a rule exists -nat_rule_exists() { +rule_exists() { iptables -t nat -C "$@" 2>/dev/null } -# Helper function to add or delete a rule idempotently -# Usage: apply_rule [add|del] -apply_nat_rule() { - local action="$1" - shift - - if [ "$action" = "add" ]; then - # Only add if rule doesn't exist - if ! rule_exists "$@"; then - iptables -t nat -A "$@" - fi - elif [ "$action" = "del" ]; then +apply_rule() { + if [ "$UNDO" = "1" ]; then if rule_exists "$@"; then iptables -t nat -D "$@" fi + else + if ! rule_exists "$@"; then + iptables -t nat -A "$@" + fi fi } -if [ "$UNDO" = 1 ]; then - action="del" -else - action="add" -fi +apply_rule PREROUTING -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport +apply_rule OUTPUT -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport -apply_nat_rule "$action" PREROUTING -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport -apply_nat_rule "$action" OUTPUT -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport \ No newline at end of file +if [ "$UNDO" = 1 ]; then + conntrack -D -p tcp -d $sip --dport $sport +fi \ No newline at end of file diff --git a/core/Cargo.lock b/core/Cargo.lock index 7875d7545..f0dacb1a0 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -7916,7 +7916,6 @@ dependencies = [ "async-compression", "async-stream", "async-trait", - "aws-lc-sys", "axum 0.8.6", "backtrace-on-stack-overflow", "barrage", diff --git a/core/build-cli.sh b/core/build-cli.sh index e575b8be7..57d5ed2c1 100755 --- a/core/build-cli.sh +++ b/core/build-cli.sh @@ -2,6 +2,8 @@ cd "$(dirname "${BASH_SOURCE[0]}")" +source ./builder-alias.sh + set -ea shopt -s expand_aliases @@ -18,15 +20,20 @@ if [ "$ARCH" = "arm64" ]; then ARCH="aarch64" fi +RUST_ARCH="$ARCH" +if [ "$ARCH" = "riscv64" ]; then + RUST_ARCH="riscv64gc" +fi + if [ -z "${KERNEL_NAME:-}" ]; then KERNEL_NAME=$(uname -s) fi if [ -z "${TARGET:-}" ]; then if [ "$KERNEL_NAME" = "Linux" ]; then - TARGET="$ARCH-unknown-linux-musl" + TARGET="$RUST_ARCH-unknown-linux-musl" elif [ "$KERNEL_NAME" = "Darwin" ]; then - TARGET="$ARCH-apple-darwin" + TARGET="$RUST_ARCH-apple-darwin" else >&2 echo "unknown kernel $KERNEL_NAME" exit 1 @@ -53,4 +60,4 @@ fi echo "FEATURES=\"$FEATURES\"" echo "RUSTFLAGS=\"$RUSTFLAGS\"" -cross build --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features $FEATURE_ARGS --locked --bin start-cli --target=$TARGET \ No newline at end of file +rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features $FEATURE_ARGS --locked --bin start-cli --target=$TARGET \ No newline at end of file diff --git a/core/builder-alias.sh b/core/builder-alias.sh index fa8d545d6..760c0bf0e 100644 --- a/core/builder-alias.sh +++ b/core/builder-alias.sh @@ -1,3 +1,8 @@ #!/bin/bash -alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -e SCCACHE_GHA_ENABLED -e SCCACHE_GHA_VERSION -e ACTIONS_RESULTS_URL -e ACTIONS_RUNTIME_TOKEN -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$HOME/.cache/sccache":/root/.cache/sccache -v "$(pwd)":/home/rust/src -w /home/rust/src -P start9/rust-musl-cross:$ARCH-musl' +USE_TTY= +if tty -s; then + USE_TTY="-it" +fi + +alias 'rust-zig-builder'='docker run '"$USE_TTY"' --rm -e "RUSTFLAGS=$RUSTFLAGS" -e "CFLAGS=-D_FORTIFY_SOURCE=2" -e "CXXFLAGS=-D_FORTIFY_SOURCE=2" -e SCCACHE_GHA_ENABLED -e SCCACHE_GHA_VERSION -e ACTIONS_RESULTS_URL -e ACTIONS_RUNTIME_TOKEN -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$HOME/.cache/sccache":/root/.cache/sccache -v "$(pwd)":/workdir -w /workdir -P start9/cargo-zigbuild' diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index 3b6ab0b0a..405d1db3b 100644 --- a/core/startos/Cargo.toml +++ b/core/startos/Cargo.toml @@ -93,7 +93,6 @@ async-compression = { version = "0.4.32", features = [ ] } async-stream = "0.3.5" async-trait = "0.1.74" -aws-lc-sys = { version = "0.32", features = ["bindgen"] } axum = { version = "0.8.4", features = ["ws"] } backtrace-on-stack-overflow = { version = "0.3.0", optional = true } barrage = "0.2.3" @@ -252,7 +251,7 @@ termion = "4.0.5" textwrap = "0.16.1" thiserror = "2.0.12" tokio = { version = "1.38.1", features = ["full"] } -tokio-rustls = "0.26.0" +tokio-rustls = "0.26.4" tokio-stream = { version = "0.1.14", features = ["io-util", "net", "sync"] } tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" } tokio-tungstenite = { version = "0.26.2", features = ["native-tls", "url"] } diff --git a/core/startos/src/bins/tunnel.rs b/core/startos/src/bins/tunnel.rs index 3a2e2337a..66921c28f 100644 --- a/core/startos/src/bins/tunnel.rs +++ b/core/startos/src/bins/tunnel.rs @@ -22,7 +22,7 @@ use crate::tunnel::tunnel_router; use crate::tunnel::web::TunnelCertHandler; use crate::util::logger::LOGGER; -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] enum WebserverListener { Http, Https(SocketAddr), diff --git a/core/startos/src/net/forward.rs b/core/startos/src/net/forward.rs index d02c8d1af..a46e02c7a 100644 --- a/core/startos/src/net/forward.rs +++ b/core/startos/src/net/forward.rs @@ -437,7 +437,8 @@ impl InterfaceForwardState { for mut entry in self.state.iter_mut() { entry.gc(ip_info, &self.port_forward).await?; } - Ok(()) + + self.port_forward.gc().await } } @@ -537,7 +538,6 @@ impl InterfacePortForwardController { _ = ip_info.changed() => { interfaces = ip_info.read(); state.sync(&interfaces).await.log_err(); - state.port_forward.gc().await.log_err(); } } } diff --git a/core/startos/src/net/gateway.rs b/core/startos/src/net/gateway.rs index 953348659..dd2fa89de 100644 --- a/core/startos/src/net/gateway.rs +++ b/core/startos/src/net/gateway.rs @@ -1,5 +1,6 @@ use std::any::Any; use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::fmt; use std::future::Future; use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6}; use std::sync::{Arc, Weak}; @@ -1551,6 +1552,14 @@ pub struct NetworkInterfaceListenerAcceptMetadata { pub inner: ::Metadata, pub info: GatewayInfo, } +impl fmt::Debug for NetworkInterfaceListenerAcceptMetadata { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NetworkInterfaceListenerAcceptMetadata") + .field("inner", &self.inner) + .field("info", &self.info) + .finish() + } +} impl Clone for NetworkInterfaceListenerAcceptMetadata where ::Metadata: Clone, @@ -1627,3 +1636,39 @@ where Self::new(Some(Either::Left(listener))) } } + +#[test] +fn test_filter() { + use crate::net::host::binding::NetInfo; + let wg1 = "wg1".parse::().unwrap(); + assert!(!InterfaceFilter::filter( + &AndFilter( + NetInfo { + private_disabled: [wg1.clone()].into_iter().collect(), + public_enabled: Default::default(), + assigned_port: None, + assigned_ssl_port: None, + }, + AndFilter(IdFilter(wg1.clone()), PublicFilter { public: false }), + ) + .into_dyn(), + &wg1, + &NetworkInterfaceInfo { + name: None, + public: None, + secure: None, + ip_info: Some(Arc::new(IpInfo { + name: "".into(), + scope_id: 3, + device_type: Some(NetworkInterfaceType::Wireguard), + subnets: ["10.59.0.2/24".parse::().unwrap()] + .into_iter() + .collect(), + lan_ip: Default::default(), + wan_ip: None, + ntp_servers: Default::default(), + dns_servers: Default::default(), + })), + }, + )); +} diff --git a/core/startos/src/net/vhost.rs b/core/startos/src/net/vhost.rs index 80c3325fd..7447b8b76 100644 --- a/core/startos/src/net/vhost.rs +++ b/core/startos/src/net/vhost.rs @@ -1,5 +1,6 @@ use std::any::Any; use std::collections::{BTreeMap, BTreeSet}; +use std::fmt; use std::net::{IpAddr, SocketAddr}; use std::sync::{Arc, Weak}; use std::task::{Poll, ready}; @@ -41,6 +42,7 @@ use crate::net::tls::{ use crate::net::web_server::{Accept, AcceptStream, ExtractVisitor, TcpMetadata, extract}; use crate::prelude::*; use crate::util::collections::EqSet; +use crate::util::future::WeakFuture; use crate::util::serde::{HandlerExtSerde, MaybeUtf8String, display_serializable}; use crate::util::sync::{SyncMutex, Watch}; @@ -134,7 +136,6 @@ impl VHostController { pub fn dump_table( &self, ) -> BTreeMap, BTreeMap>, EqSet>> { - let ip_info = self.interfaces.watcher.ip_info(); self.servers.peek(|s| { s.iter() .map(|(k, v)| { @@ -187,7 +188,7 @@ pub trait VHostTarget: std::fmt::Debug + Eq { hello: &'a ClientHello<'a>, metadata: &'a ::Metadata, ) -> impl Future> + Send + 'a; - fn handle_stream(&self, stream: AcceptStream, prev: Self::PreprocessRes); + fn handle_stream(&self, stream: AcceptStream, prev: Self::PreprocessRes, rc: Weak<()>); } pub trait DynVHostTargetT: std::fmt::Debug + Any { @@ -199,7 +200,7 @@ pub trait DynVHostTargetT: std::fmt::Debug + Any { hello: &'a ClientHello<'a>, metadata: &'a ::Metadata, ) -> BoxFuture<'a, Option<(ServerConfig, Box)>>; - fn handle_stream(&self, stream: AcceptStream, prev: Box); + fn handle_stream(&self, stream: AcceptStream, prev: Box, rc: Weak<()>); fn eq(&self, other: &dyn DynVHostTargetT) -> bool; } impl + 'static> DynVHostTargetT for T { @@ -219,9 +220,9 @@ impl + 'static> DynVHostTargetT for T { .map(|o| o.map(|(cfg, res)| (cfg, Box::new(res) as Box))) .boxed() } - fn handle_stream(&self, stream: AcceptStream, prev: Box) { + fn handle_stream(&self, stream: AcceptStream, prev: Box, rc: Weak<()>) { if let Ok(prev) = prev.downcast() { - VHostTarget::handle_stream(self, stream, *prev); + VHostTarget::handle_stream(self, stream, *prev, rc); } } fn eq(&self, other: &dyn DynVHostTargetT) -> bool { @@ -251,21 +252,27 @@ impl PartialEq for DynVHostTarget { } } impl Eq for DynVHostTarget {} -struct Preprocessed(DynVHostTarget, Box); +struct Preprocessed(DynVHostTarget, Weak<()>, Box); +impl fmt::Debug for Preprocessed { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (self.0).0.fmt(f) + } +} impl DynVHostTarget { async fn into_preprocessed( self, + rc: Weak<()>, prev: ServerConfig, hello: &ClientHello<'_>, metadata: &::Metadata, ) -> Option<(ServerConfig, Preprocessed)> { let (cfg, res) = self.0.preprocess(prev, hello, metadata).await?; - Some((cfg, Preprocessed(self, res))) + Some((cfg, Preprocessed(self, rc, res))) } } impl Preprocessed { fn finish(self, stream: AcceptStream) { - (self.0).0.handle_stream(stream, self.1); + (self.0).0.handle_stream(stream, self.2, self.1); } } @@ -279,6 +286,7 @@ pub struct ProxyTarget { impl PartialEq for ProxyTarget { fn eq(&self, other: &Self) -> bool { self.filter == other.filter + && self.acme == other.acme && self.addr == other.addr && self.connect_ssl.as_ref().map(Arc::as_ptr) == other.connect_ssl.as_ref().map(Arc::as_ptr) @@ -294,6 +302,9 @@ where type PreprocessRes = AcceptStream; fn filter(&self, metadata: &::Metadata) -> bool { let info = extract::(metadata); + if info.is_none() { + tracing::warn!("No GatewayInfo on metadata"); + } info.as_ref() .map_or(true, |i| self.filter.filter(&i.id, &i.info)) } @@ -304,7 +315,7 @@ where &'a self, mut prev: ServerConfig, hello: &'a ClientHello<'a>, - metadata: &'a ::Metadata, + _: &'a ::Metadata, ) -> Option<(ServerConfig, Self::PreprocessRes)> { let tcp_stream = TcpStream::connect(self.addr) .await @@ -345,8 +356,10 @@ where } Some((prev, Box::pin(tcp_stream))) } - fn handle_stream(&self, mut stream: AcceptStream, mut prev: Self::PreprocessRes) { - tokio::spawn(async move { tokio::io::copy_bidirectional(&mut stream, &mut prev).await }); + fn handle_stream(&self, mut stream: AcceptStream, mut prev: Self::PreprocessRes, rc: Weak<()>) { + tokio::spawn(async move { + WeakFuture::new(rc, tokio::io::copy_bidirectional(&mut stream, &mut prev)).await + }); } } @@ -436,16 +449,16 @@ where return Some(prev); } - let target = self.0.peek(|m| { + let (target, rc) = self.0.peek(|m| { m.get(&hello.server_name().map(InternedString::from)) .into_iter() .flatten() .filter(|(_, rc)| rc.strong_count() > 0) .find(|(t, _)| t.0.filter(metadata)) - .map(|(e, _)| e.clone()) + .map(|(t, rc)| (t.clone(), rc.clone())) })?; - let (prev, store) = target.into_preprocessed(prev, hello, metadata).await?; + let (prev, store) = target.into_preprocessed(rc, prev, hello, metadata).await?; self.1 = Some(store); @@ -480,6 +493,14 @@ struct VHostListenerMetadata { inner: TlsMetadata, preprocessed: Preprocessed, } +impl fmt::Debug for VHostListenerMetadata { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("VHostListenerMetadata") + .field("inner", &self.inner) + .field("preprocessed", &self.preprocessed) + .finish() + } +} impl Accept for VHostListener where for<'a> M: HasModel> @@ -637,6 +658,7 @@ impl VHostServer { changed = true; Arc::new(()) }; + targets.retain(|_, rc| rc.strong_count() > 0); targets.insert(target, Arc::downgrade(&rc)); writable.insert(hostname, targets); res = Ok(rc); diff --git a/core/startos/src/net/web_server.rs b/core/startos/src/net/web_server.rs index a03365417..c1844ec44 100644 --- a/core/startos/src/net/web_server.rs +++ b/core/startos/src/net/web_server.rs @@ -1,3 +1,4 @@ +use core::fmt; use std::any::Any; use std::collections::BTreeMap; use std::future::Future; @@ -68,7 +69,7 @@ pub fn extract< metadata: &M, ) -> Option { let mut visitor = ExtractVisitor(None); - visitor.visit(metadata); + metadata.visit(&mut visitor); visitor.0 } @@ -84,7 +85,7 @@ impl Visit for TcpMetadata { } pub trait Accept { - type Metadata; + type Metadata: fmt::Debug; fn poll_accept( &mut self, cx: &mut std::task::Context<'_>, @@ -144,7 +145,7 @@ where } } -#[derive(Clone, VisitFields)] +#[derive(Debug, Clone, VisitFields)] pub struct MapListenerMetadata { pub inner: M, pub key: K, @@ -162,7 +163,7 @@ where impl Accept for BTreeMap where - K: Clone, + K: Clone + fmt::Debug, A: Accept, { type Metadata = MapListenerMetadata; @@ -218,40 +219,38 @@ trait DynAcceptT: Send + Sync { fn poll_accept( &mut self, cx: &mut std::task::Context<'_>, - ) -> Poll< - Result< - ( - Box Visit> + Send + Sync>, - AcceptStream, - ), - Error, - >, - >; + ) -> Poll>; } impl DynAcceptT for A where A: Accept + Send + Sync, - for<'a> ::Metadata: Visit> + Send + Sync + 'static, + ::Metadata: DynMetadataT + 'static, { fn poll_accept( &mut self, cx: &mut std::task::Context<'_>, - ) -> Poll< - Result< - ( - Box Visit> + Send + Sync>, - AcceptStream, - ), - Error, - >, - > { + ) -> Poll> { let (metadata, stream) = ready!(Accept::poll_accept(self, cx)?); - Poll::Ready(Ok((Box::new(metadata), stream))) + Poll::Ready(Ok((DynMetadata(Box::new(metadata)), stream))) } } pub struct DynAccept(Box); +trait DynMetadataT: for<'a> Visit> + fmt::Debug + Send + Sync {} +impl DynMetadataT for T where for<'a> T: Visit> + fmt::Debug + Send + Sync {} + +#[derive(Debug)] +pub struct DynMetadata(Box); +impl<'a> Visit> for DynMetadata { + fn visit( + &self, + visitor: &mut ExtensionVisitor<'a>, + ) -> as Visitor>::Result { + self.0.visit(visitor) + } +} + impl Accept for DynAccept { - type Metadata = Box Visit> + Send + Sync>; + type Metadata = DynMetadata; fn poll_accept( &mut self, cx: &mut std::task::Context<'_>, @@ -325,7 +324,7 @@ impl Acceptor> { } impl Acceptor> where - K: Ord + Clone + Send + Sync + 'static, + K: Ord + Clone + fmt::Debug + Send + Sync + 'static, { pub async fn bind_map( listen: impl IntoIterator, @@ -347,7 +346,7 @@ where } impl Acceptor> where - K: Ord + Clone + Send + Sync + 'static, + K: Ord + Clone + fmt::Debug + Send + Sync + 'static, { pub async fn bind_map_dyn( listen: impl IntoIterator, diff --git a/core/startos/src/tunnel/api.rs b/core/startos/src/tunnel/api.rs index e2e60ffe7..5b88a5464 100644 --- a/core/startos/src/tunnel/api.rs +++ b/core/startos/src/tunnel/api.rs @@ -353,6 +353,7 @@ pub async fn show_config( Ok(client .client_config( ip, + subnet, wg.as_key().de()?.verifying_key(), (wan_addr, wg.as_port().de()?).into(), ) diff --git a/core/startos/src/tunnel/client.conf.template b/core/startos/src/tunnel/client.conf.template index 58673b890..c7e811d48 100644 --- a/core/startos/src/tunnel/client.conf.template +++ b/core/startos/src/tunnel/client.conf.template @@ -7,6 +7,6 @@ PrivateKey = {privkey} [Peer] PublicKey = {server_pubkey} PresharedKey = {psk} -AllowedIPs = 0.0.0.0/0,::/0 +AllowedIPs = {subnet} Endpoint = {server_addr} PersistentKeepalive = 25 \ No newline at end of file diff --git a/core/startos/src/tunnel/wg.rs b/core/startos/src/tunnel/wg.rs index 539a7438e..efe487e46 100644 --- a/core/startos/src/tunnel/wg.rs +++ b/core/startos/src/tunnel/wg.rs @@ -170,12 +170,14 @@ impl WgConfig { pub fn client_config( self, addr: Ipv4Addr, + subnet: Ipv4Net, server_pubkey: Base64, server_addr: SocketAddr, ) -> ClientConfig { ClientConfig { client_config: self, client_addr: addr, + subnet, server_pubkey, server_addr, } @@ -213,6 +215,7 @@ where pub struct ClientConfig { client_config: WgConfig, client_addr: Ipv4Addr, + subnet: Ipv4Net, #[serde(deserialize_with = "deserialize_verifying_key")] server_pubkey: Base64, server_addr: SocketAddr, @@ -226,6 +229,7 @@ impl std::fmt::Display for ClientConfig { privkey = self.client_config.key.to_padded_string(), psk = self.client_config.psk.to_padded_string(), addr = self.client_addr, + subnet = self.subnet, server_pubkey = self.server_pubkey.to_padded_string(), server_addr = self.server_addr, ) diff --git a/core/startos/src/util/future.rs b/core/startos/src/util/future.rs index d9a8369e2..4807411c8 100644 --- a/core/startos/src/util/future.rs +++ b/core/startos/src/util/future.rs @@ -1,11 +1,10 @@ use std::pin::Pin; +use std::sync::Weak; use std::task::{Context, Poll}; -use axum::middleware::FromFn; use futures::future::{BoxFuture, FusedFuture, abortable, pending}; use futures::stream::{AbortHandle, Abortable, BoxStream}; use futures::{Future, FutureExt, Stream, StreamExt}; -use rpc_toolkit::from_fn_blocking; use tokio::sync::watch; use tokio::task::LocalSet; @@ -201,3 +200,26 @@ async fn test_cancellable() { handle.cancel_and_wait().await; assert!(weak.strong_count() == 0); } + +#[pin_project::pin_project] +pub struct WeakFuture { + rc: Weak<()>, + #[pin] + fut: Fut, +} +impl WeakFuture { + pub fn new(rc: Weak<()>, fut: Fut) -> Self { + Self { rc, fut } + } +} +impl Future for WeakFuture { + type Output = Option; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + if this.rc.strong_count() > 0 { + this.fut.poll(cx).map(Some) + } else { + Poll::Ready(None) + } + } +}