rylev opened issue #7694:
My use case is essentially to allow any socket operation as long as the IP addressed used for that operation was resolved in the last call to
wasi:sockets/ip-name-lookup#resolve_addresses.Given the changes in https://github.com/bytecodealliance/wasmtime/pull/7662 this would stand to reason that somehow I need the ability to have a list of resolved names to IP addresses in the closure passed to
socket_addr_check.This could be accomplished in several ways:
- Wasmtime passes such a list directly as an argument to the
SocketAddrCheckclosure.- Wasmtime gives a way for the embedder to query what the list is at any time.
- Wasmtime allows the embedder to hook into DNS resolution and be called back with any (name, resolved IP address) pairs
- Other ways?
I think I might lean towards option 3. Perhaps this callback could be in the form
Fn(&str, IpAddress) -> Result<(), ErrorCode>which would be called anytimeresolve_next_addressis about to return aOk(Some(addr))to the guest. This would allow the embedder to do the following things:
- Observe DNS resolution as it happens (needed to unlock my use case)
- Change resolution so that a name resolves to a different set of IP addresses
- Return an
ErrorCodeerror fromresolve_next_addressIf we can agree on an approach, I'm happy to do the implementation.
alexcrichton commented on issue #7694:
I think this issue definitely makes sense, and this got me thinking a bit too. Reading over this issue and focusing solely on its text I'd also lean towards option 3 you've proposed but that quickly runs into another issue I think. The base problem is that Rust doesn't do well when building up a callback-style API with multiple callbacks. For example the "this got resolved" callback is distinct from the "is this socket address valid" callback. These two callbacks don't share state meaning that you'd have to use
Rc/Arc. With mutability you're forced to also useRefCell/Mutex. Given that these are used in aSend/Synccontext, that's requiringArc<Mutex<...>>which isn't great because there's no actual multithreading going on here.Given that problem it's prompted me to step back a bit. We solved this in Wasmtime for host functions by passing
&mut T(effectively) to all host functions. That's not possible here though because aTinStore<T>isn't availble fromWasiCtx. This means that I don't think there's an easy solution to this problem.So that leads me to a few possible alternatives:
- Implement (3) as is, use
Arc<Mutex<_>>, be a little sad on the inside but things are working.- Add a type parameter to
WasiCtx, such asWasiCtx<U>. Pass&mut Uto the callbacks configured onWasiCtxto have "shared state".- Add a trait object instead to
WasiCtx, something likeBox<dyn WasiCustomBehavior>. Move these callbacks to trait methods onWasiCustomBehaviorand then the shared state is&mut self.My personal preference given those options would be to remove
socket_addr_checkand instead have something like:impl WasiCtxBuilder { pub fn socket_config(&mut self, config: impl WasiSocketConfig) -> &mut Self; } pub trait WasiSocketConfig: Send + Sync + 'static { fn socket_addr_check(&mut self, addr: &SocketAddr, use_: SocketAddrUse) -> bool { true } fn resolved_addr(&mut self, name: &str, addr: &IpAddr) -> bool { true } // or bikeshed this signature }This trajectory of design however feels like it gets a bit silly taken to the limit though. For example we've already got traits representing address lookup, they're just generated by
wit-bindgen. Technically there's nothing stopping you from having a non-wasmtime-wasi-based implementation and usingadd_to_linkeryourself. Adding a secondWasiSocketConfigis a whole extra trait on top of this preexisting trait. At that point why limit it to sockets? Why not put all ofWasiCtxBuilderbehind a trait effectively? For exampleWasiCtxBuildercould be a builder-style version of creating a context that implements a newWasitrait, and alladd_to_linkeris defined in terms ofT: Wasi. That way embeddings could have a customT: Wasiand run with that.
Ok that's a lot of thoughts, not all of which you necessarily asked for on this issue. I think though what I might recommend for the near-term is something like:
- No changes to
wasmtime-wasi- Your embedding would
add_to_linkerfor theip-name-lookupinterfaces and hook into the results there- State would be shared with
socket_addr_checkwith anArc<Mutex<_>>That should all work today as-is, albeit not in a pretty fashion.
Looking more towards the future I think I'd personally prefer to consider a
trait Wasi { ... }style approach. All the existing implementations would be defined forT: Wasirather than justWasiCtxand that way we could decorate as many callbacks and hooks as we want ontrait Wasiand have them "easily configurable" for embeddings.
badeend commented on issue #7694:
Hi @rylev . I raised a similar issue 4 days ago. Specifically the "Domain-based policy strategy" section from that issue.
Just wanted to make sure we're not about to do duplicate work.
badeend commented on issue #7694:
@alexcrichton Option 4(?):
Create a specialized
Wasi***Viewper package/interface and place the extension points on that. Similar toWasiHttpView. NoBoxes orMutexes required.Example for TCP:
pub trait WasiTcpView: WasiView { fn check_allowed_tcp(&mut self) -> std::io::Result<()> { Err(Errno::ACCESS.into()) } fn check_tcp_addr(&mut self, addr: &SocketAddr, reason: SocketAddrUse) -> std::io::Result<()> { Err(Errno::ACCESS.into()) } }- impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T { + impl<T: WasiTcpView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
alexcrichton commented on issue #7694:
That's probably the best out of all ideas I think actually!
Last updated: Dec 06 2025 at 06:05 UTC