@@ -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,12 +335,31 @@ fn dns_resolvers() -> &'static HashSet<IpAddr> {
322335 }
323336 }
324337 }
325- set
326338 }
327- #[ cfg( not ( unix ) ) ]
339+ #[ cfg( windows ) ]
328340 {
329- HashSet :: new ( )
341+ if let Ok ( output) = std:: process:: Command :: new ( "ipconfig" ) . arg ( "/all" ) . output ( ) {
342+ let text = String :: from_utf8_lossy ( & output. stdout ) ;
343+ let mut in_dns_block = false ;
344+ for line in text. lines ( ) {
345+ let trimmed = line. trim ( ) ;
346+ if let Some ( rest) = trimmed. strip_prefix ( "DNS Servers" ) {
347+ in_dns_block = true ;
348+ let value = rest. trim_start_matches ( [ '.' , ' ' , ':' ] ) ;
349+ if let Ok ( ip) = value. parse :: < IpAddr > ( ) {
350+ set. insert ( ip) ;
351+ }
352+ } else if in_dns_block {
353+ if let Ok ( ip) = trimmed. parse :: < IpAddr > ( ) {
354+ set. insert ( ip) ;
355+ } else {
356+ in_dns_block = false ;
357+ }
358+ }
359+ }
360+ }
330361 }
362+ set
331363 } )
332364}
333365
@@ -2572,34 +2604,43 @@ mod tests {
25722604 }
25732605
25742606 #[ test]
2575- fn test_port53_arbitrary_ip_blocked ( ) {
2576- // 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.
25772627 let al = AllowList :: from_hosts ( & [ "198.51.100.1" ] ) . unwrap ( ) ;
25782628 let policy = NetworkPolicy :: AllowList ( al) ;
25792629 let addr: std:: net:: SocketAddr = "198.51.100.99:53" . parse ( ) . unwrap ( ) ;
25802630 assert ! (
25812631 policy. check( & addr) . is_err( ) ,
2582- "port 53 to a non-resolver IP must be denied"
2632+ "port 53 to an unknown IP must still be denied"
25832633 ) ;
25842634 }
25852635
2586- #[ cfg( unix) ]
25872636 #[ test]
2588- fn test_port53_real_resolver_allowed ( ) {
2589- let resolvers = dns_resolvers ( ) ;
2590- if resolvers. is_empty ( ) {
2591- eprintln ! ( "skipping: no resolvers found in /etc/resolv.conf" ) ;
2592- return ;
2593- }
2594- let resolver_ip = * resolvers. iter ( ) . next ( ) . unwrap ( ) ;
2595- // Allowlist uses a TEST-NET IP that won't match the resolver.
2637+ fn allowlist_denies_non_dns_to_unlisted_ip ( ) {
25962638 let al = AllowList :: from_hosts ( & [ "198.51.100.1" ] ) . unwrap ( ) ;
25972639 let policy = NetworkPolicy :: AllowList ( al) ;
2598- let addr = std:: net:: SocketAddr :: new ( resolver_ip , 53 ) ;
2640+ let addr: std:: net:: SocketAddr = "198.51.100.99:443" . parse ( ) . unwrap ( ) ;
25992641 assert ! (
2600- policy. check( & addr) . is_ok( ) ,
2601- "port 53 to a configured resolver ({}) must be allowed" ,
2602- resolver_ip
2642+ policy. check( & addr) . is_err( ) ,
2643+ "AllowList must deny non-DNS traffic to unlisted IPs"
26032644 ) ;
26042645 }
26052646
@@ -2658,6 +2699,17 @@ mod tests {
26582699 ) ;
26592700 }
26602701
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+
26612713 #[ test]
26622714 fn blocklist_resolves_hostnames ( ) {
26632715 let bl = BlockList :: from_hosts ( & [ "localhost" ] ) . unwrap ( ) ;
0 commit comments