From 6a47287da72ee01c63e2ccaac93a8614cf7fa578 Mon Sep 17 00:00:00 2001 From: gbrodman Date: Thu, 25 Jun 2026 14:06:33 -0400 Subject: [PATCH] Forbid non-routable IPs for host glue records (#3105) 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 d104264e4..447f2176d 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 class HostFlowUtils { 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 12e04d1f1..6ac065e64 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.HostNameNotPunyCodedException; 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 @@ class HostCreateFlowTest extends ResourceFlowTestCase { } @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 584820575..4c5890df6 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.HostNameNotNormalizedException; 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 @@ class HostUpdateFlowTest extends ResourceFlowTestCase { } @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(); }