@@ -47,9 +47,21 @@ pub fn new_udp_filter_config<EC: EnvoyUdpListenerFilterConfig, ELF: EnvoyUdpList
4747 _name : & str ,
4848 config : & [ u8 ] ,
4949) -> Option < Box < dyn UdpListenerFilterConfig < ELF > > > {
50- // Parse config as JSON
50+ // Parse config as JSON. The config arrives as a JSON-serialized google.protobuf.Any.
51+ // Supported wrappers:
52+ // - StringValue: {"@type":"...StringValue", "value":"<json string>"}
53+ // - Struct: {"@type":"...Struct", "value":{"base_ip":"...", ...}}
5154 let config_str = std:: str:: from_utf8 ( config) . ok ( ) ?;
52- let config_json: serde_json:: Value = serde_json:: from_str ( config_str) . ok ( ) ?;
55+ let outer_json: serde_json:: Value = serde_json:: from_str ( config_str) . ok ( ) ?;
56+
57+ let config_json: serde_json:: Value = match & outer_json[ "value" ] {
58+ // StringValue: "value" is a JSON string that we parse again.
59+ serde_json:: Value :: String ( s) => serde_json:: from_str ( s) . ok ( ) ?,
60+ // Struct: "value" is already an object with our config fields.
61+ serde_json:: Value :: Object ( _) => outer_json[ "value" ] . clone ( ) ,
62+ // Fallback: use the outer object directly.
63+ _ => outer_json,
64+ } ;
5365
5466 // Parse base_ip from config (default: 10.10.0.0)
5567 let base_ip_str = config_json[ "base_ip" ]
@@ -114,49 +126,63 @@ impl<ELF: EnvoyUdpListenerFilter> UdpListenerFilter<ELF> for DnsGatewayFilter {
114126 envoy_filter : & mut ELF ,
115127 ) -> abi:: envoy_dynamic_module_type_on_udp_listener_filter_status {
116128 // Get datagram data
117- let ( chunks, _total_length) = envoy_filter. get_datagram_data ( ) ;
129+ let ( chunks, total_length) = envoy_filter. get_datagram_data ( ) ;
130+ envoy_log_info ! ( "dns_gateway: received UDP datagram, {} bytes, {} chunks" , total_length, chunks. len( ) ) ;
118131 let mut data = Vec :: new ( ) ;
119132 for chunk in & chunks {
120133 data. extend_from_slice ( chunk. as_slice ( ) ) ;
121134 }
122135
136+ // Get peer address for sending the response back
137+ let peer = envoy_filter. get_peer_address ( ) ;
138+ envoy_log_info ! ( "dns_gateway: peer address: {:?}" , peer) ;
139+
123140 // Parse DNS query using hickory-dns
124141 let mut decoder = BinDecoder :: new ( & data) ;
125142 let query_message = match Message :: read ( & mut decoder) {
126143 Ok ( msg) => msg,
127144 Err ( e) => {
128- envoy_log_warn ! ( "Failed to parse DNS query: {}" , e) ;
145+ envoy_log_warn ! ( "dns_gateway: failed to parse DNS query: {}" , e) ;
129146 return abi:: envoy_dynamic_module_type_on_udp_listener_filter_status:: Continue ;
130147 }
131148 } ;
132149
150+ envoy_log_info ! ( "dns_gateway: parsed DNS message id={}, type={:?}, queries={}" ,
151+ query_message. id( ) , query_message. message_type( ) , query_message. queries( ) . len( ) ) ;
152+
133153 // Validate it's a query and has at least one question
134154 if query_message. message_type ( ) != MessageType :: Query {
135- envoy_log_warn ! ( "Received non-query DNS message" ) ;
155+ envoy_log_warn ! ( "dns_gateway: received non-query DNS message" ) ;
136156 return abi:: envoy_dynamic_module_type_on_udp_listener_filter_status:: Continue ;
137157 }
138158
139159 let question = match query_message. queries ( ) . first ( ) {
140160 Some ( q) => q,
141161 None => {
142- envoy_log_warn ! ( "DNS query has no questions" ) ;
162+ envoy_log_warn ! ( "dns_gateway: DNS query has no questions" ) ;
143163 return abi:: envoy_dynamic_module_type_on_udp_listener_filter_status:: Continue ;
144164 }
145165 } ;
146166
147167 // Only handle A record queries
148168 if question. query_type ( ) != RecordType :: A {
149- envoy_log_debug ! ( "Ignoring non-A record query: {:?}" , question. query_type( ) ) ;
169+ envoy_log_info ! ( "dns_gateway: ignoring non-A record query: {:?}" , question. query_type( ) ) ;
150170 return abi:: envoy_dynamic_module_type_on_udp_listener_filter_status:: Continue ;
151171 }
152172
153- let domain = question. name ( ) . to_utf8 ( ) ;
154- envoy_log_debug ! ( "DNS query for domain: {}" , domain) ;
173+ let domain_raw = question. name ( ) . to_utf8 ( ) ;
174+ // DNS names are fully qualified with a trailing dot (e.g. "api.aws.com.").
175+ // Strip it so our wildcard patterns like "*.aws.com" match correctly.
176+ let domain = domain_raw. strip_suffix ( '.' ) . unwrap_or ( & domain_raw) . to_string ( ) ;
177+ envoy_log_info ! ( "dns_gateway: A record query for domain: {} (raw: {})" , domain, domain_raw) ;
155178
156179 // Match against policies
157180 let matched_policy = self . policies . iter ( ) . find ( |p| p. matches ( & domain) ) ;
158181
159182 if let Some ( policy_matcher) = matched_policy {
183+ envoy_log_info ! ( "dns_gateway: matched policy pattern '{}' for domain '{}'" ,
184+ policy_matcher. domain_pattern, domain) ;
185+
160186 // Create EgressPolicy from matcher
161187 let policy = EgressPolicy {
162188 domain : domain. clone ( ) ,
@@ -168,28 +194,42 @@ impl<ELF: EnvoyUdpListenerFilter> UdpListenerFilter<ELF> for DnsGatewayFilter {
168194 let virtual_ip = cache. allocate ( policy) ;
169195
170196 envoy_log_info ! (
171- "Allocated virtual IP {} for domain {}" ,
197+ "dns_gateway: allocated virtual IP {} for domain {}" ,
172198 virtual_ip,
173199 domain
174200 ) ;
175201
176202 // Craft DNS A response using hickory-dns
177203 let response_bytes = match craft_dns_response ( & query_message, question. name ( ) , virtual_ip) {
178- Ok ( bytes) => bytes,
204+ Ok ( bytes) => {
205+ envoy_log_info ! ( "dns_gateway: crafted DNS response, {} bytes" , bytes. len( ) ) ;
206+ bytes
207+ }
179208 Err ( e) => {
180- envoy_log_error ! ( "Failed to craft DNS response: {}" , e) ;
209+ envoy_log_error ! ( "dns_gateway: failed to craft DNS response: {}" , e) ;
181210 return abi:: envoy_dynamic_module_type_on_udp_listener_filter_status:: Continue ;
182211 }
183212 } ;
184213
185- // Set datagram data to response
186- if !envoy_filter. set_datagram_data ( & response_bytes) {
187- envoy_log_error ! ( "Failed to set datagram data" ) ;
214+ // Send the DNS response back to the peer directly.
215+ // Note: set_datagram_data() only modifies the buffer in-place but doesn't
216+ // send it back. We must use send_datagram() to actually reply to the client.
217+ if let Some ( ( peer_addr, peer_port) ) = peer {
218+ envoy_log_info ! ( "dns_gateway: sending {} byte response to {}:{}" , response_bytes. len( ) , peer_addr, peer_port) ;
219+ if !envoy_filter. send_datagram ( & response_bytes, & peer_addr, peer_port) {
220+ envoy_log_error ! ( "dns_gateway: failed to send datagram to {}:{}" , peer_addr, peer_port) ;
221+ }
222+ } else {
223+ envoy_log_error ! ( "dns_gateway: no peer address available, cannot send response" ) ;
188224 }
225+
226+ // StopIteration prevents the udp_proxy filter from also forwarding this packet
227+ return abi:: envoy_dynamic_module_type_on_udp_listener_filter_status:: StopIteration ;
189228 } else {
190- envoy_log_debug ! ( "No policy matched for domain: {}" , domain) ;
229+ envoy_log_info ! ( "dns_gateway: no policy matched for domain: {}" , domain) ;
191230 }
192231
232+ // No match — let the packet continue to the next filter (udp_proxy)
193233 abi:: envoy_dynamic_module_type_on_udp_listener_filter_status:: Continue
194234 }
195235}
0 commit comments