diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 712e0ee..ad42a8e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,17 +19,25 @@ jobs: env: OUTPUT_DIR: ${{ github.workspace }}/builds steps: + - uses: actions/setup-go@v4 + with: + go-version: 1.21.x - uses: actions/checkout@v4 - - name: Build + - name: Build binaries run: | PREFIX=${OUTPUT_DIR} make artifacts + - name: Build wasm image + run: | + PREFIX=${OUTPUT_DIR} make c2w-net-proxy.wasm + - name: sha256sum + run: | ( cd ${OUTPUT_DIR}; sha256sum * ) > "${GITHUB_WORKSPACE}/SHA256SUMS" mv "${GITHUB_WORKSPACE}/SHA256SUMS" "${OUTPUT_DIR}/SHA256SUMS" - name: Create Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - SHA256SUM_OF_SHA256SUM=$(sha256sum ${OUTPUT_DIR}/SHA256SUMS | cut -d ' ' -f 1) + SHA256SUM_OF_SHA256SUMS=$(sha256sum ${OUTPUT_DIR}/SHA256SUMS | cut -d ' ' -f 1) RELEASE_TAG="${GITHUB_REF##*/}" MINIMAL_TAR=$(ls -1 ${OUTPUT_DIR} | grep container2wasm-v | head -1) MINIMAL_TAR_LIST=$(tar --list -vvf ${OUTPUT_DIR}/${MINIMAL_TAR}) @@ -50,9 +58,13 @@ jobs: + ## About \`c2w-net-proxy.wasm\` + + Please refer to [the document about networking for container on browser](https://github.com/ktock/container2wasm/tree/${RELEASE_TAG}/examples/networking/fetch/) for details and usage. + --- - The sha256sum of SHA256SUM is \`${SHA256SUM_OF_SHA256SUM}\` + The sha256sum of SHA256SUMS is \`${SHA256SUM_OF_SHA256SUMS}\` EOF ASSET_FLAGS=() diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a1f7602..4184a25 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,6 +22,8 @@ jobs: make ls -al ./out/c2w if ldd ./out/c2w ; then echo "must be static binary" ; exit 1 ; fi + ls -al ./out/c2w-net + if ldd ./out/c2w-net ; then echo "must be static binary" ; exit 1 ; fi test: runs-on: ubuntu-22.04 @@ -30,16 +32,12 @@ jobs: fail-fast: false matrix: target: ["TestWasmtime", "TestWamr", "TestWasmer", "TestWazero", "TestWasmedge"] + arch: ["x86_64", "riscv64", "aarch64"] steps: - uses: actions/checkout@v4 - - name: x86_64 + - name: test env: - GO_TEST_FLAGS: -run ${{ matrix.target }}/.*arch=x86_64.* - run: | - make test - - name: riscv64 - env: - GO_TEST_FLAGS: -run ${{ matrix.target }}/.*arch=(riscv64|aarch64).* + GO_TEST_FLAGS: -run ${{ matrix.target }}/.*arch=${{ matrix.arch }}.* run: | make test diff --git a/Dockerfile b/Dockerfile index 694cf30..efdb650 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,8 +53,8 @@ COPY --link --from=oci-image-src / /oci # /oci/rootfs : rootfs dir this Dockerfile creates container's rootfs and used by the container. # /oci/image.json : container image config file used by init # /oci/spec.json : container runtime spec file used by init -# /etc/initconfig.json : configuration file for init -RUN mkdir -p /out/oci/rootfs /out/oci/bundle /out/etc && \ +# /oci/initconfig.json : configuration file for init +RUN mkdir -p /out/oci/rootfs /out/oci/bundle && \ IS_WIZER=false && \ if test "${OPTIMIZATION_MODE}" = "wizer" ; then IS_WIZER=true ; fi && \ NO_VMTOUCH_F=false && \ @@ -65,7 +65,7 @@ RUN mkdir -p /out/oci/rootfs /out/oci/bundle /out/etc && \ --runtime-config-path=/oci/spec.json \ --rootfs-path=/oci/rootfs \ /oci "${TARGETPLATFORM}" /out/oci/rootfs && \ - mv image.json spec.json /out/oci/ && mv initconfig.json /out/etc/ + mv image.json spec.json /out/oci/ && mv initconfig.json /out/oci/ FROM ubuntu:22.04 AS gcc-riscv64-linux-gnu-base RUN apt-get update && apt-get install -y gcc-riscv64-linux-gnu libc-dev-riscv64-cross git make @@ -162,10 +162,11 @@ RUN git clone -b $BUSYBOX_VERSION --depth 1 https://git.busybox.net/busybox WORKDIR /work/busybox RUN make CROSS_COMPILE=riscv64-linux-gnu- LDFLAGS=--static defconfig RUN make CROSS_COMPILE=riscv64-linux-gnu- LDFLAGS=--static -j$(nproc) -RUN mkdir -p /out && mv busybox /out/busybox +RUN mkdir -p /out/bin && mv busybox /out/bin/busybox RUN make LDFLAGS=--static defconfig RUN make LDFLAGS=--static -j$(nproc) -RUN for i in $(./busybox --list) ; do ln -s busybox /out/$i ; done +RUN for i in $(./busybox --list) ; do ln -s busybox /out/bin/$i ; done +RUN mkdir -p /out/usr/share/udhcpc/ && cp ./examples/udhcp/simple.script /out/usr/share/udhcpc/default.script FROM gcc-riscv64-linux-gnu-base AS tini-riscv64-dev # https://github.com/krallin/tini#building-tini @@ -179,15 +180,14 @@ RUN cmake . && make && mkdir /out/ && mv tini /out/ FROM ubuntu:22.04 AS rootfs-riscv64-dev RUN apt-get update -y && apt-get install -y mkisofs -COPY --link --from=busybox-riscv64-dev /out/ /rootfs/bin/ +COPY --link --from=busybox-riscv64-dev /out/ /rootfs/ COPY --link --from=binfmt-dev / /rootfs/ COPY --link --from=runc-riscv64-dev /out/runc /rootfs/sbin/runc COPY --link --from=bundle-dev /out/ /rootfs/ COPY --link --from=init-riscv64-dev /out/init /rootfs/sbin/init COPY --link --from=vmtouch-riscv64-dev /out/vmtouch /rootfs/bin/ COPY --link --from=tini-riscv64-dev /out/tini /rootfs/sbin/tini -RUN mkdir -p /rootfs/proc /rootfs/sys /rootfs/mnt /rootfs/run /rootfs/tmp /rootfs/dev /rootfs/var && mknod /rootfs/dev/null c 1 3 && chmod 666 /rootfs/dev/null -RUN touch /rootfs/etc/resolv.conf /rootfs/etc/hosts +RUN mkdir -p /rootfs/proc /rootfs/sys /rootfs/mnt /rootfs/run /rootfs/tmp /rootfs/dev /rootfs/var /rootfs/etc && mknod /rootfs/dev/null c 1 3 && chmod 666 /rootfs/dev/null RUN mkdir /out/ && mkisofs -l -J -R -o /out/rootfs.bin /rootfs/ # RUN isoinfo -i /out/rootfs.bin -l @@ -320,10 +320,11 @@ RUN git clone -b $BUSYBOX_VERSION --depth 1 https://git.busybox.net/busybox WORKDIR /work/busybox RUN make CROSS_COMPILE=x86_64-linux-gnu- LDFLAGS=--static defconfig RUN make CROSS_COMPILE=x86_64-linux-gnu- LDFLAGS=--static -j$(nproc) -RUN mkdir -p /out && mv busybox /out/busybox +RUN mkdir -p /out/bin && mv busybox /out/bin/busybox RUN make LDFLAGS=--static defconfig RUN make LDFLAGS=--static -j$(nproc) -RUN for i in $(./busybox --list) ; do ln -s busybox /out/$i ; done +RUN for i in $(./busybox --list) ; do ln -s busybox /out/bin/$i ; done +RUN mkdir -p /out/usr/share/udhcpc/ && cp ./examples/udhcp/simple.script /out/usr/share/udhcpc/default.script FROM golang-base AS runc-amd64-dev ARG RUNC_VERSION @@ -385,14 +386,13 @@ RUN git clone https://github.com/hoytech/vmtouch.git && \ FROM ubuntu:22.04 AS rootfs-amd64-dev RUN apt-get update -y && apt-get install -y mkisofs -COPY --link --from=busybox-amd64-dev /out/ /rootfs/bin/ +COPY --link --from=busybox-amd64-dev /out/ /rootfs/ COPY --link --from=runc-amd64-dev /out/runc /rootfs/sbin/runc COPY --link --from=bundle-dev /out/ /rootfs/ COPY --link --from=init-amd64-dev /out/init /rootfs/sbin/init COPY --link --from=vmtouch-amd64-dev /out/vmtouch /rootfs/bin/ COPY --link --from=tini-amd64-dev /out/tini /rootfs/sbin/tini RUN mkdir -p /rootfs/proc /rootfs/sys /rootfs/mnt /rootfs/run /rootfs/tmp /rootfs/dev /rootfs/var /rootfs/etc && mknod /rootfs/dev/null c 1 3 && chmod 666 /rootfs/dev/null -RUN touch /rootfs/etc/resolv.conf /rootfs/etc/hosts RUN mkdir /out/ && mkisofs -l -J -R -o /out/rootfs.bin /rootfs/ # RUN isoinfo -i /out/rootfs.bin -l @@ -450,6 +450,10 @@ RUN ${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -O2 RUN ${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -O2 --target=wasm32-unknown-wasi -Wl,--export=wasm_setjmp -c jmp.S -o jmp_wrapper.o RUN ${WASI_SDK_PATH}/bin/wasm-ld jmp.o jmp_wrapper.o --export=wasm_setjmp --export=wasm_longjmp --export=handle_jmp --no-entry -r -o jmp +COPY --link --from=assets ./patches/bochs/vfs /vfs +WORKDIR /vfs +RUN ${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -O2 --target=wasm32-unknown-wasi -c vfs.c -I . -o vfs.o + COPY --link --from=assets /patches/bochs/Bochs /Bochs WORKDIR /Bochs/bochs ARG INIT_DEBUG @@ -461,7 +465,7 @@ RUN LOGGING_FLAG=--disable-logging && \ ./configure --host wasm32-unknown-wasi --enable-x86-64 --with-nogui --enable-usb --enable-usb-ehci \ --disable-large-ramfile --disable-show-ips --disable-stats ${LOGGING_FLAG} \ --enable-repeat-speedups --enable-fast-function-calls --disable-trace-linking --enable-handlers-chaining # TODO: --enable-trace-linking causes "out of bounds memory access" -RUN make -j$(nproc) bochs EMU_DEPS="/tools/wasi-vfs/libwasi_vfs.a /jmp/jmp -lrt" +RUN make -j$(nproc) bochs EMU_DEPS="/tools/wasi-vfs/libwasi_vfs.a /jmp/jmp /vfs/vfs.o -lrt" RUN /binaryen/binaryen-version_${BINARYEN_VERSION}/bin/wasm-opt bochs --asyncify -O2 -o bochs.async --pass-arg=asyncify-ignore-imports RUN mv bochs.async bochs diff --git a/Dockerfile.test b/Dockerfile.test index 8db36c9..6a634ec 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -16,6 +16,16 @@ COPY ./tests/wazero /wazero WORKDIR /wazero RUN go build -o /out/wazero-test main.go +FROM golang:1.21 AS httphello-dev +COPY ./tests/httphello /httphello +WORKDIR /httphello +RUN go build -o /out/httphello main.go + +FROM golang:1.21 AS c2w-net-proxy-test-dev +COPY ./tests/c2w-net-proxy-test /c2w-net-proxy-test +WORKDIR /c2w-net-proxy-test +RUN go build -o /out/c2w-net-proxy-test main.go + FROM ubuntu:22.04 ARG BUILDX_VERSION ARG DOCKER_VERSION @@ -58,6 +68,8 @@ RUN wget https://github.com/WasmEdge/WasmEdge/releases/download/${WASMEDGE_VERSI # install wazero COPY --from=wazero-test-dev /out/wazero-test /usr/local/bin/ +COPY --from=httphello-dev /out/httphello /usr/local/bin/ +COPY --from=c2w-net-proxy-test-dev /out/c2w-net-proxy-test /usr/local/bin/ # install golang RUN wget https://go.dev/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz @@ -69,6 +81,8 @@ ENV PATH=$PATH:/usr/local/go/bin COPY . /test/ WORKDIR /test/ RUN go build -o /usr/local/bin/ ./cmd/c2w +RUN go build -o /usr/local/bin/ ./cmd/c2w-net +RUN cd extras/c2w-net-proxy/ ; GOOS=wasip1 GOARCH=wasm go build -o /opt/c2w-net-proxy.wasm . ENTRYPOINT ["dockerd-entrypoint.sh"] CMD [] diff --git a/Makefile b/Makefile index 4db3b00..1057804 100644 --- a/Makefile +++ b/Makefile @@ -8,24 +8,31 @@ GO_EXTRA_LDFLAGS=-extldflags '-static' GO_LD_FLAGS=-ldflags '-s -w -X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) $(GO_EXTRA_LDFLAGS)' GO_BUILDTAGS=-tags "osusergo netgo static_build" -all: c2w +all: c2w c2w-net -build: c2w +build: c2w c2w-net c2w: CGO_ENABLED=0 go build -o $(PREFIX)/c2w $(GO_LD_FLAGS) $(GO_BUILDTAGS) -v ./cmd/c2w +c2w-net: + CGO_ENABLED=0 go build -o $(PREFIX)/c2w-net $(GO_LD_FLAGS) $(GO_BUILDTAGS) -v ./cmd/c2w-net + +c2w-net-proxy.wasm: + cd extras/c2w-net-proxy/ ; GOOS=wasip1 GOARCH=wasm go build -o $(PREFIX)/c2w-net-proxy.wasm . + install: install -D -m 755 $(PREFIX)/c2w $(CMD_DESTDIR)/bin + install -D -m 755 $(PREFIX)/c2w-net $(CMD_DESTDIR)/bin artifacts: clean - GOOS=linux GOARCH=amd64 make c2w - tar -C $(PREFIX) --owner=0 --group=0 -zcvf $(PREFIX)/container2wasm-$(VERSION)-linux-amd64.tar.gz c2w + GOOS=linux GOARCH=amd64 make c2w c2w-net + tar -C $(PREFIX) --owner=0 --group=0 -zcvf $(PREFIX)/container2wasm-$(VERSION)-linux-amd64.tar.gz c2w c2w-net - GOOS=linux GOARCH=arm64 make c2w - tar -C $(PREFIX) --owner=0 --group=0 -zcvf $(PREFIX)/container2wasm-$(VERSION)-linux-arm64.tar.gz c2w + GOOS=linux GOARCH=arm64 make c2w c2w-net + tar -C $(PREFIX) --owner=0 --group=0 -zcvf $(PREFIX)/container2wasm-$(VERSION)-linux-arm64.tar.gz c2w c2w-net - rm -f $(PREFIX)/c2w + rm -f $(PREFIX)/c2w $(PREFIX)/c2w-net test: ./tests/test.sh diff --git a/README.md b/README.md index 17fb862..4e06220 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,8 @@ $ wasmtime --mapdir /mnt/share::/tmp/share out.wasm cat /mnt/share/from-host hi ``` +> Please refer to [`./examples/networking/wasi/`](./examples/networking/wasi/) for enabling networking + ### Container on Browser ![Container on browser](./docs/images/ubuntu-wasi-on-browser.png) @@ -55,7 +57,9 @@ hi You can run the container on browser as well. There are two methods for running the container on browser. -> NOTE: Please also refer to [`./examples/wasi-browser`](./examples/wasi-browser/) (WASI-on-browser example) and [`./examples/emscripten`](./examples/emscripten/) (emscripten example). +> Please also refer to [`./examples/wasi-browser`](./examples/wasi-browser/) (WASI-on-browser example) and [`./examples/emscripten`](./examples/emscripten/) (emscripten example). + +> Please refer to [`./examples/networking/`](./examples/networking/) for details about enabling networking. #### WASI on browser @@ -80,6 +84,41 @@ $ docker run --rm -p 8080:80 \ You can run the container on browser via `localhost:8080`. +##### WASI on browser with networking + +![Debian container on browser with browser networking](./docs/images/debian-curl-wasi-on-browser-frontend-networking.png) + +Container can also perform networking. +This section is the demo of using curl command in the container. + +> Tested only on Chrome. The example might not work on other browsers. + +``` +$ cat <> /usr/local/apache2/conf/httpd.conf && httpd-foreground' +``` + +You can run the container on browser with several types of configurations: + +- `localhost:8080/?net=browser`: Container with networking. [Network stack](./extras/c2w-net-proxy/) based on [`gvisor-tap-vsock`](https://github.com/containers/gvisor-tap-vsock) runs on browser and forwards HTTP/HTTPS packets using the browser's Fetch API. The set of accesible sites is restricted by the browser configuration (e.g. CORS restriction). See also [`./examples/networking/fetch`](./examples/networking/fetch/) for detalis. +- `localhost:8080/?net=delegate=ws://localhost:8888`: Container with networking. You need to run [user-space network stack](./cmd/c2w-net/) based on [`gvisor-tap-vsock`](https://github.com/containers/gvisor-tap-vsock) on the host (outside of browser). It forwards all packets received from the browser over WebSocket. See also [`./examples/networking/websocket`](./examples/networking/websocket/) for detalis and configuration. +- `localhost:8080`: Container without networking. + #### emscripten on browser This example uses emscripten for converting the container to WASM. @@ -108,20 +147,25 @@ You can run the container on browser via `localhost:8080`. > NOTE: It can take some time to load and start the container. +Networking can also be enabled using the [user-space network stack](./cmd/c2w-net/) based on [`gvisor-tap-vsock`](https://github.com/containers/gvisor-tap-vsock) serving over WebSocket on the host (outside of browser). +See also [`./examples/networking/websocket`](./examples/networking/websocket/) for detalis. + ## Getting Started - requirements - Docker 18.09+ (w/ `DOCKER_BUILDKIT=1`) - [Docker Buildx](https://docs.docker.com/build/install-buildx/) v0.8+ (recommended) or `docker build` (w/ `DOCKER_BUILDKIT=1`) -You can install the converter command `c2w` using one of the following methods: +You can install the converter command `c2w` using one of the following methods. + +> NOTE: The output binary also contains [`c2w-net`](./cmd/c2w-net/) which a command usable for controlling networking feature (please see also [./examples/networking](./examples/networking/) for details). -### Release binary +### Release binaries Binaries are available from https://github.com/ktock/container2wasm/releases Extract the tarball and put the binary somewhere under `$PATH`. -### Building binary using make +### Building binaries using make Go 1.19+ is needed. @@ -218,23 +262,23 @@ The following shows the techniqual details: ### x86_64 containers -|runtime |stdio|mapdir|note| -|---|---|---|---| -|wasmtime|:heavy_check_mark:|:heavy_check_mark:|| -|wamr(wasm-micro-runtime)|:heavy_check_mark:|:heavy_check_mark:|| -|wazero|:heavy_check_mark:|:heavy_check_mark:|| -|wasmer|:construction: (stdin unsupported)|:heavy_check_mark:|non-blocking stdin doesn't seem to work| -|wasmedge|:construction: (stdin unsupported)|:heavy_check_mark:|non-blocking stdin doesn't seem to work| +|runtime|stdio|mapdir|networking|note| +|---|---|---|---|---| +|wasmtime|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark: (w/ [host-side network stack](./examples/networking/wasi/))|| +|wamr(wasm-micro-runtime)|:heavy_check_mark:|:heavy_check_mark:|:construction:|| +|wazero|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark: (w/ [host-side network stack](./examples/networking/wasi/)|| +|wasmer|:construction: (stdin unsupported)|:heavy_check_mark:|:construction:|non-blocking stdin doesn't seem to work| +|wasmedge|:construction: (stdin unsupported)|:heavy_check_mark:|:construction:|non-blocking stdin doesn't seem to work| ### risc-v and other architecutre's containers -|runtime |stdio|mapdir|note| -|---|---|---|---| -|wasmtime|:heavy_check_mark:|:heavy_check_mark:|| -|wamr(wasm-micro-runtime)|:heavy_check_mark:|:heavy_check_mark:|| -|wazero|:heavy_check_mark:|:heavy_check_mark:|| -|wasmer|:construction: (stdin unsupported)|:heavy_check_mark:|non-blocking stdin doesn't seem to work| -|wasmedge|:construction: (stdin unsupported)|:heavy_check_mark:|non-blocking stdin doesn't seem to work| +|runtime |stdio|mapdir|networking|note| +|---|---|---|---|---| +|wasmtime|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark: (w/ [host-side network stack](./examples/networking/wasi/))|| +|wamr(wasm-micro-runtime)|:heavy_check_mark:|:heavy_check_mark:|:construction:|| +|wazero|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark: (w/ [host-side network stack](./examples/networking/wasi/))|| +|wasmer|:construction: (stdin unsupported)|:heavy_check_mark:|:construction:|non-blocking stdin doesn't seem to work| +|wasmedge|:construction: (stdin unsupported)|:heavy_check_mark:|:construction:|non-blocking stdin doesn't seem to work| ## Similar projects @@ -282,6 +326,7 @@ Re-compilation (and possibe re-implementation) of the application is needed. - vmtouch ([license](https://github.com/hoytech/vmtouch/blob/master/LICENSE)): https://github.com/hoytech/vmtouch - BusyBox ([GNU General Public License version 2](https://www.busybox.net/license.html)): https://git.busybox.net/busybox -- On-browser example relies on xterm-pty and `browser_wasi_shim`(for WASI-on-browser). +- On-browser example relies on the following softwares. - xterm-pty ([MIT License](https://github.com/mame/xterm-pty/blob/main/LICENSE.txt)): https://github.com/mame/xterm-pty - `browser_wasi_shim` (either of [MIT License](https://github.com/bjorn3/browser_wasi_shim/blob/main/LICENSE-MIT) and [Apache License 2.0](https://github.com/bjorn3/browser_wasi_shim/blob/main/LICENSE-APACHE)): https://github.com/bjorn3/browser_wasi_shim + - `gvisor-tap-vsock` ([Apache License 2.0](https://github.com/containers/gvisor-tap-vsock/blob/main/LICENSE)): https://github.com/containers/gvisor-tap-vsock diff --git a/cmd/c2w-net/main.go b/cmd/c2w-net/main.go new file mode 100644 index 0000000..864d649 --- /dev/null +++ b/cmd/c2w-net/main.go @@ -0,0 +1,137 @@ +package main + +import ( + "context" + "flag" + "fmt" + "net" + "net/http" + "os" + "os/exec" + "strings" + "time" + + gvntypes "github.com/containers/gvisor-tap-vsock/pkg/types" + gvnvirtualnetwork "github.com/containers/gvisor-tap-vsock/pkg/virtualnetwork" + "golang.org/x/net/websocket" +) + +const ( + gatewayIP = "192.168.127.1" + vmIP = "192.168.127.3" + vmMAC = "02:00:00:00:00:01" +) + +func main() { + var portFlags sliceFlags + flag.Var(&portFlags, "p", "map port between host and guest (host:guest). -mac must be set correctly.") + var ( + debug = flag.Bool("debug", false, "enable debug print") + listenWS = flag.Bool("listen-ws", false, "listen on a websocket port specified as argument") + invoke = flag.Bool("invoke", false, "invoke the container with NW support") + mac = flag.String("mac", vmMAC, "mac address assigned to the container") + wasiAddr = flag.String("wasi-addr", "127.0.0.1:1234", "IP address used to communicate between wasi and network stack (valid only with invoke flag)") // TODO: automatically use empty random port or unix socket + ) + flag.Parse() + args := flag.Args() + if len(args) < 1 { + panic("specify args") + } + qemuAddr := args[0] + forwards := make(map[string]string) + for _, p := range portFlags { + parts := strings.Split(p, ":") + switch len(parts) { + case 3: + // IP:PORT1:PORT2 + forwards[strings.Join(parts[0:2], ":")] = strings.Join([]string{vmIP, parts[2]}, ":") + case 2: + // PORT1:PORT2 + forwards["0.0.0.0:"+parts[0]] = vmIP + ":" + parts[1] + } + } + if *debug { + fmt.Fprintf(os.Stderr, "port mapping: %+v\n", forwards) + } + config := &gvntypes.Configuration{ + Debug: *debug, + MTU: 1500, + Subnet: "192.168.127.0/24", + GatewayIP: gatewayIP, + GatewayMacAddress: "5a:94:ef:e4:0c:dd", + DHCPStaticLeases: map[string]string{ + vmIP: *mac, + }, + Forwards: forwards, + NAT: map[string]string{ + "192.168.127.254": "127.0.0.1", + }, + GatewayVirtualIPs: []string{"192.168.127.254"}, + Protocol: gvntypes.QemuProtocol, + } + vn, err := gvnvirtualnetwork.New(config) + if err != nil { + panic(err) + } + if *invoke { + go func() { + var conn net.Conn + for i := 0; i < 5; i++ { + time.Sleep(1 * time.Second) + fmt.Fprintf(os.Stderr, "connecting to NW...\n") + conn, err = net.Dial("tcp", *wasiAddr) + if err == nil { + break + } + fmt.Fprintf(os.Stderr, "failed connecting to NW: %v\n", err) + } + if conn == nil { + panic("failed to connect to vm") + } + // We register our VM network as a qemu "-netdev socket". + if err := vn.AcceptQemu(context.TODO(), conn); err != nil { + fmt.Fprintf(os.Stderr, "failed AcceptQemu: %v\n", err) + } + }() + cmd := exec.Command("wasmtime", append([]string{"run", "--tcplisten=" + *wasiAddr, "--env='LISTEN_FDS=1'", "--"}, args...)...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + panic(err) + } + return + } + if *listenWS { + http.Handle("/", websocket.Handler(func(ws *websocket.Conn) { + ws.PayloadType = websocket.BinaryFrame + if err := vn.AcceptQemu(context.TODO(), ws); err != nil { + fmt.Fprintf(os.Stderr, "forwarding finished: %v\n", err) + } + })) + if err := http.ListenAndServe(qemuAddr, nil); err != nil { + panic(err) + } + return + } + conn, err := net.Dial("tcp", qemuAddr) + if err != nil { + panic(err) + } + // We register our VM network as a qemu "-netdev socket". + if err := vn.AcceptQemu(context.TODO(), conn); err != nil { + panic(err) + } +} + +type sliceFlags []string + +func (f *sliceFlags) String() string { + var s []string = *f + return fmt.Sprintf("%v", s) +} + +func (f *sliceFlags) Set(value string) error { + *f = append(*f, value) + return nil +} diff --git a/cmd/create-spec/main.go b/cmd/create-spec/main.go index e387ea8..022cf60 100644 --- a/cmd/create-spec/main.go +++ b/cmd/create-spec/main.go @@ -9,7 +9,6 @@ import ( "io" "os" "path/filepath" - "syscall" "github.com/containerd/containerd/archive" "github.com/containerd/containerd/archive/compression" @@ -244,7 +243,9 @@ func generateSpec(config spec.Image, rootfs string) (_ *specs.Spec, err error) { if config.Architecture == "amd64" { p = "linux/amd64" } - s, err := ctdoci.GenerateSpecWithPlatform(ctdCtx, nil, p, &ctdcontainers.Container{}) + s, err := ctdoci.GenerateSpecWithPlatform(ctdCtx, nil, p, &ctdcontainers.Container{}, + ctdoci.WithHostNamespace(specs.NetworkNamespace), + ) if err != nil { return nil, fmt.Errorf("failed to generate spec: %w", err) } @@ -365,35 +366,19 @@ func generateBootConfig(config spec.Image, debug, debugInit bool, imageConfigPat }, }, { - // FSType: "bind", - Src: "/run/etc/hosts", - Dst: "/etc/hosts", - Data: "bind", - Flags: syscall.MS_BIND, - Dir: []inittype.DirInfo{ - { - Path: "/run/etc", - Mode: 0666, // TODO: better mode - }, - }, - File: []inittype.FileInfo{ + // make etc writable (e.g. by udhcpc) + FSType: "tmpfs", + Src: "tmpfs", + Dst: "/etc", + PostFile: []inittype.FileInfo{ { - Path: "/run/etc/hosts", - Mode: 0666, + Path: "/etc/hosts", + Mode: 0644, Contents: "127.0.0.1 localhost\n", }, - }, - }, - { - // FSType: "bind", - Src: "/run/etc/resolv.conf", - Dst: "/etc/resolv.conf", - Data: "bind", - Flags: syscall.MS_BIND, - File: []inittype.FileInfo{ { - Path: "/run/etc/resolv.conf", - Mode: 0666, + Path: "/etc/resolv.conf", + Mode: 0644, Contents: "", }, }, @@ -428,6 +413,28 @@ func generateBootConfig(config spec.Image, debug, debugInit bool, imageConfigPat Mode: 0755, }, }, + PostDir: []inittype.DirInfo{ + { + Path: "/run/rootfs/etc/", + Mode: 0644, + }, + { + Path: "/run/rootfs/etc/", + Mode: 0644, + }, + }, + PostFile: []inittype.FileInfo{ + { + Path: "/run/rootfs/etc/hosts", + Mode: 0644, + Contents: "127.0.0.1 localhost\n", + }, + { + Path: "/run/rootfs/etc/resolv.conf", + Mode: 0644, + Contents: "", + }, + }, }, }, } diff --git a/cmd/init/main.go b/cmd/init/main.go index 21cf44f..c2d5576 100644 --- a/cmd/init/main.go +++ b/cmd/init/main.go @@ -20,7 +20,7 @@ import ( const ( // initConfigPath is path to the config file used by this init process. - initConfigPath = "/etc/initconfig.json" + initConfigPath = "/oci/initconfig.json" // wasi0: wasi root directory rootFSTag = "wasi0" @@ -157,7 +157,9 @@ func doInit() error { return err } log.Printf("INFO:\n%s\n", string(infoD)) - s = patchSpec(s, infoD, imageConfig) + var withNet bool + var mac string + s, withNet, mac = patchSpec(s, infoD, imageConfig) log.Printf("Running: %+v\n", s.Process.Args) sd, err := json.Marshal(s) if err != nil { @@ -167,6 +169,31 @@ func doInit() error { return err } + if withNet { + if mac != "" { + if o, err := exec.Command("ip", "link", "set", "dev", "eth0", "down").CombinedOutput(); err != nil { + return fmt.Errorf("failed eth0 down: %v: %w", string(o), err) + } + if o, err := exec.Command("ip", "link", "set", "dev", "eth0", "address", mac).CombinedOutput(); err != nil { + return fmt.Errorf("failed change mac address of eth0: %v: %w", string(o), err) + } + } + if o, err := exec.Command("ip", "link", "set", "dev", "eth0", "up").CombinedOutput(); err != nil { + return fmt.Errorf("failed eth0 up: %v: %w", string(o), err) + } + if o, err := exec.Command("udhcpc", "-i", "eth0").CombinedOutput(); err != nil { + return fmt.Errorf("failed udhcpc: %w", err) + } else if cfg.Debug { + o2, _ := exec.Command("ip", "a").CombinedOutput() + log.Printf("finished udhcpc: %s\n %s\n", string(o), string(o2)) + } + for _, f := range []string{"/etc/hosts", "/etc/resolv.conf"} { + if err := syscall.Mount(f, filepath.Join("/run/rootfs", f), "", syscall.MS_BIND, ""); err != nil { + return fmt.Errorf("cannot mount %q: %w", f, err) + } + } + } + var lastErr error for _, cmd := range cfg.Cmd { log.Printf("executing: %+v\n", cmd) @@ -219,6 +246,23 @@ func mount(m inittype.MountInfo) error { return fmt.Errorf("failed to run command %+v: %w", m.Cmd, err) } } + for _, d := range m.PostDir { + if err := os.MkdirAll(d.Path, os.FileMode(d.Mode)); err != nil { + return fmt.Errorf("failed to create %q: %w", d.Path, err) + } + } + for _, f := range m.PostFile { + cf, err := os.Create(f.Path) + if err != nil { + return fmt.Errorf("failed to create %q: %w", f.Path, err) + } + if _, err := cf.Write([]byte(f.Contents)); err != nil { + return fmt.Errorf("failed to write contents to %q: %w", f.Path, err) + } + if err := cf.Close(); err != nil { + return fmt.Errorf("failed to close %q: %w", f.Path, err) + } + } return nil } @@ -227,7 +271,7 @@ var ( delimArgs = regexp.MustCompile(`[^\\] `) ) -func patchSpec(s runtimespec.Spec, infoD []byte, imageConfig imagespec.Image) runtimespec.Spec { +func patchSpec(s runtimespec.Spec, infoD []byte, imageConfig imagespec.Image) (_ runtimespec.Spec, withNet bool, mac string) { var options []string lmchs := delimLines.FindAllIndex(infoD, -1) prev := 0 @@ -275,6 +319,15 @@ func patchSpec(s runtimespec.Spec, infoD []byte, imageConfig imagespec.Image) ru entrypoint = []string{o} case "env": s.Process.Env = append(s.Process.Env, o) + case "n": + withNet = true // TODO: check mode (e.g. dhcp, ...) + mac = o + case "t": + if o != "" { + if err := exec.Command("date", "+%s", "-s", "@"+o).Run(); err != nil { + log.Printf("failed setting date: %v", err) // TODO: return error + } + } default: log.Printf("unsupported prefix: %q", inst) } @@ -286,5 +339,5 @@ func patchSpec(s runtimespec.Spec, infoD []byte, imageConfig imagespec.Image) ru args = imageConfig.Config.Cmd } s.Process.Args = append(entrypoint, args...) - return s + return s, withNet, mac } diff --git a/cmd/init/types/types.go b/cmd/init/types/types.go index d1e9714..f675913 100644 --- a/cmd/init/types/types.go +++ b/cmd/init/types/types.go @@ -23,6 +23,8 @@ type MountInfo struct { Data string `json:"data,omitempty"` Dir []DirInfo `json:"dir,omitempty"` File []FileInfo `json:"file,omitempty"` + PostDir []DirInfo `json:"post_dir,omitempty"` + PostFile []FileInfo `json:"post_file,omitempty"` Cmd []string `json:"cmd,omitempty"` Async bool `json:"async,omitempty"` Optional bool `json:"optional,omitempty"` diff --git a/docs/images/alpine-emscripten-host-networking.png b/docs/images/alpine-emscripten-host-networking.png new file mode 100644 index 0000000..778ca56 Binary files /dev/null and b/docs/images/alpine-emscripten-host-networking.png differ diff --git a/docs/images/alpine-wasi-on-browser-host-networking.png b/docs/images/alpine-wasi-on-browser-host-networking.png new file mode 100644 index 0000000..47a0d55 Binary files /dev/null and b/docs/images/alpine-wasi-on-browser-host-networking.png differ diff --git a/docs/images/debian-curl-wasi-on-browser-frontend-networking.png b/docs/images/debian-curl-wasi-on-browser-frontend-networking.png new file mode 100644 index 0000000..1d5cf1d Binary files /dev/null and b/docs/images/debian-curl-wasi-on-browser-frontend-networking.png differ diff --git a/docs/images/nix-wasi-on-browser-frontend-networking.png b/docs/images/nix-wasi-on-browser-frontend-networking.png new file mode 100644 index 0000000..e228453 Binary files /dev/null and b/docs/images/nix-wasi-on-browser-frontend-networking.png differ diff --git a/examples/README.md b/examples/README.md index 16e0851..1364792 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,3 +11,4 @@ Please refer to [README](./../README.md) for basic examples (e.g. Ubuntu on WASM - [`./php-x86_64`](./php-x86_64/): Running x86_64 PHP container on the wasm runtime and browser. - [`./php-riscv64`](./php-riscv64/): Running RISC-V PHP container on the wasm runtime and browser. - [`./target-aarch64`](./target-aarch64/): Running aarch64 container on the wasm runtime and browser. +- [`./networking`](./networking/): Running container on WASM with networking support. diff --git a/examples/emscripten/README.md b/examples/emscripten/README.md index 7fdc9e5..75244f3 100644 --- a/examples/emscripten/README.md +++ b/examples/emscripten/README.md @@ -9,4 +9,6 @@ This example relies on [xterm-pty](https://github.com/mame/xterm-pty). Please refer to [Emscripten integration](https://github.com/mame/xterm-pty#emscripten-integration) section on the xterm-pty's README for detail. Instead of emscripten WASI image, you can also run [WASI](https://github.com/WebAssembly/WASI)-compiled image on browser, with leveraging WASI-specific performance optimization including pre-initialization by [Wizer](https://github.com/bytecodealliance/wizer/). -Please see [wasi-browser example](../wasi-browser) for details. +Please see [wasi-browser example](../wasi-browser) for details about WASI-on-browser. + +Examples of enabling networking can be found at [`./../networking`](./../networking/). diff --git a/examples/emscripten/htdocs/index.html b/examples/emscripten/htdocs/index.html index 4e2adbe..2a1d5a0 100644 --- a/examples/emscripten/htdocs/index.html +++ b/examples/emscripten/htdocs/index.html @@ -21,7 +21,7 @@ //termios.cflag |= CS8; slave.ioctl("TCSETS", new Termios(termios.iflag, termios.oflag, termios.cflag, termios.lflag, termios.cc)); xterm.loadAddon(master); - const worker = new Worker("./worker.js"); + const worker = new Worker("./worker.js"+location.search); new TtyServer(slave).start(worker); diff --git a/examples/emscripten/htdocs/module.js b/examples/emscripten/htdocs/module.js new file mode 100644 index 0000000..f9fd23e --- /dev/null +++ b/examples/emscripten/htdocs/module.js @@ -0,0 +1,29 @@ +var Module = {}; + +var netParam = getNetParam(); +if (netParam && (netParam.mode == 'delegate')) { + Module['arguments'] = ['--net', 'qemu', '--mac', genmac()]; + Module['websocket'] = { + 'url': netParam.param + }; +} + +function getNetParam() { + var vars = location.search.substring(1).split('&'); + for (var i = 0; i < vars.length; i++) { + var kv = vars[i].split('='); + if (decodeURIComponent(kv[0]) == 'net') { + return { + mode: kv[1], + param: kv[2], + }; + } + } + return null; +} + +function genmac(){ + return "02:XX:XX:XX:XX:XX".replace(/X/g, function() { + return "0123456789ABCDEF".charAt(Math.floor(Math.random() * 16)) + }); +} diff --git a/examples/emscripten/htdocs/worker.js b/examples/emscripten/htdocs/worker.js index ccd4775..33577dc 100644 --- a/examples/emscripten/htdocs/worker.js +++ b/examples/emscripten/htdocs/worker.js @@ -1,6 +1,7 @@ importScripts("https://cdn.jsdelivr.net/npm/xterm-pty@0.9.4/workerTools.js"); onmessage = (msg) => { + importScripts(location.origin + "/module.js"+location.search); importScripts(location.origin + "/out.js"); var c = new TtyClient(msg.data); diff --git a/examples/networking/README.md b/examples/networking/README.md new file mode 100644 index 0000000..ae62489 --- /dev/null +++ b/examples/networking/README.md @@ -0,0 +1,7 @@ +# Networking examples + +Examples of container-on-wasm with enabling networking. + +- [`./fetch`](./fetch/): Running container on browser with on-browser network stack based on Fetch API. +- [`./websocket`](./websocket/): Running container on browser with the network stack running on the host and accessible over WebSocket. +- [`./wasi`](./wasi/): Running container on WASI runtimes, with network stack running on the host. diff --git a/examples/networking/fetch/README.md b/examples/networking/fetch/README.md new file mode 100644 index 0000000..36de554 --- /dev/null +++ b/examples/networking/fetch/README.md @@ -0,0 +1,129 @@ +# Running container on browser with on-browser network stack based on Fetch API + +This is an example of running a container on browser with networking support. + +Please refer to [`../../wasi-browser`](../../wasi-browser/) for the basics of WASI-on-browser. + +This example runs the container with network stack running on browser. +The entire network stack runs on browser so this doesn't require network stack daemon outside of browser. + +- pros: No need to run network stack daemon on the host. Networking is done based on browser's Fetch API and follows its security configuration including CORS restriction. +- cons: Container can send only HTTP/HTTPS packets to outside of the browser. And the set of accesible HTTP/HTTPS sites is limited by the browser's security rule (e.g. limited CORS). + +We use [`gvisor-tap-vsock`](https://github.com/containers/gvisor-tap-vsock) as the network stack written in Go. +We provide the customized version of the network stack [`c2w-net-proxy`](../../../extras/c2w-net-proxy) for container-on-browser use-case. +This is compiled to WASM and runs on browser. + +`c2w-net-proxy` running on browser provides HTTP/HTTPS proxy for the container. +The proxy runs on top of `gvisor-tap-vsock`'s network stack that receives packets from the container. +`c2w-net-proxy` forwards HTTP/HTTPS requests using the browser's `fetch` API so it doesn't require network stack daemon outside of the browser. + +For HTTPS, the proxy teminates the TLS connection from the contaienr with its own certificate and re-encrypt the connection to the destination using the Fetch API. +So the proxy's certificate needs to be trusted by the processes in the container (`SSL_CERT_FILE` envvar is pre-configured). +In [our example JS wrapper for container](../../wasi-browser/), by defualt, the following well-known proxy-related envvars are configured and the proxy's certificate for HTTPS proxy is provided to `/.wasmenv/proxy.crt`. + +- `SSL_CERT_FILE=/.wasmenv/proxy.crt` +- `https_proxy=http://192.168.127.253:80` +- `http_proxy=http://192.168.127.253:80` +- `HTTPS_PROXY=http://192.168.127.253:80` +- `HTTP_PROXY=http://192.168.127.253:80` + +## Current limitations + +- Containers can't access to sites not allowing CORS access. For example, we haven't find apt mirrors accessible from browser so the container can't run `apt-get`. We expect more sites will allow CORS access. +- The proxy supports only HTTP/HTTPS and the implementation isn't mature. So it's possible that some HTTP networking fails on some cases. We'll work on support for more features. +- Only chrome is our tested browser. The set of accesible sites might be different among browsers and the configurations. +- WASI-on-browser container is only supported. Emscripten support is the future work. + +## Example1: curl + +> Tested only on Chrome (116.0.5845.179). The example might not work on other browsers. + +![Debian container on browser with browser networking](../../../docs/images/debian-curl-wasi-on-browser-frontend-networking.png) + +First, prepare a WASI image named `out.wasm`. + +``` +$ cat < Alternatively, you can also build it as the following: +> +> NOTE: Run this at the project repo root directory. Go >= 1.21 is needed on your machine. +> +> ``` +> $ PREFIX=/tmp/out-js2/htdocs/ make c2w-net-proxy.wasm +> ``` + +Finally, serve them to browser. + +> Run this at the project repo root directory. + +``` +$ cp -R ./examples/wasi-browser/* /tmp/out-js2/ && chmod 755 /tmp/out-js2/htdocs +$ docker run --rm -p 8080:80 \ + -v "/tmp/out-js2/htdocs:/usr/local/apache2/htdocs/:ro" \ + -v "/tmp/out-js2/xterm-pty.conf:/usr/local/apache2/conf/extra/xterm-pty.conf:ro" \ + --entrypoint=/bin/sh httpd -c 'echo "Include conf/extra/xterm-pty.conf" >> /usr/local/apache2/conf/httpd.conf && httpd-foreground' +``` + +You can run the container on browser via `localhost:8080/?net=browser`. + +The proxy's certificate for HTTPS connection is available at `/.wasmenv/proxy.crt`. +This needs to be trusted to perform HTTPS proxy. +`SSL_CERT_FILE` is configured in the container by default. + +The container can access to the sites allowed by the browser (e.g. CORS-enabled sites). +The example accesses to a site published via GitHub Pages (`curl https://ktock.github.io/container2wasm-demo/`). + +## Example2: nix + +> Tested only on Chrome (116.0.5845.179). The example might not work on other browsers. + +![Nix container on browser with browser networking](../../../docs/images/nix-wasi-on-browser-frontend-networking.png) + +Nix's binary cache (`https://cache.nixos.org/`) seems accessbile from browser. +So binaries available there can be installed and run on browser. + +First, prepare a WASI image named `out.wasm` that contains `nixos/nix` image. + +``` +$ c2w nixos/nix /tmp/out-js2/htdocs/out.wasm +``` + +Then, put the WASM-compiled network stack (`c2w-net-proxy.wasm`) to `/tmp/out-js2/htdocs/`. +The WASM binary can be found at the [release page](https://github.com/ktock/container2wasm/releases). + +> Alternatively, you can also build it as the following: +> +> NOTE: Run this at the project repo root directory. Go >= 1.21 is needed on your machine. +> +> ``` +> $ PREFIX=/tmp/out-js2/htdocs/ make c2w-net-proxy.wasm +> ``` + +Finally, serve them to browser. + +> Run this at the project repo root directory. + +``` +$ cp -R ./examples/wasi-browser/* /tmp/out-js2/ && chmod 755 /tmp/out-js2/htdocs +$ docker run --rm -p 8080:80 \ + -v "/tmp/out-js2/htdocs:/usr/local/apache2/htdocs/:ro" \ + -v "/tmp/out-js2/xterm-pty.conf:/usr/local/apache2/conf/extra/xterm-pty.conf:ro" \ + --entrypoint=/bin/sh httpd -c 'echo "Include conf/extra/xterm-pty.conf" >> /usr/local/apache2/conf/httpd.conf && httpd-foreground' +``` + +You can run the container on browser via `localhost:8080/?net=browser`. +The proxy's certificate needs to be trusted by nix using `export NIX_SSL_CERT_FILE=/.wasmenv/proxy.crt`. + +The example installs and runs `hello` package in the container (`nix-env -iA nixpkgs.hello`). + +> It might takes several minutes to complete `nix-env`. diff --git a/examples/networking/wasi/README.md b/examples/networking/wasi/README.md new file mode 100644 index 0000000..faa1ee5 --- /dev/null +++ b/examples/networking/wasi/README.md @@ -0,0 +1,148 @@ +# Running container on WASI runtimes with host networking + +Networking on WASI runtimes is possible by relying on the network stack running on the host (outside of the WASI runtime). + +We use [`gvisor-tap-vsock`](https://github.com/containers/gvisor-tap-vsock) as the user-space network stack running on the host. +We provide a wrapper command [`c2w-net`](../../../cmd/c2w-net/) for container-on-WASI-runtime use-case. + +The WASM image converted from the container can be configured to expose packets sent from the container via WASI's socket (`sock_*` API). +WASI runtime binds the socket on a TCP port (e.g. using wasmtime's `--tcplisten` flag and wazero's [`WithTCPListener`](https://github.com/tetratelabs/wazero/blob/405a5c9daca906cc8f52ee13e16511f44ae79557/experimental/sock/sock.go#L31) option). +`c2w-net` running on the host connects to that port and forwards packets sent to/from the container. + +The WASM image converted from the container has `--net` flag that enables this networking feature. +`--net=qemu` is the only supported mode which sends packets to `gvisor-tap-vsock` (wrapped by `c2w-net`) using [QEMU's forwarding protocol](https://github.com/containers/gvisor-tap-vsock#run-with-qemu-linux-or-macos). + +> NOTE: By default, the WASM image tries to establish connection with `c2w-net` via WASI's fd=3. +> However WASI runtimes might use larger fd when directory sharing is enabled. +> In that case, `--net=qemu=listenfd=` flag can be used for configuring the WASM image to use the correct socket fd. + +## Example + +This doc shows examples for wasmtime and wazero. + +`c2w` and `c2w-net` are available on the [release page](https://github.com/ktock/container2wasm/releases). +You can also build them using `make` command: + +``` +$ make +``` + +### wasmtime + +- Requirement + - wasmtime needs to be installed. + +`c2w-net` command with `--invoke` flag runs containers on wasmtime with automatically configuring it. + +First, prepare a WASI image named `out.wasm`. + +``` +$ c2w alpine:3.18 /tmp/out/out.wasm +``` + +Then launch it on wasmtime with enabling networking. + +```console +$ c2w-net --invoke /tmp/out/out.wasm --net=qemu sh +connecting to NW... +INFO[0001] new connection from 127.0.0.1:1234 to 127.0.0.1:50470 +/ # apk update && apk add --no-progress figlet +apk update && apk add --no-progress figlet +apk update && apk add --no-progress figlet +fetch https://dl-cdn.alpinelinux.org/alpine/v3.18/main/x86_64/APKINDEX.tar.gz +fetch https://dl-cdn.alpinelinux.org/alpine/v3.18/community/x86_64/APKINDEX.tar.gz +v3.18.3-149-g8225da85c11 [https://dl-cdn.alpinelinux.org/alpine/v3.18/main] +v3.18.3-151-g6953e6f988a [https://dl-cdn.alpinelinux.org/alpine/v3.18/community] +OK: 20071 distinct packages available +(1/1) Installing figlet (2.2.5-r3) +Executing busybox-1.36.0-r9.trigger +OK: 8 MiB in 16 packages +/ # figlet hello +figlet hello +figlet hello + _ _ _ +| |__ ___| | | ___ +| '_ \ / _ \ | |/ _ \ +| | | | __/ | | (_) | +|_| |_|\___|_|_|\___/ + +``` + +> It might takes several minutes to complete `apk add`. + +Port mapping is also supported. +The following example launches httpd server listening on `localhost:8000`. + +``` +$ c2w httpd /tmp/out/httpd.wasm +$ c2w-net --invoke -p localhost:8000:80 /tmp/out/httpd.wasm --net=qemu +``` + +> It might takes several seconds to the server becoming up-and-running. + +The server is accessible via `localhost:8000`. + +``` +$ curl localhost:8000 +

It works!

+``` + +### wazero + +Wazero doesn't require `c2w-net` but the network stack can be directly implemented on the Go code that imports Wazero runtime. +[`../../../tests/wazero/`](../../../tests/wazero/) is an example command for wazero with enabling networking of the container. +This is used in our integration test CI for wazero. + +First, prepare a WASI image named `out.wasm`. + +``` +$ c2w alpine:3.18 /tmp/out/out.wasm +``` + +Then, it can run on wazero with networking support: + +> Run this at the project repo root directory. + +``` +$ ( cd ./tests/wazero && go build -o ../../out/wazero-test . ) +$ ./out/wazero-test -net /tmp/out/out.wasm --net=qemu sh +connecting to NW... +INFO[0001] new connection from 127.0.0.1:1234 to 127.0.0.1:53666 +/ # apk update && apk add --no-progress figlet +apk update && apk add --no-progress figlet +fetch https://dl-cdn.alpinelinux.org/alpine/v3.18/main/x86_64/APKINDEX.tar.gz +fetch https://dl-cdn.alpinelinux.org/alpine/v3.18/community/x86_64/APKINDEX.tar.gz +v3.18.3-149-g8225da85c11 [https://dl-cdn.alpinelinux.org/alpine/v3.18/main] +v3.18.3-151-g6953e6f988a [https://dl-cdn.alpinelinux.org/alpine/v3.18/community] +OK: 20071 distinct packages available +(1/1) Installing figlet (2.2.5-r3) +Executing busybox-1.36.0-r9.trigger +OK: 8 MiB in 16 packages +/ # figlet hello +figlet hello + _ _ _ +| |__ ___| | | ___ +| '_ \ / _ \ | |/ _ \ +| | | | __/ | | (_) | +|_| |_|\___|_|_|\___/ + +``` + +> It might takes several minutes to complete `apk add`. + +Port mapping is also supported. +The following example launches httpd server listening on `localhost:8000`. + +``` +$ c2w httpd /tmp/out/httpd.wasm +$ ./out/wazero-test -net -p localhost:8000:80 /tmp/out/httpd.wasm --net=qemu +``` + +> It might takes several seconds to the server becoming up-and-running. + +The server is accessible via `localhost:8000`. + +``` +$ curl localhost:8000 +

It works!

+``` diff --git a/examples/networking/websocket/README.md b/examples/networking/websocket/README.md new file mode 100644 index 0000000..78ca23b --- /dev/null +++ b/examples/networking/websocket/README.md @@ -0,0 +1,97 @@ +# Running container on browser with the network stack on the host accessible over WebSocket + +This is an example of container on browser with networking support relying on the network stack on the host. + +Please refer to [`../../wasi-browser`](../../wasi-browser/) for the basics of WASI-on-browser and [`../../emscripten`](../../emscripten/) for the basics of containers on emscripten. + +This example relies on the network stack running on the host (outside of the browser) accessible via WebSocket. + +> NOTE: Please see also [`../fetch`](../fetch/) for networking using on-browser network stack and Fetch API, without host-side daemon. + +- pros: Container can access to anywhere accesible from the network stack daemon running on the host. +- cons: The network stack daemon needs to run on the machine and forward packets received over WebSocket. + +We use [`gvisor-tap-vsock`](https://github.com/containers/gvisor-tap-vsock) as the user-space network stack running on the host. +We provide a wrapper command [`c2w-net`](../../../cmd/c2w-net/) for container-on-wasm use-case. + +`c2w-net` exposes an WebSocket port and it forwards all packets received from that WebSocket. +The container running on browser can connect to that WebSocket and pass all packets there and let `c2w-net` forward them on the host. + +## Getting `c2w` and `c2w-net` + +`c2w` and `c2w-net` are available on the [release page](https://github.com/ktock/container2wasm/releases). +You can also build them using `make` command: + +``` +$ make +``` + +## Example1: WASI-on-browser + +![Alpine container on browser with host networking](../../../docs/images/alpine-wasi-on-browser-host-networking.png) + +The following builds and starts the network stack listening on the WebSocket `localhost:8888`. + +``` +$ c2w-net --listen-ws localhost:8888 +``` + +Prepare a WASI image named `out.wasm`. + +``` +$ c2w alpine:3.18 /tmp/out-js2/htdocs/out.wasm +``` + +Then, serve it via browser. + +> Run this at the project repo root directory. + +``` +$ cp -R ./examples/wasi-browser/* /tmp/out-js2/ && chmod 755 /tmp/out-js2/htdocs +$ docker run --rm -p 8080:80 \ + -v "/tmp/out-js2/htdocs:/usr/local/apache2/htdocs/:ro" \ + -v "/tmp/out-js2/xterm-pty.conf:/usr/local/apache2/conf/extra/xterm-pty.conf:ro" \ + --entrypoint=/bin/sh httpd -c 'echo "Include conf/extra/xterm-pty.conf" >> /usr/local/apache2/conf/httpd.conf && httpd-foreground' +``` + +You can run the container on browser via `localhost:8080/?net=delegate=ws://localhost:8888`. +The parameter `net=delegate` tells the [container's Javascript wrapper](../../wasi-browser/) to forward packets via the specified WebSocket address listened by `c2w-net`. + +This example installs and runs `figlet` command in the container (`apk update && apk add figlet`). + +> It might takes several minutes to complete `apk add`. + +## Example2: emscripten + +![Alpine container on browser with host networking](../../../docs/images/alpine-emscripten-host-networking.png) + +The following builds and starts the network stack listening on the WebSocket `localhost:8888`. + +``` +$ c2w-net --listen-ws localhost:8888 +``` + +Prepare a WASI image named `out.wasm`. + +``` +$ c2w --to-js alpine:3.18 /tmp/out-js/htdocs/ +``` + +Then, serve it via browser. + +> Run this at the project repo root directory. + +``` +$ cp -R ./examples/emscripten/* /tmp/out-js/ && chmod 755 /tmp/out-js/htdocs +$ docker run --rm -p 8080:80 \ + -v "/tmp/out-js/htdocs:/usr/local/apache2/htdocs/:ro" \ + -v "/tmp/out-js/xterm-pty.conf:/usr/local/apache2/conf/extra/xterm-pty.conf:ro" \ + --entrypoint=/bin/sh httpd -c 'echo "Include conf/extra/xterm-pty.conf" >> /usr/local/apache2/conf/httpd.conf && httpd-foreground' +``` + +You can run the container on browser via `localhost:8080/?net=delegate=ws://localhost:8888`. +The parameter `net=delegate` tells the [container's Javascript wrapper](../../wasi-browser/) to forward packets via the specified WebSocket address listened by `c2w-net`. + +This example installs and runs `figlet` command in the container (`apk update && apk add figlet`). + +> It might takes several minutes to complete `apk add`. diff --git a/examples/wasi-browser/README.md b/examples/wasi-browser/README.md index c391f76..3433bb9 100644 --- a/examples/wasi-browser/README.md +++ b/examples/wasi-browser/README.md @@ -9,6 +9,8 @@ The difference between this and [emscripten example](../emscripten) is that this This example leverages polyfill library [browser_wasi_shim](https://github.com/bjorn3/browser_wasi_shim) provides WASI APIs to the WASM binary on browser. We integrated that WASI polyfill's IO to xterm-pty for allowing the user connecting to that container via the terminal. +Examples of enabling networking can be found at [`./../networking`](./../networking/). + ## Example ![Ubuntu container on browser](../../docs/images/ubuntu-wasi-on-browser.png) diff --git a/examples/wasi-browser/htdocs/index.html b/examples/wasi-browser/htdocs/index.html index 84669ff..27b1b70 100644 --- a/examples/wasi-browser/htdocs/index.html +++ b/examples/wasi-browser/htdocs/index.html @@ -7,6 +7,8 @@
+ + diff --git a/examples/wasi-browser/htdocs/stack-worker.js b/examples/wasi-browser/htdocs/stack-worker.js new file mode 100644 index 0000000..de3411a --- /dev/null +++ b/examples/wasi-browser/htdocs/stack-worker.js @@ -0,0 +1,245 @@ +importScripts(location.origin + "/browser_wasi_shim/index.js"); +importScripts(location.origin + "/browser_wasi_shim/wasi_defs.js"); +importScripts(location.origin + "/worker-util.js"); +importScripts(location.origin + "/wasi-util.js"); + +onmessage = (msg) => { + serveIfInitMsg(msg); + var fds = [ + undefined, // 0: stdin + undefined, // 1: stdout + undefined, // 2: stderr + undefined, // 3: receive certificates + undefined, // 4: socket listenfd + undefined, // 5: accepted socket fd (multi-connection is unsupported) + // 6...: used by wasi shim + ]; + var certfd = 3; + var listenfd = 4; + var args = ['arg0', '--certfd='+certfd, '--net-listenfd='+listenfd, '--debug']; + var env = []; + var wasi = new WASI(args, env, fds); + wasiHack(wasi, certfd, 5); + wasiHackSocket(wasi, listenfd, 5); + fetch(getImagename(), { credentials: 'same-origin' }).then((resp) => { + resp['arrayBuffer']().then((wasm) => { + WebAssembly.instantiate(wasm, { + "wasi_snapshot_preview1": wasi.wasiImport, + "env": envHack(wasi), + }).then((inst) => { + wasi.start(inst.instance); + }); + }) + }); +}; + +// definition from wasi-libc https://github.com/WebAssembly/wasi-libc/blob/wasi-sdk-19/expected/wasm32-wasi/predefined-macros.txt +const ERRNO_INVAL = 28; +const ERRNO_AGAIN= 6; + +function wasiHack(wasi, certfd, connfd) { + var certbuf = new Uint8Array(0); + var _fd_close = wasi.wasiImport.fd_close; + wasi.wasiImport.fd_close = (fd) => { + if (fd == certfd) { + sendCert(certbuf); + return 0; + } + return _fd_close.apply(wasi.wasiImport, [fd]); + } + var _fd_fdstat_get = wasi.wasiImport.fd_fdstat_get; + wasi.wasiImport.fd_fdstat_get = (fd, fdstat_ptr) => { + if (fd == certfd) { + return 0; + } + return _fd_fdstat_get.apply(wasi.wasiImport, [fd, fdstat_ptr]); + } + wasi.wasiImport.fd_fdstat_set_flags = (fd, fdflags) => { + // TODO + return 0; + } + var _fd_write = wasi.wasiImport.fd_write; + wasi.wasiImport.fd_write = (fd, iovs_ptr, iovs_len, nwritten_ptr) => { + if ((fd == 1) || (fd == 2) || (fd == certfd)) { + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer); + var iovecs = Ciovec.read_bytes_array(buffer, iovs_ptr, iovs_len); + var wtotal = 0 + for (i = 0; i < iovecs.length; i++) { + var iovec = iovecs[i]; + var buf = buffer8.slice(iovec.buf, iovec.buf + iovec.buf_len); + if (buf.length == 0) { + continue; + } + console.log(new TextDecoder().decode(buf)); + if (fd == certfd) { + certbuf = appendData(certbuf, buf); + } + wtotal += buf.length; + } + buffer.setUint32(nwritten_ptr, wtotal, true); + return 0; + } + console.log("fd_write: unknown fd " + fd); + return _fd_write.apply(wasi.wasiImport, [fd, iovs_ptr, iovs_len, nwritten_ptr]); + } + wasi.wasiImport.poll_oneoff = (in_ptr, out_ptr, nsubscriptions, nevents_ptr) => { + if (nsubscriptions == 0) { + return ERRNO_INVAL; + } + let buffer = new DataView(wasi.inst.exports.memory.buffer); + let in_ = Subscription.read_bytes_array(buffer, in_ptr, nsubscriptions); + let isReadPollStdin = false; + let isReadPollConn = false; + let isClockPoll = false; + let pollSubStdin; + let pollSubConn; + let clockSub; + let timeout = Number.MAX_VALUE; + for (let sub of in_) { + if (sub.u.tag.variant == "fd_read") { + if ((sub.u.data.fd != 0) && (sub.u.data.fd != connfd)) { + return ERRNO_INVAL; // only fd=0 and connfd is supported as of now (FIXME) + } + if (sub.u.data.fd == 0) { + isReadPollStdin = true; + pollSubStdin = sub; + } else { + isReadPollConn = true; + pollSubConn = sub; + } + } else if (sub.u.tag.variant == "clock") { + if (sub.u.data.timeout < timeout) { + timeout = sub.u.data.timeout + isClockPoll = true; + clockSub = sub; + } + } else { + return ERRNO_INVAL; // FIXME + } + } + let events = []; + if (isReadPollStdin || isReadPollConn || isClockPoll) { + var sockreadable = sockWaitForReadable(timeout / 1000000000); + if (isReadPollConn) { + if (sockreadable == errStatus) { + return ERRNO_INVAL; + } else if (sockreadable == true) { + let event = new Event(); + event.userdata = pollSubConn.userdata; + event.error = 0; + event.type = new EventType("fd_read"); + events.push(event); + } + } + if (isClockPoll) { + let event = new Event(); + event.userdata = clockSub.userdata; + event.error = 0; + event.type = new EventType("clock"); + events.push(event); + } + } + var len = events.length; + Event.write_bytes_array(buffer, out_ptr, events); + buffer.setUint32(nevents_ptr, len, true); + return 0; + } +} + +function envHack(wasi){ + return { + http_send: function(addressP, addresslen, reqP, reqlen, idP){ + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var address = new Uint8Array(wasi.inst.exports.memory.buffer, addressP, addresslen); + var req = new Uint8Array(wasi.inst.exports.memory.buffer, reqP, reqlen); + streamCtrl[0] = 0; + postMessage({ + type: "http_send", + address: address, + req: req, + }); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + return ERRNO_INVAL; + } + var id = streamStatus[0]; + buffer.setUint32(idP, id, true); + return 0; + }, + http_writebody: function(id, bodyP, bodylen, nwrittenP, isEOF){ + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var body = new Uint8Array(wasi.inst.exports.memory.buffer, bodyP, bodylen); + streamCtrl[0] = 0; + postMessage({ + type: "http_writebody", + id: id, + body: body, + isEOF: isEOF, + }); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + return ERRNO_INVAL; + } + buffer.setUint32(nwrittenP, bodylen, true); + return 0; + }, + http_isreadable: function(id, isOKP){ + var buffer = new DataView(wasi.inst.exports.memory.buffer); + streamCtrl[0] = 0; + postMessage({type: "http_isreadable", id: id}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + return ERRNO_INVAL; + } + var readable = 0; + if (streamData[0] == 1) { + readable = 1; + } + buffer.setUint32(isOKP, readable, true); + return 0; + }, + http_recv: function(id, respP, bufsize, respsizeP, isEOFP){ + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer); + + streamCtrl[0] = 0; + postMessage({type: "http_recv", id: id, len: bufsize}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + return ERRNO_INVAL; + } + var ddlen = streamLen[0]; + var resp = streamData.slice(0, ddlen); + buffer8.set(resp, respP); + buffer.setUint32(respsizeP, ddlen, true); + if (streamStatus[0] == 1) { + buffer.setUint32(isEOFP, 1, true); + } else { + buffer.setUint32(isEOFP, 0, true); + } + return 0; + }, + http_readbody: function(id, bodyP, bufsize, bodysizeP, isEOFP){ + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer); + + streamCtrl[0] = 0; + postMessage({type: "http_readbody", id: id, len: bufsize}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + return ERRNO_INVAL; + } + var ddlen = streamLen[0]; + var body = streamData.slice(0, ddlen); + buffer8.set(body, bodyP); + buffer.setUint32(bodysizeP, ddlen, true); + if (streamStatus[0] == 1) { + buffer.setUint32(isEOFP, 1, true); + } else { + buffer.setUint32(isEOFP, 0, true); + } + return 0; + } + }; +} diff --git a/examples/wasi-browser/htdocs/stack.js b/examples/wasi-browser/htdocs/stack.js new file mode 100644 index 0000000..126becc --- /dev/null +++ b/examples/wasi-browser/htdocs/stack.js @@ -0,0 +1,248 @@ +function newStack(worker, workerImageName, stackWorker, stackImageName) { + let p2vbuf = { + buf: new Uint8Array(0) // proxy => vm + }; + let v2pbuf = { + buf: new Uint8Array(0) // vm => proxy + }; + var proxyConn = { + sendbuf: p2vbuf, + recvbuf: v2pbuf + }; + var vmConn = { + sendbuf: v2pbuf, + recvbuf: p2vbuf + } + var proxyShared = new SharedArrayBuffer(12 + 4096); + var certbuf = { + buf: new Uint8Array(0), + done: false + } + stackWorker.onmessage = connect("proxy", proxyShared, proxyConn, certbuf); + stackWorker.postMessage({type: "init", buf: proxyShared, imagename: stackImageName}); + + var vmShared = new SharedArrayBuffer(12 + 4096); + worker.postMessage({type: "init", buf: vmShared, imagename: workerImageName}); + return connect("vm", vmShared, vmConn, certbuf); +} + +function connect(name, shared, conn, certbuf) { + var streamCtrl = new Int32Array(shared, 0, 1); + var streamStatus = new Int32Array(shared, 4, 1); + var streamLen = new Int32Array(shared, 8, 1); + var streamData = new Uint8Array(shared, 12); + var sendbuf = conn.sendbuf; + var recvbuf = conn.recvbuf; + let accepted = false; + var httpConnections = {}; + var curID = 0; + var maxID = 0x7FFFFFFF; // storable in streamStatus(signed 32bits) + function getID() { + var startID = curID; + while (true) { + if (httpConnections[curID] == undefined) { + return curID; + } + if (curID >= maxID) { + curID = 0; + } else { + curID++; + } + if (curID == startID) { + return -1; // exhausted + } + } + return curID; + } + function serveData(data, len) { + var length = len; + if (length > streamData.byteLength) + length = streamData.byteLength; + if (length > data.byteLength) + length = data.byteLength + var buf = data.slice(0, length); + var remain = data.slice(length, data.byteLength); + streamLen[0] = buf.byteLength; + streamData.set(buf, 0); + return remain; + } + return function(msg){ + const req_ = msg.data; + if (typeof req_ == "object" && req_.type) { + switch (req_.type) { + case "accept": + accepted = true; + streamData[0] = 1; // opened + streamStatus[0] = 0; + break; + case "send": + if (!accepted) { + console.log(name + ":" + "cannot send to unaccepted socket"); + streamStatus[0] = -1; + break; + } + sendbuf.buf = appendData(sendbuf.buf, req_.buf); + streamStatus[0] = 0; + break; + case "recv": + if (!accepted) { + console.log(name + ":" + "cannot recv from unaccepted socket"); + streamStatus[0] = -1; + break; + } + recvbuf.buf = serveData(recvbuf.buf, req_.len); + streamStatus[0] = 0; + break; + case "recv-is-readable": + var recvbufP = recvbuf.buf; + if (recvbufP.byteLength > 0) { + streamData[0] = 1; // ready for reading + } else { + if ((req_.timeout != undefined) && (req_.timeout > 0)) { + setTimeout(() => { + if (recvbuf.buf.byteLength > 0) { + streamData[0] = 1; // ready for reading + } else { + streamData[0] = 0; // timeout + } + streamStatus[0] = 0; + Atomics.store(streamCtrl, 0, 1); + Atomics.notify(streamCtrl, 0); + }, req_.timeout * 1000); + return; + } + streamData[0] = 0; // timeout + } + streamStatus[0] = 0; + break; + case "http_send": + var reqObj = JSON.parse(new TextDecoder().decode(req_.req)); + reqObj.mode = "cors"; + reqObj.credentials = "omit"; + var reqID = getID(); + if (reqID < 0) { + console.log(name + ":" + "failed to get id"); + streamStatus[0] = -1; + break; + } + var connObj = { + address: new TextDecoder().decode(req_.address), + request: reqObj, + requestSent: false, + reqBodybuf: new Uint8Array(0), + reqBodyEOF: false, + }; + httpConnections[reqID] = connObj; + streamStatus[0] = reqID; + break; + case "http_writebody": + httpConnections[req_.id].reqBodybuf = appendData(httpConnections[req_.id].reqBodybuf, req_.body) + httpConnections[req_.id].reqBodyEOF = req_.isEOF; + streamStatus[0] = 0; + if (req_.isEOF && !httpConnections[req_.id].requestSent) { + httpConnections[req_.id].requestSent = true; + var connObj = httpConnections[req_.id]; + if ((connObj.request.method != "HEAD") && (connObj.request.method != "GET")) { + connObj.request.body = connObj.reqBodybuf; + } + fetch(connObj.address, connObj.request).then((resp) => { + connObj.response = new TextEncoder().encode(JSON.stringify({ + bodyUsed: resp.bodyUsed, + headers: resp.headers, + redirected: resp.redirected, + status: resp.status, + statusText: resp.statusText, + type: resp.type, + url: resp.url + })), + connObj.done = false; + connObj.respBodybuf = new Uint8Array(0); + if (resp.ok) { + resp.arrayBuffer().then((data) => { + connObj.respBodybuf = new Uint8Array(data); + connObj.done = true; + }).catch((error) => { + connObj.respBodybuf = new Uint8Array(0); + connObj.done = true; + console.log("failed to fetch body: " + error); + }); + } else { + connObj.done = true; + } + }).catch((error) => { + connObj.response = new TextEncoder().encode(JSON.stringify({ + status: 503, + statusText: "Service Unavailable", + })) + connObj.respBodybuf = new Uint8Array(0); + connObj.done = true; + }); + } + break; + case "http_isreadable": + if ((httpConnections[req_.id] != undefined) && (httpConnections[req_.id].response != undefined)) { + streamData[0] = 1; // ready for reading + } else { + streamData[0] = 0; // nothing to read + } + streamStatus[0] = 0; + break; + case "http_recv": + if ((httpConnections[req_.id] == undefined) || (httpConnections[req_.id].response == undefined)) { + console.log(name + ":" + "response is not available"); + streamStatus[0] = -1; + break; + } + httpConnections[req_.id].response = serveData(httpConnections[req_.id].response, req_.len); + streamStatus[0] = 0; + if (httpConnections[req_.id].response.byteLength == 0) { + streamStatus[0] = 1; // isEOF + } + break; + case "http_readbody": + if ((httpConnections[req_.id] == undefined) || (httpConnections[req_.id].response == undefined)) { + console.log(name + ":" + "response body is not available"); + streamStatus[0] = -1; + break; + } + httpConnections[req_.id].respBodybuf = serveData(httpConnections[req_.id].respBodybuf, req_.len); + streamStatus[0] = 0; + if ((httpConnections[req_.id].done) && (httpConnections[req_.id].respBodybuf.byteLength == 0)) { + streamStatus[0] = 1; + delete httpConnections[req_.id]; // connection done + } + break; + case "send_cert": + certbuf.buf = appendData(certbuf.buf, req_.buf); + certbuf.done = true; + streamStatus[0] = 0; + break; + case "recv_cert": + if (!certbuf.done) { + streamStatus[0] = -1; + break; + } + certbuf.buf = serveData(certbuf.buf, req_.len); + streamStatus[0] = 0; + if (certbuf.buf.byteLength == 0) { + streamStatus[0] = 1; // isEOF + } + break; + default: + console.log(name + ":" + "unknown request: " + req_.type) + return; + } + Atomics.store(streamCtrl, 0, 1); + Atomics.notify(streamCtrl, 0); + } else { + console.log("UNKNOWN MSG " + msg); + } + } +} + +function appendData(data1, data2) { + buf2 = new Uint8Array(data1.byteLength + data2.byteLength); + buf2.set(new Uint8Array(data1), 0); + buf2.set(new Uint8Array(data2), data1.byteLength); + return buf2; +} diff --git a/examples/wasi-browser/htdocs/wasi-util.js b/examples/wasi-browser/htdocs/wasi-util.js new file mode 100644 index 0000000..7714298 --- /dev/null +++ b/examples/wasi-browser/htdocs/wasi-util.js @@ -0,0 +1,127 @@ +//////////////////////////////////////////////////////////// +// +// event-related classes adopted from the on-going discussion +// towards poll_oneoff support in browser_wasi_sim project. +// Ref: https://github.com/bjorn3/browser_wasi_shim/issues/14#issuecomment-1450351935 +// +//////////////////////////////////////////////////////////// + +class EventType { + /*:: variant: "clock" | "fd_read" | "fd_write"*/ + + constructor(variant/*: "clock" | "fd_read" | "fd_write"*/) { + this.variant = variant; + } + + static from_u8(data/*: number*/)/*: EventType*/ { + switch (data) { + case EVENTTYPE_CLOCK: + return new EventType("clock"); + case EVENTTYPE_FD_READ: + return new EventType("fd_read"); + case EVENTTYPE_FD_WRITE: + return new EventType("fd_write"); + default: + throw "Invalid event type " + String(data); + } + } + + to_u8()/*: number*/ { + switch (this.variant) { + case "clock": + return EVENTTYPE_CLOCK; + case "fd_read": + return EVENTTYPE_FD_READ; + case "fd_write": + return EVENTTYPE_FD_WRITE; + default: + throw "unreachable"; + } + } +} + +class Event { + /*:: userdata: UserData*/ + /*:: error: number*/ + /*:: type: EventType*/ + /*:: fd_readwrite: EventFdReadWrite | null*/ + + write_bytes(view/*: DataView*/, ptr/*: number*/) { + view.setBigUint64(ptr, this.userdata, true); + view.setUint8(ptr + 8, this.error); + view.setUint8(ptr + 9, 0); + view.setUint8(ptr + 10, this.type.to_u8()); + // if (this.fd_readwrite) { + // this.fd_readwrite.write_bytes(view, ptr + 16); + // } + } + + static write_bytes_array(view/*: DataView*/, ptr/*: number*/, events/*: Array*/) { + for (let i = 0; i < events.length; i++) { + events[i].write_bytes(view, ptr + 32 * i); + } + } +} + +class SubscriptionClock { + /*:: timeout: number*/ + + static read_bytes(view/*: DataView*/, ptr/*: number*/)/*: SubscriptionFdReadWrite*/ { + let self = new SubscriptionClock(); + self.timeout = Number(view.getBigUint64(ptr + 8, true)); + return self; + } +} + +class SubscriptionFdReadWrite { + /*:: fd: number*/ + + static read_bytes(view/*: DataView*/, ptr/*: number*/)/*: SubscriptionFdReadWrite*/ { + let self = new SubscriptionFdReadWrite(); + self.fd = view.getUint32(ptr, true); + return self; + } +} + +class SubscriptionU { + /*:: tag: EventType */ + /*:: data: SubscriptionClock | SubscriptionFdReadWrite */ + + static read_bytes(view/*: DataView*/, ptr/*: number*/)/*: SubscriptionU*/ { + let self = new SubscriptionU(); + self.tag = EventType.from_u8(view.getUint8(ptr)); + switch (self.tag.variant) { + case "clock": + self.data = SubscriptionClock.read_bytes(view, ptr + 8); + break; + case "fd_read": + case "fd_write": + self.data = SubscriptionFdReadWrite.read_bytes(view, ptr + 8); + break; + default: + throw "unreachable"; + } + return self; + } +} + +class Subscription { + /*:: userdata: UserData */ + /*:: u: SubscriptionU */ + + static read_bytes(view/*: DataView*/, ptr/*: number*/)/*: Subscription*/ { + let subscription = new Subscription(); + subscription.userdata = view.getBigUint64(ptr, true); + subscription.u = SubscriptionU.read_bytes(view, ptr + 8); + return subscription; + } + + static read_bytes_array(view/*: DataView*/, ptr/*: number*/, len/*: number*/)/*: Array*/ { + let subscriptions = []; + for (let i = 0; i < len; i++) { + subscriptions.push(Subscription.read_bytes(view, ptr + 48 * i)); + } + return subscriptions; + } +} + diff --git a/examples/wasi-browser/htdocs/worker-util.js b/examples/wasi-browser/htdocs/worker-util.js new file mode 100644 index 0000000..82b4627 --- /dev/null +++ b/examples/wasi-browser/htdocs/worker-util.js @@ -0,0 +1,266 @@ +var streamCtrl; +var streamStatus; +var streamLen; +var streamData; +function registerSocketBuffer(shared){ + streamCtrl = new Int32Array(shared, 0, 1); + streamStatus = new Int32Array(shared, 4, 1); + streamLen = new Int32Array(shared, 8, 1); + streamData = new Uint8Array(shared, 12); +} + +var imagename; +function serveIfInitMsg(msg) { + const req_ = msg.data; + if (typeof req_ == "object"){ + if (req_.type == "init") { + if (req_.buf) + var shared = req_.buf; + registerSocketBuffer(shared); + if (req_.imagename) + imagename = req_.imagename; + return true; + } + } + + return false; +} + +function getImagename() { + return imagename; +} + +const errStatus = { + val: 0, +}; + +function sockAccept(){ + streamCtrl[0] = 0; + postMessage({type: "accept"}); + Atomics.wait(streamCtrl, 0, 0); + return streamData[0] == 1; +} +function sockSend(data){ + streamCtrl[0] = 0; + postMessage({type: "send", buf: data}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + errStatus.val = streamStatus[0] + return errStatus; + } +} +function sockRecv(len){ + streamCtrl[0] = 0; + postMessage({type: "recv", len: len}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + errStatus.val = streamStatus[0] + return errStatus; + } + let ddlen = streamLen[0]; + var res = streamData.slice(0, ddlen); + return res; +} + +function sockWaitForReadable(timeout){ + streamCtrl[0] = 0; + postMessage({type: "recv-is-readable", timeout: timeout}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + errStatus.val = streamStatus[0] + return errStatus; + } + return streamData[0] == 1; +} + +function sendCert(data){ + streamCtrl[0] = 0; + postMessage({type: "send_cert", buf: data}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + errStatus.val = streamStatus[0] + return errStatus; + } +} + +function recvCert(){ + var buf = new Uint8Array(0); + return new Promise((resolve, reject) => { + function getCert(){ + streamCtrl[0] = 0; + postMessage({type: "recv_cert"}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + setTimeout(getCert, 100); + return; + } + var ddlen = streamLen[0]; + buf = appendData(buf, streamData.slice(0, ddlen)); + if (streamStatus[0] == 0) { + resolve(buf); // EOF + } else { + setTimeout(getCert, 0); + return; + } + } + getCert(); + }); +} + +function appendData(data1, data2) { + buf2 = new Uint8Array(data1.byteLength + data2.byteLength); + buf2.set(new Uint8Array(data1), 0); + buf2.set(new Uint8Array(data2), data1.byteLength); + return buf2; +} + +function getCertDir(cert) { + var certDir = new PreopenDirectory("/.wasmenv", { + "proxy.crt": new File(cert) + }); + var _path_open = certDir.path_open; + certDir.path_open = (e, r, s, n, a, d) => { + var ret = _path_open.apply(certDir, [e, r, s, n, a, d]); + if (ret.fd_obj != null) { + var o = ret.fd_obj; + ret.fd_obj.fd_pread = (view8, iovs, offset) => { + var old_offset = o.file_pos; + var r = o.fd_seek(offset, WHENCE_SET); + if (r.ret != 0) { + return { ret: -1, nread: 0 }; + } + var read_ret = o.fd_read(view8, iovs); + r = o.fd_seek(old_offset, WHENCE_SET); + if (r.ret != 0) { + return { ret: -1, nread: 0 }; + } + return read_ret; + } + } + return ret; + } + certDir.dir.contents["."] = certDir.dir; + return certDir; +} + +function wasiHackSocket(wasi, listenfd, connfd) { + // definition from wasi-libc https://github.com/WebAssembly/wasi-libc/blob/wasi-sdk-19/expected/wasm32-wasi/predefined-macros.txt + const ERRNO_INVAL = 28; + const ERRNO_AGAIN= 6; + var connfdUsed = false; + var connbuf = new Uint8Array(0); + var _fd_close = wasi.wasiImport.fd_close; + wasi.wasiImport.fd_close = (fd) => { + if (fd == connfd) { + connfdUsed = false; + return 0; + } + return _fd_close.apply(wasi.wasiImport, [fd]); + } + var _fd_read = wasi.wasiImport.fd_read; + wasi.wasiImport.fd_read = (fd, iovs_ptr, iovs_len, nread_ptr) => { + if (fd == connfd) { + return wasi.wasiImport.sock_recv(fd, iovs_ptr, iovs_len, 0, nread_ptr, 0); + } + return _fd_read.apply(wasi.wasiImport, [fd, iovs_ptr, iovs_len, nread_ptr]); + } + var _fd_write = wasi.wasiImport.fd_write; + wasi.wasiImport.fd_write = (fd, iovs_ptr, iovs_len, nwritten_ptr) => { + if (fd == connfd) { + return wasi.wasiImport.sock_send(fd, iovs_ptr, iovs_len, 0, nwritten_ptr); + } + return _fd_write.apply(wasi.wasiImport, [fd, iovs_ptr, iovs_len, nwritten_ptr]); + } + var _fd_fdstat_get = wasi.wasiImport.fd_fdstat_get; + wasi.wasiImport.fd_fdstat_get = (fd, fdstat_ptr) => { + if ((fd == listenfd) || (fd == connfd) && connfdUsed){ + let buffer = new DataView(wasi.inst.exports.memory.buffer); + // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fdstat-struct + buffer.setUint8(fdstat_ptr, 6); // filetype = 6 (socket_stream) + buffer.setUint8(fdstat_ptr + 1, 2); // fdflags = 2 (nonblock) + return 0; + } + return _fd_fdstat_get.apply(wasi.wasiImport, [fd, fdstat_ptr]); + } + wasi.wasiImport.sock_accept = (fd, flags, fd_ptr) => { + if (fd != listenfd) { + console.log("sock_accept: unknown fd " + fd); + return ERRNO_INVAL; + } + if (connfdUsed) { + console.log("sock_accept: multi-connection is unsupported"); + return ERRNO_INVAL; + } + if (!sockAccept()) { + return ERRNO_AGAIN; + } + connfdUsed = true; + var buffer = new DataView(wasi.inst.exports.memory.buffer); + buffer.setUint32(fd_ptr, connfd, true); + return 0; + } + wasi.wasiImport.sock_send = (fd, iovs_ptr, iovs_len, si_flags/*not defined*/, nwritten_ptr) => { + if (fd != connfd) { + console.log("sock_send: unknown fd " + fd); + return ERRNO_INVAL; + } + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer); + var iovecs = Ciovec.read_bytes_array(buffer, iovs_ptr, iovs_len); + var wtotal = 0 + for (i = 0; i < iovecs.length; i++) { + var iovec = iovecs[i]; + var buf = buffer8.slice(iovec.buf, iovec.buf + iovec.buf_len); + if (buf.length == 0) { + continue; + } + var ret = sockSend(buf.buffer.slice(0, iovec.buf_len)); + if (ret == errStatus) { + return ERRNO_INVAL; + } + wtotal += buf.length; + } + buffer.setUint32(nwritten_ptr, wtotal, true); + return 0; + } + wasi.wasiImport.sock_recv = (fd, iovs_ptr, iovs_len, ri_flags, nread_ptr, ro_flags_ptr) => { + if (ri_flags != 0) { + console.log("ri_flags are unsupported"); // TODO + } + if (fd != connfd) { + console.log("sock_recv: unknown fd " + fd); + return ERRNO_INVAL; + } + var sockreadable = sockWaitForReadable(); + if (sockreadable == errStatus) { + return ERRNO_INVAL; + } else if (sockreadable == false) { + return ERRNO_AGAIN; + } + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer); + var iovecs = Iovec.read_bytes_array(buffer, iovs_ptr, iovs_len); + var nread = 0; + for (i = 0; i < iovecs.length; i++) { + var iovec = iovecs[i]; + if (iovec.buf_len == 0) { + continue; + } + var data = sockRecv(iovec.buf_len); + if (data == errStatus) { + return ERRNO_INVAL; + } + buffer8.set(data, iovec.buf); + nread += data.length; + } + buffer.setUint32(nread_ptr, nread, true); + // TODO: support ro_flags_ptr + return 0; + } + wasi.wasiImport.sock_shutdown = (fd, sdflags) => { + if (fd == connfd) { + connfdUsed = false; + } + return 0; + } +} diff --git a/examples/wasi-browser/htdocs/worker.js b/examples/wasi-browser/htdocs/worker.js index ca0820c..6794788 100644 --- a/examples/wasi-browser/htdocs/worker.js +++ b/examples/wasi-browser/htdocs/worker.js @@ -1,28 +1,72 @@ importScripts("https://cdn.jsdelivr.net/npm/xterm-pty@0.9.4/workerTools.js"); importScripts(location.origin + "/browser_wasi_shim/index.js"); importScripts(location.origin + "/browser_wasi_shim/wasi_defs.js"); +importScripts(location.origin + "/worker-util.js"); +importScripts(location.origin + "/wasi-util.js"); onmessage = (msg) => { + if (serveIfInitMsg(msg)) { + return; + } var ttyClient = new TtyClient(msg.data); var args = []; var env = []; var fds = []; - var wasi = new WASI(args, env, fds); - wasiHack(ttyClient, wasi); - fetch(location.origin + "/out.wasm", { credentials: 'same-origin' }).then((resp) => { + var netParam = getNetParam(); + var listenfd = 3; + fetch(getImagename(), { credentials: 'same-origin' }).then((resp) => { resp['arrayBuffer']().then((wasm) => { - WebAssembly.instantiate(wasm, { - "wasi_snapshot_preview1": wasi.wasiImport, - }).then((inst) => { - wasi.start(inst.instance); - }); + if (netParam) { + if (netParam.mode == 'delegate') { + args = ['arg0', '--net=qemu', '--mac', genmac()]; + } else if (netParam.mode == 'browser') { + recvCert().then((cert) => { + var certDir = getCertDir(cert); + fds = [ + undefined, // 0: stdin + undefined, // 1: stdout + undefined, // 2: stderr + certDir, // 3: certificates dir + undefined, // 4: socket listenfd + undefined, // 5: accepted socket fd (multi-connection is unsupported) + // 6...: used by wasi shim + ]; + args = ['arg0', '--net=qemu=listenfd=4', '--mac', genmac()]; + env = [ + "SSL_CERT_FILE=/.wasmenv/proxy.crt", + "https_proxy=http://192.168.127.253:80", + "http_proxy=http://192.168.127.253:80", + "HTTPS_PROXY=http://192.168.127.253:80", + "HTTP_PROXY=http://192.168.127.253:80" + ]; + listenfd = 4; + startWasi(wasm, ttyClient, args, env, fds, listenfd, 5); + }); + return; + } + } + startWasi(wasm, ttyClient, args, env, fds, listenfd, 5); }) - }) + }); }; +function startWasi(wasm, ttyClient, args, env, fds, listenfd, connfd) { + var wasi = new WASI(args, env, fds); + wasiHack(wasi, ttyClient, connfd); + wasiHackSocket(wasi, listenfd, connfd); + WebAssembly.instantiate(wasm, { + "wasi_snapshot_preview1": wasi.wasiImport, + }).then((inst) => { + wasi.start(inst.instance); + }); +} + // wasiHack patches wasi object for integrating it to xterm-pty. -function wasiHack(ttyClient, wasi) { +function wasiHack(wasi, ttyClient, connfd) { + // definition from wasi-libc https://github.com/WebAssembly/wasi-libc/blob/wasi-sdk-19/expected/wasm32-wasi/predefined-macros.txt const ERRNO_INVAL = 28; + const ERRNO_AGAIN= 6; + var _fd_read = wasi.wasiImport.fd_read; wasi.wasiImport.fd_read = (fd, iovs_ptr, iovs_len, nread_ptr) => { if (fd == 0) { var buffer = new DataView(wasi.inst.exports.memory.buffer); @@ -40,9 +84,13 @@ function wasiHack(ttyClient, wasi) { } buffer.setUint32(nread_ptr, nread, true); return 0; + } else { + console.log("fd_read: unknown fd " + fd); + return _fd_read.apply(wasi.wasiImport, [fd, iovs_ptr, iovs_len, nread_ptr]); } return ERRNO_INVAL; } + var _fd_write = wasi.wasiImport.fd_write; wasi.wasiImport.fd_write = (fd, iovs_ptr, iovs_len, nwritten_ptr) => { if ((fd == 1) || (fd == 2)) { var buffer = new DataView(wasi.inst.exports.memory.buffer); @@ -60,6 +108,9 @@ function wasiHack(ttyClient, wasi) { } buffer.setUint32(nwritten_ptr, wtotal, true); return 0; + } else { + console.log("fd_write: unknown fd " + fd); + return _fd_write.apply(wasi.wasiImport, [fd, iovs_ptr, iovs_len, nwritten_ptr]); } return ERRNO_INVAL; } @@ -69,18 +120,26 @@ function wasiHack(ttyClient, wasi) { } let buffer = new DataView(wasi.inst.exports.memory.buffer); let in_ = Subscription.read_bytes_array(buffer, in_ptr, nsubscriptions); - let isReadPoll = false; + let isReadPollStdin = false; + let isReadPollConn = false; let isClockPoll = false; - let pollSub; + let pollSubStdin; + let pollSubConn; let clockSub; let timeout = Number.MAX_VALUE; for (let sub of in_) { if (sub.u.tag.variant == "fd_read") { - if (sub.u.data.fd != 0) { - return ERRNO_INVAL; // only fd=0 is supported as of now (FIXME) + if ((sub.u.data.fd != 0) && (sub.u.data.fd != connfd)) { + console.log("poll_oneoff: unknown fd " + sub.u.data.fd); + return ERRNO_INVAL; // only fd=0 and connfd is supported as of now (FIXME) + } + if (sub.u.data.fd == 0) { + isReadPollStdin = true; + pollSubStdin = sub; + } else { + isReadPollConn = true; + pollSubConn = sub; } - isReadPoll = true; - pollSub = sub; } else if (sub.u.tag.variant == "clock") { if (sub.u.data.timeout < timeout) { timeout = sub.u.data.timeout @@ -88,19 +147,35 @@ function wasiHack(ttyClient, wasi) { clockSub = sub; } } else { + console.log("poll_oneoff: unknown variant " + sub.u.tag.variant); return ERRNO_INVAL; // FIXME } } let events = []; - if (isReadPoll || isClockPoll) { - var readable = ttyClient.onWaitForReadable(timeout / 1000000000); - if (readable && isReadPoll) { + if (isReadPollStdin || isReadPollConn || isClockPoll) { + var readable = false; + if (isReadPollStdin || (isClockPoll && timeout > 0)) { + readable = ttyClient.onWaitForReadable(timeout / 1000000000); + } + if (readable && isReadPollStdin) { let event = new Event(); - event.userdata = pollSub.userdata; + event.userdata = pollSubStdin.userdata; event.error = 0; event.type = new EventType("fd_read"); events.push(event); } + if (isReadPollConn) { + var sockreadable = sockWaitForReadable(); + if (sockreadable == errStatus) { + return ERRNO_INVAL; + } else if (sockreadable == true) { + let event = new Event(); + event.userdata = pollSubConn.userdata; + event.error = 0; + event.type = new EventType("fd_read"); + events.push(event); + } + } if (isClockPoll) { let event = new Event(); event.userdata = clockSub.userdata; @@ -116,129 +191,22 @@ function wasiHack(ttyClient, wasi) { } } -//////////////////////////////////////////////////////////// -// -// event-related classes adopted from the on-going discussion -// towards poll_oneoff support in browser_wasi_sim project. -// Ref: https://github.com/bjorn3/browser_wasi_shim/issues/14#issuecomment-1450351935 -// -//////////////////////////////////////////////////////////// - -class EventType { - /*:: variant: "clock" | "fd_read" | "fd_write"*/ - - constructor(variant/*: "clock" | "fd_read" | "fd_write"*/) { - this.variant = variant; - } - - static from_u8(data/*: number*/)/*: EventType*/ { - switch (data) { - case EVENTTYPE_CLOCK: - return new EventType("clock"); - case EVENTTYPE_FD_READ: - return new EventType("fd_read"); - case EVENTTYPE_FD_WRITE: - return new EventType("fd_write"); - default: - throw "Invalid event type " + String(data); - } - } - - to_u8()/*: number*/ { - switch (this.variant) { - case "clock": - return EVENTTYPE_CLOCK; - case "fd_read": - return EVENTTYPE_FD_READ; - case "fd_write": - return EVENTTYPE_FD_WRITE; - default: - throw "unreachable"; +function getNetParam() { + var vars = location.search.substring(1).split('&'); + for (var i = 0; i < vars.length; i++) { + var kv = vars[i].split('='); + if (decodeURIComponent(kv[0]) == 'net') { + return { + mode: kv[1], + param: kv[2], + }; } } + return null; } -class Event { - /*:: userdata: UserData*/ - /*:: error: number*/ - /*:: type: EventType*/ - /*:: fd_readwrite: EventFdReadWrite | null*/ - - write_bytes(view/*: DataView*/, ptr/*: number*/) { - view.setBigUint64(ptr, this.userdata, true); - view.setUint8(ptr + 8, this.error); - view.setUint8(ptr + 9, 0); - view.setUint8(ptr + 10, this.type.to_u8()); - // if (this.fd_readwrite) { - // this.fd_readwrite.write_bytes(view, ptr + 16); - // } - } - - static write_bytes_array(view/*: DataView*/, ptr/*: number*/, events/*: Array*/) { - for (let i = 0; i < events.length; i++) { - events[i].write_bytes(view, ptr + 32 * i); - } - } -} - -class SubscriptionClock { - /*:: timeout: number*/ - - static read_bytes(view/*: DataView*/, ptr/*: number*/)/*: SubscriptionFdReadWrite*/ { - let self = new SubscriptionClock(); - self.timeout = Number(view.getBigUint64(ptr + 8, true)); - return self; - } -} - -class SubscriptionFdReadWrite { - /*:: fd: number*/ - - static read_bytes(view/*: DataView*/, ptr/*: number*/)/*: SubscriptionFdReadWrite*/ { - let self = new SubscriptionFdReadWrite(); - self.fd = view.getUint32(ptr, true); - return self; - } -} - -class SubscriptionU { - /*:: tag: EventType */ - /*:: data: SubscriptionClock | SubscriptionFdReadWrite */ - - static read_bytes(view/*: DataView*/, ptr/*: number*/)/*: SubscriptionU*/ { - let self = new SubscriptionU(); - self.tag = EventType.from_u8(view.getUint8(ptr)); - switch (self.tag.variant) { - case "clock": - self.data = SubscriptionClock.read_bytes(view, ptr + 8); - break; - case "fd_read": - case "fd_write": - self.data = SubscriptionFdReadWrite.read_bytes(view, ptr + 8); - break; - default: - throw "unreachable"; - } - return self; - } -} - -class Subscription { - /*:: userdata: UserData */ - /*:: u: SubscriptionU */ - - static read_bytes(view/*: DataView*/, ptr/*: number*/)/*: Subscription*/ { - let subscription = new Subscription(); - subscription.userdata = view.getBigUint64(ptr, true); - subscription.u = SubscriptionU.read_bytes(view, ptr + 8); - return subscription; - } - - static read_bytes_array(view/*: DataView*/, ptr/*: number*/, len/*: number*/)/*: Array*/ { - let subscriptions = []; - for (let i = 0; i < len; i++) { - subscriptions.push(Subscription.read_bytes(view, ptr + 48 * i)); - } - return subscriptions; - } +function genmac(){ + return "02:XX:XX:XX:XX:XX".replace(/X/g, function() { + return "0123456789ABCDEF".charAt(Math.floor(Math.random() * 16)) + }); } diff --git a/examples/wasi-browser/htdocs/ws-delegate.js b/examples/wasi-browser/htdocs/ws-delegate.js new file mode 100644 index 0000000..199a873 --- /dev/null +++ b/examples/wasi-browser/htdocs/ws-delegate.js @@ -0,0 +1,105 @@ +function delegate(worker, workerImageName, address) { + var shared = new SharedArrayBuffer(8 + 4096); + var streamCtrl = new Int32Array(shared, 0, 1); + var streamStatus = new Int32Array(shared, 4, 1); + var streamLen = new Int32Array(shared, 8, 1); + var streamData = new Uint8Array(shared, 12); + worker.postMessage({type: "init", buf: shared, imagename: workerImageName}); + + var opts = 'binary'; + var ongoing = false; + var opened = false; + var accepted = false; + var wsconn; + var connbuf = new Uint8Array(0); + return function(msg) { + const req_ = msg.data; + if (typeof req_ == "object" && req_.type) { + switch (req_.type) { + case "accept": + if (opened) { + streamData[0] = 1; // opened + accepted = true; + } else { + streamData[0] = 0; // not opened + if (!ongoing) { + ongoing = true; + wsconn = new WebSocket(address, opts); + wsconn.binaryType = 'arraybuffer'; + wsconn.onmessage = function(event) { + buf2 = new Uint8Array(connbuf.length + event.data.byteLength); + var o = connbuf.length; + buf2.set(connbuf, 0); + buf2.set(new Uint8Array(event.data), o); + connbuf = buf2; + }; + wsconn.onclose = function(event) { + console.log("websocket closed" + event.code + " " + event.reason + " " + event.wasClean); + opened = false; + accepted = false; + ongoing = false; + }; + wsconn.onopen = function(event) { + opened = true; + accepted = false; + ongoing = false; + }; + wsconn.onerror = function(error) { + console.log("websocket error: "+error.data); + opened = false; + accepted = false; + ongoing = false; + }; + } + } + streamStatus[0] = 0; + break; + case "send": + if (!accepted) { + console.log("ERROR: cannot send to unaccepted websocket"); + streamStatus[0] = -1; + break; + } + wsconn.send(req_.buf); + streamStatus[0] = 0; + break; + case "recv": + if (!accepted) { + console.log("ERROR: cannot receive from unaccepted websocket"); + streamStatus[0] = -1; + break; + } + var length = req_.len; + if (length > streamData.length) + length = streamData.length; + if (length > connbuf.length) + length = connbuf.length + var buf = connbuf.slice(0, length); + var remain = connbuf.slice(length, connbuf.length); + connbuf = remain; + streamLen[0] = buf.length; + streamData.set(buf, 0); + streamStatus[0] = 0; + break; + case "recv-is-readable": + if (!accepted) { + console.log("ERROR: cannot poll unaccepted websocket"); + streamStatus[0] = -1; + break; + } + if (connbuf.length > 0) { + streamData[0] = 1; // ready for reading + } else { + streamData[0] = 0; // timeout + } + streamStatus[0] = 0; + break; + default: + console.log("unknown request: " + req_.type) + return; + } + Atomics.store(streamCtrl, 0, 1); + Atomics.notify(streamCtrl, 0); + } + } +} diff --git a/extras/c2w-net-proxy/README.md b/extras/c2w-net-proxy/README.md new file mode 100644 index 0000000..dd13879 --- /dev/null +++ b/extras/c2w-net-proxy/README.md @@ -0,0 +1,19 @@ +# Document is at [`../../examples/networking/fetch/`](../../examples/networking/fetch/). + +## how to build + +- Requirement: Go >= 1.21 + +The following builds the binary at `/tmp/out/c2w-net-proxy.wasm`. + +Makefile is avaliable at the root dir of this repo: + +``` +$ PREFIX=/tmp/out/ make c2w-net-proxy.wasm +``` + +Or you can manually build using go command: + +``` +$ GOOS=wasip1 GOARCH=wasm go build -o /tmp/out/c2w-net-proxy.wasm . +``` diff --git a/extras/c2w-net-proxy/go.mod b/extras/c2w-net-proxy/go.mod new file mode 100644 index 0000000..3850b5a --- /dev/null +++ b/extras/c2w-net-proxy/go.mod @@ -0,0 +1,35 @@ +module c2w-net-proxy + +go 1.21.0 + +require ( + github.com/containers/gvisor-tap-vsock v0.7.0 + github.com/sirupsen/logrus v1.9.3 +) + +require ( + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/apparentlymart/go-cidr v1.1.0 // indirect + github.com/google/btree v1.0.1 // indirect + github.com/google/gopacket v1.1.19 // indirect + github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f // indirect + github.com/miekg/dns v1.1.55 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.12.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + golang.org/x/tools v0.9.3 // indirect + gvisor.dev/gvisor v0.0.0-20230715022000-fd277b20b8db // indirect + inet.af/tcpproxy v0.0.0-20220326234310-be3ee21c9fa0 // indirect +) + +replace github.com/sirupsen/logrus => github.com/sirupsen/logrus v1.9.3-0.20230531171720-7165f5e779a5 + +// Patched for enabling to compile it to wasi +replace github.com/insomniacslk/dhcp => github.com/ktock/insomniacslk-dhcp v0.0.0-20230911142651-b86573a014b1 + +// Patched for enabling to compile it to wasi +replace github.com/u-root/uio => github.com/ktock/u-root-uio v0.0.0-20230911142931-5cf720bc8a29 diff --git a/extras/c2w-net-proxy/go.sum b/extras/c2w-net-proxy/go.sum new file mode 100644 index 0000000..d816afc --- /dev/null +++ b/extras/c2w-net-proxy/go.sum @@ -0,0 +1,130 @@ +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= +github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= +github.com/containers/gvisor-tap-vsock v0.7.0 h1:lL5UpfXhl+tuTwK43CXhKWwfvAiZtde6G5alFeoO+Z8= +github.com/containers/gvisor-tap-vsock v0.7.0/go.mod h1:edQTwl8ar+ACuQOkazpQkgd/ZMF6TJ2Xr3fv+MKUaw8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= +github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= +github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/ktock/insomniacslk-dhcp v0.0.0-20230911142651-b86573a014b1 h1:KD92SLhTiV7HRgw3BicNh7mPT1+OpRpaWvyIQLT9by8= +github.com/ktock/insomniacslk-dhcp v0.0.0-20230911142651-b86573a014b1/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= +github.com/ktock/u-root-uio v0.0.0-20230911142931-5cf720bc8a29 h1:BNd7VYl9yNjxsA4Bt9YKAyKJKIymo1v9f7M2cWhh9iU= +github.com/ktock/u-root-uio v0.0.0-20230911142931-5cf720bc8a29/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= +github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w= +github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= +github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3-0.20230531171720-7165f5e779a5 h1:4gcU4XfYM+65xu4TiRFTE0fVJ854zjKHq0tcMwszt2g= +github.com/sirupsen/logrus v1.9.3-0.20230531171720-7165f5e779a5/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gvisor.dev/gvisor v0.0.0-20230715022000-fd277b20b8db h1:WZSmkyu/hep9YhWIlBZefwGVBrnGE5yW8JPD56YRsXs= +gvisor.dev/gvisor v0.0.0-20230715022000-fd277b20b8db/go.mod h1:sQuqOkxbfJq/GS2uSnqHphtXclHyk/ZrAGhZBxxsq6g= +inet.af/tcpproxy v0.0.0-20220326234310-be3ee21c9fa0 h1:PqdHrvQRVK1zapJkd0qf6+tevvSIcWdfenVqJd3PHWU= +inet.af/tcpproxy v0.0.0-20220326234310-be3ee21c9fa0/go.mod h1:Tojt5kmHpDIR2jMojxzZK2w2ZR7OILODmUo2gaSwjrk= diff --git a/extras/c2w-net-proxy/main.go b/extras/c2w-net-proxy/main.go new file mode 100644 index 0000000..bf0a53e --- /dev/null +++ b/extras/c2w-net-proxy/main.go @@ -0,0 +1,533 @@ +package main + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "encoding/pem" + "errors" + "flag" + "fmt" + "io" + "log" + "math/big" + "net" + "net/http" + "os" + "strings" + "syscall" + "time" + "unsafe" + + gvntypes "github.com/containers/gvisor-tap-vsock/pkg/types" + gvnvirtualnetwork "github.com/containers/gvisor-tap-vsock/pkg/virtualnetwork" + "github.com/sirupsen/logrus" +) + +var proxyKey crypto.PrivateKey +var proxyCert *x509.Certificate + +const proxyIP = "192.168.127.253" + +func handleTunneling(w http.ResponseWriter, r *http.Request) { + serverURL := r.URL + cert, err := generateCert(serverURL.Hostname()) + if err != nil { + log.Printf("failed to generate cert: %v\n", err) + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + w.WriteHeader(http.StatusOK) + hijacker, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Hijacking not supported", http.StatusInternalServerError) + return + } + client_conn, _, err := hijacker.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + go func() { + defer client_conn.Close() + l := newListener(&stringAddr{"tcp", proxyIP + ":80"}, client_conn) + server := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Scheme == "" { + r.URL.Scheme = "https" + } + if r.URL.Host == "" { + r.URL.Host = serverURL.Host + } + handleHTTP(w, r) + }), + // Disable HTTP/2 (FIXME) + // https://pkg.go.dev/net/http#hdr-HTTP_2 + TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), + } + server.TLSConfig = &tls.Config{ + Certificates: []tls.Certificate{*cert}, + } + log.Printf("serving server for %s...\n", serverURL.Host) + server.ServeTLS(l, "", "") + }() +} + +func generateCert(host string) (*tls.Certificate, error) { + ser, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 60)) + if err != nil { + return nil, err + } + cert := &x509.Certificate{ + SerialNumber: ser, + Subject: pkix.Name{ + CommonName: host, + }, + NotAfter: time.Now().Add(365 * 24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + DNSNames: []string{host}, + } + if ip := net.ParseIP(host); ip != nil { + cert.IPAddresses = append(cert.IPAddresses, ip) + } + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + certD, err := x509.CreateCertificate(rand.Reader, cert, proxyCert, key.Public(), proxyKey) + if err != nil { + return nil, err + } + return &tls.Certificate{ + Certificate: [][]byte{certD}, + PrivateKey: key, + }, nil +} + +type listener struct { + ch net.Conn + addr net.Addr + closeCh chan struct{} +} + +func newListener(addr net.Addr, ch net.Conn) *listener { + return &listener{ + ch: ch, + addr: addr, + closeCh: make(chan struct{}), + } +} + +func (l *listener) Accept() (net.Conn, error) { + if l.ch == nil { + <-l.closeCh + return nil, fmt.Errorf("closed") + } + c := l.ch + l.ch = nil + return c, nil +} + +func (l *listener) Close() error { close(l.closeCh); return nil } + +func (l *listener) Addr() net.Addr { return l.addr } + +type stringAddr struct { + network string + address string +} + +func (a *stringAddr) Network() string { return a.network } +func (a *stringAddr) String() string { return a.address } + +type FetchParameters struct { + Method string `json:"method,omitempty"` + Headers map[string]string `json:"headers,omitempty"` +} + +type FetchResponse struct { + Headers map[string]string `json:"headers,omitempty"` + Status int `json:"status,omitempty"` + StatusText string `json:"statusText,omitempty"` +} + +func encodeHeader(h http.Header) map[string]string { + res := make(map[string]string) + for k, vs := range h { + res[k] = strings.Join(vs, ", ") + } + return res +} + +func decodeHeader(h map[string]string) http.Header { + res := make(map[string][]string) + for k, v := range h { + res[k] = []string{v} // TODO: separate fields + } + return http.Header(res) +} + +func httpRequestToFetchParameters(req *http.Request) *FetchParameters { + return &FetchParameters{ + Method: req.Method, + Headers: encodeHeader(req.Header), + } +} + +func fetchResponseToHTTPResponse(resp *FetchResponse) *http.Response { + return &http.Response{ + Status: resp.StatusText, + StatusCode: resp.Status, + Header: decodeHeader(resp.Headers), + } +} + +//go:wasmimport env http_send +func http_send(addressP uint32, addresslen uint32, reqP, reqlen uint32, idP uint32) uint32 + +//go:wasmimport env http_writebody +func http_writebody(id uint32, chunkP, len uint32, nwrittenP uint32, isEOF uint32) uint32 + +//go:wasmimport env http_isreadable +func http_isreadable(id uint32, isOKP uint32) uint32 + +//go:wasmimport env http_recv +func http_recv(id uint32, respP uint32, bufsize uint32, respsizeP uint32, isEOFP uint32) uint32 + +//go:wasmimport env http_readbody +func http_readbody(id uint32, bodyP uint32, bufsize uint32, bodysizeP uint32, isEOFP uint32) uint32 + +func doHttpRoundTrip(req *http.Request) (*http.Response, error) { + defer req.Body.Close() + + address := req.URL.String() + if address == "" { + return nil, fmt.Errorf("specify destination address") + } + + fetchReqD, err := json.Marshal(httpRequestToFetchParameters(req)) + if err != nil { + return nil, err + } + if len(fetchReqD) == 0 { + return nil, fmt.Errorf("empty request") + } + + var id uint32 + res := http_send( + uint32(uintptr(unsafe.Pointer(&[]byte(address)[0]))), + uint32(len(address)), + uint32(uintptr(unsafe.Pointer(&[]byte(fetchReqD)[0]))), + uint32(len(fetchReqD)), + uint32(uintptr(unsafe.Pointer(&id))), + ) + if res != 0 { + return nil, fmt.Errorf("failed to send request") + } + + var isEOF uint32 + var reqBodyD []byte = make([]byte, 4096) + var nwritten uint32 = 0 + idx := 0 + chunksize := 0 + for { + if idx >= chunksize { + // chunk is fully written. full another one. + chunksize, err = req.Body.Read(reqBodyD) + if err != nil && err != io.EOF { + return nil, err + } + if err == io.EOF { + isEOF = 1 + } + idx = 0 + } + res := http_writebody( + id, + uint32(uintptr(unsafe.Pointer(&[]byte(reqBodyD[idx:])[0]))), + uint32(chunksize), + uint32(uintptr(unsafe.Pointer(&nwritten))), + isEOF, + ) + if res != 0 { + return nil, fmt.Errorf("failed to write request body") + } + idx += int(nwritten) + if idx < chunksize { + // not fully written. retry for the remaining. + continue + } + if isEOF == 1 { + break + } + } + + var isOK uint32 = 0 + for { + res := http_isreadable(id, uint32(uintptr(unsafe.Pointer(&isOK)))) + if res != 0 { + return nil, fmt.Errorf("response body is not readable") + } + if isOK == 1 { + break + } + time.Sleep(10 * time.Millisecond) + } + + var respD []byte = make([]byte, 4096) + var respsize uint32 + var respFull []byte + isEOF = 0 + for { + res := http_recv( + id, + uint32(uintptr(unsafe.Pointer(&[]byte(respD)[0]))), + 4096, + uint32(uintptr(unsafe.Pointer(&respsize))), + uint32(uintptr(unsafe.Pointer(&isEOF))), + ) + if res != 0 { + return nil, fmt.Errorf("failed to receive response") + } + respFull = append(respFull, respD[:int(respsize)]...) + if isEOF == 1 { + break + } + } + var resp FetchResponse + if err := json.Unmarshal(respFull, &resp); err != nil { + return nil, err + } + + isEOF = 0 + pr, pw := io.Pipe() + go func() { + var body []byte = make([]byte, 4096) + var bodysize uint32 + for { + res := http_readbody( + id, + uint32(uintptr(unsafe.Pointer(&[]byte(body)[0]))), + 4096, + uint32(uintptr(unsafe.Pointer(&bodysize))), + uint32(uintptr(unsafe.Pointer(&isEOF))), + ) + if res != 0 { + pw.CloseWithError(fmt.Errorf("failed to read response body")) + return + } + if bodysize > 0 { + if _, err := pw.Write(body[:int(bodysize)]); err != nil { + pw.CloseWithError(err) + return + } + } + if isEOF == 1 { + break + } + } + pw.Close() + }() + r := fetchResponseToHTTPResponse(&resp) + r.Body = pr + return r, nil +} + +func handleHTTP(w http.ResponseWriter, req *http.Request) { + resp, err := doHttpRoundTrip(req) + if err != nil { + log.Printf("failed to proxy request: %v\n", err) + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + defer resp.Body.Close() + copyHeader(w.Header(), resp.Header) + w.WriteHeader(resp.StatusCode) + io.Copy(w, resp.Body) +} + +func copyHeader(dst, src http.Header) { + for k, vv := range src { + for _, v := range vv { + dst.Add(k, v) + } + } +} + +const ( + gatewayIP = "192.168.127.1" + vmIP = "192.168.127.3" +) + +func main() { + var listenFd int + flag.IntVar(&listenFd, "net-listenfd", 0, "fd to listen for the connection") + var certFd int + flag.IntVar(&certFd, "certfd", 0, "fd to output cert") + var certFile string + flag.StringVar(&certFile, "certfile", "", "file to output cert") + var debug bool + flag.BoolVar(&debug, "debug", false, "debug log") + flag.Parse() + + if debug { + log.SetOutput(os.Stdout) + logrus.SetLevel(logrus.DebugLevel) + } else { + log.SetOutput(io.Discard) + logrus.SetLevel(logrus.FatalLevel) + } + + ser, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 60)) + if err != nil { + panic(err) + } + cert := &x509.Certificate{ + SerialNumber: ser, + Subject: pkix.Name{ + CommonName: proxyIP, + OrganizationalUnit: []string{"proxy"}, + Organization: []string{"proxy"}, + Country: []string{"US"}, + }, + NotAfter: time.Now().Add(365 * 24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + DNSNames: []string{proxyIP}, + IPAddresses: []net.IP{net.ParseIP(proxyIP)}, + IsCA: true, + BasicConstraintsValid: true, + } + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + panic(err) + } + certD, err := x509.CreateCertificate(rand.Reader, cert, cert, key.Public(), key) + if err != nil { + panic(err) + } + var f io.WriteCloser + if certFd != 0 { + f = os.NewFile(uintptr(certFd), "") + } else if certFile != "" { + var err error + f, err = os.OpenFile(certFile, os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + panic(err) + } + } else { + panic("specify cert destination") + } + if err := pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: certD}); err != nil { + panic(err) + } + if err := f.Close(); err != nil { + panic(err) + } + tlsCert := &tls.Certificate{ + Certificate: [][]byte{certD}, + PrivateKey: key, + } + proxyCert, err = x509.ParseCertificate(tlsCert.Certificate[0]) + if err != nil { + panic(err) + } + proxyKey = key + + config := &gvntypes.Configuration{ + Debug: debug, + MTU: 1500, + Subnet: "192.168.127.0/24", + GatewayIP: gatewayIP, + GatewayMacAddress: "5a:94:ef:e4:0c:dd", + GatewayVirtualIPs: []string{proxyIP}, + Protocol: gvntypes.QemuProtocol, + } + vn, err := gvnvirtualnetwork.New(config) + if err != nil { + panic(err) + } + + server := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodConnect { + handleTunneling(w, r) + } else { + handleHTTP(w, r) + } + }), + // Disable HTTP/2 (FIXME) + // https://pkg.go.dev/net/http#hdr-HTTP_2 + TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), + } + + go func() { + s := server + s.Addr = "0.0.0.0:80" + l, err := vn.Listen("tcp", proxyIP+":80") + if err != nil { + panic(err) + } + log.Println("serving proxy with http") + log.Fatal(server.Serve(l)) + }() + go func() { + s := server + s.Addr = "0.0.0.0:443" + l, err := vn.Listen("tcp", proxyIP+":443") + if err != nil { + panic(err) + } + s.TLSConfig = &tls.Config{ + Certificates: make([]tls.Certificate, 1), + } + s.TLSConfig.Certificates[0] = *tlsCert + log.Println("serving proxy with https") + log.Fatal(server.ServeTLS(l, "", "")) + }() + ql, err := findListener(listenFd) + if err != nil { + panic(err) + } + if ql == nil { + panic("socket fd not found") + } + qconn, err := ql.Accept() + if err != nil { + panic(err) + } + if err := vn.AcceptQemu(context.TODO(), qconn); err != nil { + panic(err) + } +} + +func findListener(listenFd int) (net.Listener, error) { + if listenFd == 0 { + for preopenFd := 3; ; preopenFd++ { + var stat syscall.Stat_t + if err := syscall.Fstat(preopenFd, &stat); err != nil { + var se syscall.Errno + if errors.As(err, &se) && se == syscall.EBADF { + err = nil + } + log.Printf("findListner failed (fd=%d): %v\n", preopenFd, err) + return nil, err + } else if stat.Filetype == syscall.FILETYPE_SOCKET_STREAM { + listenFd = preopenFd + break + } + } + } + syscall.SetNonblock(listenFd, true) + f := os.NewFile(uintptr(listenFd), "") + defer f.Close() + log.Printf("using socket at fd=%d\n", listenFd) + return net.FileListener(f) +} diff --git a/go.mod b/go.mod index c5eacfb..7252594 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,19 @@ go 1.19 require ( github.com/containerd/containerd v1.7.5 + github.com/containers/gvisor-tap-vsock v0.7.0 github.com/opencontainers/image-spec v1.1.0-rc4 github.com/opencontainers/runc v1.1.9 github.com/opencontainers/runtime-spec v1.1.0 github.com/urfave/cli v1.22.14 + golang.org/x/net v0.12.0 gotest.tools/v3 v3.5.0 ) require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/hcsshim v0.10.0-rc.8 // indirect + github.com/apparentlymart/go-cidr v1.1.0 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/containerd/continuity v0.4.2 // indirect github.com/containerd/ttrpc v1.2.2 // indirect @@ -22,20 +25,29 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/btree v1.0.1 // indirect github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gopacket v1.1.19 // indirect + github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f // indirect github.com/klauspost/compress v1.16.0 // indirect + github.com/miekg/dns v1.1.55 // indirect github.com/moby/sys/mountinfo v0.6.2 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/mod v0.9.0 // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/mod v0.10.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.11.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + golang.org/x/tools v0.9.3 // indirect google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect google.golang.org/grpc v1.53.0 // indirect google.golang.org/protobuf v1.29.1 // indirect + gvisor.dev/gvisor v0.0.0-20230715022000-fd277b20b8db // indirect + inet.af/tcpproxy v0.0.0-20220326234310-be3ee21c9fa0 // indirect ) diff --git a/go.sum b/go.sum index a55468e..9c73eb7 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,9 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= github.com/Microsoft/hcsshim v0.10.0-rc.8/go.mod h1:OEthFdQv/AD2RAdzR6Mm1N1KPCztGKDurW1Z8b8VGMM= +github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= +github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -18,6 +21,8 @@ github.com/containerd/ttrpc v1.2.2 h1:9vqZr0pxwOF5koz6N0N3kJ0zDHokrcPxIR/ZR2YFtO github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak= github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= +github.com/containers/gvisor-tap-vsock v0.7.0 h1:lL5UpfXhl+tuTwK43CXhKWwfvAiZtde6G5alFeoO+Z8= +github.com/containers/gvisor-tap-vsock v0.7.0/go.mod h1:edQTwl8ar+ACuQOkazpQkgd/ZMF6TJ2Xr3fv+MKUaw8= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -27,6 +32,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -47,25 +54,53 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= +github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f h1:l1QCwn715k8nYkj4Ql50rzEog3WnMdrd4YYMMwemxEo= +github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= +github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= +github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= +github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w= +github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= +github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= @@ -84,18 +119,23 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA= +github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -105,24 +145,35 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -134,28 +185,43 @@ golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -191,11 +257,16 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gvisor.dev/gvisor v0.0.0-20230715022000-fd277b20b8db h1:WZSmkyu/hep9YhWIlBZefwGVBrnGE5yW8JPD56YRsXs= +gvisor.dev/gvisor v0.0.0-20230715022000-fd277b20b8db/go.mod h1:sQuqOkxbfJq/GS2uSnqHphtXclHyk/ZrAGhZBxxsq6g= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +inet.af/tcpproxy v0.0.0-20220326234310-be3ee21c9fa0 h1:PqdHrvQRVK1zapJkd0qf6+tevvSIcWdfenVqJd3PHWU= +inet.af/tcpproxy v0.0.0-20220326234310-be3ee21c9fa0/go.mod h1:Tojt5kmHpDIR2jMojxzZK2w2ZR7OILODmUo2gaSwjrk= diff --git a/patches/bochs/Bochs/bochs/iodev/devices.cc b/patches/bochs/Bochs/bochs/iodev/devices.cc index 4cc9782..f82480f 100644 --- a/patches/bochs/Bochs/bochs/iodev/devices.cc +++ b/patches/bochs/Bochs/bochs/iodev/devices.cc @@ -363,6 +363,7 @@ void bx_devices_c::init(BX_MEM_C *newmem) PLUG_load_plugin(mapdirVirtio9p, PLUGTYPE_STANDARD); PLUG_load_plugin(packVirtio9p, PLUGTYPE_STANDARD); PLUG_load_plugin(stdioVirtioConsole, PLUGTYPE_STANDARD); + PLUG_load_plugin(qemuVirtioNet, PLUGTYPE_STANDARD); #endif bx_init_plugins(); diff --git a/patches/bochs/Bochs/bochs/main.cc b/patches/bochs/Bochs/bochs/main.cc index 1869575..fd58442 100644 --- a/patches/bochs/Bochs/bochs/main.cc +++ b/patches/bochs/Bochs/bochs/main.cc @@ -411,10 +411,52 @@ int write_env(FSVirtFile *f, int pos1, const char *env) return pos - pos1; } +int write_net(FSVirtFile *f, int pos1, const char *mac) +{ + int p, pos = pos1; + + p = write_info(f, pos, 3, "n: "); + if (p < 0) { + return -1; + } + pos += p; + for (int j = 0; j < strlen(mac); j++) { + if (putchar_info(f, pos++, mac[j]) != 1) { + return -1; + } + } + if (putchar_info(f, pos++, '\n') != 1) { + return -1; + } + return pos - pos1; +} + +int write_time(FSVirtFile *f, int pos1, const char *timestr) +{ + int p, pos = pos1; + + p = write_info(f, pos, 3, "t: "); + if (p < 0) { + return -1; + } + pos += p; + for (int j = 0; j < strlen(timestr); j++) { + if (putchar_info(f, pos++, timestr[j]) != 1) { + return -1; + } + } + if (putchar_info(f, pos++, '\n') != 1) { + return -1; + } + return pos - pos1; +} + static struct option options[] = { { "help", no_argument, NULL, 'h' }, { "no-stdin", no_argument }, { "entrypoint", required_argument }, + { "net", required_argument }, + { "mac", required_argument }, { NULL }, }; @@ -426,20 +468,24 @@ void print_usage(void) "OPTIONS:\n" " -entrypoint : entrypoint command. (default: entrypoint specified in the image config)\n" " -no-stdin : disable stdin. (default: false)\n" + " -net : enable networking with the specified mode (default: disabled. supported mode: \"qemu\")\n" + " -mac : use a custom mac address for the VM\n" "\n" "This tool is based on Bochs emulator.\n" ); exit(0); } -int init_wasi_info(int argc, char **argv, FSVirtFile *info) +int CDECL init_func(void); + +int init_vm(int argc, char **argv, FSVirtFile *info) { info->contents = (char *)calloc(1024, sizeof(char)); info->len = 0; info->lim = 1024; /* const char *cmdline, *build_preload_file; */ - char *entrypoint = NULL; + char *entrypoint = NULL, *net = NULL, *mac = NULL; bool enable_stdin = true; int pos, c, option_index, i; for(;;) { @@ -458,6 +504,12 @@ int init_wasi_info(int argc, char **argv, FSVirtFile *info) case 2: /* entrypoint */ entrypoint = optarg; break; + case 3: /* net */ + net = optarg; + break; + case 4: /* mac */ + mac = optarg; + break; default: fprintf(stderr, "unknown option index: %d\n", option_index); exit(1); @@ -471,6 +523,14 @@ int init_wasi_info(int argc, char **argv, FSVirtFile *info) } } + if (!vm_init_done) { + int ret = init_func(); + if (ret != CI_INIT_DONE) { + printf("initialization failed\n"); + return -1; + } + } + if (!enable_stdin) { SIM->get_param_bool(BXPN_WASM_NOSTDIN)->set(1); } @@ -484,7 +544,7 @@ int init_wasi_info(int argc, char **argv, FSVirtFile *info) } pos += p; } - + if (optind < argc) { int p = write_args(info, argc, argv, optind, pos); if (p < 0) { @@ -507,17 +567,41 @@ int init_wasi_info(int argc, char **argv, FSVirtFile *info) } #endif + if (net != NULL) { + if (!strncmp(net, "qemu", 4)) { + if (start_qemu_net(net) != 0) { + fprintf(stderr, "failed to wait qemu net"); + exit(1); + } + } + int p = write_net(info, pos, mac); + if (p < 0) { + printf("failed to prepare net info\n"); + exit(1); + } + pos += p; + } + + char buf[32]; + snprintf(buf, sizeof(buf), "%d", (unsigned)time(NULL)); + int ps = write_time(info, pos, buf); + if (ps < 0) { + printf("failed to prepare time info\n"); + exit(1); + } + pos += ps; + info->len = pos; #ifdef WASI info->len += write_preopen_info(info, pos); #endif + return 0; } int bxmain(void) { if (vm_init_done) { - init_wasi_info(bx_startup_flags.argc, bx_startup_flags.argv, get_vm_info()); bx_param_enum_c *ci_param = SIM->get_param_enum(BXPN_SEL_CONFIG_INTERFACE); const char *ci_name = ci_param->get_selected(); int status = SIM->configuration_interface(ci_name, CI_START); @@ -801,15 +885,10 @@ int CDECL main(int argc, char *argv[]) return -1; } #endif - if (!vm_init_done) { - int ret = init_func(); - if (ret != CI_INIT_DONE) { - printf("initialization failed\n"); - return -1; - } + if (init_vm(argc, argv, get_vm_info()) < 0) { + fprintf(stderr, "failed to init vm"); + return -1; } - bx_startup_flags.argc = argc; - bx_startup_flags.argv = argv; return start_vm(); } #endif diff --git a/patches/bochs/Bochs/bochs/plugin.cc b/patches/bochs/Bochs/bochs/plugin.cc index 27d0fb4..1a07a61 100644 --- a/patches/bochs/Bochs/bochs/plugin.cc +++ b/patches/bochs/Bochs/bochs/plugin.cc @@ -1065,6 +1065,7 @@ plugin_t bx_builtin_plugins[] = { BUILTIN_OPTPCI_PLUGIN_ENTRY(mapdirVirtio9p), BUILTIN_OPTPCI_PLUGIN_ENTRY(packVirtio9p), BUILTIN_OPTPCI_PLUGIN_ENTRY(stdioVirtioConsole), + BUILTIN_OPTPCI_PLUGIN_ENTRY(qemuVirtioNet), #if BX_SUPPORT_SOUNDLOW BUILTIN_SND_PLUGIN_ENTRY(dummy), BUILTIN_SND_PLUGIN_ENTRY(file), diff --git a/patches/bochs/Bochs/bochs/plugin.h b/patches/bochs/Bochs/bochs/plugin.h index 0ce9926..f497fa6 100644 --- a/patches/bochs/Bochs/bochs/plugin.h +++ b/patches/bochs/Bochs/bochs/plugin.h @@ -79,6 +79,7 @@ extern "C" { #define BX_PLUGIN_MAPDIR_VIRTIO_9P "mapdirVirtio9p" #define BX_PLUGIN_PACK_VIRTIO_9P "packVirtio9p" #define BX_PLUGIN_STDIO_VIRTIO_CONSOLE "stdioVirtioConsole" +#define BX_PLUGIN_QEMU_VIRTIO_NET "qemuVirtioNet" #define BX_REGISTER_DEVICE_DEVMODEL(a,b,c,d) pluginRegisterDeviceDevmodel(a,b,c,d) #define BX_UNREGISTER_DEVICE_DEVMODEL(a,b) pluginUnregisterDeviceDevmodel(a,b) @@ -495,6 +496,7 @@ PLUGIN_ENTRY_FOR_IMG_MODULE(vvfat); PLUGIN_ENTRY_FOR_MODULE(mapdirVirtio9p); PLUGIN_ENTRY_FOR_MODULE(packVirtio9p); PLUGIN_ENTRY_FOR_MODULE(stdioVirtioConsole); +PLUGIN_ENTRY_FOR_MODULE(qemuVirtioNet); #endif #ifdef __cplusplus diff --git a/patches/bochs/Bochs/bochs/wasm.cc b/patches/bochs/Bochs/bochs/wasm.cc index 08b3ecd..968308d 100644 --- a/patches/bochs/Bochs/bochs/wasm.cc +++ b/patches/bochs/Bochs/bochs/wasm.cc @@ -57,15 +57,31 @@ #include "iodev/pci.h" #include "wasm.h" +#ifdef EMSCRIPTEN +#include +#endif + +#include +#include +// not defined in wasi-libc +#ifdef WASI +#define PF_INET 1 +#define PF_INET6 2 +#endif + +#define LOG_THIS genlog-> + ///////////////////////////////////////////////////////////////////////// // Register mapdir device via viritio-9p ///////////////////////////////////////////////////////////////////////// bx_virtio_9p_ctrl_c *wasi0; bx_virtio_9p_ctrl_c *wasi1; -FSVirtFile *info; +FSVirtFile *info = NULL; FSVirtFile *get_vm_info() { + if (info == NULL) + info = (FSVirtFile *)malloc(sizeof(FSVirtFile)); return info; } @@ -91,8 +107,7 @@ PLUGIN_ENTRY_FOR_MODULE(mapdirVirtio9p) PLUGIN_ENTRY_FOR_MODULE(packVirtio9p) { if (mode == PLUGIN_INIT) { - info = (FSVirtFile *)malloc(sizeof(FSVirtFile)); - FSDevice *wasi1fs = fs_disk_init_with_info("pack", "info", info); + FSDevice *wasi1fs = fs_disk_init_with_info("pack", "info", get_vm_info()); wasi1 = new bx_virtio_9p_ctrl_c(BX_PLUGIN_PACK_VIRTIO_9P, "wasi1", wasi1fs); BX_REGISTER_DEVICE_DEVMODEL(plugin, type, wasi1, BX_PLUGIN_PACK_VIRTIO_9P); } else if (mode == PLUGIN_FINI) { @@ -246,6 +261,27 @@ PLUGIN_ENTRY_FOR_MODULE(stdioVirtioConsole) return 0; // Success } +bx_virtio_net_ctrl_c *qemu0; +EthernetDevice *qemu_net; +static EthernetDevice *qemu_net_init(); + +PLUGIN_ENTRY_FOR_MODULE(qemuVirtioNet) +{ + if (mode == PLUGIN_INIT) { + qemu_net = qemu_net_init(); + qemu0 = new bx_virtio_net_ctrl_c(BX_PLUGIN_QEMU_VIRTIO_NET, qemu_net); + BX_REGISTER_DEVICE_DEVMODEL(plugin, type, qemu0, BX_PLUGIN_QEMU_VIRTIO_NET); + } else if (mode == PLUGIN_FINI) { + delete qemu0; + } else if (mode == PLUGIN_PROBE) { + return (int)PLUGTYPE_STANDARD; + } else if (mode == PLUGIN_FLAGS) { + return PLUGFLAG_PCI; + } + return 0; // Success +} + + ///////////////////////////////////////////////////////////////////////// // WASI preopens ///////////////////////////////////////////////////////////////////////// @@ -400,6 +436,14 @@ void *mallocz(size_t size) return ptr; } +static inline int max_int(int a, int b) +{ + if (a > b) + return a; + else + return b; +} + static inline int min_int(int a, int b) { if (a < b) @@ -1268,7 +1312,7 @@ bx_virtio_ctrl_c::bx_virtio_ctrl_c() bx_virtio_ctrl_c::~bx_virtio_ctrl_c() { SIM->get_bochs_root()->remove("virtio"); - // BX_DEBUG(("Exit")); + BX_DEBUG(("Exit")); } int bx_virtio_ctrl_c::pci_add_capability(Bit8u *buf, int size) @@ -1822,7 +1866,7 @@ bx_virtio_9p_ctrl_c::bx_virtio_9p_ctrl_c(char *plugin_name, char *mount_tag, FSD bx_virtio_9p_ctrl_c::~bx_virtio_9p_ctrl_c() { SIM->get_bochs_root()->remove("virtio_9p"); - // BX_DEBUG(("Exit")); + BX_DEBUG(("Exit")); } void bx_virtio_9p_ctrl_c::init() @@ -2727,7 +2771,7 @@ bx_virtio_console_ctrl_c::bx_virtio_console_ctrl_c(char *plugin_name, CharacterD bx_virtio_console_ctrl_c::~bx_virtio_console_ctrl_c() { SIM->get_bochs_root()->remove("virtio_console"); - // BX_DEBUG(("Exit")); + BX_DEBUG(("Exit")); } void bx_virtio_console_ctrl_c::init() @@ -2886,4 +2930,399 @@ void bx_virtio_console_ctrl_c::rx_timer_handler(void *this_ptr) } } +///////////////////////////////////////////////////////////////////////// +// Virtio-net +///////////////////////////////////////////////////////////////////////// +bx_virtio_net_ctrl_c::bx_virtio_net_ctrl_c(char *plugin_name, EthernetDevice *es) +{ + put("VIRTIO NET"); + BX_VIRTIO_NET_THIS plugin_name = plugin_name; + BX_VIRTIO_NET_THIS es = es; // TODO +} + +bx_virtio_net_ctrl_c::~bx_virtio_net_ctrl_c() +{ + SIM->get_bochs_root()->remove("virtio_net"); + BX_DEBUG(("Exit")); +} + +bool bx_virtio_net_ctrl_c::virtio_net_can_write_packet(EthernetDevice *es) +{ + QueueState *qs = &BX_VIRTIO_THIS s.queue[0]; + uint16_t avail_idx; + + if (!qs->ready) + return false; + avail_idx = virtio_read16(qs->avail_addr + 2); + return qs->last_avail_idx != avail_idx; +} + +void bx_virtio_net_ctrl_c::virtio_net_write_packet(EthernetDevice *es, const uint8_t *buf, int buf_len) +{ + VIRTIONetDevice *s1 = &BX_VIRTIO_NET_THIS net; + int queue_idx = 0; + QueueState *qs = &BX_VIRTIO_THIS s.queue[0]; + int desc_idx; + VIRTIONetHeader h; + int len, read_size, write_size; + uint16_t avail_idx; + + if (!qs->ready) + return; + avail_idx = virtio_read16(qs->avail_addr + 2); + if (qs->last_avail_idx == avail_idx) + return; + desc_idx = virtio_read16(qs->avail_addr + 4 + + (qs->last_avail_idx & (qs->num - 1)) * 2); + if (get_desc_rw_size(&read_size, &write_size, queue_idx, desc_idx)) + return; + len = s1->header_size + buf_len; + if (len > write_size) + return; + memset(&h, 0, s1->header_size); + memcpy_to_queue(queue_idx, desc_idx, 0, &h, s1->header_size); + memcpy_to_queue(queue_idx, desc_idx, s1->header_size, (void*)buf, buf_len); + virtio_consume_desc(queue_idx, desc_idx, len); + qs->last_avail_idx++; +} + +void bx_virtio_net_ctrl_c::init() +{ + bx_virtio_ctrl_c::init(BX_VIRTIO_NET_THIS plugin_name, 0x1000, 0x0200, 0x1, 1 << 5, "Virtio-net"); // initialize as virtio-net + + BX_VIRTIO_NET_THIS config_space_size = 6 + 2; + Bit8u *cfg; + cfg = BX_VIRTIO_NET_THIS config_space; + memcpy(cfg, BX_VIRTIO_NET_THIS es->mac_addr, 6); + /* status */ + cfg[6] = 0; + cfg[7] = 0; + QueueState *qs = &BX_VIRTIO_NET_THIS s.queue[0]; + qs->manual_recv = true; + BX_VIRTIO_NET_THIS net.header_size = sizeof(VIRTIONetHeader); + BX_VIRTIO_NET_THIS es->virtio_device = this; + BX_VIRTIO_NET_THIS timer_id = DEV_register_timer(this, rx_timer_handler, 0, false, false, "virtio-net.rx");; + bx_pc_system.activate_timer(BX_VIRTIO_NET_THIS timer_id, 100, false); /* not continuous */ +} + +void bx_virtio_net_ctrl_c::rx_timer_handler(void *this_ptr) +{ + bx_virtio_net_ctrl_c *class_ptr = (bx_virtio_net_ctrl_c *)this_ptr; + EthernetDevice *es = class_ptr->es; + int n_fd_max = -1, delay = 0, n_ret = 0; + fd_set n_rfds, n_wfds, n_efds; + struct timeval tv; + + FD_ZERO(&n_rfds); + FD_ZERO(&n_wfds); + FD_ZERO(&n_efds); + es->select_fill(es, &n_fd_max, &n_rfds, &n_wfds, &n_efds, &delay); + if (n_fd_max >= 0) { + tv.tv_sec = 0; + tv.tv_usec = 0; + n_ret = select(n_fd_max + 1, &n_rfds, NULL, NULL, &tv); + es->select_poll(es, &n_rfds, &n_wfds, &n_efds, n_ret); + } + int watch_res = es->watch(es); + int duration = 100; + if (watch_res < 0) { + duration = 1000000; + } else if (n_ret <= 0) { + duration = 100000; + } + bx_pc_system.activate_timer(class_ptr->timer_id, duration, false); /* not continuous */ +} + +int bx_virtio_net_ctrl_c::device_recv(int queue_idx, int desc_idx, int read_size, int write_size) +{ + VIRTIONetDevice *s1 = &BX_VIRTIO_NET_THIS net; + EthernetDevice *es = BX_VIRTIO_NET_THIS es; + VIRTIONetHeader h; + uint8_t *buf; + int len; + + if (queue_idx == 1) { + /* send to network */ + if (memcpy_from_queue(&h, queue_idx, desc_idx, 0, s1->header_size) < 0) + return 0; + len = read_size - s1->header_size; + buf = (uint8_t *)malloc(len); + memcpy_from_queue(buf, queue_idx, desc_idx, s1->header_size, len); + es->write_packet(es, buf, len); + free(buf); + virtio_consume_desc(queue_idx, desc_idx, 0); + } + return 0; +} + +typedef struct { + int fd; + bool select_filled; + char *raw_flag; + + int tmpfd; + bool enabled; + int retrynum; + + uint32_t sizebuf; + int sizeoff; + int pktsize; + int pktoff; + uint8_t *pktbuf; +} QemuSocketState; + +static void qemu_write_packet(EthernetDevice *net, + const uint8_t *buf, int len) +{ + QemuSocketState *s = (QemuSocketState *)net->opaque; + uint32_t size = htonl(len); + int ret; + + if (s->fd < 0) { + return; + } + + ret = send(s->fd, &size, 4, 0); // TODO: check error + if (ret < 0) { + close(s->fd); + s->fd = -1; // invalid fd. hopefully the watch loop will recover this. + return; + } + ret = send(s->fd, buf, len, 0); // TODO: check error + if (ret < 0) { + close(s->fd); + s->fd = -1; // invalid fd. hopefully the watch loop will recover this. + return; + } +} + +static int try_get_fd(QemuSocketState *s); + +static int qemu_watch1(EthernetDevice *net) +{ + QemuSocketState *s = (QemuSocketState *)net->opaque; + + if (!s->enabled) + return -1; + + if ((s->fd >= 0) || (try_get_fd((QemuSocketState *)net->opaque) >= 0)) + return 0; + + return -1; +} + +static void qemu_select_fill1(EthernetDevice *net, int *pfd_max, + fd_set *rfds, fd_set *wfds, fd_set *efds, + int *pdelay) +{ + QemuSocketState *s = (QemuSocketState *)net->opaque; + int fd = s->fd; + + if (fd < 0) { + return; + } + + s->select_filled = net->virtio_device->virtio_net_can_write_packet(net); + if (s->select_filled) { + FD_SET(fd, rfds); + *pfd_max = max_int(*pfd_max, fd); + } +} + +static void qemu_select_poll1(EthernetDevice *net, + fd_set *rfds, fd_set *wfds, fd_set *efds, + int select_ret) +{ + QemuSocketState *s = (QemuSocketState *)net->opaque; + int fd = s->fd; + uint32_t size = 0; + int ret; + + if (fd < 0) { + return; + } + + if (select_ret <= 0) { + if (select_ret < 0) { + fflush(stdout); + close(s->fd); + s->fd = -1; // invalid fd. hopefully the watch loop will recover this. + } + return; + } + + if (s->select_filled && FD_ISSET(fd, rfds)) { + if (s->pktsize <= 0) { + while (s->sizeoff < 4) { + ret = recv(fd, &s->sizebuf + s->sizeoff, 4 - s->sizeoff, 0); + if (ret <= 0) { + if (errno == EAGAIN) + return; // try later + close(s->fd); + s->fd = -1; // invalid fd. hopefully the watch loop will recover this. + return; + } + s->sizeoff += ret; + } + s->pktsize = ntohl(s->sizebuf); + s->sizeoff = 0; + s->sizebuf = 0; + } + + if (s->pktsize > 0) { + size = s->pktsize; + if (s->pktbuf == NULL) { + s->pktoff = 0; + s->pktbuf = (uint8_t *)mallocz(size); // TODO: make limit + } + while (s->pktoff < size) { + ret = recv(fd, s->pktbuf + s->pktoff, size - s->pktoff, 0); + if (ret <= 0) { + if (errno == EAGAIN) + return; // try later + close(s->fd); + s->fd = -1; // invalid fd. hopefully the watch loop will recover this. + return; + } + s->pktoff += ret; + } + net->virtio_device->virtio_net_write_packet(net, s->pktbuf, size); + free(s->pktbuf); + s->pktbuf = NULL; + s->pktsize = 0; + s->pktoff = 0; + } + } +} + +static EthernetDevice *qemu_net_init() +{ + EthernetDevice *net; + QemuSocketState *s; + + net = (EthernetDevice *)mallocz(sizeof(*net)); + net->mac_addr[0] = 0x02; + net->mac_addr[1] = 0x00; + net->mac_addr[2] = 0x00; + net->mac_addr[3] = 0x00; + net->mac_addr[4] = 0x00; + net->mac_addr[5] = 0x01; + net->opaque = NULL; + s = (QemuSocketState *)mallocz(sizeof(*s)); + s->fd = -1; + s->tmpfd = -1; + s->enabled = false; + net->opaque = s; + net->write_packet = qemu_write_packet; + net->select_fill = qemu_select_fill1; + net->select_poll = qemu_select_poll1; + net->watch = qemu_watch1; + + return net; +} + +static void reset_qemu_socket_state(QemuSocketState *s) +{ + s->sizebuf = 0; + s->sizeoff = 0; + s->pktsize = 0; + s->pktoff = 0; + if (s->pktbuf != NULL) { + free(s->pktbuf); + } + s->pktbuf = NULL; +} + +#ifdef EMSCRIPTEN +static int try_get_fd(QemuSocketState *s) +{ + int sock= s->fd; + fd_set wfds; + struct sockaddr_in qemuAddr; + + if (s->tmpfd < 0) { + if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + BX_INFO(("failed to prepare socket: %s", strerror(errno))); + return -1; + } + // connect to the proxy + memset(&qemuAddr, 0, sizeof(qemuAddr)); + qemuAddr.sin_family = AF_INET; + int cret = connect(sock, (struct sockaddr *) &qemuAddr, sizeof(qemuAddr)); + bool connected = false; + if ((cret == 0) || (errno == EISCONN)) { + BX_INFO(("socket connected")); + s->fd = sock; + s->tmpfd = -1; + reset_qemu_socket_state(s); + return 0; + } else if (errno != EINPROGRESS) { + BX_INFO(("failed to connect: %s", strerror(errno))); + s->fd = -1; + s->tmpfd = -1; + return -1; + } + s->retrynum = 0; + s->tmpfd = sock; + } + + BX_INFO(("waiting for connection...")); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + FD_ZERO(&wfds); + FD_SET(s->tmpfd, &wfds); + int sret = select(s->tmpfd + 1, NULL, &wfds, NULL, &tv); + if (sret > 0) { + s->fd = s->tmpfd; + s->tmpfd = -1; + reset_qemu_socket_state(s); + return 0; + } else if (sret < 0) { + s->fd = -1; + s->tmpfd = -1; + } // else, still wait for the connection. + BX_INFO(("select result: %d", sret)); + + s->retrynum++; + if (s->retrynum > 5) { // TODO: make max retry number configurable + close(s->tmpfd); + s->tmpfd = -1; // too many errors. retry connection. + } + + return -1; +} +#elif defined(WASI) +static int try_get_fd(QemuSocketState *s) +{ + int sock = 3; + if ((s->raw_flag != NULL) && (!strncmp(s->raw_flag, "qemu=", 5))) { + // TODO: allow specifying more options + if (!strncmp(s->raw_flag + 5, "listenfd=", 9)) { + sock = atoi(s->raw_flag + 5 + 9); + } + } + int sock_a = 0; + // wait for connection + BX_INFO(("accept trying...(sockfd=%d)", sock)); + sock_a = accept4(sock, NULL, NULL, SOCK_NONBLOCK); + if (sock_a > 0) { + BX_INFO(("accepted fd=%d", sock_a)); + s->fd = sock_a; + reset_qemu_socket_state(s); + return 0; + } + BX_INFO(("failed to accept socket: %s", strerror(errno))); + return -1; +} +#endif + +int start_qemu_net(char *flag) +{ + if (qemu_net != NULL) { + ((QemuSocketState *)qemu_net->opaque)->enabled = true; + ((QemuSocketState *)qemu_net->opaque)->raw_flag = flag; + } + return 0; +} #endif /* BX_SUPPORT_PCI */ diff --git a/patches/bochs/Bochs/bochs/wasm.h b/patches/bochs/Bochs/bochs/wasm.h index 5b9fd7a..e2dbe35 100644 --- a/patches/bochs/Bochs/bochs/wasm.h +++ b/patches/bochs/Bochs/bochs/wasm.h @@ -371,10 +371,8 @@ class bx_virtio_ctrl_c : public bx_pci_device_c { Bit32u device_features; Bit32u next_cap_offset; } s; - int get_desc_rw_size(int *pread_size, int *pwrite_size, int queue_idx, int desc_idx); - private: bool mem_read(bx_phy_address addr, unsigned len, void *data); bool mem_write(bx_phy_address addr, unsigned len, void *data); @@ -534,7 +532,6 @@ class bx_virtio_console_ctrl_c : public bx_virtio_ctrl_c { int virtio_console_get_write_len(); int virtio_console_write_data(const uint8_t *buf, int buf_len); void virtio_console_resize_event(int width, int height); - static void rx_timer_handler(void *this_ptr); private: @@ -543,4 +540,60 @@ class bx_virtio_console_ctrl_c : public bx_virtio_ctrl_c { VIRTIOConsoleDevice dev; }; +///////////////////////////////////////////////////////////////////////// +// Virtio-net +///////////////////////////////////////////////////////////////////////// +#define BX_VIRTIO_NET_THIS this-> + +typedef struct bx_virtio_net_ctrl_c bx_virtio_net_ctrl_c; + +struct EthernetDevice { + uint8_t mac_addr[6]; /* mac address of the interface */ + void (*write_packet)(EthernetDevice *net, + const uint8_t *buf, int len); + void *opaque; + bx_virtio_net_ctrl_c *virtio_device; + void (*select_fill)(EthernetDevice *net, int *pfd_max, + fd_set *rfds, fd_set *wfds, fd_set *efds, + int *pdelay); + void (*select_poll)(EthernetDevice *net, + fd_set *rfds, fd_set *wfds, fd_set *efds, + int select_ret); + int (*watch)(EthernetDevice *net); +}; + +typedef struct VIRTIONetDevice { + int header_size; +} VIRTIONetDevice; + +typedef struct { + uint8_t flags; + uint8_t gso_type; + uint16_t hdr_len; + uint16_t gso_size; + uint16_t csum_start; + uint16_t csum_offset; + uint16_t num_buffers; +} VIRTIONetHeader; + +class bx_virtio_net_ctrl_c : public bx_virtio_ctrl_c { +public: + bx_virtio_net_ctrl_c(char *plugin_name, EthernetDevice *es); + virtual ~bx_virtio_net_ctrl_c(); + virtual void init(void); + virtual int device_recv(int queue_idx, int desc_idx, int read_size, int write_size); + + bool virtio_net_can_write_packet(EthernetDevice *es); + void virtio_net_write_packet(EthernetDevice *es, const uint8_t *buf, int buf_len); + static void rx_timer_handler(void *this_ptr); + +private: + char *plugin_name; + int timer_id; + VIRTIONetDevice net; + EthernetDevice *es; +}; + +int start_qemu_net(char *flag); + #endif diff --git a/patches/bochs/grub.cfg.template b/patches/bochs/grub.cfg.template index 7dbd50e..0a7f7b9 100644 --- a/patches/bochs/grub.cfg.template +++ b/patches/bochs/grub.cfg.template @@ -2,5 +2,5 @@ set default=0 set timeout=0 menuentry 'linux' { - linux /boot/grub/bzImage console=hvc0 root=/dev/sr0 rootwait ro quiet loglevel=${LOGLEVEL} init=/sbin/tini -- /sbin/init + linux /boot/grub/bzImage console=hvc0 root=/dev/sr0 rootwait ro virtio_net.napi_tx=false quiet loglevel=${LOGLEVEL} init=/sbin/tini -- /sbin/init } \ No newline at end of file diff --git a/patches/bochs/vfs/vfs.c b/patches/bochs/vfs/vfs.c new file mode 100644 index 0000000..eff0418 --- /dev/null +++ b/patches/bochs/vfs/vfs.c @@ -0,0 +1,20 @@ +#include + +// allow directly controlling fd for sockets, etc. +int32_t poll_oneoff(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) __attribute__(( + __import_module__("wasi_snapshot_preview1"), + __import_name__("poll_oneoff") +)); + +int32_t __imported_wasi_snapshot_preview1_poll_oneoff(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) { + return poll_oneoff(arg0, arg1, arg2, arg3); +} + +int32_t fd_close(int32_t arg0) __attribute__(( + __import_module__("wasi_snapshot_preview1"), + __import_name__("fd_close") +)); + +int32_t __imported_wasi_snapshot_preview1_fd_close(int32_t arg0) { + return fd_close(arg0); +} diff --git a/patches/tinyemu/tinyemu.config.template b/patches/tinyemu/tinyemu.config.template index 7e2222e..e0c86a8 100644 --- a/patches/tinyemu/tinyemu.config.template +++ b/patches/tinyemu/tinyemu.config.template @@ -4,6 +4,6 @@ memory_size: ${MEMORY_SIZE}, bios: "/pack/bbl.bin", kernel: "/pack/Image", - cmdline: "console=hvc0 root=/dev/vda ro quiet loglevel=${LOGLEVEL} init=/sbin/tini -- /sbin/init", + cmdline: "console=hvc0 root=/dev/vda ro quiet virtio_net.napi_tx=false loglevel=${LOGLEVEL} init=/sbin/tini -- /sbin/init", drive0: { file: "/pack/rootfs.bin" }, } diff --git a/patches/tinyemu/tinyemu/temu.c b/patches/tinyemu/tinyemu/temu.c index 57d966b..85a2344 100644 --- a/patches/tinyemu/tinyemu/temu.c +++ b/patches/tinyemu/tinyemu/temu.c @@ -64,6 +64,14 @@ extern char **environ; #include #endif +#include +#include +#ifndef ON_BROWSER +// not defined in wasi-libc +#define PF_INET 1 +#define PF_INET6 2 +#endif + #ifndef _WIN32 typedef struct { @@ -583,6 +591,276 @@ static EthernetDevice *slirp_open(void) #endif /* CONFIG_SLIRP */ +/*******************************************************/ +/* qemu */ + +typedef struct { + int fd; + BOOL select_filled; + char *raw_flag; + + int tmpfd; + BOOL enabled; + int next_watch; + int retrynum; + + uint32_t sizebuf; + int sizeoff; + int pktsize; + int pktoff; + uint8_t *pktbuf; +} QemuSocketState; + +static void qemu_write_packet(EthernetDevice *net, + const uint8_t *buf, int len) +{ + QemuSocketState *s = net->opaque; + uint32_t size = htonl(len); + int ret; + + if (s->fd < 0) { + return; + } + + ret = send(s->fd, &size, 4, 0); // TODO: check error + if (ret < 0) { + close(s->fd); + s->fd = -1; + return; + } + ret = send(s->fd, buf, len, 0); // TODO: check error + if (ret < 0) { + close(s->fd); + s->fd = -1; + return; + } +} + +static int try_get_fd(QemuSocketState *s); + +static int qemu_watch1(EthernetDevice *net) +{ + QemuSocketState *s = (QemuSocketState *)net->opaque; + + if (!s->enabled) + return -1; + + if (s->fd >= 0) + return 0; + + s->next_watch--; + if (s->next_watch > 0) + return -1; + if (try_get_fd(s) >= 0) + return 0; + if (s->next_watch <= 0) { + s->next_watch = 500; // TODO: make it configable or should be time interval + } + + return -1; +} + +static void qemu_select_fill1(EthernetDevice *net, int *pfd_max, + fd_set *rfds, fd_set *wfds, fd_set *efds, + int *pdelay) +{ + QemuSocketState *s = net->opaque; + int fd = s->fd; + + if (s->fd < 0) { + return; + } + + s->select_filled = net->device_can_write_packet(net); + if (s->select_filled) { + FD_SET(fd, rfds); + *pfd_max = max_int(*pfd_max, fd); + } +} + +static void qemu_select_poll1(EthernetDevice *net, + fd_set *rfds, fd_set *wfds, fd_set *efds, + int select_ret) +{ + QemuSocketState *s = net->opaque; + int fd = s->fd; + uint32_t size = 0; + int ret; + + if (s->fd < 0) { + return; + } + + if (select_ret <= 0) + return; + + if (s->select_filled && FD_ISSET(fd, rfds)) { + if (s->pktsize <= 0) { + while (s->sizeoff < 4) { + ret = recv(fd, &s->sizebuf + s->sizeoff, 4 - s->sizeoff, 0); + if (ret <= 0) { + if (errno == EAGAIN) + return; // try later + close(s->fd); + s->fd = -1; // invalid fd. hopefully the watch loop will recover this. + return; + } + s->sizeoff += ret; + } + s->pktsize = ntohl(s->sizebuf); + s->sizeoff = 0; + s->sizebuf = 0; + } + + if (s->pktsize > 0) { + size = s->pktsize; + if (s->pktbuf == NULL) { + s->pktoff = 0; + s->pktbuf = (uint8_t *)mallocz(size); // TODO: make limit + } + while (s->pktoff < size) { + ret = recv(fd, s->pktbuf + s->pktoff, size - s->pktoff, 0); + if (ret <= 0) { + if (errno == EAGAIN) + return; // try later + close(s->fd); + s->fd = -1; // invalid fd. hopefully the watch loop will recover this. + return; + } + s->pktoff += ret; + } + net->device_write_packet(net, s->pktbuf, size); + free(s->pktbuf); + s->pktbuf = NULL; + s->pktsize = 0; + s->pktoff = 0; + } + } +} + +static EthernetDevice *qemu_net_init() +{ + EthernetDevice *net; + QemuSocketState *s; + + net = mallocz(sizeof(*net)); + net->mac_addr[0] = 0x02; + net->mac_addr[1] = 0x00; + net->mac_addr[2] = 0x00; + net->mac_addr[3] = 0x00; + net->mac_addr[4] = 0x00; + net->mac_addr[5] = 0x01; + net->opaque = NULL; + s = mallocz(sizeof(*s)); + s->fd = -1; + s->tmpfd = -1; + s->enabled = FALSE; + s->next_watch = 0; + net->opaque = s; + net->write_packet = qemu_write_packet; + net->select_fill = qemu_select_fill1; + net->select_poll = qemu_select_poll1; + net->watch = qemu_watch1; + + return net; +} + +static void reset_qemu_socket_state(QemuSocketState *s) +{ + s->sizebuf = 0; + s->sizeoff = 0; + s->pktsize = 0; + s->pktoff = 0; + if (s->pktbuf != NULL) { + free(s->pktbuf); + } + s->pktbuf = NULL; +} + +#ifdef ON_BROWSER +static int try_get_fd(QemuSocketState *s) +{ + int sock= s->fd; + fd_set wfds; + struct sockaddr_in qemuAddr; + + if (s->tmpfd < 0) { + if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + printf("failed to prepare socket: %s\n", strerror(errno)); + return -1; + } + // connect to the network stack + memset(&qemuAddr, 0, sizeof(qemuAddr)); + qemuAddr.sin_family = AF_INET; + int cret = connect(sock, (struct sockaddr *) &qemuAddr, sizeof(qemuAddr)); + BOOL connected = FALSE; + if ((cret == 0) || (errno == EISCONN)) { + /* printf("socket connected\n"); */ + s->fd = sock; + s->tmpfd = -1; + reset_qemu_socket_state(s); + return 0; + } else if (errno != EINPROGRESS) { + printf("failed to connect: %s\n", strerror(errno)); + s->fd = -1; + s->tmpfd = -1; + return -1; + } + s->retrynum = 0; + s->tmpfd = sock; + } + + /* printf("waiting for connection...\n"); */ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + FD_ZERO(&wfds); + FD_SET(s->tmpfd, &wfds); + int sret = select(s->tmpfd + 1, NULL, &wfds, NULL, &tv); + if (sret > 0) { + s->fd = s->tmpfd; + s->tmpfd = -1; + reset_qemu_socket_state(s); + return 0; + } else if (sret < 0) { + s->fd = -1; + s->tmpfd = -1; + } // else, still wait for the connection. + /* printf("select result: %d\n", sret); */ + + s->retrynum++; + if (s->retrynum > 5) { // TODO: make max retry number configurable + close(s->tmpfd); + s->tmpfd = -1; // too many errors. retry connection. + } + + return -1; +} +#elif defined(WASI) +static int try_get_fd(QemuSocketState *s) +{ + int sock = 3; + if ((s->raw_flag != NULL) && (!strncmp(s->raw_flag, "qemu=", 5))) { + // TODO: allow specifying more options + if (!strncmp(s->raw_flag + 5, "listenfd=", 9)) { + sock = atoi(s->raw_flag + 5 + 9); + } + } + int sock_a = 0; + // wait for connection + /* printf("accept trying...(sockfd=%d)\n", sock); */ + sock_a = accept4(sock, NULL, NULL, SOCK_NONBLOCK); + if (sock_a > 0) { + /* printf("accepted fd=%d\n", sock_a); */ + s->fd = sock_a; + reset_qemu_socket_state(s); + return 0; + } + /* printf("failed to accept socket: %s\n", strerror(errno)); */ + return -1; +} +#endif + #define MAX_EXEC_CYCLE 500000 #define MAX_SLEEP_TIME 10 /* in ms */ @@ -617,12 +895,16 @@ void virt_machine_run(VirtMachine *m, int enable_stdin) } } #endif + fd_set n_rfds, n_wfds, n_efds; + FD_ZERO(&n_rfds); + FD_ZERO(&n_wfds); + FD_ZERO(&n_efds); + int n_fd_max = -1; if (m->net) { - // TODO: do this when enable net - // m->net->select_fill(m->net, &fd_max, &rfds, &wfds, &efds, &delay); + m->net->select_fill(m->net, &n_fd_max, &n_rfds, &n_wfds, &n_efds, &delay); } #ifdef CONFIG_FS_NET - fs_net_set_fdset(&fd_max, &rfds, &wfds, &efds, &delay); + fs_net_set_fdset(&n_fd_max, &n_rfds, &n_wfds, &n_efds, &delay); #endif tv.tv_sec = delay / 1000; tv.tv_usec = (delay % 1000) * 1000; @@ -634,8 +916,12 @@ void virt_machine_run(VirtMachine *m, int enable_stdin) if (enable_stdin && init_done) ret = select(fd_max + 1, &rfds, &wfds, NULL, &tv); if (m->net) { - // TODO: do this when enable net - // m->net->select_poll(m->net, &rfds, &wfds, &efds, ret); + if (n_fd_max >= 0) { + // TODO: select should be unified (stdin + net), but it doesn't work as of now. + int n_ret = select(n_fd_max + 1, &n_rfds, &n_wfds, NULL, &tv); + m->net->select_poll(m->net, &n_rfds, &n_wfds, &n_efds, n_ret); + } + m->net->watch(m->net); } if ((ret > 0) || (init_start && !init_done)) { #ifndef _WIN32 @@ -646,7 +932,7 @@ void virt_machine_run(VirtMachine *m, int enable_stdin) len = min_int(len, sizeof(buf)); ret = m->console->read_data(m->console->opaque, buf, len); if (ret > 0) { - virtio_console_write_data(m->console_dev, buf, ret); + virtio_console_write_data(m->console_dev, buf, ret); } } #endif @@ -668,6 +954,8 @@ static struct option options[] = { { "help", no_argument, NULL, 'h' }, { "no-stdin", no_argument }, { "entrypoint", required_argument }, + { "net", required_argument }, + { "mac", required_argument }, // The following flags are unsupported as of now: // { "ctrlc", no_argument }, // { "rw", no_argument }, @@ -686,6 +974,8 @@ void help(void) "OPTIONS:\n" " -entrypoint : entrypoint command. (default: entrypoint specified in the image config)\n" " -no-stdin : disable stdin. (default: false)\n" + " -net : enable networking with the specified mode (default: disabled. supported mode: \"qemu\")\n" + " -mac : use a custom mac address for the VM\n" "\n" "This tool is based on:\n" "temu version 2019-12-21, Copyright (c) 2016-2018 Fabrice Bellard\n" @@ -713,7 +1003,7 @@ int initialized = FALSE; VirtMachine *s; VirtMachineParams p_s, *p = &p_s; -void init_func() +void init_func_args(char *net) { const char *path, *cmdline = NULL; int i = 0; @@ -794,6 +1084,7 @@ void init_func() } p->fs_count++; + // networking for(i = 0; i < p->eth_count; i++) { #ifdef CONFIG_SLIRP if (!strcmp(p->tab_eth[i].driver, "user")) { @@ -816,6 +1107,15 @@ void init_func() exit(1); } } + + if ((p->eth_count == 0) && (net != NULL)) { + if (!strncmp(net, "qemu", 4)) { + p->tab_eth[0].net = qemu_net_init(); + p->eth_count++; + if (!p->tab_eth[0].net) + exit(1); + } + } #ifdef CONFIG_SDL if (p->display_device) { @@ -853,6 +1153,11 @@ void init_func() initialized = TRUE; } +void init_func() +{ + init_func_args("qemu"); +} + #ifdef WASI WIZER_INIT(init_func); #endif @@ -948,6 +1253,46 @@ int write_env(FSVirtFile *f, int pos1, const char *env) return pos - pos1; } +int write_net(FSVirtFile *f, int pos1, const char *mac) +{ + int p, pos = pos1; + + p = write_info(f, pos, 3, "n: "); + if (p < 0) { + return -1; + } + pos += p; + for (int j = 0; j < strlen(mac); j++) { + if (putchar_info(f, pos++, mac[j]) != 1) { + return -1; + } + } + if (putchar_info(f, pos++, '\n') != 1) { + return -1; + } + return pos - pos1; +} + +int write_time(FSVirtFile *f, int pos1, const char *timestr) +{ + int p, pos = pos1; + + p = write_info(f, pos, 3, "t: "); + if (p < 0) { + return -1; + } + pos += p; + for (int j = 0; j < strlen(timestr); j++) { + if (putchar_info(f, pos++, timestr[j]) != 1) { + return -1; + } + } + if (putchar_info(f, pos++, '\n') != 1) { + return -1; + } + return pos - pos1; +} + int main(int argc, char **argv) { #ifdef WASI @@ -957,14 +1302,8 @@ int main(int argc, char **argv) } #endif - if (!initialized) { - init_func(); - } else { - virt_machine_resume(s); - } - /* const char *cmdline, *build_preload_file; */ - char *entrypoint = NULL; + char *entrypoint = NULL, *net = NULL, *mac = NULL; int pos, c, option_index, i, enable_stdin = TRUE; for(;;) { c = getopt_long_only(argc, argv, "+h", options, &option_index); @@ -982,6 +1321,12 @@ int main(int argc, char **argv) case 2: /* entrypoint */ entrypoint = optarg; break; + case 3: /* net */ + net = optarg; + break; + case 4: /* mac */ + mac = optarg; + break; default: fprintf(stderr, "unknown option index: %d\n", option_index); exit(1); @@ -995,6 +1340,12 @@ int main(int argc, char **argv) } } + if (!initialized) { + init_func_args(net); + } else { + virt_machine_resume(s); + } + pos = info->len; if (entrypoint) { int p = write_entrypoint(info, pos, entrypoint); @@ -1027,6 +1378,34 @@ int main(int argc, char **argv) } #endif + if (net) { + if (!strncmp(net, "qemu", 4)) { + EthernetDevice *netd; + for(i = 0; i < p->eth_count; i++) { + netd = p->tab_eth[i].net; + if (netd != NULL) { + ((QemuSocketState *)netd->opaque)->enabled = TRUE; + ((QemuSocketState *)netd->opaque)->raw_flag = net; + } + } + } + int p = write_net(info, pos, mac); + if (p < 0) { + printf("failed to prepare net info\n"); + exit(1); + } + pos += p; + } + + char buf[32]; + snprintf(buf, sizeof(buf), "%d", (unsigned)time(NULL)); + int ps = write_time(info, pos, buf); + if (ps < 0) { + printf("failed to prepare time info\n"); + exit(1); + } + pos += ps; + info->len = pos; #ifdef WASI info->len += write_preopen_info(info, pos); diff --git a/patches/tinyemu/tinyemu/virtio.h b/patches/tinyemu/tinyemu/virtio.h index d53c8c4..0664b95 100644 --- a/patches/tinyemu/tinyemu/virtio.h +++ b/patches/tinyemu/tinyemu/virtio.h @@ -95,6 +95,7 @@ struct EthernetDevice { fd_set *rfds, fd_set *wfds, fd_set *efds, int select_ret); #endif + int (*watch)(EthernetDevice *net); /* the following is set by the device */ void *device_opaque; BOOL (*device_can_write_packet)(EthernetDevice *net); diff --git a/patches/tinyemu/tinyemu/wasi.c b/patches/tinyemu/tinyemu/wasi.c index b601021..b5e599c 100644 --- a/patches/tinyemu/tinyemu/wasi.c +++ b/patches/tinyemu/tinyemu/wasi.c @@ -159,6 +159,26 @@ int write_preopen_info(FSVirtFile *f, int pos1) extern void __wasi_vfs_rt_init(void); +// allow directly controlling fd for sockets, etc. + +int32_t poll_oneoff(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) __attribute__(( + __import_module__("wasi_snapshot_preview1"), + __import_name__("poll_oneoff") +)); + +int32_t __imported_wasi_snapshot_preview1_poll_oneoff(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) { + return poll_oneoff(arg0, arg1, arg2, arg3); +} + +int32_t fd_close(int32_t arg0) __attribute__(( + __import_module__("wasi_snapshot_preview1"), + __import_name__("fd_close") +)); + +int32_t __imported_wasi_snapshot_preview1_fd_close(int32_t arg0) { + return fd_close(arg0); +} + int init_wasi() { __wasilibc_ensure_environ(); diff --git a/tests/c2w-net-proxy-test/go.mod b/tests/c2w-net-proxy-test/go.mod new file mode 100644 index 0000000..ec744ed --- /dev/null +++ b/tests/c2w-net-proxy-test/go.mod @@ -0,0 +1,5 @@ +module c2w-net-proxy-test + +go 1.19 + +require github.com/tetratelabs/wazero v1.5.0 diff --git a/tests/c2w-net-proxy-test/go.sum b/tests/c2w-net-proxy-test/go.sum new file mode 100644 index 0000000..68da83b --- /dev/null +++ b/tests/c2w-net-proxy-test/go.sum @@ -0,0 +1,2 @@ +github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0= +github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= diff --git a/tests/c2w-net-proxy-test/main.go b/tests/c2w-net-proxy-test/main.go new file mode 100644 index 0000000..259ea6b --- /dev/null +++ b/tests/c2w-net-proxy-test/main.go @@ -0,0 +1,398 @@ +package main + +import ( + "bytes" + "context" + crand "crypto/rand" + "encoding/json" + "flag" + "fmt" + "io" + "log" + "net" + "net/http" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental/sock" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" +) + +func main() { + var ( + stack = flag.String("stack", "", "path to the stack wasm path") + stackPort = flag.Int("stack-port", 8999, "listen port of network stack") + vmPort = flag.Int("vm-port", 1234, "listen port of vm") + debug = flag.Bool("debug", false, "enable debug log") + ) + var envs envFlags + flag.Var(&envs, "env", "environment variables") + flag.Parse() + if *debug { + log.SetOutput(os.Stdout) + } else { + log.SetOutput(io.Discard) + } + args := flag.Args() + + if stack == nil || *stack == "" { + panic("specify network stack wasm") + } + + crtDir, err := os.MkdirTemp("", "test") + if err != nil { + panic(err) + } + defer func() { + os.Remove(filepath.Join(crtDir, "ca.crt")) + os.Remove(crtDir) + }() + + go func() { + ctx := context.Background() + sockCfg := sock.NewConfig().WithTCPListener("127.0.0.1", *stackPort) + ctx = sock.WithConfig(ctx, sockCfg) + c, err := os.ReadFile(*stack) + if err != nil { + panic(err) + } + r := wazero.NewRuntime(ctx) + defer func() { + r.Close(ctx) + }() + wasi_snapshot_preview1.MustInstantiate(ctx, r) + b := r.NewHostModuleBuilder("env"). + NewFunctionBuilder().WithFunc(http_send).Export("http_send"). + NewFunctionBuilder().WithFunc(http_writebody).Export("http_writebody"). + NewFunctionBuilder().WithFunc(http_isreadable).Export("http_isreadable"). + NewFunctionBuilder().WithFunc(http_recv).Export("http_recv"). + NewFunctionBuilder().WithFunc(http_readbody).Export("http_readbody") + if _, err := b.Instantiate(ctx); err != nil { + panic(err) + } + compiled, err := r.CompileModule(ctx, c) + if err != nil { + panic(err) + } + stackFSConfig := wazero.NewFSConfig() + stackFSConfig = stackFSConfig.WithDirMount(crtDir, "/test") + flagargs := []string{"--certfile=/test/proxy.crt"} + if *debug { + flagargs = append(flagargs, "--debug") + } + conf := wazero.NewModuleConfig().WithSysWalltime().WithSysNanotime().WithSysNanosleep().WithRandSource(crand.Reader).WithStdout(os.Stdout).WithStderr(os.Stderr).WithFSConfig(stackFSConfig).WithArgs(append([]string{"arg0"}, flagargs...)...) + _, err = r.InstantiateModule(ctx, compiled, conf) + if err != nil { + panic(err) + } + }() + go func() { + var conn1 net.Conn + var conn2 net.Conn + var err error + for { + conn1, err = net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", *stackPort)) + if err == nil { + break + } + log.Println("conn1 retry...") + time.Sleep(10 * time.Millisecond) + } + for { + conn2, err = net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", *vmPort)) + if err == nil { + break + } + log.Println("conn2 retry...") + time.Sleep(10 * time.Millisecond) + } + done1 := make(chan struct{}) + done2 := make(chan struct{}) + go func() { + if _, err := io.Copy(conn1, conn2); err != nil { + panic(err) + } + close(done1) + }() + go func() { + if _, err := io.Copy(conn2, conn1); err != nil { + panic(err) + } + close(done2) + }() + <-done1 + <-done2 + }() + + fsConfig := wazero.NewFSConfig() + crtFile := filepath.Join(crtDir, "proxy.crt") + for { + if _, err := os.Stat(crtFile); err == nil { + break + } + log.Println("waiting for cert", crtFile) + time.Sleep(time.Second) + } + + fsConfig = fsConfig.WithDirMount(crtDir, "/.wasmenv") + ctx := context.Background() + sockCfg := sock.NewConfig().WithTCPListener("127.0.0.1", *vmPort) + ctx = sock.WithConfig(ctx, sockCfg) + c, err := os.ReadFile(args[0]) + if err != nil { + panic(err) + } + r := wazero.NewRuntime(ctx) + defer func() { + r.Close(ctx) + }() + wasi_snapshot_preview1.MustInstantiate(ctx, r) + compiled, err := r.CompileModule(ctx, c) + if err != nil { + panic(err) + } + conf := wazero.NewModuleConfig().WithSysWalltime().WithSysNanotime().WithSysNanosleep().WithRandSource(crand.Reader).WithStdout(os.Stdout).WithStderr(os.Stderr).WithStdin(os.Stdin).WithFSConfig(fsConfig).WithArgs(append([]string{"arg0"}, args[1:]...)...) + envs = append(envs, []string{ + "SSL_CERT_FILE=/.wasmenv/proxy.crt", + "https_proxy=http://192.168.127.253:80", + "http_proxy=http://192.168.127.253:80", + "HTTPS_PROXY=http://192.168.127.253:80", + "HTTP_PROXY=http://192.168.127.253:80", + }...) + for _, v := range envs { + es := strings.SplitN(v, "=", 2) + if len(es) == 2 { + conf = conf.WithEnv(es[0], es[1]) + } else { + panic("env must be a key value pair") + } + } + _, err = r.InstantiateModule(ctx, compiled, conf) + if err != nil { + panic(err) + } +} + +var respMap = make(map[uint32]*http.Response) +var respMapMu sync.Mutex + +var respRMap = make(map[uint32]io.Reader) +var respRMapMu sync.Mutex + +type fetchParameters struct { + Method string `json:"method,omitempty"` + Headers map[string]string `json:"headers,omitempty"` +} + +var reqMap = make(map[uint32]*io.PipeWriter) +var reqMapMu sync.Mutex +var reqMapId uint32 + +const ERRNO_INVAL = 28 + +func http_send(ctx context.Context, m api.Module, addressP uint32, addresslen uint32, reqP, reqlen uint32, idP uint32) uint32 { + mem := m.Memory() + + addressB, ok := mem.Read(addressP, uint32(addresslen)) + if !ok { + log.Println("failed to get address") + return ERRNO_INVAL + } + address := string(addressB) + + reqB, ok := mem.Read(reqP, uint32(reqlen)) + if !ok { + log.Println("failed to get req") + return ERRNO_INVAL + } + var fetchReq fetchParameters + if err := json.Unmarshal(reqB, &fetchReq); err != nil { + log.Println("failed to marshal req:", err) + return ERRNO_INVAL + } + + pr, pw := io.Pipe() + req, err := http.NewRequest(fetchReq.Method, address, pr) + if err != nil { + log.Println("failed to create req:", err) + return ERRNO_INVAL + } + if req.Header == nil { + req.Header = make(map[string][]string) + } + for k, v := range fetchReq.Headers { + req.Header[k] = append(req.Header[k], v) // TODO: separate fields + } + + reqMapMu.Lock() + id := reqMapId + reqMapId++ + reqMapMu.Unlock() + reqMap[id] = pw + go func() { + c := &http.Client{} + resp, err := c.Do(req) + if err != nil { + log.Println("failed to do request:", err) + return + } + respMapMu.Lock() + respMap[id] = resp + respMapMu.Unlock() + }() + if !mem.WriteUint32Le(idP, id) { + log.Println("failed to pass id") + return ERRNO_INVAL + } + return 0 +} + +func http_writebody(ctx context.Context, m api.Module, id uint32, chunkP, len uint32, nwrittenP uint32, isEOF uint32) uint32 { + mem := m.Memory() + + chunkB, ok := mem.Read(chunkP, uint32(len)) + if !ok { + log.Println("failed to get chunk") + return ERRNO_INVAL + } + reqMapMu.Lock() + w := reqMap[id] + reqMapMu.Unlock() + if _, err := w.Write(chunkB); err != nil { + w.CloseWithError(err) + log.Println("failed to write req:", err) + return ERRNO_INVAL + } + if isEOF == 1 { + w.Close() + } + if !mem.WriteUint32Le(nwrittenP, len) { + log.Println("failed to pass written number") + return ERRNO_INVAL + } + return 0 +} + +func http_isreadable(ctx context.Context, m api.Module, id uint32, isOKP uint32) uint32 { + mem := m.Memory() + respMapMu.Lock() + _, ok := respMap[id] + respMapMu.Unlock() + v := uint32(0) + if ok { + v = 1 + } + if !mem.WriteUint32Le(isOKP, v) { + log.Println("failed to pass status") + return ERRNO_INVAL + } + return 0 +} + +func http_recv(ctx context.Context, m api.Module, id uint32, respP uint32, bufsize uint32, respsizeP uint32, isEOFP uint32) uint32 { + mem := m.Memory() + respRMapMu.Lock() + respR, ok := respRMap[id] + respRMapMu.Unlock() + if !ok { + respMapMu.Lock() + resp, ok := respMap[id] + respMapMu.Unlock() + if !ok { + log.Println("failed to get resp") + return ERRNO_INVAL + } + var fetchResp struct { + Headers map[string]string `json:"headers,omitempty"` + Status int `json:"status,omitempty"` + StatusText string `json:"statusText,omitempty"` + } + fetchResp.Headers = make(map[string]string) + for k, v := range resp.Header { + fetchResp.Headers[k] = strings.Join(v, ", ") + } + fetchResp.Status = resp.StatusCode + fetchResp.StatusText = resp.Status + respD, err := json.Marshal(fetchResp) + if err != nil { + log.Println("failed to marshal resp:", err) + return ERRNO_INVAL + } + respR = bytes.NewReader(respD) + respRMapMu.Lock() + respRMap[id] = respR + respRMapMu.Unlock() + } + + isEOF := uint32(0) + buf := make([]byte, bufsize) + n, err := respR.Read(buf) + if err != nil && err != io.EOF { + log.Println("failed to read resp:", err) + return ERRNO_INVAL + } else if err == io.EOF { + isEOF = 1 + } + if !mem.Write(respP, buf[:n]) { + log.Println("failed to write resp") + return ERRNO_INVAL + } + if !mem.WriteUint32Le(respsizeP, uint32(n)) { + log.Println("failed to write resp size") + return ERRNO_INVAL + } + if !mem.WriteUint32Le(isEOFP, isEOF) { + log.Println("failed to write EOF status") + return ERRNO_INVAL + } + return 0 +} + +func http_readbody(ctx context.Context, m api.Module, id uint32, bodyP uint32, bufsize uint32, bodysizeP uint32, isEOFP uint32) uint32 { + mem := m.Memory() + respMapMu.Lock() + resp, ok := respMap[id] + respMapMu.Unlock() + if !ok { + log.Println("failed to get resp") + return ERRNO_INVAL + } + + isEOF := uint32(0) + buf := make([]byte, bufsize) + n, err := resp.Body.Read(buf) + if err != nil && err != io.EOF { + log.Println("failed to read resp body:", err) + return ERRNO_INVAL + } else if err == io.EOF { + isEOF = 1 + } + if !mem.Write(bodyP, buf[:n]) { + log.Println("failed to write resp body") + return ERRNO_INVAL + } + if !mem.WriteUint32Le(bodysizeP, uint32(n)) { + log.Println("failed to write resp body size") + return ERRNO_INVAL + } + if !mem.WriteUint32Le(isEOFP, isEOF) { + log.Println("failed to write resp body EOF status") + return ERRNO_INVAL + } + return 0 +} + +type envFlags []string + +func (i *envFlags) String() string { + return fmt.Sprintf("%v", []string(*i)) +} +func (i *envFlags) Set(value string) error { + *i = append(*i, value) + return nil +} diff --git a/tests/httphello/main.go b/tests/httphello/main.go new file mode 100644 index 0000000..b641c5d --- /dev/null +++ b/tests/httphello/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + "net/http" + "os" +) + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hello") + }) + if err := http.ListenAndServe(os.Args[1], nil); err != nil { + fmt.Println(err) + } +} diff --git a/tests/integration/utils/utils.go b/tests/integration/utils/utils.go index 1fe9031..28446e8 100644 --- a/tests/integration/utils/utils.go +++ b/tests/integration/utils/utils.go @@ -4,18 +4,24 @@ import ( "context" "fmt" "io" + "net/http" "os" "os/exec" "path/filepath" + "strconv" "strings" + "sync" "testing" + "time" "gotest.tools/v3/assert" ) // Defined in Dockerfile.test. // TODO: Make it a flag -const assetPath = "/test/" +const AssetPath = "/test/" +const C2wBin = "c2w" +const C2wNetProxyBin = "/opt/c2w-net-proxy.wasm" type Architecture int @@ -43,23 +49,24 @@ type Input struct { Image string ConvertOpts []string Architecture Architecture + Dockerfile string } type TestSpec struct { - Name string - Inputs []Input - Prepare func(t *testing.T, workdir string) - Finalize func(t *testing.T, workdir string) - ImageName string // default: test.wasm - Runtime string - RuntimeOpts func(t *testing.T, workdir string) []string - Args func(t *testing.T, workdir string) []string - Want func(t *testing.T, workdir string, in io.Writer, out io.Reader) - NoParallel bool + Name string + Inputs []Input + Prepare func(t *testing.T, workdir string) + Finalize func(t *testing.T, workdir string) + ImageName string // default: test.wasm + Runtime string + RuntimeOpts func(t *testing.T, workdir string) []string + Args func(t *testing.T, workdir string) []string + Want func(t *testing.T, workdir string, in io.Writer, out io.Reader) + NoParallel bool + IgnoreExitCode bool } func RunTestRuntimes(t *testing.T, tests ...TestSpec) { - c2wBin := "c2w" for _, tt := range tests { tt := tt for _, in := range tt.Inputs { @@ -72,10 +79,21 @@ func RunTestRuntimes(t *testing.T, tests ...TestSpec) { tmpdir, err := os.MkdirTemp("", "testc2w") assert.NilError(t, err) t.Logf("test root: %v", tmpdir) - defer assert.NilError(t, os.RemoveAll(tmpdir)) + defer func() { + assert.NilError(t, os.RemoveAll(tmpdir)) + }() + + if in.Dockerfile != "" { + df := filepath.Join(tmpdir, "Dockerfile-integrationtest") + assert.NilError(t, os.WriteFile(df, []byte(in.Dockerfile), 0755)) + dcmd := exec.Command("docker", "build", "--progress=plain", "-t", in.Image, "-f", df, AssetPath) + dcmd.Stdout = os.Stdout + dcmd.Stderr = os.Stderr + assert.NilError(t, dcmd.Run()) + } testWasm := filepath.Join(tmpdir, "test.wasm") - c2wCmd := exec.Command(c2wBin, append(in.ConvertOpts, "--assets="+assetPath, in.Image, testWasm)...) + c2wCmd := exec.Command(C2wBin, append(in.ConvertOpts, "--assets="+AssetPath, in.Image, testWasm)...) c2wCmd.Stdout = os.Stdout c2wCmd.Stderr = os.Stderr assert.NilError(t, c2wCmd.Run()) @@ -106,13 +124,20 @@ func RunTestRuntimes(t *testing.T, tests ...TestSpec) { inW, err := testCmd.StdinPipe() assert.NilError(t, err) defer inW.Close() + testCmd.Stderr = os.Stderr assert.NilError(t, testCmd.Start()) tt.Want(t, tmpdir, inW, io.TeeReader(outR, os.Stdout)) inW.Close() - assert.NilError(t, testCmd.Wait()) + if !tt.IgnoreExitCode { + assert.NilError(t, testCmd.Wait()) + } else { + if err := testCmd.Wait(); err != nil { + t.Logf("command test error: %v", err) + } + } // cleanup cache assert.NilError(t, exec.Command("docker", "buildx", "prune", "-f", "--keep-storage=10GB").Run()) @@ -186,3 +211,61 @@ func readUntilPrompt(ctx context.Context, prompt string, outR io.Reader) (out [] func StringFlags(opts ...string) func(t *testing.T, workdir string) []string { return func(t *testing.T, workdir string) []string { return opts } } + +var usedPorts = make(map[int]struct{}) +var usedPortsMu sync.Mutex + +func GetPort(t *testing.T) int { + usedPortsMu.Lock() + defer usedPortsMu.Unlock() + for i := 8001; i < 9000; i++ { + if _, ok := usedPorts[i]; !ok { + usedPorts[i] = struct{}{} + return i + } + } + t.Fatalf("ports exhausted") + return -1 +} + +func DonePort(i int) { + usedPortsMu.Lock() + defer usedPortsMu.Unlock() + delete(usedPorts, i) +} + +func ReadInt(t *testing.T, p string) int { + d, err := os.ReadFile(p) + assert.NilError(t, err) + i, err := strconv.Atoi(string(d)) + assert.NilError(t, err) + return i +} + +func ReadString(t *testing.T, p string) string { + d, err := os.ReadFile(p) + assert.NilError(t, err) + return string(d) +} + +func StartHelloServer(t *testing.T) (pid int, port int) { + port = GetPort(t) + t.Logf("launching server on %d", port) + cmd := exec.Command("httphello", fmt.Sprintf("localhost:%d", port)) + assert.NilError(t, cmd.Start()) + go func() { + if err := cmd.Wait(); err != nil { + t.Logf("hello server error: %v\n", err) + } + DonePort(port) + }() + for { + if cmd.Process != nil { + if _, err := http.Get(fmt.Sprintf("http://localhost:%d/", port)); err == nil { + break + } + } + time.Sleep(1 * time.Millisecond) + } + return cmd.Process.Pid, port +} diff --git a/tests/integration/wasmtime_test.go b/tests/integration/wasmtime_test.go index d1251d6..c0498da 100644 --- a/tests/integration/wasmtime_test.go +++ b/tests/integration/wasmtime_test.go @@ -1,7 +1,12 @@ package integration import ( + "crypto/rand" + "fmt" + "io" + "math/big" "os" + "os/exec" "path/filepath" "testing" @@ -10,6 +15,8 @@ import ( "github.com/ktock/container2wasm/tests/integration/utils" ) +const hostVirtIP = "192.168.127.254" + func TestWasmtime(t *testing.T) { utils.RunTestRuntimes(t, []utils.TestSpec{ { @@ -115,6 +122,139 @@ func TestWasmtime(t *testing.T) { Args: utils.StringFlags("/bin/sh", "-c", "echo -n $AAA $BBB"), Want: utils.WantString("hello world"), }, + { + Name: "wasmtime-net", + Inputs: []utils.Input{ + {Image: "alpine:3.17", Architecture: utils.X86_64}, + {Image: "riscv64/alpine:20221110", ConvertOpts: []string{"--target-arch=riscv64"}, Architecture: utils.RISCV64}, + }, + Prepare: func(t *testing.T, workdir string) { + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-wasi-port"), []byte(fmt.Sprintf("%d", utils.GetPort(t))), 0755)) + pid, port := utils.StartHelloServer(t) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-pid"), []byte(fmt.Sprintf("%d", pid)), 0755)) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-port"), []byte(fmt.Sprintf("%d", port)), 0755)) + }, + Finalize: func(t *testing.T, workdir string) { + p, err := os.FindProcess(utils.ReadInt(t, filepath.Join(workdir, "httphello-pid"))) + assert.NilError(t, err) + assert.NilError(t, p.Kill()) + if _, err := p.Wait(); err != nil { + t.Logf("hello server error: %v\n", err) + } + utils.DonePort(utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port"))) + }, + Runtime: "c2w-net", + RuntimeOpts: func(t *testing.T, workdir string) []string { + t.Logf("wasi-addr is %s", fmt.Sprintf("localhost:%d", utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port")))) + return []string{"--invoke", "--wasi-addr", fmt.Sprintf("localhost:%d", utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port")))} + }, + Args: func(t *testing.T, workdir string) []string { + t.Logf("RUNNING: %s", fmt.Sprintf("wget -q -O - http://%s:%d/", hostVirtIP, utils.ReadInt(t, filepath.Join(workdir, "httphello-port")))) + return []string{"--net=qemu", "sh", "-c", fmt.Sprintf("wget -q -O - http://%s:%d/", hostVirtIP, utils.ReadInt(t, filepath.Join(workdir, "httphello-port")))} + }, + Want: utils.WantString("hello"), + }, + { + Name: "wasmtime-net-port", + Inputs: []utils.Input{ + {Image: "httphello-alpine-x86-64", Architecture: utils.X86_64, Dockerfile: ` +FROM golang:1.21-bullseye AS dev +COPY ./tests/httphello /httphello +WORKDIR /httphello +RUN GOARCH=amd64 go build -ldflags "-s -w -extldflags '-static'" -tags "osusergo netgo static_build" -o /out/httphello main.go + +FROM alpine:3.17 +COPY --from=dev /out/httphello / +ENTRYPOINT ["/httphello", "0.0.0.0:80"] +`}, + {Image: "httphello-alpine-rv64", ConvertOpts: []string{"--target-arch=riscv64"}, Architecture: utils.RISCV64, Dockerfile: ` +FROM golang:1.21-bullseye AS dev +COPY ./tests/httphello /httphello +WORKDIR /httphello +RUN GOARCH=riscv64 go build -ldflags "-s -w -extldflags '-static'" -tags "osusergo netgo static_build" -o /out/httphello main.go + +FROM riscv64/alpine:20221110 +COPY --from=dev /out/httphello / +ENTRYPOINT ["/httphello", "0.0.0.0:80"] +`}, + }, + Prepare: func(t *testing.T, workdir string) { + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-port"), []byte(fmt.Sprintf("%d", utils.GetPort(t))), 0755)) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-wasi-port"), []byte(fmt.Sprintf("%d", utils.GetPort(t))), 0755)) + }, + Finalize: func(t *testing.T, workdir string) { + utils.DonePort(utils.ReadInt(t, filepath.Join(workdir, "httphello-port"))) + utils.DonePort(utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port"))) + }, + Runtime: "c2w-net", + RuntimeOpts: func(t *testing.T, workdir string) []string { + t.Logf("wasi-addr is %s", fmt.Sprintf("localhost:%d", utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port")))) + wasiPort := utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port")) + port := utils.ReadInt(t, filepath.Join(workdir, "httphello-port")) + t.Logf("port is %s", fmt.Sprintf("localhost:%d:80", port)) + return []string{"--invoke", "--wasi-addr", fmt.Sprintf("localhost:%d", wasiPort), "-p", fmt.Sprintf("localhost:%d:80", port)} + }, + Args: func(t *testing.T, workdir string) []string { + return []string{"--net=qemu"} + }, + Want: func(t *testing.T, workdir string, in io.Writer, out io.Reader) { + port := utils.ReadInt(t, filepath.Join(workdir, "httphello-port")) + cmd := exec.Command("wget", "-q", "-O", "-", fmt.Sprintf("localhost:%d", port)) + cmd.Stderr = os.Stderr + d, err := cmd.Output() + assert.NilError(t, err) + assert.Equal(t, string(d), "hello") + t.Logf("GOT %s", string(d)) + }, + IgnoreExitCode: true, + }, + { + Name: "wasmtime-net-mac", + Inputs: []utils.Input{ + {Image: "alpine:3.17", Architecture: utils.X86_64}, + {Image: "riscv64/alpine:20221110", ConvertOpts: []string{"--target-arch=riscv64"}, Architecture: utils.RISCV64}, + }, + Prepare: func(t *testing.T, workdir string) { + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-wasi-port"), []byte(fmt.Sprintf("%d", utils.GetPort(t))), 0755)) + pid, port := utils.StartHelloServer(t) + var v [5]int64 + for i := 0; i < 5; i++ { + n, err := rand.Int(rand.Reader, big.NewInt(256)) + assert.NilError(t, err) + v[i] = n.Int64() + } + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "mac"), []byte(fmt.Sprintf("02:%02x:%02x:%02x:%02x:%02x", v[0], v[1], v[2], v[3], v[4])), 0755)) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-pid"), []byte(fmt.Sprintf("%d", pid)), 0755)) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-port"), []byte(fmt.Sprintf("%d", port)), 0755)) + }, + Finalize: func(t *testing.T, workdir string) { + p, err := os.FindProcess(utils.ReadInt(t, filepath.Join(workdir, "httphello-pid"))) + assert.NilError(t, err) + assert.NilError(t, p.Kill()) + if _, err := p.Wait(); err != nil { + t.Logf("hello server error: %v\n", err) + } + utils.DonePort(utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port"))) + }, + Runtime: "c2w-net", + RuntimeOpts: func(t *testing.T, workdir string) []string { + t.Logf("wasi-addr is %s", fmt.Sprintf("localhost:%d", utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port")))) + return []string{"--invoke", "--wasi-addr", fmt.Sprintf("localhost:%d", utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port")))} + }, + Args: func(t *testing.T, workdir string) []string { + t.Logf("RUNNING: %s", fmt.Sprintf("wget -q -O - http://%s:%d/", hostVirtIP, utils.ReadInt(t, filepath.Join(workdir, "httphello-port")))) + t.Logf("MAC: %s", utils.ReadString(t, filepath.Join(workdir, "mac"))) + return []string{"--net=qemu", fmt.Sprintf("--mac=%s", utils.ReadString(t, filepath.Join(workdir, "mac"))), "sh"} + }, + Want: utils.WantPromptWithWorkdir("/ # ", + func(workdir string) [][2]string { + return [][2]string{ + [2]string{fmt.Sprintf("wget -q -O - http://%s:%d/\n", hostVirtIP, utils.ReadInt(t, filepath.Join(workdir, "httphello-port"))), "hello"}, + [2]string{`/bin/sh -c 'ip a show eth0 | grep ether | sed -E "s/ +/ /g" | cut -f 3 -d " " | tr -d "\n"'` + "\n", utils.ReadString(t, filepath.Join(workdir, "mac"))}, + } + }, + ), + }, // Other architectures { Name: "wasmtime-hello-arch-aarch64", diff --git a/tests/integration/wazero_test.go b/tests/integration/wazero_test.go index 3e7693a..aaa2c08 100644 --- a/tests/integration/wazero_test.go +++ b/tests/integration/wazero_test.go @@ -1,7 +1,12 @@ package integration import ( + "crypto/rand" + "fmt" + "io" + "math/big" "os" + "os/exec" "path/filepath" "testing" @@ -117,5 +122,180 @@ func TestWazero(t *testing.T) { Args: utils.StringFlags("/bin/sh", "-c", "echo -n $AAA $BBB"), Want: utils.WantString("hello world"), }, + { + Name: "wazero-net-proxy", + Runtime: "c2w-net-proxy-test", + Inputs: []utils.Input{ + {Image: "debian-wget-x86-64", Architecture: utils.X86_64, Dockerfile: ` +FROM debian:sid-slim +RUN apt-get update && apt-get install -y wget +`}, + {Image: "debian-wget-rv64", ConvertOpts: []string{"--target-arch=riscv64"}, Architecture: utils.RISCV64, Dockerfile: ` +FROM riscv64/debian:sid-slim +RUN apt-get update && apt-get install -y wget +`}, + }, + Prepare: func(t *testing.T, workdir string) { + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-vm-port"), []byte(fmt.Sprintf("%d", utils.GetPort(t))), 0755)) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-stack-port"), []byte(fmt.Sprintf("%d", utils.GetPort(t))), 0755)) + pid, port := utils.StartHelloServer(t) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-pid"), []byte(fmt.Sprintf("%d", pid)), 0755)) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-port"), []byte(fmt.Sprintf("%d", port)), 0755)) + }, + Finalize: func(t *testing.T, workdir string) { + p, err := os.FindProcess(utils.ReadInt(t, filepath.Join(workdir, "httphello-pid"))) + assert.NilError(t, err) + assert.NilError(t, p.Kill()) + if _, err := p.Wait(); err != nil { + t.Logf("hello server error: %v\n", err) + } + utils.DonePort(utils.ReadInt(t, filepath.Join(workdir, "httphello-vm-port"))) + utils.DonePort(utils.ReadInt(t, filepath.Join(workdir, "httphello-stack-port"))) + }, + RuntimeOpts: func(t *testing.T, workdir string) []string { + return []string{ + "--stack=" + utils.C2wNetProxyBin, + fmt.Sprintf("--stack-port=%d", utils.ReadInt(t, filepath.Join(workdir, "httphello-stack-port"))), + fmt.Sprintf("--vm-port=%d", utils.ReadInt(t, filepath.Join(workdir, "httphello-vm-port"))), + } + }, + Args: func(t *testing.T, workdir string) []string { + return []string{"--net=qemu=listenfd=4", "sh", "-c", fmt.Sprintf("for I in $(seq 1 50) ; do if wget -O - http://127.0.0.1:%d/ 2>/dev/null ; then break ; fi ; sleep 1 ; done", utils.ReadInt(t, filepath.Join(workdir, "httphello-port")))} + }, + Want: utils.WantString("hello"), + }, + { + Name: "wazero-net", + Inputs: []utils.Input{ + {Image: "alpine:3.17", Architecture: utils.X86_64}, + {Image: "riscv64/alpine:20221110", ConvertOpts: []string{"--target-arch=riscv64"}, Architecture: utils.RISCV64}, + }, + Prepare: func(t *testing.T, workdir string) { + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-wasi-port"), []byte(fmt.Sprintf("%d", utils.GetPort(t))), 0755)) + pid, port := utils.StartHelloServer(t) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-pid"), []byte(fmt.Sprintf("%d", pid)), 0755)) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-port"), []byte(fmt.Sprintf("%d", port)), 0755)) + }, + Finalize: func(t *testing.T, workdir string) { + p, err := os.FindProcess(utils.ReadInt(t, filepath.Join(workdir, "httphello-pid"))) + assert.NilError(t, err) + assert.NilError(t, p.Kill()) + if _, err := p.Wait(); err != nil { + t.Logf("hello server error: %v\n", err) + } + utils.DonePort(utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port"))) + }, + Runtime: "wazero-test", + RuntimeOpts: func(t *testing.T, workdir string) []string { + t.Logf("wasi-addr is %s", fmt.Sprintf("localhost:%d", utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port")))) + return []string{"-net", "--wasi-addr", fmt.Sprintf("localhost:%d", utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port")))} + }, + Args: func(t *testing.T, workdir string) []string { + t.Logf("RUNNING: %s", fmt.Sprintf("wget -q -O - http://%s:%d/", hostVirtIP, utils.ReadInt(t, filepath.Join(workdir, "httphello-port")))) + return []string{"--net=qemu", "sh", "-c", fmt.Sprintf("wget -q -O - http://%s:%d/", hostVirtIP, utils.ReadInt(t, filepath.Join(workdir, "httphello-port")))} + }, + Want: utils.WantString("hello"), + }, + { + Name: "wazero-net-port", + Inputs: []utils.Input{ + {Image: "httphello-alpine-x86-64", Architecture: utils.X86_64, Dockerfile: ` +FROM golang:1.21-bullseye AS dev +COPY ./tests/httphello /httphello +WORKDIR /httphello +RUN GOARCH=amd64 go build -ldflags "-s -w -extldflags '-static'" -tags "osusergo netgo static_build" -o /out/httphello main.go + +FROM alpine:3.17 +COPY --from=dev /out/httphello / +ENTRYPOINT ["/httphello", "0.0.0.0:80"] +`}, + {Image: "httphello-alpine-rv64", ConvertOpts: []string{"--target-arch=riscv64"}, Architecture: utils.RISCV64, Dockerfile: ` +FROM golang:1.21-bullseye AS dev +COPY ./tests/httphello /httphello +WORKDIR /httphello +RUN GOARCH=riscv64 go build -ldflags "-s -w -extldflags '-static'" -tags "osusergo netgo static_build" -o /out/httphello main.go + +FROM riscv64/alpine:20221110 +COPY --from=dev /out/httphello / +ENTRYPOINT ["/httphello", "0.0.0.0:80"] +`}, + }, + Prepare: func(t *testing.T, workdir string) { + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-port"), []byte(fmt.Sprintf("%d", utils.GetPort(t))), 0755)) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-wasi-port"), []byte(fmt.Sprintf("%d", utils.GetPort(t))), 0755)) + }, + Finalize: func(t *testing.T, workdir string) { + utils.DonePort(utils.ReadInt(t, filepath.Join(workdir, "httphello-port"))) + utils.DonePort(utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port"))) + }, + Runtime: "wazero-test", + RuntimeOpts: func(t *testing.T, workdir string) []string { + t.Logf("wasi-addr is %s", fmt.Sprintf("localhost:%d", utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port")))) + wasiPort := utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port")) + port := utils.ReadInt(t, filepath.Join(workdir, "httphello-port")) + t.Logf("port is %s", fmt.Sprintf("localhost:%d:80", port)) + return []string{"-net", "--wasi-addr", fmt.Sprintf("localhost:%d", wasiPort), "-p", fmt.Sprintf("localhost:%d:80", port)} + }, + Args: func(t *testing.T, workdir string) []string { + return []string{"--net=qemu"} + }, + Want: func(t *testing.T, workdir string, in io.Writer, out io.Reader) { + port := utils.ReadInt(t, filepath.Join(workdir, "httphello-port")) + cmd := exec.Command("wget", "-q", "-O", "-", fmt.Sprintf("localhost:%d", port)) + cmd.Stderr = os.Stderr + d, err := cmd.Output() + assert.NilError(t, err) + assert.Equal(t, string(d), "hello") + t.Logf("GOT %s", string(d)) + }, + IgnoreExitCode: true, + }, + { + Name: "wazero-net-mac", + Inputs: []utils.Input{ + {Image: "alpine:3.17", Architecture: utils.X86_64}, + {Image: "riscv64/alpine:20221110", ConvertOpts: []string{"--target-arch=riscv64"}, Architecture: utils.RISCV64}, + }, + Prepare: func(t *testing.T, workdir string) { + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-wasi-port"), []byte(fmt.Sprintf("%d", utils.GetPort(t))), 0755)) + pid, port := utils.StartHelloServer(t) + var v [5]int64 + for i := 0; i < 5; i++ { + n, err := rand.Int(rand.Reader, big.NewInt(256)) + assert.NilError(t, err) + v[i] = n.Int64() + } + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "mac"), []byte(fmt.Sprintf("02:%02x:%02x:%02x:%02x:%02x", v[0], v[1], v[2], v[3], v[4])), 0755)) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-pid"), []byte(fmt.Sprintf("%d", pid)), 0755)) + assert.NilError(t, os.WriteFile(filepath.Join(workdir, "httphello-port"), []byte(fmt.Sprintf("%d", port)), 0755)) + }, + Finalize: func(t *testing.T, workdir string) { + p, err := os.FindProcess(utils.ReadInt(t, filepath.Join(workdir, "httphello-pid"))) + assert.NilError(t, err) + assert.NilError(t, p.Kill()) + if _, err := p.Wait(); err != nil { + t.Logf("hello server error: %v\n", err) + } + utils.DonePort(utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port"))) + }, + Runtime: "wazero-test", + RuntimeOpts: func(t *testing.T, workdir string) []string { + t.Logf("wasi-addr is %s", fmt.Sprintf("localhost:%d", utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port")))) + return []string{"-net", "--wasi-addr", fmt.Sprintf("localhost:%d", utils.ReadInt(t, filepath.Join(workdir, "httphello-wasi-port")))} + }, + Args: func(t *testing.T, workdir string) []string { + t.Logf("RUNNING: %s", fmt.Sprintf("wget -q -O - http://%s:%d/", hostVirtIP, utils.ReadInt(t, filepath.Join(workdir, "httphello-port")))) + t.Logf("MAC: %s", utils.ReadString(t, filepath.Join(workdir, "mac"))) + return []string{"--net=qemu", fmt.Sprintf("--mac=%s", utils.ReadString(t, filepath.Join(workdir, "mac"))), "sh"} + }, + Want: utils.WantPromptWithWorkdir("/ # ", + func(workdir string) [][2]string { + return [][2]string{ + [2]string{fmt.Sprintf("wget -q -O - http://%s:%d/\n", hostVirtIP, utils.ReadInt(t, filepath.Join(workdir, "httphello-port"))), "hello"}, + [2]string{`/bin/sh -c 'ip a show eth0 | grep ether | sed -E "s/ +/ /g" | cut -f 3 -d " " | tr -d "\n"'` + "\n", utils.ReadString(t, filepath.Join(workdir, "mac"))}, + } + }, + ), + }, }...) } diff --git a/tests/wazero/go.mod b/tests/wazero/go.mod index 28e7095..85ec487 100644 --- a/tests/wazero/go.mod +++ b/tests/wazero/go.mod @@ -2,4 +2,27 @@ module github.com/ktock/container2wasm/examples/wazero go 1.19 -require github.com/tetratelabs/wazero v1.5.0 +require ( + github.com/containers/gvisor-tap-vsock v0.7.0 + github.com/tetratelabs/wazero v1.5.0 +) + +require ( + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/apparentlymart/go-cidr v1.1.0 // indirect + github.com/google/btree v1.0.1 // indirect + github.com/google/gopacket v1.1.19 // indirect + github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f // indirect + github.com/miekg/dns v1.1.55 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.12.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + golang.org/x/tools v0.9.3 // indirect + gvisor.dev/gvisor v0.0.0-20230715022000-fd277b20b8db // indirect + inet.af/tcpproxy v0.0.0-20220326234310-be3ee21c9fa0 // indirect +) diff --git a/tests/wazero/go.sum b/tests/wazero/go.sum index 68da83b..3a02cf6 100644 --- a/tests/wazero/go.sum +++ b/tests/wazero/go.sum @@ -1,2 +1,121 @@ +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= +github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= +github.com/containers/gvisor-tap-vsock v0.7.0 h1:lL5UpfXhl+tuTwK43CXhKWwfvAiZtde6G5alFeoO+Z8= +github.com/containers/gvisor-tap-vsock v0.7.0/go.mod h1:edQTwl8ar+ACuQOkazpQkgd/ZMF6TJ2Xr3fv+MKUaw8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= +github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f h1:l1QCwn715k8nYkj4Ql50rzEog3WnMdrd4YYMMwemxEo= +github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= +github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= +github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= +github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w= +github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= +github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0= github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= +github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA= +github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gvisor.dev/gvisor v0.0.0-20230715022000-fd277b20b8db h1:WZSmkyu/hep9YhWIlBZefwGVBrnGE5yW8JPD56YRsXs= +gvisor.dev/gvisor v0.0.0-20230715022000-fd277b20b8db/go.mod h1:sQuqOkxbfJq/GS2uSnqHphtXclHyk/ZrAGhZBxxsq6g= +inet.af/tcpproxy v0.0.0-20220326234310-be3ee21c9fa0 h1:PqdHrvQRVK1zapJkd0qf6+tevvSIcWdfenVqJd3PHWU= +inet.af/tcpproxy v0.0.0-20220326234310-be3ee21c9fa0/go.mod h1:Tojt5kmHpDIR2jMojxzZK2w2ZR7OILODmUo2gaSwjrk= diff --git a/tests/wazero/main.go b/tests/wazero/main.go index 360cf54..1f78da2 100644 --- a/tests/wazero/main.go +++ b/tests/wazero/main.go @@ -5,18 +5,38 @@ import ( crand "crypto/rand" "flag" "fmt" + "net" + "net/url" "os" + "strconv" "strings" + "time" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + + gvntypes "github.com/containers/gvisor-tap-vsock/pkg/types" + gvnvirtualnetwork "github.com/containers/gvisor-tap-vsock/pkg/virtualnetwork" + "github.com/tetratelabs/wazero/experimental/sock" +) + +const ( + gatewayIP = "192.168.127.1" + vmIP = "192.168.127.3" + vmMAC = "02:00:00:00:00:01" ) func main() { var ( - mapDir = flag.String("mapdir", "", "directory mapping to the image") + mapDir = flag.String("mapdir", "", "directory mapping to the image") + debug = flag.Bool("debug", false, "debug log") + mac = flag.String("mac", vmMAC, "mac address assigned to the container") + wasiAddr = flag.String("wasi-addr", "127.0.0.1:1234", "IP address used to communicate between wasi and network stack") // TODO: automatically use empty random port or unix socket + enableNet = flag.Bool("net", false, "enable network") ) - var envs envFlags + var portFlags sliceFlags + flag.Var(&portFlags, "p", "map port between host and guest (host:guest). -mac must be set correctly.") + var envs sliceFlags flag.Var(&envs, "env", "environment variables") flag.Parse() @@ -31,6 +51,69 @@ func main() { } ctx := context.Background() + if *enableNet { + forwards := make(map[string]string) + for _, p := range portFlags { + parts := strings.Split(p, ":") + switch len(parts) { + case 3: + // IP:PORT1:PORT2 + forwards[strings.Join(parts[0:2], ":")] = strings.Join([]string{vmIP, parts[2]}, ":") + case 2: + // PORT1:PORT2 + forwards["0.0.0.0:"+parts[0]] = vmIP + ":" + parts[1] + } + } + config := &gvntypes.Configuration{ + Debug: *debug, + MTU: 1500, + Subnet: "192.168.127.0/24", + GatewayIP: gatewayIP, + GatewayMacAddress: "5a:94:ef:e4:0c:dd", + DHCPStaticLeases: map[string]string{ + vmIP: *mac, + }, + Forwards: forwards, + NAT: map[string]string{ + "192.168.127.254": "127.0.0.1", + }, + GatewayVirtualIPs: []string{"192.168.127.254"}, + Protocol: gvntypes.QemuProtocol, + } + vn, err := gvnvirtualnetwork.New(config) + if err != nil { + panic(err) + } + go func() { + var conn net.Conn + for i := 0; i < 100; i++ { + time.Sleep(1 * time.Second) + fmt.Fprintf(os.Stderr, "connecting to NW...\n") + conn, err = net.Dial("tcp", *wasiAddr) + if err == nil { + break + } + fmt.Fprintf(os.Stderr, "failed connecting to NW: %v; retrying...\n", err) + } + if conn == nil { + panic("failed to connect to vm") + } + // We register our VM network as a qemu "-netdev socket". + if err := vn.AcceptQemu(context.TODO(), conn); err != nil { + fmt.Fprintf(os.Stderr, "failed AcceptQemu: %v\n", err) + } + }() + u, err := url.Parse("dummy://" + *wasiAddr) + if err != nil { + panic(err) + } + p, err := strconv.Atoi(u.Port()) + if err != nil { + panic(err) + } + sockCfg := sock.NewConfig().WithTCPListener(u.Hostname(), p) + ctx = sock.WithConfig(ctx, sockCfg) + } c, err := os.ReadFile(args[0]) if err != nil { panic(err) @@ -57,15 +140,16 @@ func main() { if err != nil { panic(err) } - return } -type envFlags []string +type sliceFlags []string -func (i *envFlags) String() string { - return fmt.Sprintf("%v", []string(*i)) +func (f *sliceFlags) String() string { + var s []string = *f + return fmt.Sprintf("%v", s) } -func (i *envFlags) Set(value string) error { - *i = append(*i, value) + +func (f *sliceFlags) Set(value string) error { + *f = append(*f, value) return nil }