Skip to content

Commit faecdc9

Browse files
Copilotpattonwebz
authored andcommitted
feat: cache is_domain_loopback DNS lookup in a short-lived transient
Co-authored-by: pattonwebz <3902039+pattonwebz@users.noreply.github.com>
1 parent 0e491be commit faecdc9

2 files changed

Lines changed: 78 additions & 0 deletions

File tree

admin/class-helpers.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,22 @@ public static function get_option_as_array( $option_name ) {
143143
/**
144144
* Determine if a domain is hosted on a local loopback
145145
*
146+
* Results are cached in a short-lived transient to avoid repeated DNS
147+
* lookups when the same domain is checked multiple times during a scan.
148+
*
146149
* @param string $domain The domain to check.
147150
* @return boolean
148151
*/
149152
public static function is_domain_loopback( $domain ) {
150153

154+
// Check the transient cache first to avoid repeated DNS lookups.
155+
$cache_key = 'edac_loopback_' . md5( sanitize_text_field( $domain ) );
156+
$cached = get_transient( $cache_key );
157+
158+
if ( false !== $cached ) {
159+
return (bool) $cached;
160+
}
161+
151162
// Check if this is an ipv4 address in the loopback range.
152163

153164
$record = gethostbyname( $domain );
@@ -156,6 +167,9 @@ public static function is_domain_loopback( $domain ) {
156167
$ip_long = ip2long( $record );
157168

158169
if ( $ip_long >= $loopback_start && $ip_long <= $loopback_end ) {
170+
// Store as int (1/0) so that a false result is distinguishable from a
171+
// cache miss, since get_transient() also returns false on a miss.
172+
set_transient( $cache_key, 1, HOUR_IN_SECONDS );
159173
return true;
160174
}
161175

@@ -164,6 +178,7 @@ public static function is_domain_loopback( $domain ) {
164178
try {
165179
$records = dns_get_record( $domain, DNS_AAAA );
166180
} catch ( \Throwable $th ) {
181+
set_transient( $cache_key, 0, HOUR_IN_SECONDS );
167182
return false;
168183
}
169184

@@ -179,11 +194,13 @@ public static function is_domain_loopback( $domain ) {
179194
$loopback_ipv6 = inet_pton( '::1' );
180195

181196
if ( $normalized_ipv6 === $loopback_ipv6 ) {
197+
set_transient( $cache_key, 1, HOUR_IN_SECONDS );
182198
return true;
183199
}
184200
}
185201
}
186202

203+
set_transient( $cache_key, 0, HOUR_IN_SECONDS );
187204
return false;
188205
}
189206

tests/phpunit/Admin/HelpersLoopbackTest.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,65 @@ public function test_performance_multiple_calls() {
242242
// Should complete within a reasonable time (5 seconds).
243243
$this->assertLessThan( 5.0, $execution_time, 'Method took too long to execute multiple calls' );
244244
}
245+
246+
/**
247+
* Test that the result is cached in a transient after the first call.
248+
*/
249+
public function test_result_is_cached_in_transient() {
250+
$domain = 'localhost';
251+
$cache_key = 'edac_loopback_' . md5( sanitize_text_field( $domain ) );
252+
253+
// Clear any existing transient.
254+
delete_transient( $cache_key );
255+
256+
// First call performs the DNS lookup and stores the result.
257+
$result = Helpers::is_domain_loopback( $domain );
258+
$this->assertTrue( $result );
259+
260+
// The transient should now be set with an integer value.
261+
$cached = get_transient( $cache_key );
262+
$this->assertNotFalse( $cached, 'Transient should be set after first call.' );
263+
$this->assertSame( 1, (int) $cached );
264+
}
265+
266+
/**
267+
* Test that a non-loopback result is also cached correctly.
268+
*/
269+
public function test_non_loopback_result_is_cached() {
270+
$domain = '8.8.8.8';
271+
$cache_key = 'edac_loopback_' . md5( sanitize_text_field( $domain ) );
272+
273+
// Clear any existing transient.
274+
delete_transient( $cache_key );
275+
276+
// First call performs the DNS lookup.
277+
$result = Helpers::is_domain_loopback( $domain );
278+
$this->assertFalse( $result );
279+
280+
// The transient should be set with 0 (not false) so a cache miss can
281+
// be distinguished from a cached false result.
282+
$cached = get_transient( $cache_key );
283+
$this->assertNotFalse( $cached, 'Transient should be set even for non-loopback domains.' );
284+
$this->assertSame( 0, (int) $cached );
285+
}
286+
287+
/**
288+
* Test that a cached result is returned without re-doing the DNS lookup.
289+
*/
290+
public function test_cached_result_is_returned() {
291+
$domain = '127.0.0.1';
292+
$cache_key = 'edac_loopback_' . md5( sanitize_text_field( $domain ) );
293+
294+
// Pre-seed the transient with a value opposite to the real DNS answer.
295+
// If the cache is respected, this value will be returned as-is.
296+
set_transient( $cache_key, 0, HOUR_IN_SECONDS );
297+
298+
$result = Helpers::is_domain_loopback( $domain );
299+
300+
// The seeded value (0 / false) should be returned from cache.
301+
$this->assertFalse( $result );
302+
303+
// Clean up.
304+
delete_transient( $cache_key );
305+
}
245306
}

0 commit comments

Comments
 (0)