From 070c9f78787b556d98fb68e16f0673d29bb5ff97 Mon Sep 17 00:00:00 2001 From: Gus Brodman Date: Tue, 23 Jun 2026 17:04:18 -0400 Subject: [PATCH] Forbid non-routable IPs for host glue records Reject loopback, link-local, site-local, wildcard, and multicast IP addresses during host creation and update flows. Glue records (A/AAAA records published in the parent zone for subordinate name servers) must point to globally routable, public IP addresses to ensure that recursive DNS resolvers on the public internet can reach the authoritative name servers. Using non-public or non-routable IP addresses in glue records is invalid for the following reasons: - Loopback (127.0.0.1, ::1) and Any-Local (0.0.0.0, ::) addresses point back to the client or are unspecified, causing resolvers to query themselves and fail. - Private/Site-Local (e.g., 10.0.0.0/8, 192.168.0.0/16) and Link-Local (169.254.0.0/16) addresses are not routable on the public internet, rendering the delegated domain completely unreachable to external clients. - Multicast addresses are designed for one-to-many delivery and cannot be used for standard unicast DNS queries to a specific name server. Rename LoopbackIpNotValidForHostException to IpAddressNotRoutableException to reflect the broader set of forbidden non-routable IP addresses. --- .../registry/flows/host/HostFlowUtils.java | 18 ++++--- .../flows/host/HostCreateFlowTest.java | 50 +++++++++++++++-- .../flows/host/HostUpdateFlowTest.java | 54 +++++++++++++++++-- 3 files changed, 106 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/google/registry/flows/host/HostFlowUtils.java b/core/src/main/java/google/registry/flows/host/HostFlowUtils.java index d104264e4c2..447f2176de4 100644 --- a/core/src/main/java/google/registry/flows/host/HostFlowUtils.java +++ b/core/src/main/java/google/registry/flows/host/HostFlowUtils.java @@ -116,15 +116,21 @@ public static void validateInetAddresses(ImmutableSet inetAddresses if (inetAddresses == null) { return; } - if (inetAddresses.stream().anyMatch(InetAddress::isLoopbackAddress)) { - throw new LoopbackIpNotValidForHostException(); + for (InetAddress inetAddress : inetAddresses) { + if (inetAddress.isLoopbackAddress() + || inetAddress.isLinkLocalAddress() + || inetAddress.isSiteLocalAddress() + || inetAddress.isAnyLocalAddress() + || inetAddress.isMulticastAddress()) { + throw new IpAddressNotRoutableException(inetAddress.getHostAddress()); + } } } - /** Loopback IPs are not valid for hosts. */ - static class LoopbackIpNotValidForHostException extends ParameterValuePolicyErrorException { - public LoopbackIpNotValidForHostException() { - super("Loopback IPs are not valid for hosts"); + /** IP address is not a public, routable address. */ + static class IpAddressNotRoutableException extends ParameterValuePolicyErrorException { + public IpAddressNotRoutableException(String ipAddress) { + super(String.format("IP address %s is not a public, globally routable address", ipAddress)); } } diff --git a/core/src/test/java/google/registry/flows/host/HostCreateFlowTest.java b/core/src/test/java/google/registry/flows/host/HostCreateFlowTest.java index 12e04d1f14f..6ac065e64a3 100644 --- a/core/src/test/java/google/registry/flows/host/HostCreateFlowTest.java +++ b/core/src/test/java/google/registry/flows/host/HostCreateFlowTest.java @@ -48,7 +48,7 @@ import google.registry.flows.host.HostFlowUtils.HostNameTooLongException; import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException; import google.registry.flows.host.HostFlowUtils.InvalidHostNameException; -import google.registry.flows.host.HostFlowUtils.LoopbackIpNotValidForHostException; +import google.registry.flows.host.HostFlowUtils.IpAddressNotRoutableException; import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException; import google.registry.flows.host.HostFlowUtils.SuperordinateDomainInPendingDeleteException; import google.registry.model.ForeignKeyUtils; @@ -354,22 +354,62 @@ void testFailure_ccTldInBailiwick() { } @Test - void testFailure_localhostInetAddress_ipv4() { + void testFailure_loopbackInetAddress_ipv4() { createTld("tld"); persistActiveDomain("example.tld"); setEppHostCreateInput("ns1.example.tld", "127.0.0.1"); assertAboutEppExceptions() - .that(assertThrows(LoopbackIpNotValidForHostException.class, this::runFlow)) + .that(assertThrows(IpAddressNotRoutableException.class, this::runFlow)) .marshalsToXml(); } @Test - void testFailure_localhostInetAddress_ipv6() { + void testFailure_loopbackInetAddress_ipv6() { createTld("tld"); persistActiveDomain("example.tld"); setEppHostCreateInput("ns1.example.tld", "::1"); assertAboutEppExceptions() - .that(assertThrows(LoopbackIpNotValidForHostException.class, this::runFlow)) + .that(assertThrows(IpAddressNotRoutableException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_linkLocalInetAddress_ipv4() { + createTld("tld"); + persistActiveDomain("example.tld"); + setEppHostCreateInput("ns1.example.tld", "169.254.1.1"); + assertAboutEppExceptions() + .that(assertThrows(IpAddressNotRoutableException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_linkLocalInetAddress_ipv6() { + createTld("tld"); + persistActiveDomain("example.tld"); + setEppHostCreateInput("ns1.example.tld", "fe80::1"); + assertAboutEppExceptions() + .that(assertThrows(IpAddressNotRoutableException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_privateInetAddress_ipv4() { + createTld("tld"); + persistActiveDomain("example.tld"); + setEppHostCreateInput("ns1.example.tld", "192.168.1.1"); + assertAboutEppExceptions() + .that(assertThrows(IpAddressNotRoutableException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_anyLocalInetAddress_ipv4() { + createTld("tld"); + persistActiveDomain("example.tld"); + setEppHostCreateInput("ns1.example.tld", "0.0.0.0"); + assertAboutEppExceptions() + .that(assertThrows(IpAddressNotRoutableException.class, this::runFlow)) .marshalsToXml(); } diff --git a/core/src/test/java/google/registry/flows/host/HostUpdateFlowTest.java b/core/src/test/java/google/registry/flows/host/HostUpdateFlowTest.java index 5848205755a..4c5890df678 100644 --- a/core/src/test/java/google/registry/flows/host/HostUpdateFlowTest.java +++ b/core/src/test/java/google/registry/flows/host/HostUpdateFlowTest.java @@ -65,7 +65,7 @@ import google.registry.flows.host.HostFlowUtils.HostNameNotPunyCodedException; import google.registry.flows.host.HostFlowUtils.HostNameTooLongException; import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException; -import google.registry.flows.host.HostFlowUtils.LoopbackIpNotValidForHostException; +import google.registry.flows.host.HostFlowUtils.IpAddressNotRoutableException; import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException; import google.registry.flows.host.HostFlowUtils.SuperordinateDomainInPendingDeleteException; import google.registry.flows.host.HostUpdateFlow.CannotAddIpToExternalHostException; @@ -1391,24 +1391,68 @@ void testFailure_ccTldInBailiwick() throws Exception { } @Test - void testFailure_localhostInetAddress_ipv4() throws Exception { + void testFailure_loopbackInetAddress_ipv4() throws Exception { createTld("tld"); persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld")); setEppHostUpdateInput( "ns1.example.tld", "ns2.example.tld", "127.0.0.1", null); assertAboutEppExceptions() - .that(assertThrows(LoopbackIpNotValidForHostException.class, this::runFlow)) + .that(assertThrows(IpAddressNotRoutableException.class, this::runFlow)) .marshalsToXml(); } @Test - void testFailure_localhostInetAddress_ipv6() throws Exception { + void testFailure_loopbackInetAddress_ipv6() throws Exception { createTld("tld"); persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld")); setEppHostUpdateInput( "ns1.example.tld", "ns2.example.tld", "::1", null); assertAboutEppExceptions() - .that(assertThrows(LoopbackIpNotValidForHostException.class, this::runFlow)) + .that(assertThrows(IpAddressNotRoutableException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_linkLocalInetAddress_ipv4() throws Exception { + createTld("tld"); + persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld")); + setEppHostUpdateInput( + "ns1.example.tld", "ns2.example.tld", "169.254.1.1", null); + assertAboutEppExceptions() + .that(assertThrows(IpAddressNotRoutableException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_linkLocalInetAddress_ipv6() throws Exception { + createTld("tld"); + persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld")); + setEppHostUpdateInput( + "ns1.example.tld", "ns2.example.tld", "fe80::1", null); + assertAboutEppExceptions() + .that(assertThrows(IpAddressNotRoutableException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_privateInetAddress_ipv4() throws Exception { + createTld("tld"); + persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld")); + setEppHostUpdateInput( + "ns1.example.tld", "ns2.example.tld", "192.168.1.1", null); + assertAboutEppExceptions() + .that(assertThrows(IpAddressNotRoutableException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_anyLocalInetAddress_ipv4() throws Exception { + createTld("tld"); + persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld")); + setEppHostUpdateInput( + "ns1.example.tld", "ns2.example.tld", "0.0.0.0", null); + assertAboutEppExceptions() + .that(assertThrows(IpAddressNotRoutableException.class, this::runFlow)) .marshalsToXml(); }