3

I'm trying to run my WASM Go filter to make an external HTTP call using the net/http module. Envoy fails to load the WASM code. Why is the import failing?

Envoy/Istio version: istio/proxyv2:1.11.4

SDK version: v0.16.1-0.20220127085108-af57b89bc067

TinyGo version: tinygo version 0.22.0 darwin/amd64 (using go version go1.17.6 and LLVM version 13.0.0)

Error Logs

2022-01-31T20:34:18.513749Z error   envoy wasm  Failed to load Wasm module due to a missing import: env.time.resetTimer
2022-01-31T20:34:18.513794Z error   envoy wasm  Failed to load Wasm module due to a missing import: env.time.stopTimer
2022-01-31T20:34:18.513807Z error   envoy wasm  Failed to load Wasm module due to a missing import: env.time.startTimer
2022-01-31T20:34:18.513817Z error   envoy wasm  Failed to load Wasm module due to a missing import: env.sync/atomic.AddInt32
2022-01-31T20:34:18.513826Z error   envoy wasm  Failed to load Wasm module due to a missing import: wasi_snapshot_preview1.fd_filestat_get
2022-01-31T20:34:18.513833Z error   envoy wasm  Failed to load Wasm module due to a missing import: wasi_snapshot_preview1.fd_pread
2022-01-31T20:34:18.513840Z error   envoy wasm  Failed to load Wasm module due to a missing import: wasi_snapshot_preview1.fd_prestat_get
2022-01-31T20:34:18.513846Z error   envoy wasm  Failed to load Wasm module due to a missing import: wasi_snapshot_preview1.fd_prestat_dir_name
2022-01-31T20:34:18.513854Z error   envoy wasm  Failed to load Wasm module due to a missing import: wasi_snapshot_preview1.path_open
2022-01-31T20:34:18.513864Z error   envoy wasm  Wasm VM failed Failed to initialize Wasm code
2022-01-31T20:34:18.517062Z critical    envoy wasm  Plugin configured to fail closed failed to load
2022-01-31T20:34:18.517191Z warning envoy config    gRPC config for type.googleapis.com/envoy.config.core.v3.TypedExtensionConfig rejected: Unable to create Wasm HTTP filter

tinygo build -o main.wasm -scheduler=asyncify -target=wasi main.go

Actual Code

package main

import (
    "errors"

    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
    "io/ioutil"
    "time"
    "net/http"
)

const (
    sharedDataKey                 = "hello_world_shared_data_key"
)

func main() {
    proxywasm.SetVMContext(&vmContext{})
}

type (
    vmContext     struct{}
    pluginContext struct {
        // Embed the default plugin context here,
        // so that we don't need to reimplement all the methods.
        types.DefaultPluginContext
    }

    httpContext struct {
        // Embed the default http context here,
        // so that we don't need to reimplement all the methods.
        types.DefaultHttpContext
    }
)

// Override types.VMContext.
func (*vmContext) OnVMStart(vmConfigurationSize int) types.OnVMStartStatus {

    proxywasm.LogInfo("Inside OnVMStart")


    http := http.Client{Timeout: time.Duration(10) * time.Second}
    resp, err := http.Get("http://SOME_URL:8001/echo?message=hello_world")
    if err != nil {
        proxywasm.LogWarnf("Error calling hello_world/echo on OnVMStart: %v", err)
    }

    defer resp.Body.Close()
    
    body, err := ioutil.ReadAll(resp.Body)

    if err != nil {
        proxywasm.LogWarnf("Error parsing hello_world/echo response on OnVMStart: %v", err)
    }


    proxywasm.LogInfof("Response Body : %s", body)
    
    

    initialValueBuf := []byte("body")
    if err := proxywasm.SetSharedData(sharedDataKey, initialValueBuf, 0); err != nil {
        proxywasm.LogWarnf("Error setting shared hello_world data on OnVMStart: %v", err)
    }
    return types.OnVMStartStatusOK
}

// Override types.DefaultVMContext.
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
    return &pluginContext{}
}

// Override types.DefaultPluginContext.
func (*pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
    return &httpContext{}
}

// Override types.DefaultHttpContext.
func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
    for {
        value, err := ctx.getSharedData()
        if err == nil {
            proxywasm.LogInfof("shared data value: %s", value)
        } else if errors.Is(err, types.ErrorStatusCasMismatch) {
            continue
        }
        break
    }
    return types.ActionContinue
}

func (ctx *httpContext) getSharedData() (string, error) {
    value, cas, err := proxywasm.GetSharedData(sharedDataKey)
    if err != nil {
        proxywasm.LogWarnf("error getting shared data on OnHttpRequestHeaders with cas %d: %v ", cas, err)
        return "error", err
    }

    shared_value := string(value)
    
    return shared_value, err
}
enigma
  • 93
  • 1
  • 10
  • Tinygo says that http/net module is supported. https://tinygo.org/docs/reference/lang-support/stdlib/ – enigma Jan 31 '22 at 21:42

1 Answers1

5

Unfortunately, this is not so easy.

TinyGo might support the module, but you can't "just" call some arbitrary API when using a WASM module for Envoy.

To be slightly more precise, WASM modules run a in sandbox and can only make calls which are explicitly allowed by the runtime. In the case of Envoy, the wasm proxy sdk provides a simple mechanism to call those API.

proxy-wasm-go-sdk provides these API calls which you can use.

There is a function proxywasm.DispatchHttpCall. However, you have to "use the Envoy way" of making http calls.

Note that the "cluster" in that call is not a simple URL, but an Envoy Cluster. You might also try to use Istio-defined cluster like outbound|80||some-service.some-namespace.svc.cluster.local if you have any services defined with Istio Proxies.

You can look up the proxy-config, for example, for an ingress gateway, with istioctl:

istioctl proxy-config all istio-ingressgateway-YOUR-POD -o json | less

When adding ServiceEntries in Istio, you might also get such a "cluster" in your mesh. Note that Service Entries can also refer to external hosts, not only in-cluster services.

Otherwise, you might try adding a manual cluster like in an Envoy-based rate limiting, although this is also easy to get wrong.

- applyTo: CLUSTER
  match:
    cluster:
      service: ratelimit.default.svc.cluster.local
  patch:
    operation: ADD
    # Adds the rate limit service cluster for rate limit service defined in step 1.
    value:
      name: rate_limit_cluster
      type: STRICT_DNS
      connect_timeout: 10s
      lb_policy: ROUND_ROBIN
      http2_protocol_options: {}
      load_assignment:
        cluster_name: rate_limit_cluster
        endpoints:
        - lb_endpoints:
          - endpoint:
              address:
                 socket_address:
                  address: ratelimit.default.svc.cluster.local
                  port_value: 8081

In this description of Envoy Lua Filters, you see some examples. Although it is not WASM, the principle remains the same

For Go, you might try something like

headers := [][2]string{
    {":method", "GET"},
    {":path", "/echo?message=hello_world"},
    {":authority", "SOME_HOST"},
    {":scheme", "http"},
}

_, err := proxywasm.DispatchHttpCall("CLUSTER",
    headers,
    nil,
    nil,
    1000,
    func(numHeaders, bodySize, numTrailers int) {
        resp, _ := proxywasm.GetHttpCallResponseBody(0, 10000)
        r := string(resp)
        proxywasm.LogDebugf("RESPONSE %v", r)
    },
)
user140547
  • 7,750
  • 3
  • 28
  • 80
  • Got it! Thanks. Correct me if I'm wrong, so WASM can't make any external calls i.e. outside the K8s cluster. And to make calls to the services in the cluster the DispatchHttpCall should work. Right? FYI, I'm trying to hit something like a http://dummy.restapiexample.com/api/v1/employees on the web. – enigma Feb 01 '22 at 18:29
  • 1
    @enigma Added some more details. Basically WASM modules run in a sandbox and can only make calls explicitly allowed by the runtime. It should be possible to add "clusters" refering to external hosts though, for example using an Istio ServiceEntry (Note that this "cluster" is not the same thing as a k8s cluster). See examples at https://istio.io/latest/docs/reference/config/networking/service-entry/ – user140547 Feb 01 '22 at 19:33