envoy icon indicating copy to clipboard operation
envoy copied to clipboard

[golang filter] Envoy returns 200 OK despite SendLocalReply is done with a different status code in EncodeData callback

Open daum3ns opened this issue 7 months ago • 3 comments

simple_filter.so.tar.gz

Bug in EncodeData callback of golang filter, Envoy returns 200 OK in EncodeData and not the status code set with SendLocalReply

Description: We found out that Envoy does not apply the Status code set with SendLocalReply by a golang filter, but sends 200 OK. This only happens in the EncodeData callback, (i.e. ResponseBody Phase), in all other callbacks the correct status code is returned. I would expect the response code set by the golang plugin in the access_logs and on the client side.

Repro steps: To reproduce you can use the attached shared object and load it in the envoy config like this:

[...]
          http_filters:
          - name: envoy.filters.http.golang
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config
              library_id: simple
              library_path: "./simple_filter.so"
              plugin_name: "simple_filter"
[...]

we also need a backend that returns some data, otherwise the phase is not executed in the golang filter, I used httpbin:

docker run -p 8080:80 kennethreitz/httpbin

now when we send a request to /bytes/10 (which returns 10 bytes), instead of 403 status we get 200:


curl  http://localhost:8443/bytes/10 -v 
< HTTP/1.1 200 OK
< server: envoy
< date: Fri, 06 Jun 2025 14:37:39 GMT
< content-type: application/octet-stream
< content-length: 10
< access-control-allow-origin: *
< access-control-allow-credentials: true
< x-envoy-upstream-service-time: 1
< 
* end of response with 10 bytes missing
* closing connection #0
curl: (18) end of response with 10 bytes missing

It's also visible in the access_log:

[2025-06-06T14:37:39.126Z] "GET /bytes/10 HTTP/1.1" 200 - 0 0 2 1 "-" "curl/8.13.0" "799404a9-6cbe-4b31-b768-9aa6c71b756c" "localhost:8443" "[::1]:8080"

Complete config:

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8443
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          access_log:
          - name: envoy.access_loggers.stdout
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
          http_filters:
          - name: envoy.filters.http.golang
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config
              library_id: simple
              library_path: "./lib/simple_filter.so"
              plugin_name: "simple_filter"
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: httpbin
  clusters:
  - name: httpbin
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: httpbin
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: localhost
                port_value: 8080

Filter Code: The Golang Plugin does nothing except returning http.StatusForbidden in the EncodeData callback. (Note: I tested it in all other functions, the problem is only in this specific callback)

func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType {
	api.LogInfo("DECODE HEADERS")
	return api.Continue
}

func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType {
	return api.Continue
}

func (f *filter) DecodeTrailers(trailers api.RequestTrailerMap) api.StatusType {
	return api.Continue
}

func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api.StatusType {
	return api.Continue
}

func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType {
	f.callbacks.EncoderFilterCallbacks().SendLocalReply(http.StatusForbidden, "", map[string][]string{}, 0, "")
	return api.LocalReply
}

func (f *filter) EncodeTrailers(trailers api.ResponseTrailerMap) api.StatusType {
	return api.Continue
}

daum3ns avatar Jun 06 '25 14:06 daum3ns

It's not a bug, since response header is already sent to downstream after returing api.Continue in EncodeHeaders. you could try return api.StopAndBuffer in EncodeHeaders, it will stop sent response header to downstream.

doujiang24 avatar Jun 07 '25 07:06 doujiang24

okay thanks for pointing that out.. but does this also apply to upstream? as far as i can tell no, i.e despite calling api.continue no headers arrive at the upstream server when i send a local reply in the body phase... Is it intended to behave that differently?

daum3ns avatar Jun 17 '25 12:06 daum3ns

SendLocalReply will response to client in DecodeHeader/Data, not data sent to upstream.

doujiang24 avatar Jun 18 '25 16:06 doujiang24

This issue has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in the next 7 days unless it is tagged "help wanted" or "no stalebot" or other activity occurs. Thank you for your contributions.

github-actions[bot] avatar Jul 18 '25 20:07 github-actions[bot]

This issue has been automatically closed because it has not had activity in the last 37 days. If this issue is still valid, please ping a maintainer and ask them to label it as "help wanted" or "no stalebot". Thank you for your contributions.

github-actions[bot] avatar Jul 26 '25 00:07 github-actions[bot]