fix: handle additional ipv6/ipv4 translation mechanisms when checking if remote is local

Signed-off-by: Robin Appelman <robin@icewind.nl>

# Conflicts:
#	tests/lib/Net/IpAddressClassifierTest.php
pull/62811/head
Robin Appelman 2 months ago
parent e68d903945
commit 5dc786b937
No known key found for this signature in database
GPG Key ID: 42B69D8A64526EFB
  1. 49
      lib/private/Net/IpAddressClassifier.php
  2. 25
      tests/lib/Net/IpAddressClassifierTest.php

@ -13,6 +13,8 @@ use IPLib\Address\IPv4;
use IPLib\Address\IPv6;
use IPLib\Factory;
use IPLib\ParseStringFlag;
use IPLib\Range\RangeInterface;
use IPLib\Range\Subnet;
use Symfony\Component\HttpFoundation\IpUtils;
use function filter_var;
@ -27,6 +29,41 @@ class IpAddressClassifier {
'192.0.0.0/24', // See RFC 6890
];
private RangeInterface $nat64Range;
private RangeInterface $rfc8215;
private RangeInterface $teredo;
private RangeInterface $ipv4Compatible;
public function __construct() {
$this->nat64Range = Subnet::parseString('64:ff9b::/96');
$this->rfc8215 = Subnet::parseString('64:ff9b:1::/48');
$this->teredo = Subnet::parseString('2001::/32');
$this->ipv4Compatible = Subnet::parseString('::0:0/96');
}
/**
* Get the ipv4 that an ipv6 address maps to, if any.
*
* Note that this is not just ipv6 representations of ipv4 addresses,
* but also any NAT or proxy style translation addresses
*/
public function getMappedIpv4(IPv6 $ip): ?IPv4 {
$ipv4 = $ip->toIPv4();
$ipv6Bytes = $ip->getBytes();
if ($ipv4) {
return $ipv4;
} elseif ($this->nat64Range->contains($ip)) {
return IPv4::fromBytes(array_slice($ipv6Bytes, -4, 4));
} elseif ($this->ipv4Compatible->contains($ip)) {
return IPv4::fromBytes(array_slice($ipv6Bytes, -4, 4));
} elseif ($this->teredo->contains($ip)) {
$xorBytes = array_slice($ipv6Bytes, -4, 4);
return IPv4::fromBytes(array_map(fn (int $byte) => $byte ^ 0xFF, $xorBytes));
}
return null;
}
/**
* Check host identifier for local IPv4 and IPv6 address ranges
*
@ -43,12 +80,16 @@ class IpAddressClassifier {
}
/* Replace by normalized form */
if ($parsedIp instanceof IPv6) {
$ipv4 = $parsedIp->toIPv4();
$ipv6Bytes = $parsedIp->getBytes();
// rfc8215 is a generic reservation for ipv6/ipv4 translation mechanisms,
// no assumptions can be made about how ipv4 addresses are encoded within.
//
// Thus the only thing we can do is treat them all as local
if ($this->rfc8215->contains($parsedIp)) {
return true;
}
$ipv4 = $this->getMappedIpv4($parsedIp);
if ($ipv4) {
$ip = (string)$ipv4;
} elseif (array_slice($ipv6Bytes, 0, 4) === [0x00, 0x64, 0xFF, 0x9B]) {
$ip = (string)IPv4::fromBytes(array_slice($ipv6Bytes, -4, 4));
} else {
$ip = (string)$parsedIp;
}

@ -9,6 +9,8 @@ declare(strict_types=1);
namespace lib\Net;
use IPLib\Address\IPv4;
use IPLib\Address\IPv6;
use OC\Net\IpAddressClassifier;
use Test\TestCase;
@ -53,6 +55,7 @@ class IpAddressClassifierTest extends TestCase {
['100.100.100.200'],
['192.0.0.1'],
['64:ff9b::a9fe:a9fe'], // NAT64 of 169.254.169.254
['64:ff9b:1::a9fe:a9fe'], // rfc8215
['::ffff:127.0.0.1'],
['2130706433'],
['0177.0.0.1'],
@ -66,4 +69,26 @@ class IpAddressClassifierTest extends TestCase {
self::assertTrue($isLocal);
}
public static function mappedAddresses(): array {
return [
['64:ff9b::a9fe:a9fe', '169.254.169.254'],
['::ffff:7f00:1', '127.0.0.1'],
['::127.0.0.1', '127.0.0.1'],
['::7f00:1', '127.0.0.1'],
['2001:0000:4136:e378:8000:63bf:3fff:fdd2', '192.0.2.45'],
['2001:4860:4860::8888', null],
];
}
#[\PHPUnit\Framework\Attributes\DataProvider('mappedAddresses')]
public function testMappedAddresses(string $ipv6, ?string $ipv4): void {
$mapped = $this->classifier->getMappedIpv4(IPv6::parseString($ipv6));
if ($ipv4 === null) {
self::assertEquals(null, $mapped);
} else {
self::assertEquals(IPv4::parseString($ipv4), $mapped);
}
}
}

Loading…
Cancel
Save