Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ RUN CC="zig cc -target aarch64-linux-gnu" CXX="zig c++ -target aarch64-linux-gnu
RUN CC="zig cc -target x86_64-linux-gnu" CXX="zig c++ -target x86_64-linux-gnu" CGO_ENABLED=1 GOARCH=amd64 go build -buildmode=c-shared -o /build/amd64_libgo_module.so .

##### Build the final image #####
FROM envoyproxy/envoy:v1.37.0 AS envoy
FROM envoyproxy/envoy:v1.38.0 AS envoy
ARG TARGETARCH
ENV ENVOY_DYNAMIC_MODULES_SEARCH_PATH=/usr/local/lib
COPY --from=rust_builder /build/${TARGETARCH}_librust_module.so /usr/local/lib/librust_module.so
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Dynamic Modules Examples

> Envoy Version: v1.37.0
> Envoy Version: v1.38.0
>
> Since dynamic modules are tied with a specific Envoy version, this repository is based on the specific commit of Envoy.
> For examples for a specific Envoy version, please check out `release/v<version>` branches:
Expand Down
3 changes: 3 additions & 0 deletions go/delay.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func (p *delayFilterFactory) Create(handle shared.HttpFilterHandle) shared.HttpF
return &delayFilter{handle: handle}
}

// OnDestroy implements [shared.HttpFilterFactory].
func (p *delayFilterFactory) OnDestroy() {}

// OnRequestHeaders implements [shared.HttpFilter].
func (p *delayFilter) OnRequestHeaders(headers shared.HeaderMap, endOfStream bool) shared.HeadersStatus {
// Check if the headers contain the "do-delay" header to trigger the delay.
Expand Down
2 changes: 1 addition & 1 deletion go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ tool (

require (
github.com/dop251/goja v0.0.0-20250630131328-58d95d85e994
github.com/envoyproxy/envoy/source/extensions/dynamic_modules v0.0.0-20260129014508-e8c1dc7dcbcd
github.com/envoyproxy/envoy/source/extensions/dynamic_modules v0.0.0-20260423231439-f1dd21b16c24
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ github.com/dop251/goja v0.0.0-20250630131328-58d95d85e994 h1:aQYWswi+hRL2zJqGacd
github.com/dop251/goja v0.0.0-20250630131328-58d95d85e994/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/envoyproxy/envoy/source/extensions/dynamic_modules v0.0.0-20260129014508-e8c1dc7dcbcd h1:IQMTVU/I2HaXkUUYvHD3gtUrFAGLZm8DcH/4Z20vRQQ=
github.com/envoyproxy/envoy/source/extensions/dynamic_modules v0.0.0-20260129014508-e8c1dc7dcbcd/go.mod h1:NpQosaDAX20s0ak0o/4b5dLOdvkbk15XqoakhSNX1Gg=
github.com/envoyproxy/envoy/source/extensions/dynamic_modules v0.0.0-20260423231439-f1dd21b16c24 h1:NM/c/D2pvvSpEqmF2p8tWnZPbeiZ1B8gS5TPH764qao=
github.com/envoyproxy/envoy/source/extensions/dynamic_modules v0.0.0-20260423231439-f1dd21b16c24/go.mod h1:NpQosaDAX20s0ak0o/4b5dLOdvkbk15XqoakhSNX1Gg=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
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=
Expand Down
7 changes: 5 additions & 2 deletions go/header_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,17 @@ func (p *headerAuthFilterFactory) Create(handle shared.HttpFilterHandle) shared.
return &headerAuthFilter{handle: handle, authHeaderName: p.authHeaderName}
}

// OnDestroy implements [shared.HttpFilterFactory].
func (p *headerAuthFilterFactory) OnDestroy() {}

// OnRequestHeaders implements [shared.HttpFilter].
func (p *headerAuthFilter) OnRequestHeaders(headers shared.HeaderMap, endOfStream bool) shared.HeadersStatus {
v := headers.GetOne(p.authHeaderName)
if v == "" {
if v.Len == 0 {
p.handle.SendLocalResponse(http.StatusUnauthorized, [][2]string{{"Content-Type", "text/plain"}}, []byte("Unauthorized by Go Module at on_request_headers\n"), "unauthorized")
return shared.HeadersStatusStop
}
p.sendOnResponseHeaderPhase = v == "on_response_headers"
p.sendOnResponseHeaderPhase = v.ToString() == "on_response_headers"
return shared.HeadersStatusContinue
}

Expand Down
7 changes: 5 additions & 2 deletions go/javascript.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ func (p *javaScriptFilterConfigFactory) Create(handle shared.HttpFilterConfigHan
return c, nil
}

// OnDestroy implements [shared.HttpFilterFactory].
func (p *javaScriptFilterFactory) OnDestroy() {}

// Create implements [shared.HttpFilterFactory].
func (p *javaScriptFilterFactory) Create(handle shared.HttpFilterHandle) shared.HttpFilter {
vm := p.vms[rand.Intn(numberOfVMPool)]
Expand Down Expand Up @@ -122,7 +125,7 @@ func newJavaScriptVM(script string, w io.Writer) (*javaScriptVM, error) {
// OnRequestHeaders implements [shared.HttpFilter].
func (p *javaScriptFilter) OnRequestHeaders(headers shared.HeaderMap, _ bool) shared.HeadersStatus {
for _, header := range headers.GetAll() {
p.requestHeaders[header[0]] = header[1]
p.requestHeaders[header[0].ToString()] = header[1].ToString()
}
p.vm.mux.Lock()
defer p.vm.mux.Unlock()
Expand Down Expand Up @@ -155,7 +158,7 @@ func (p *javaScriptFilter) OnRequestHeaders(headers shared.HeaderMap, _ bool) sh
// OnResponseHeaders implements [shared.HttpFilter].
func (p *javaScriptFilter) OnResponseHeaders(headers shared.HeaderMap, _ bool) shared.HeadersStatus {
for _, header := range headers.GetAll() {
p.responseHeaders[header[0]] = header[1]
p.responseHeaders[header[0].ToString()] = header[1].ToString()
}
p.vm.mux.Lock()
defer p.vm.mux.Unlock()
Expand Down
31 changes: 17 additions & 14 deletions go/passthrough.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,23 @@ func (p *passthroughFilterFactory) Create(handle shared.HttpFilterHandle) shared
return &passthroughFilter{handle: handle}
}

// OnDestroy implements [shared.HttpFilterFactory].
func (p *passthroughFilterFactory) OnDestroy() {}

// OnRequestHeaders implements [shared.HttpFilter].
func (p *passthroughFilter) OnRequestHeaders(headers shared.HeaderMap, endOfStream bool) shared.HeadersStatus {
fooValue := headers.GetOne("foo")
fmt.Printf("gosdk: RequestHeaders, foo: %v\n", fooValue)
fmt.Printf("gosdk: RequestHeaders, foo: %v\n", fooValue.ToString())
fmt.Printf("gosdk: RequestHeaders, endOfStream: %v\n", endOfStream)
for _, header := range headers.GetAll() {
fmt.Printf("gosdk: RequestHeaders, header: %s: %s\n", header[0], header[1])
fmt.Printf("gosdk: RequestHeaders, header: %s: %s\n", header[0].ToString(), header[1].ToString())
}
sourceAddr, _ := p.handle.GetAttributeString(shared.AttributeIDSourceAddress)
destAddr, _ := p.handle.GetAttributeString(shared.AttributeIDDestinationAddress)
protocol, _ := p.handle.GetAttributeString(shared.AttributeIDRequestProtocol)
fmt.Printf("gosdk: RequestHeaders, source address: %s\n", sourceAddr)
fmt.Printf("gosdk: RequestHeaders, destination address: %s\n", destAddr)
fmt.Printf("gosdk: RequestHeaders, request protocol: %s\n", protocol)
fmt.Printf("gosdk: RequestHeaders, source address: %s\n", sourceAddr.ToString())
fmt.Printf("gosdk: RequestHeaders, destination address: %s\n", destAddr.ToString())
fmt.Printf("gosdk: RequestHeaders, request protocol: %s\n", protocol.ToString())
return shared.HeadersStatusContinue
}

Expand All @@ -57,15 +60,15 @@ func (p *passthroughFilter) OnRequestBody(body shared.BodyBuffer, endOfStream bo
chunks := body.GetChunks()
var original []byte
for _, chunk := range chunks {
original = append(original, chunk...)
original = append(original, chunk.ToBytes()...)
}
fmt.Printf("gosdk: RequestBody, body: %s\n", original)
body.Drain(uint64(len(original)))
body.Append([]byte("hello world"))
chunks = body.GetChunks()
var modified []byte
for _, chunk := range chunks {
modified = append(modified, chunk...)
modified = append(modified, chunk.ToBytes()...)
}
if string(modified) != "hello world" {
panic("request body should be modified")
Expand All @@ -77,7 +80,7 @@ func (p *passthroughFilter) OnRequestBody(body shared.BodyBuffer, endOfStream bo
chunks = body.GetChunks()
modified = nil
for _, chunk := range chunks {
modified = append(modified, chunk...)
modified = append(modified, chunk.ToBytes()...)
}
if string(modified) != string(original) {
panic("request body should be modified")
Expand All @@ -88,13 +91,13 @@ func (p *passthroughFilter) OnRequestBody(body shared.BodyBuffer, endOfStream bo
// OnResponseHeaders implements [shared.HttpFilter].
func (p *passthroughFilter) OnResponseHeaders(headers shared.HeaderMap, endOfStream bool) shared.HeadersStatus {
status := headers.GetOne(":status")
if status == "" {
if status.Len == 0 {
panic("x-status header should be set")
}
fmt.Printf("gosdk: ResponseHeaders, status: %v\n", status)
fmt.Printf("gosdk: ResponseHeaders, status: %v\n", status.ToString())
headers.Set("x-passthrough-response-header", "true")
for _, header := range headers.GetAll() {
fmt.Printf("gosdk: ResponseHeaders, header: %s: %s\n", header[0], header[1])
fmt.Printf("gosdk: ResponseHeaders, header: %s: %s\n", header[0].ToString(), header[1].ToString())
}
return shared.HeadersStatusContinue
}
Expand All @@ -109,15 +112,15 @@ func (p *passthroughFilter) OnResponseBody(body shared.BodyBuffer, endOfStream b
chunks := body.GetChunks()
var original []byte
for _, chunk := range chunks {
original = append(original, chunk...)
original = append(original, chunk.ToBytes()...)
}
fmt.Printf("gosdk: ResponseBody, body: %s\n", original)
body.Drain(uint64(len(original)))
body.Append([]byte("hello world"))
chunks = body.GetChunks()
var modified []byte
for _, chunk := range chunks {
modified = append(modified, chunk...)
modified = append(modified, chunk.ToBytes()...)
}
if string(modified) != "hello world" {
panic("response body should be modified")
Expand All @@ -128,7 +131,7 @@ func (p *passthroughFilter) OnResponseBody(body shared.BodyBuffer, endOfStream b
chunks = body.GetChunks()
modified = nil
for _, chunk := range chunks {
modified = append(modified, chunk...)
modified = append(modified, chunk.ToBytes()...)
}
if string(modified) != string(original) {
panic("response body should be modified")
Expand Down
2 changes: 1 addition & 1 deletion integration/.envoy-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.37.0
1.38.0
2 changes: 1 addition & 1 deletion rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ repository = "https://github.com/envoyproxy/dynamic-modules-example"

[dependencies]
# The SDK version must match the Envoy version due to the strict compatibility requirements.
envoy-proxy-dynamic-modules-rust-sdk = { git = "https://github.com/envoyproxy/envoy", rev = "6d9bb7d9a85d616b220d1f8fe67b61f82bbdb8d3" }
envoy-proxy-dynamic-modules-rust-sdk = { git = "https://github.com/envoyproxy/envoy", rev = "f1dd21b16c244bda00edfb5ffce577e12d0d2ec2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rand = "0.9.0"
Expand Down
11 changes: 7 additions & 4 deletions rust/src/http_access_logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,13 @@ mod tests {
let mut envoy_filter = envoy_proxy_dynamic_modules_rust_sdk::MockEnvoyHttpFilter::new();
envoy_filter
.expect_get_request_headers()
.returning(|| vec![(EnvoyBuffer::new("host"), EnvoyBuffer::new("example.com"))]);
envoy_filter
.expect_get_response_headers()
.returning(|| vec![(EnvoyBuffer::new("content-length"), EnvoyBuffer::new("123"))]);
.returning(|| vec![(EnvoyBuffer::new(b"host"), EnvoyBuffer::new(b"example.com"))]);
envoy_filter.expect_get_response_headers().returning(|| {
vec![(
EnvoyBuffer::new(b"content-length"),
EnvoyBuffer::new(b"123"),
)]
});
access_logger_filter.on_request_headers(&mut envoy_filter, false);
access_logger_filter.on_response_headers(&mut envoy_filter, false);

Expand Down
4 changes: 2 additions & 2 deletions rust/src/http_header_mutation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ mod tests {
.expect_get_attribute_string()
.returning(|id| match id {
abi::envoy_dynamic_module_type_attribute_id::SourceAddress => {
return Some(EnvoyBuffer::new("1.1.1.1:12345"));
return Some(EnvoyBuffer::new(b"1.1.1.1:12345"));
}
abi::envoy_dynamic_module_type_attribute_id::UpstreamAddress => {
return Some(EnvoyBuffer::new("2.2.2.2:12345"));
return Some(EnvoyBuffer::new(b"2.2.2.2:12345"));
}
_ => panic!("Unexpected attribute id"),
});
Expand Down
2 changes: 1 addition & 1 deletion rust/src/http_random_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl<EHF: EnvoyHttpFilter> HttpFilter<EHF> for Filter {
) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status {
let reject = rand::rng().random::<bool>();
if reject {
envoy_filter.send_response(403, vec![], Some(b"Access forbidden"), None);
envoy_filter.send_response(403, &[], Some(b"Access forbidden"), None);
return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration;
}
abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue
Expand Down
10 changes: 5 additions & 5 deletions rust/src/http_zero_copy_regex_waf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl<EHF: EnvoyHttpFilter> HttpFilter<EHF> for Filter {
.expect("Failed to do regex match");
if matched {
// If the regex matches, we send a 403 response.
envoy_filter.send_response(403, vec![], Some(b"Access forbidden"), None);
envoy_filter.send_response(403, &[], Some(b"Access forbidden"), None);
return abi::envoy_dynamic_module_type_on_http_filter_request_body_status::StopIterationNoBuffer;
}
abi::envoy_dynamic_module_type_on_http_filter_request_body_status::Continue
Expand Down Expand Up @@ -141,8 +141,8 @@ mod tests {
static mut HELLO: [u8; 6] = *b"Hello ";
static mut WORLD: [u8; 6] = *b"World!";
Some(vec![
EnvoyMutBuffer::new(unsafe { &mut HELLO }),
EnvoyMutBuffer::new(unsafe { &mut WORLD }),
unsafe { EnvoyMutBuffer::new(&raw mut HELLO) },
unsafe { EnvoyMutBuffer::new(&raw mut WORLD) },
])
})
.times(1);
Expand All @@ -160,8 +160,8 @@ mod tests {
static mut GOOD: [u8; 5] = *b"Good ";
static mut MORNING: [u8; 8] = *b"Morning!";
Some(vec![
EnvoyMutBuffer::new(unsafe { &mut GOOD }),
EnvoyMutBuffer::new(unsafe { &mut MORNING }),
unsafe { EnvoyMutBuffer::new(&raw mut GOOD) },
unsafe { EnvoyMutBuffer::new(&raw mut MORNING) },
])
})
.times(1);
Expand Down
41 changes: 40 additions & 1 deletion rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(unpredictable_function_pointer_comparisons)] // <- can be removed once https://github.com/envoyproxy/envoy/pull/44654 is released

//! Envoy Dynamic Modules Rust SDK Examples
//!
//! This crate contains example implementations of Envoy dynamic modules using the Rust SDK.
Expand Down Expand Up @@ -60,7 +62,14 @@ pub mod listener_ip_allowlist;
pub mod listener_sni_router;
pub mod listener_tls_detector;

declare_init_functions!(init, new_http_filter_config_fn);
// DNS gateway filter examples.
pub mod dns_gateway;

declare_all_init_functions!(init,
http: new_http_filter_config_fn,
network: new_network_filter_config_fn,
udp_listener: new_udp_listener_filter_config_fn
);

/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::ProgramInitFunction`].
///
Expand Down Expand Up @@ -102,3 +111,33 @@ fn new_http_filter_config_fn<EC: EnvoyHttpFilterConfig, EHF: EnvoyHttpFilter>(
_ => panic!("Unknown filter name: {filter_name}"),
}
}

fn new_network_filter_config_fn<EC: EnvoyNetworkFilterConfig, ENF: EnvoyNetworkFilter>(
_envoy_filter_config: &mut EC,
filter_name: &str,
filter_config: &[u8],
) -> Option<Box<dyn NetworkFilterConfig<ENF>>> {
match filter_name {
"cache_lookup" => Some(Box::new(
dns_gateway::cache_lookup::CacheLookupFilterConfig::new(filter_config),
)),
_ => panic!("Unknown network filter name: {filter_name}"),
}
}

fn new_udp_listener_filter_config_fn<
EC: EnvoyUdpListenerFilterConfig,
ELF: EnvoyUdpListenerFilter,
>(
_envoy_filter_config: &mut EC,
filter_name: &str,
filter_config: &[u8],
) -> Option<Box<dyn UdpListenerFilterConfig<ELF>>> {
match filter_name {
"dns_gateway" => {
let config = dns_gateway::DnsGatewayFilterConfig::new(filter_config)?;
Some(Box::new(config))
}
_ => panic!("Unknown UDP listener filter name: {filter_name}"),
}
}
20 changes: 10 additions & 10 deletions rust/src/network_rate_limiter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,16 +218,16 @@ impl<ENF: EnvoyNetworkFilter> NetworkFilter<ENF> for RateLimiterFilter {
) {
match event {
abi::envoy_dynamic_module_type_network_connection_event::RemoteClose
| abi::envoy_dynamic_module_type_network_connection_event::LocalClose => {
if self.connection_counted {
let previous = self
.shared_state
.active_connections
.fetch_sub(1, Ordering::SeqCst);
let _ = envoy_filter
.set_gauge(self.active_connections_gauge, previous.saturating_sub(1));
envoy_log_debug!("Connection closed. Active connections: {}", previous - 1);
}
| abi::envoy_dynamic_module_type_network_connection_event::LocalClose
if self.connection_counted =>
{
let previous = self
.shared_state
.active_connections
.fetch_sub(1, Ordering::SeqCst);
let _ = envoy_filter
.set_gauge(self.active_connections_gauge, previous.saturating_sub(1));
envoy_log_debug!("Connection closed. Active connections: {}", previous - 1);
}
_ => {}
}
Expand Down
Loading