dispatchrun / wasi-go

A Go implementation of the WebAssembly System Interface (WASI)
Apache License 2.0
124 stars 7 forks source link

Name resolution does not work without filesystem access #82

Open KEANO89 opened 1 year ago

KEANO89 commented 1 year ago

At the moment DNS lookups seem to require filesystem access. Is that intended behavior?

Steps to reproduce:

package main

import (
    "fmt"
    "log"
    "net"

    _ "github.com/stealthrocket/net/wasip1"
)

func main() {
    ips, err := net.LookupIP("example.org")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(ips)
}
❯ GOOS=wasip1 GOARCH=wasm gotip build -o dnstest.wasm dnstest.go
❯ gotip version
go version devel go1.21-5c15498 Fri Jul 7 22:02:26 2023 +0000 linux/amd64
❯ wasirun --version
wasirun v0.7.2

Result:

❯ wasirun --dir=/ dnstest.wasm
[2606:2800:220:1:248:1893:25c8:1946 93.184.216.34]
❯ wasirun --dns-server 9.9.9.9:53 dnstest.wasm
2023/07/10 08:37:11 lookup example.org on [::1]:53: Connection refused

Without docs saying otherwise I'd expect the last invocation to work as well

achille-roussel commented 1 year ago

Thanks for reporting!

The --dns-server option configures the DNS server used when the guest module calls sock_getaddrinfo.

The guest program you use is compiled with Go 1.21 and https://github.com/stealthrocket/net, so by default, it will use the pure Go resolver as documented in https://github.com/stealthrocket/net#name-resolution; This means that the guest application opens a UDP socket and sends the queries to the DNS server it finds in /etc/resolv.conf, which is why the file system needs to be exposed.

I tried building your example with -tags=getaddrinfo but in this case you're using the Go resolver via net.LookupIP which still tries to connect to a DNS server on localhost:

SockAddressInfo(127.0.0.1, 53, {Flags:NumericHost|NumericService,Family:UnspecifiedFamily,SocketType:DatagramSocket,Protocol:UDPProtocol}, [8]AddressInfo) => [{Flags:NumericHost|NumericService,Family:InetFamily,SocketType:DatagramSocket,Protocol:UDPProtocol,Address:127.0.0.1:53}]
SockOpen(InetFamily, DatagramSocket, IPProtocol, SockConnectionRights|SockListenRights, SockConnectionRights) => 4
FDStatGet(4) => {FileType:SocketDGramType,RightsBase:SockConnectionRights|SockListenRights,RightsInheriting:SockConnectionRights}
FDStatSetFlags(4, NonBlock) => ok
SockSetOpt(4, Broadcast, 1) => ok
SockConnect(4, 127.0.0.1:53) => 127.0.0.1:60268
SockLocalAddress(4) => 127.0.0.1:60268
SockRemoteAddress(4) => 127.0.0.1:53
FDStatGet(4) => {FileType:SocketDGramType,Flags:NonBlock,RightsBase:SockConnectionRights|SockListenRights,RightsInheriting:SockConnectionRights}
ClockTimeGet(1, 0) => 185606458
ClockTimeGet(1, 0) => 185608583
FDWrite(4, [1]IOVec{[40]byte("8\x93\1\0\0\1\0\0\0\0\0\1\7example\3org\0\0\1\0\1\0\0)"...)}) => 40
SockRecvFrom(4, [1]IOVec{[1232]Byte}, RIFlags(0)) => ECONNREFUSED (Connection refused)

There is a hard dependency on /etc/resolv.conf that cannot be worked around here.

The "right way" to address this issue would probably be to modify wasi-go's unix system to support "mounting" /etc/resolv.conf to a virtual file generated to have the DNS server passed to --dns-server (pretty similar to what docker does). However, it might be a bit outside the scope of our goals for this project, we provide wasirun as a test utility for the wasi-go library; it's not intended to be used as a general-purpose production runtime.

If you can take on the work to implement such feature, we would happily review and commit to the long-term maintenance of the code 👍

achille-roussel commented 1 year ago

@chriso pointed out that another option could be to add a lookup functions in the wasip1 package such as wasip1.LookupIP, which would bypass the Go resolver when building with -tags=getaddrinfo.