Skip to content

Commit 57b1c1e

Browse files
authored
Merge pull request #742 from CleanTalk/ipv6_resolve_av
Fix. Code. Edits ip resolving
2 parents 1179b5c + 54419d5 commit 57b1c1e

File tree

2 files changed

+143
-6
lines changed

2 files changed

+143
-6
lines changed

lib/Cleantalk/Common/Helper.php

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,8 @@ public static function ipV6Reduce($ip)
538538
public static function ipResolve($ip)
539539
{
540540
// Validate IP first
541-
if (!self::ipValidate($ip)) {
541+
$ip_version = self::ipValidate($ip);
542+
if (!$ip_version) {
542543
return false;
543544
}
544545

@@ -550,20 +551,41 @@ public static function ipResolve($ip)
550551
return false;
551552
}
552553

553-
// Forward DNS lookup (A/AAAA records) - verify the hostname points back to the IP
554-
$forward_ips = gethostbynamel($hostname);
554+
// Forward DNS lookup - use dns_get_record() to support both IPv4 (A) and IPv6 (AAAA) records
555+
$record_type = ($ip_version === 'v6') ? DNS_AAAA : DNS_A;
556+
$ip_field = ($ip_version === 'v6') ? 'ipv6' : 'ip';
557+
558+
$records = @dns_get_record($hostname, $record_type);
555559

556560
// If forward lookup fails, we can't verify
557-
if (!$forward_ips) {
561+
if (empty($records)) {
562+
return false;
563+
}
564+
565+
// Extract IPs from DNS records
566+
$forward_ips = array();
567+
foreach ($records as $record) {
568+
if (isset($record[$ip_field])) {
569+
$forward_ips[] = $record[$ip_field];
570+
}
571+
}
572+
573+
if (empty($forward_ips)) {
558574
return false;
559575
}
560576

561577
// Check if the original IP is in the list of IPs the hostname resolves to
562-
if (in_array($ip, $forward_ips, true)) {
578+
if ($ip_version === 'v6') {
579+
$normalized_ip = self::ipV6Normalize($ip);
580+
foreach ($forward_ips as $forward_ip) {
581+
if (self::ipV6Normalize($forward_ip) === $normalized_ip) {
582+
return $hostname;
583+
}
584+
}
585+
} elseif (in_array($ip, $forward_ips, true)) {
563586
return $hostname;
564587
}
565588

566-
// FCrDNS verification failed - possible PTR spoofing attempt
567589
return false;
568590
}
569591

tests/Common/HelperTest.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,119 @@ public function test_http__multi_request_success() {
1717
$this->assertIsArray( $res );
1818
$this->assertContainsOnly( 'string', $res );
1919
}
20+
21+
/**
22+
* Test ipResolve returns false for invalid IP
23+
*/
24+
public function test_ipResolve_invalid_ip()
25+
{
26+
$this->assertFalse(Helper::ipResolve('invalid'));
27+
$this->assertFalse(Helper::ipResolve('999.999.999.999'));
28+
$this->assertFalse(Helper::ipResolve(''));
29+
$this->assertFalse(Helper::ipResolve('abc.def.ghi.jkl'));
30+
}
31+
32+
/**
33+
* Test ipResolve returns false for null/empty values
34+
*/
35+
public function test_ipResolve_null_empty()
36+
{
37+
$this->assertFalse(Helper::ipResolve(null));
38+
$this->assertFalse(Helper::ipResolve(false));
39+
}
40+
41+
/**
42+
* Test ipResolve returns false for reserved/special IPs (0.0.0.0)
43+
*/
44+
public function test_ipResolve_reserved_ip()
45+
{
46+
$this->assertFalse(Helper::ipResolve('0.0.0.0'));
47+
}
48+
49+
/**
50+
* Test ipResolve with Google DNS (8.8.8.8) - should return hostname
51+
* This is an integration test that requires network access
52+
*
53+
* @group integration
54+
*/
55+
public function test_ipResolve_google_dns_ipv4()
56+
{
57+
$result = Helper::ipResolve('8.8.8.8');
58+
59+
// Google DNS should resolve to dns.google
60+
if ($result !== false) {
61+
$this->assertStringContainsString('dns.google', $result);
62+
} else {
63+
// DNS resolution might fail in some environments
64+
$this->markTestSkipped('DNS resolution not available in this environment');
65+
}
66+
}
67+
68+
/**
69+
* Test ipResolve with private IP - should return false (no PTR record)
70+
*/
71+
public function test_ipResolve_private_ip()
72+
{
73+
// Private IPs typically don't have public PTR records
74+
$result = Helper::ipResolve('192.168.1.1');
75+
76+
// Either false or the IP itself (no PTR) - both are acceptable
77+
$this->assertTrue($result === false || is_string($result));
78+
}
79+
80+
/**
81+
* Test ipResolve with localhost IPv4
82+
*/
83+
public function test_ipResolve_localhost_ipv4()
84+
{
85+
$result = Helper::ipResolve('127.0.0.1');
86+
87+
// Localhost might resolve to 'localhost' or return false depending on system config
88+
$this->assertTrue($result === false || is_string($result));
89+
}
90+
91+
/**
92+
* Test ipResolve with IPv6 localhost (::1)
93+
*/
94+
public function test_ipResolve_localhost_ipv6()
95+
{
96+
// IPv6 localhost is considered invalid (0::0) by ipValidate
97+
$result = Helper::ipResolve('::1');
98+
99+
$this->assertTrue($result === false || is_string($result));
100+
}
101+
102+
/**
103+
* Test ipResolve with Google DNS IPv6 (2001:4860:4860::8888)
104+
*
105+
* @group integration
106+
*/
107+
public function test_ipResolve_google_dns_ipv6()
108+
{
109+
$result = Helper::ipResolve('2001:4860:4860::8888');
110+
111+
// Google DNS IPv6 should resolve to dns.google
112+
if ($result !== false) {
113+
$this->assertStringContainsString('dns.google', $result);
114+
} else {
115+
// IPv6 DNS resolution might not work in all environments
116+
$this->markTestSkipped('IPv6 DNS resolution not available in this environment');
117+
}
118+
}
119+
120+
/**
121+
* Test ipResolve returns string hostname on success
122+
*
123+
* @group integration
124+
*/
125+
public function test_ipResolve_return_type()
126+
{
127+
$result = Helper::ipResolve('8.8.8.8');
128+
129+
// Result should be either false or a non-empty string
130+
$this->assertTrue(
131+
$result === false || (is_string($result) && strlen($result) > 0),
132+
'ipResolve should return false or a non-empty string hostname'
133+
);
134+
}
20135
}

0 commit comments

Comments
 (0)