Skip to content

Commit 5cd6716

Browse files
author
wangbaiping(wbpcode)
committed
my first try of rust
Signed-off-by: wangbaiping(wbpcode) <wangbaiping@bytedance.com>
1 parent da0057a commit 5cd6716

6 files changed

Lines changed: 229 additions & 4 deletions

File tree

Cargo.lock

Lines changed: 47 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[workspace]
2-
members = ["dynamic/rust/hello"]
2+
members = ["dynamic/rust/ip_restriction"]

dynamic/rust/hello/src/lib.rs

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
[package]
3-
name = "envoy-proxy-dynamic-modules-rust-hello"
3+
name = "envoy-proxy-dynamic-modules-rust-ip-restriction"
44
version = "0.0.1"
55
edition = "2021"
66
license = "Apache-2.0"
@@ -9,8 +9,10 @@ repository = "https://github.com/envoyproxy/modules"
99
[dependencies]
1010
# The SDK version must match the Envoy version due to the strict compatibility requirements.
1111
envoy-proxy-dynamic-modules-rust-sdk = { git = "https://github.com/envoyproxy/envoy", rev = "73fe00fc139fd5053f4c4a5d66569cc254449896" }
12+
serde = { version = "1.0", features = ["derive"] }
13+
serde_json = "1.0"
1214

1315
[lib]
14-
name = "hello_proxy"
16+
name = "ip_restriction"
1517
path = "src/lib.rs"
1618
crate-type = ["cdylib"]
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
use envoy_proxy_dynamic_modules_rust_sdk::*;
2+
use serde::{Deserialize, Serialize};
3+
use std::collections::HashSet;
4+
use std::net::Ipv4Addr;
5+
use std::net::Ipv6Addr;
6+
use std::str::FromStr;
7+
use std::sync::Arc;
8+
9+
// The raw filter config that will be deserialized from the JSON configuration.
10+
// TODO(wbpcode): To support protobuf based API declaration in the future.
11+
// TODO(wbpcode): to support ip range in the future.
12+
#[derive(Serialize, Deserialize, Debug)]
13+
pub struct RawFilterConfig {
14+
denied_addresses: HashSet<String>,
15+
allowed_addresses: HashSet<String>,
16+
}
17+
18+
#[derive(Debug)]
19+
pub struct FilterConfigImpl {
20+
denied_addresses_exact: HashSet<String>,
21+
allowed_addresses_exact: HashSet<String>,
22+
}
23+
24+
// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilterConfig`] trait.
25+
//
26+
// The trait corresponds to a Envoy filter chain configuration.
27+
#[derive(Debug, Clone)]
28+
pub struct FilterConfig {
29+
config: Arc<FilterConfigImpl>,
30+
}
31+
32+
impl FilterConfig {
33+
/// This is the constructor for the [`FilterConfig`].
34+
///
35+
/// filter_config is the filter config from the Envoy config here:
36+
/// https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/dynamic_modules/v3/dynamic_modules.proto#envoy-v3-api-msg-extensions-dynamic-modules-v3-dynamicmoduleconfig
37+
pub fn new(filter_config: &str) -> Option<Self> {
38+
let filter_config: RawFilterConfig = match serde_json::from_str(filter_config) {
39+
Ok(cfg) => cfg,
40+
Err(err) => {
41+
eprintln!("Error parsing filter config: {err}");
42+
return None;
43+
},
44+
};
45+
46+
// One and only one of denied_addresses and allowed_addresses should be set.
47+
if filter_config.denied_addresses.is_empty() != filter_config.allowed_addresses.is_empty() {
48+
eprintln!(
49+
"Error parsing filter config: one and only one of denied_addresses\
50+
and allowed_addresses should be set"
51+
);
52+
return None;
53+
}
54+
55+
let mut denied_addresses_exact = HashSet::new();
56+
let mut allowed_addresses_exact = HashSet::new();
57+
58+
// Validate every ip in the set is a valid IPv4 address or IPv6 address.
59+
for ip in &filter_config.allowed_addresses {
60+
if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() {
61+
eprintln!("Error parsing ip in allowed_addresses: {ip}");
62+
return None;
63+
}
64+
allowed_addresses_exact.insert(ip.clone());
65+
}
66+
for ip in &filter_config.denied_addresses {
67+
if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() {
68+
eprintln!("Error parsing ip in denied_addresses: {ip}");
69+
return None;
70+
}
71+
denied_addresses_exact.insert(ip.clone());
72+
}
73+
74+
Some(FilterConfig {
75+
config: Arc::new(FilterConfigImpl {
76+
denied_addresses_exact: denied_addresses_exact,
77+
allowed_addresses_exact: allowed_addresses_exact,
78+
}),
79+
})
80+
}
81+
}
82+
83+
impl<EC: EnvoyHttpFilterConfig, EHF: EnvoyHttpFilter> HttpFilterConfig<EC, EHF> for FilterConfig {
84+
/// This is called for each new HTTP filter.
85+
fn new_http_filter(&mut self, _envoy: &mut EC) -> Box<dyn HttpFilter<EHF>> {
86+
Box::new(Filter {
87+
filter_config: self.clone(),
88+
})
89+
}
90+
}
91+
92+
/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilter`] trait.
93+
///
94+
/// This sets the request and response headers to the values specified in the filter config.
95+
pub struct Filter {
96+
// The filter config have longer lifetime than the filter.
97+
filter_config: FilterConfig,
98+
}
99+
100+
/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilter`] trait.
101+
impl<EHF: EnvoyHttpFilter> HttpFilter<EHF> for Filter {
102+
fn on_request_headers(
103+
&mut self,
104+
envoy_filter: &mut EHF,
105+
_end_stream: bool,
106+
) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status {
107+
let downstream_addr =
108+
envoy_filter.get_attribute_string(abi::envoy_dynamic_module_type_attribute_id::SourceAddress);
109+
let downstream_port =
110+
envoy_filter.get_attribute_int(abi::envoy_dynamic_module_type_attribute_id::SourcePort);
111+
112+
if downstream_addr.is_none() || downstream_port.is_none() {
113+
envoy_filter.send_response(
114+
403,
115+
vec![],
116+
Some(b"No remote address and request is forbidden."),
117+
);
118+
return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration;
119+
}
120+
121+
let mut downstream_addr_str = String::new();
122+
let address_buffer = downstream_addr.unwrap();
123+
let downstream_addr_slice = address_buffer.as_slice();
124+
125+
if downstream_port.is_none() {
126+
// Covert the slice of downstream addr to string.
127+
unsafe {
128+
downstream_addr_str
129+
.as_mut_vec()
130+
.extend_from_slice(downstream_addr_slice);
131+
}
132+
} else {
133+
// Strip the port from the downstream addr.
134+
let downstream_addr_slice = &downstream_addr_slice
135+
[0..downstream_addr_slice.len() - downstream_port.unwrap().to_string().len() - 1];
136+
137+
unsafe {
138+
downstream_addr_str
139+
.as_mut_vec()
140+
.extend_from_slice(downstream_addr_slice);
141+
}
142+
}
143+
144+
// Check if the downstream addr is in the allowed list.
145+
if !self.filter_config.config.allowed_addresses_exact.is_empty() {
146+
if !self
147+
.filter_config
148+
.config
149+
.allowed_addresses_exact
150+
.contains(&downstream_addr_str)
151+
{
152+
envoy_filter.send_response(403, vec![], Some(b"Request is forbidden."));
153+
return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration;
154+
}
155+
}
156+
157+
// Check if the downstream addr is in the denied list.
158+
if !self.filter_config.config.denied_addresses_exact.is_empty() {
159+
if self
160+
.filter_config
161+
.config
162+
.denied_addresses_exact
163+
.contains(&downstream_addr_str)
164+
{
165+
envoy_filter.send_response(403, vec![], Some(b"Request is forbidden."));
166+
return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration;
167+
}
168+
}
169+
170+
abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue
171+
}
172+
}
173+
174+
#[cfg(test)]
175+
mod tests {
176+
use super::*;
177+
}

0 commit comments

Comments
 (0)