@@ -304,12 +304,25 @@ impl BlockList {
304304 }
305305}
306306
307+ /// DNS resolver IPs that the AllowList exempts on port 53.
308+ ///
309+ /// Includes the host's configured resolvers (from `/etc/resolv.conf` on
310+ /// Unix, `ipconfig /all` on Windows) **plus** well-known public DNS
311+ /// servers (Google, Cloudflare) that the guest may hardcode in its own
312+ /// `/etc/resolv.conf`.
307313fn dns_resolvers ( ) -> & ' static HashSet < IpAddr > {
308314 static RESOLVERS : std:: sync:: OnceLock < HashSet < IpAddr > > = std:: sync:: OnceLock :: new ( ) ;
309315 RESOLVERS . get_or_init ( || {
316+ let mut set = HashSet :: new ( ) ;
317+ // Well-known public DNS that the guest's initrd may hardcode.
318+ for ip in [
319+ "8.8.8.8" , "8.8.4.4" , // Google
320+ "1.1.1.1" , "1.0.0.1" , // Cloudflare
321+ ] {
322+ set. insert ( ip. parse :: < IpAddr > ( ) . unwrap ( ) ) ;
323+ }
310324 #[ cfg( unix) ]
311325 {
312- let mut set = HashSet :: new ( ) ;
313326 if let Ok ( contents) = std:: fs:: read_to_string ( "/etc/resolv.conf" ) {
314327 for line in contents. lines ( ) {
315328 let line = line. trim ( ) ;
@@ -322,11 +335,9 @@ fn dns_resolvers() -> &'static HashSet<IpAddr> {
322335 }
323336 }
324337 }
325- set
326338 }
327339 #[ cfg( windows) ]
328340 {
329- let mut set = HashSet :: new ( ) ;
330341 if let Ok ( output) = std:: process:: Command :: new ( "ipconfig" ) . arg ( "/all" ) . output ( ) {
331342 let text = String :: from_utf8_lossy ( & output. stdout ) ;
332343 let mut in_dns_block = false ;
@@ -347,12 +358,8 @@ fn dns_resolvers() -> &'static HashSet<IpAddr> {
347358 }
348359 }
349360 }
350- set
351- }
352- #[ cfg( not( any( unix, windows) ) ) ]
353- {
354- HashSet :: new ( )
355361 }
362+ set
356363 } )
357364}
358365
@@ -2597,34 +2604,43 @@ mod tests {
25972604 }
25982605
25992606 #[ test]
2600- fn test_port53_arbitrary_ip_blocked ( ) {
2601- // RFC5737 TEST-NET-2 address — guaranteed not in /etc/resolv.conf.
2607+ fn allowlist_permits_dns_to_well_known_resolvers ( ) {
2608+ // AllowList exempts port 53 to well-known public DNS and host
2609+ // resolvers so the guest's hardcoded nameservers (8.8.8.8 etc.)
2610+ // work even when the host uses different resolvers.
2611+ let al = AllowList :: from_hosts ( & [ "198.51.100.1" ] ) . unwrap ( ) ;
2612+ let policy = NetworkPolicy :: AllowList ( al) ;
2613+ for ip in [ "8.8.8.8:53" , "8.8.4.4:53" , "1.1.1.1:53" , "1.0.0.1:53" ] {
2614+ let addr: std:: net:: SocketAddr = ip. parse ( ) . unwrap ( ) ;
2615+ assert ! (
2616+ policy. check( & addr) . is_ok( ) ,
2617+ "AllowList must permit DNS (port 53) to well-known resolver {}" ,
2618+ ip
2619+ ) ;
2620+ }
2621+ }
2622+
2623+ #[ test]
2624+ fn allowlist_denies_port53_to_arbitrary_ip ( ) {
2625+ // Port 53 is NOT a blanket bypass — only known DNS resolvers
2626+ // are exempted. RFC5737 TEST-NET addresses are never resolvers.
26022627 let al = AllowList :: from_hosts ( & [ "198.51.100.1" ] ) . unwrap ( ) ;
26032628 let policy = NetworkPolicy :: AllowList ( al) ;
26042629 let addr: std:: net:: SocketAddr = "198.51.100.99:53" . parse ( ) . unwrap ( ) ;
26052630 assert ! (
26062631 policy. check( & addr) . is_err( ) ,
2607- "port 53 to a non-resolver IP must be denied"
2632+ "port 53 to an unknown IP must still be denied"
26082633 ) ;
26092634 }
26102635
2611- #[ cfg( unix) ]
26122636 #[ test]
2613- fn test_port53_real_resolver_allowed ( ) {
2614- let resolvers = dns_resolvers ( ) ;
2615- if resolvers. is_empty ( ) {
2616- eprintln ! ( "skipping: no resolvers found in /etc/resolv.conf" ) ;
2617- return ;
2618- }
2619- let resolver_ip = * resolvers. iter ( ) . next ( ) . unwrap ( ) ;
2620- // Allowlist uses a TEST-NET IP that won't match the resolver.
2637+ fn allowlist_denies_non_dns_to_unlisted_ip ( ) {
26212638 let al = AllowList :: from_hosts ( & [ "198.51.100.1" ] ) . unwrap ( ) ;
26222639 let policy = NetworkPolicy :: AllowList ( al) ;
2623- let addr = std:: net:: SocketAddr :: new ( resolver_ip , 53 ) ;
2640+ let addr: std:: net:: SocketAddr = "198.51.100.99:443" . parse ( ) . unwrap ( ) ;
26242641 assert ! (
2625- policy. check( & addr) . is_ok( ) ,
2626- "port 53 to a configured resolver ({}) must be allowed" ,
2627- resolver_ip
2642+ policy. check( & addr) . is_err( ) ,
2643+ "AllowList must deny non-DNS traffic to unlisted IPs"
26282644 ) ;
26292645 }
26302646
@@ -2683,6 +2699,17 @@ mod tests {
26832699 ) ;
26842700 }
26852701
2702+ #[ test]
2703+ fn blocklist_permits_dns_to_non_blocked_ip ( ) {
2704+ let bl = BlockList :: from_hosts ( & [ "203.0.113.1" ] ) . unwrap ( ) ;
2705+ let policy = NetworkPolicy :: BlockList ( bl) ;
2706+ let addr: std:: net:: SocketAddr = "8.8.8.8:53" . parse ( ) . unwrap ( ) ;
2707+ assert ! (
2708+ policy. check( & addr) . is_ok( ) ,
2709+ "BlockList must allow DNS to non-blocked IPs"
2710+ ) ;
2711+ }
2712+
26862713 #[ test]
26872714 fn blocklist_resolves_hostnames ( ) {
26882715 let bl = BlockList :: from_hosts ( & [ "localhost" ] ) . unwrap ( ) ;
0 commit comments