@@ -304,22 +304,47 @@ impl BlockList {
304304 }
305305}
306306
307+ fn dns_resolvers ( ) -> & ' static HashSet < IpAddr > {
308+ static RESOLVERS : std:: sync:: OnceLock < HashSet < IpAddr > > = std:: sync:: OnceLock :: new ( ) ;
309+ RESOLVERS . get_or_init ( || {
310+ #[ cfg( unix) ]
311+ {
312+ let mut set = HashSet :: new ( ) ;
313+ if let Ok ( contents) = std:: fs:: read_to_string ( "/etc/resolv.conf" ) {
314+ for line in contents. lines ( ) {
315+ let line = line. trim ( ) ;
316+ if let Some ( rest) = line. strip_prefix ( "nameserver" ) {
317+ if let Some ( ip_str) = rest. split_whitespace ( ) . next ( ) {
318+ if let Ok ( ip) = ip_str. parse :: < IpAddr > ( ) {
319+ set. insert ( ip) ;
320+ }
321+ }
322+ }
323+ }
324+ }
325+ set
326+ }
327+ #[ cfg( not( unix) ) ]
328+ {
329+ HashSet :: new ( )
330+ }
331+ } )
332+ }
333+
307334impl NetworkPolicy {
308335 fn check ( & self , addr : & std:: net:: SocketAddr ) -> Result < ( ) > {
309336 match self {
310337 NetworkPolicy :: AllowAll => Ok ( ( ) ) ,
311338 NetworkPolicy :: AllowList ( al) => {
312- if addr. port ( ) == 53 || al. is_allowed ( & addr. ip ( ) ) {
339+ if al. is_allowed ( & addr. ip ( ) )
340+ || ( addr. port ( ) == 53 && dns_resolvers ( ) . contains ( & addr. ip ( ) ) )
341+ {
313342 Ok ( ( ) )
314343 } else {
315344 Err ( anyhow ! ( "network policy denies connection to {}" , addr) )
316345 }
317346 }
318347 NetworkPolicy :: BlockList ( bl) => {
319- // DNS (port 53) is always allowed so the guest can resolve names.
320- if addr. port ( ) == 53 {
321- return Ok ( ( ) ) ;
322- }
323348 if bl. is_blocked ( & addr. ip ( ) ) {
324349 Err ( anyhow ! ( "network policy denies connection to {}" , addr) )
325350 } else {
@@ -2737,6 +2762,50 @@ mod tests {
27372762 assert ! ( err. to_string( ) . contains( "network policy denies" ) , "{err}" ) ;
27382763 }
27392764
2765+ #[ test]
2766+ fn test_port53_arbitrary_ip_blocked ( ) {
2767+ // RFC5737 TEST-NET-2 address — guaranteed not in /etc/resolv.conf.
2768+ let al = AllowList :: from_hosts ( & [ "198.51.100.1" ] ) . unwrap ( ) ;
2769+ let policy = NetworkPolicy :: AllowList ( al) ;
2770+ let addr: std:: net:: SocketAddr = "198.51.100.99:53" . parse ( ) . unwrap ( ) ;
2771+ assert ! (
2772+ policy. check( & addr) . is_err( ) ,
2773+ "port 53 to a non-resolver IP must be denied"
2774+ ) ;
2775+ }
2776+
2777+ #[ cfg( unix) ]
2778+ #[ test]
2779+ fn test_port53_real_resolver_allowed ( ) {
2780+ let resolvers = dns_resolvers ( ) ;
2781+ if resolvers. is_empty ( ) {
2782+ eprintln ! ( "skipping: no resolvers found in /etc/resolv.conf" ) ;
2783+ return ;
2784+ }
2785+ let resolver_ip = * resolvers. iter ( ) . next ( ) . unwrap ( ) ;
2786+ // Allowlist uses a TEST-NET IP that won't match the resolver.
2787+ let al = AllowList :: from_hosts ( & [ "198.51.100.1" ] ) . unwrap ( ) ;
2788+ let policy = NetworkPolicy :: AllowList ( al) ;
2789+ let addr = std:: net:: SocketAddr :: new ( resolver_ip, 53 ) ;
2790+ assert ! (
2791+ policy. check( & addr) . is_ok( ) ,
2792+ "port 53 to a configured resolver ({}) must be allowed" ,
2793+ resolver_ip
2794+ ) ;
2795+ }
2796+
2797+ #[ test]
2798+ fn test_port53_blocklist_enforced ( ) {
2799+ // RFC5737 TEST-NET-1 address — blocklist always wins.
2800+ let bl = BlockList :: from_hosts ( & [ "192.0.2.1" ] ) . unwrap ( ) ;
2801+ let policy = NetworkPolicy :: BlockList ( bl) ;
2802+ let addr: std:: net:: SocketAddr = "192.0.2.1:53" . parse ( ) . unwrap ( ) ;
2803+ assert ! (
2804+ policy. check( & addr) . is_err( ) ,
2805+ "blocklisted IP must be denied even on port 53"
2806+ ) ;
2807+ }
2808+
27402809 #[ test]
27412810 fn allowlist_resolves_hostnames ( ) {
27422811 let al = AllowList :: from_hosts ( & [ "localhost" ] ) . unwrap ( ) ;
@@ -2769,11 +2838,15 @@ mod tests {
27692838 }
27702839
27712840 #[ test]
2772- fn network_policy_blocklist_allows_dns ( ) {
2773- let bl = BlockList :: from_hosts ( & [ "1.2.3.4" ] ) . unwrap ( ) ;
2841+ fn network_policy_blocklist_denies_blocked_ip_on_port53 ( ) {
2842+ // RFC5737 TEST-NET-3 — blocklist always wins over DNS exemption.
2843+ let bl = BlockList :: from_hosts ( & [ "203.0.113.1" ] ) . unwrap ( ) ;
27742844 let policy = NetworkPolicy :: BlockList ( bl) ;
2775- let addr: std:: net:: SocketAddr = "1.2.3.4:53" . parse ( ) . unwrap ( ) ;
2776- assert ! ( policy. check( & addr) . is_ok( ) ) ;
2845+ let addr: std:: net:: SocketAddr = "203.0.113.1:53" . parse ( ) . unwrap ( ) ;
2846+ assert ! (
2847+ policy. check( & addr) . is_err( ) ,
2848+ "blocked IP must be denied even on port 53"
2849+ ) ;
27772850 }
27782851
27792852 #[ test]
0 commit comments