Generic proxy
The network filter could be used to add multiple protocols support to Envoy. The community has implemented lots of this kind of network filter, such as Dubbo proxy, Thrift proxy, etc. Developers may also implement their own protocol proxying by creating a new network filter.
Adding a new network filter to support a new protocol is not easy. Developers need to implement the codec to parse the binary, to implement the stream management to handle request/response, to implement the connection management to handle connection lifecycle, etc. There are many common features like route, L7 filter chain, tracing, metrics, logging, etc. that need to be implemented again and again in each new network filter.
Many RPC protocols has a similar request/response based architecture. The request could be routed to different upstreams based on the request properties. For these similar protocols, their network filters also are similar and could be abstracted to following modules:
Codec: parse the binary to request/response object.
Stream management: handle the request/response stream.
Route: route the request to different upstream based on the request properties.
L7 filter chain: filter the request/response.
Observability: tracing, metrics, logging, etc.
With the exception of the codec, all other modules are common and could be shared by different protocols. This is the motivation of generic proxy.
Generic proxy is a network filter that could be used to implement new protocol proxying. An extension point is provided to let the users configure specific codec. The developers only need to implement the codec to parse the binary and let users configure the codec in the generic proxy. The generic proxy will handle the rest of work.
Abstract request/response
Abstraction of the request/response is the core of generic proxy. Different L7 protocols may have a different request/response data structure. In the generic proxy, the codec is extended and could be configured by users, but other modules are common and can be shared by different protocols. These different request/response data structures of different protocols need to be abstracted and managed in a common abstraction.
An abstract Request
class is defined to represent the request. The Request
provides some virtual methods to get/set the request properties.
Different protocols can extend the Request
and implement the virtual methods to get/set the request properties in their own data structure.
An abstract class Response
is defined to represent the response. It is similar to Request
.
Based on the abstraction of the request/response, the generic proxy can handle the request/response stream, route the request to different upstreams, filter the request/response, etc. without needing to know the L7 application and specific data structure of the request/response.
If the developers want to implement a new protocol proxying, they only need to implement the codec to parse the binary data to specific request/response
and ensure they implement Request
or Response
. It is much easier than implement a new network filter.
Extendable matcher and route
Generic matcher API is used to construct the route table of the generic proxy. The developers could extend input and matcher to support new matching logic.
By default, the generic proxy supports following input and matcher:
host: match the host of the request. The host should represents set of instances that can be used for load balancing. It could be DNS name or VIP of the target service.
path: match the path of the request. The path should represents RPC service name that used to represents set of method or functionality provided by target service.
method: match the method of the request.
property: match the property of the request. The property could be any property of the request that indexed by string key.
request input and request matcher: match the whole request by combining host, path, method and properties in AND semantics. This is used to match multiple fields of the downstream request and avoid complex combinations of host, path, method and properties in the generic matching tree.
Note
Virtual methods are used to get the request properties in Request
like host()
, path()
, method()
, etc. The developers need to
implement these virtual methods and determine what these fields are in their own protocol. It is possible that a protocol does not have some of
these fields. For example, the protocol does not have host
field. In this case, the developers could return empty string in the virtual method
to indicate the field does not exist in the protocol. The only drawback is that the generic proxy could not match the request by this field.
21 "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.codecs.dubbo.v3.DubboCodecConfig
22 route_config:
23 name: route_config
24 virtual_hosts:
25 - name: route_config_default_virtual_host
26 hosts:
27 - "org.apache.dubbo.UserProvider"
28 routes:
29 matcher_list:
30 matchers:
31 - predicate:
32 single_predicate:
33 input:
34 name: request
35 typed_config:
36 "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.matcher.v3.RequestMatchInput
37 custom_match:
38 name: request
39 typed_config:
40 "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.matcher.v3.RequestMatcher
41 host:
42 exact: "org.apache.dubbo.UserProvider"
43 method:
44 exact: "getUser"
45 properties:
46 - name: "id"
47 string_match:
48 exact: "1"
49 on_match:
50 action:
51 name: route
52 typed_config:
53 "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.action.v3.RouteAction
Asynchronous codec API
The generic proxy provides an extension point to let the developers implement a codec specific to their own protocol. The codec API is designed to be asynchronous to avoid blocking the worker thread. And the asynchronous codec API makes it possible to accelerate the codec by offloading the parsing work to specific hardware.
Configurable connection
Different protocols may have different connection lifecycles or connection management. The generic proxy provides additional options to let codec developers configure the connection lifecycle and connection management.
For example, developers can configure whether the upstream connection is bound to the downstream connection or not. If the upstream connection is bound to the downstream connection, the upstream connection will have same lifetime as the downstream connection. The bound upstream connection will be used only by requests that come from the related downstream connection. This is useful for the protocols that need to keep the connection state.
Developers can also operate the downstream connection and upstream connection in the codec directly. This gives developers more control over the connection.
Example codec implementation
The community has implemented a dubbo codec based on generic proxy. The dubbo codec is a good example showing how to implement a new codec for new protocol because of its moderate complexity.
You could find the dubbo codec implementation in contrib/generic_proxy/filters/network/source/codecs/dubbo directory. You can also configure the dubbo codec in the generic proxy with the following configuration:
1static_resources:
2 listeners:
3 - name: main
4 address:
5 socket_address:
6 address: 0.0.0.0
7 port_value: 9090
8 filter_chains:
9 - filters:
10 - name: generic_proxy
11 typed_config:
12 "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.v3.GenericProxy
13 stat_prefix: stats_prefix
14 filters:
15 - name: envoy.filters.generic.router
16 typed_config:
17 "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.router.v3.Router
18 codec_config:
19 name: http
20 typed_config:
21 "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.codecs.dubbo.v3.DubboCodecConfig
22 route_config:
23 name: route_config
24 virtual_hosts:
25 - name: route_config_default_virtual_host
26 hosts:
27 - "org.apache.dubbo.UserProvider"
28 routes:
29 matcher_list:
30 matchers:
31 - predicate:
32 single_predicate:
33 input:
34 name: request
35 typed_config:
36 "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.matcher.v3.RequestMatchInput
37 custom_match:
38 name: request
39 typed_config:
40 "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.matcher.v3.RequestMatcher
41 host:
42 exact: "org.apache.dubbo.UserProvider"
43 method:
44 exact: "getUser"
45 properties:
46 - name: "id"
47 string_match:
48 exact: "1"
49 on_match:
50 action:
51 name: route
52 typed_config:
53 "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.action.v3.RouteAction
54 cluster: dubbo
55 clusters:
56 - name: dubbo
57 connect_timeout: 5s
58 type: STRICT_DNS
59 load_assignment:
60 cluster_name: dubbo
61 endpoints:
62 - lb_endpoints:
63 - endpoint:
64 address:
65 socket_address:
66 address: localhost
67 port_value: 8080