Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## `6.x`

- Detect embedding strategies via the new `Contracts\StrategyDetectionInterface`
deprecating the old `IpInterface::isEmbedded()` and associated methods.
- Convert IP addresses to and from integers: `fromInteger()`/`toInteger()` via
the new `Contracts\Factory4Interface` (IPv4 and Multi only), plus
arbitrary-precision `fromIntegerString()`/`toIntegerString()` and fixed-width
Expand Down
63 changes: 63 additions & 0 deletions docs/07-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,69 @@ $ip = IP::factory('::7f00:1');
$ip->isCompatible(); // bool(true)
```

### NAT64

Whether the IP is a NAT64 address within the Well-Known Prefix `64:ff9b::/96`,
according to [RFC 6052 section 2.1](https://tools.ietf.org/html/rfc6052 "IPv6
Addressing of IPv4/IPv6 Translators"), or within the Local-use Prefix
`64:ff9b:1::/48`, according to [RFC 8215 section 4](https://tools.ietf.org/html/rfc8215
"Local-Use IPv4/IPv6 Translation Prefix").

```php
<?php
use Darsyn\IP\Version\IPv6 as IP;

IP::fromProtocol('64:ff9b::7f00:1')->isNat64WellKnown(); // bool(true)
IP::fromProtocol('64:ff9b:1:7f00:0:1::')->isNat64LocalUse(); // bool(true)
```

### Teredo

Whether the IP is a Teredo tunnelling address within `2001::/32`, according to
[RFC 4380 section 4](https://tools.ietf.org/html/rfc4380 "Teredo: Tunneling
IPv6 over UDP through NATs").

```php
<?php
use Darsyn\IP\Version\IPv6 as IP;

$ip = IP::fromProtocol('2001::8500:0:0:80ff:fffe');
$ip->isTeredo(); // bool(true)
```

### According to Any Strategy

`isEmbeddedAccordingToStrategy()` is the generic form of the named detection
predicates: any [embedding strategy](./05-strategies.md), including a
user-defined one, can be tested against the address without constructing a new
IP object around it.

```php
<?php
use Darsyn\IP\Strategy\Derived;
use Darsyn\IP\Version\IPv6 as IP;

$ip = IP::fromProtocol('2002:7f00:1::');
$ip->isEmbeddedAccordingToStrategy(new Derived()); // bool(true)
```

### Embedded IP

`getEmbeddedIp()` extracts the IPv4 address embedded in this address as an
`IPv4` instance, and the inverse of `IPv6::fromEmbedded()`. A
`WrongVersionException` is thrown when no IPv4 address is embedded according to
the strategy in effect. A null strategy falls back to the instance's own
embedding strategy on `Multi`, or to the global default set via
`Multi::setDefaultEmbeddingStrategy()`

```php
<?php
use Darsyn\IP\Version\Multi as IP;

$ip = IP::fromProtocol('::ffff:7f00:1');
$ip->getEmbeddedIp()->getDotAddress(); // string("127.0.0.1")
```

## Detecting Address Types

### Link Local
Expand Down
99 changes: 52 additions & 47 deletions docs/10-api.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,54 @@
# API Reference

| Method | Returns | IPv4 | IPv6 | Multi |
|---------------------------------------|----------------------|------|------|-------|
| `factory(string $ip, [$strategy])` | Static `IpInterface` | ✓ | ✓ | ✓ |
| `getBinary()` | `string` | ✓ | ✓ | ✓ |
| `getOctets()` | `list<int>` | ✓ | ✓ | ✓ |
| `toString()` | `string` | ✓ | ✓ | ✓ |
| `jsonSerialize()` | `string` | ✓ | ✓ | ✓ |
| `toIntegerString()` | `string` | ✓ | ✓ | ✓ |
| `toHexString()` | `string` | ✓ | ✓ | ✓ |
| `equals(IpInterface $ip)` | `bool` | ✓ | ✓ | ✓ |
| `getVersion()` | `int` | ✓ | ✓ | ✓ |
| `isVersion(int $version)` | `bool` | ✓ | ✓ | ✓ |
| `isVersion4()` | `bool` | ✓ | ✓ | ✓ |
| `isVersion6()` | `bool` | ✓ | ✓ | ✓ |
| `getNetworkIp(int $cidr)` | Static `IpInterface` | ✓ | ✓ | ✓ |
| `getBroadcastIp(int $cidr)` | Static `IpInterface` | ✓ | ✓ | ✓ |
| `next()` | Static `IpInterface` | ✓ | ✓ | ✓ |
| `previous()` | Static `IpInterface` | ✓ | ✓ | ✓ |
| `offset(int $offset)` | Static `IpInterface` | ✓ | ✓ | ✓ |
| `inRange(IpInterface $ip, int $cidr)` | `bool` | ✓ | ✓ | ✓ |
| `getCommonCidr(IpInterface $ip)` | `int` | ✓ | ✓ | ✓ |
| `isMapped()` | `bool` | ✓ | ✓ | ✓ |
| `isDerived()` | `bool` | ✓ | ✓ | ✓ |
| `isCompatible()` | `bool` | ✓ | ✓ | ✓ |
| `isEmbedded()` | `bool` | ✓ | ✓ | ✓ |
| `isLinkLocal()` | `bool` | ✓ | ✓ | ✓ |
| `isLoopback()` | `bool` | ✓ | ✓ | ✓ |
| `isMulticast()` | `bool` | ✓ | ✓ | ✓ |
| `isPrivateUse()` | `bool` | ✓ | ✓ | ✓ |
| `isUnspecified()` | `bool` | ✓ | ✓ | ✓ |
| `isBenchmarking()` | `bool` | ✓ | ✓ | ✓ |
| `isDocumentation()` | `bool` | ✓ | ✓ | ✓ |
| `isGloballyReachable()` | `bool` | ✓ | ✓ | ✓ |
| `isBroadcast()` | `bool` | ✓ | | ✓ |
| `isShared()` | `bool` | ✓ | | ✓ |
| `isFutureReserved()` | `bool` | ✓ | | ✓ |
| `getDotAddress()` | `string` | ✓ | | ✓ |
| `toInteger()` | `int` | ✓ | | ✓ |
| `getCompactedAddress()` | `string` | | ✓ | ✓ |
| `getExpandedAddress()` | `string` | | ✓ | ✓ |
| `getCompactedAddress()` | `string` | | ✓ | ✓ |
| `getSegments()` | `list<int>` | | ✓ | ✓ |
| `getMulticastScope()` | `?int` | | ✓ | ✓ |
| `isUniqueLocal()` | `bool` | | ✓ | ✓ |
| `isUnicast()` | `bool` | | ✓ | ✓ |
| `isUnicastGlobal()` | `bool` | | ✓ | ✓ |
| `getProtocolAppropriateAddress()` | `string` | | | ✓ |
| Method | Returns | IPv4 | IPv6 | Multi |
|--------------------------------------------|----------------------|------|------|-------|
| `factory(string $ip, [$strategy])` | Static `IpInterface` | ✓ | ✓ | ✓ |
| `getBinary()` | `string` | ✓ | ✓ | ✓ |
| `getOctets()` | `list<int>` | ✓ | ✓ | ✓ |
| `toString()` | `string` | ✓ | ✓ | ✓ |
| `jsonSerialize()` | `string` | ✓ | ✓ | ✓ |
| `toIntegerString()` | `string` | ✓ | ✓ | ✓ |
| `toHexString()` | `string` | ✓ | ✓ | ✓ |
| `equals(IpInterface $ip)` | `bool` | ✓ | ✓ | ✓ |
| `getVersion()` | `int` | ✓ | ✓ | ✓ |
| `isVersion(int $version)` | `bool` | ✓ | ✓ | ✓ |
| `isVersion4()` | `bool` | ✓ | ✓ | ✓ |
| `isVersion6()` | `bool` | ✓ | ✓ | ✓ |
| `getNetworkIp(int $cidr)` | Static `IpInterface` | ✓ | ✓ | ✓ |
| `getBroadcastIp(int $cidr)` | Static `IpInterface` | ✓ | ✓ | ✓ |
| `next()` | Static `IpInterface` | ✓ | ✓ | ✓ |
| `previous()` | Static `IpInterface` | ✓ | ✓ | ✓ |
| `offset(int $offset)` | Static `IpInterface` | ✓ | ✓ | ✓ |
| `inRange(IpInterface $ip, int $cidr)` | `bool` | ✓ | ✓ | ✓ |
| `getCommonCidr(IpInterface $ip)` | `int` | ✓ | ✓ | ✓ |
| `isMapped()` | `bool` | ✓ | ✓ | ✓ |
| `isDerived()` | `bool` | ✓ | ✓ | ✓ |
| `isCompatible()` | `bool` | ✓ | ✓ | ✓ |
| `isEmbedded()` | `bool` | ✓ | ✓ | ✓ |
| `isLinkLocal()` | `bool` | ✓ | ✓ | ✓ |
| `isLoopback()` | `bool` | ✓ | ✓ | ✓ |
| `isMulticast()` | `bool` | ✓ | ✓ | ✓ |
| `isPrivateUse()` | `bool` | ✓ | ✓ | ✓ |
| `isUnspecified()` | `bool` | ✓ | ✓ | ✓ |
| `isBenchmarking()` | `bool` | ✓ | ✓ | ✓ |
| `isDocumentation()` | `bool` | ✓ | ✓ | ✓ |
| `isGloballyReachable()` | `bool` | ✓ | ✓ | ✓ |
| `isBroadcast()` | `bool` | ✓ | | ✓ |
| `isShared()` | `bool` | ✓ | | ✓ |
| `isFutureReserved()` | `bool` | ✓ | | ✓ |
| `getDotAddress()` | `string` | ✓ | | ✓ |
| `toInteger()` | `int` | ✓ | | ✓ |
| `getCompactedAddress()` | `string` | | ✓ | ✓ |
| `getExpandedAddress()` | `string` | | ✓ | ✓ |
| `getCompactedAddress()` | `string` | | ✓ | ✓ |
| `getSegments()` | `list<int>` | | ✓ | ✓ |
| `getMulticastScope()` | `?int` | | ✓ | ✓ |
| `isUniqueLocal()` | `bool` | | ✓ | ✓ |
| `isUnicast()` | `bool` | | ✓ | ✓ |
| `isUnicastGlobal()` | `bool` | | ✓ | ✓ |
| `isEmbeddedAccordingToStrategy($strategy)` | `bool` | | ✓ | ✓ |
| `isNat64WellKnown()` | `bool` | | ✓ | ✓ |
| `isNat64LocalUse()` | `bool` | | ✓ | ✓ |
| `isTeredo()` | `bool` | | ✓ | ✓ |
| `getEmbeddedIp([$strategy])` | `IPv4` | | ✓ | ✓ |
| `getProtocolAppropriateAddress()` | `string` | | | ✓ |
6 changes: 3 additions & 3 deletions src/AbstractIP.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,17 +193,17 @@ public function getCommonCidr(IpInterface $ip): int

public function isMapped(): bool
{
return (new Strategy\Mapped())->isEmbedded($this->getBinary());
return false;
}

public function isDerived(): bool
{
return (new Strategy\Derived())->isEmbedded($this->getBinary());
return false;
}

public function isCompatible(): bool
{
return (new Strategy\Compatible())->isEmbedded($this->getBinary());
return false;
}

public function isEmbedded(): bool
Expand Down
72 changes: 72 additions & 0 deletions src/Contracts/StrategyDetectionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace Darsyn\IP\Contracts;

use Darsyn\IP\Strategy\EmbeddingStrategyInterface;
use Darsyn\IP\Version\IPv4;

/**
* @experimental
*/
interface StrategyDetectionInterface
{
/**
* Whether an IPv4 address is embedded within this address, according to
* the supplied embedding strategy.
*
* This is the generic form of the named detection predicates; any strategy
* implementation (including a user-defined one) can be tested against the
* address without constructing a new IP object around it.
*/
public function isEmbeddedAccordingToStrategy(EmbeddingStrategyInterface $strategy): bool;

/**
* Whether the IP is an IPv4-mapped IPv6 address, according to
* RFC 4291 § 2.5.5.2 (eg, "::ffff:7f00:1").
*/
public function isMapped(): bool;

/**
* Whether the IP is a 6to4-derived address, according to RFC 3056 § 2 (eg,
* "2002:7f00:1::").
*/
public function isDerived(): bool;

/**
* Whether the IP is an IPv4-compatible IPv6 address, according to
* RFC 4291 § 2.5.5.1 (eg, `::7f00:1`); deprecated by that same RFC.
*/
public function isCompatible(): bool;

/**
* Whether the IP is a NAT64 address within the Well-Known Prefix
* `64:ff9b::/96`, according to RFC 6052 § 2.1 (eg, "64:ff9b::7f00:1").
*/
public function isNat64WellKnown(): bool;

/**
* Whether the IP is a NAT64 address within the Local-use Prefix
* `64:ff9b:1::/48`, according to RFC 8215 § 4.
*/
public function isNat64LocalUse(): bool;

/**
* Whether the IP is a Teredo tunnelling address within `2001::/32`,
* according to RFC 4380 § 4.
*/
public function isTeredo(): bool;

/**
* Get the IPv4 address embedded in this address as an IPv4 instance.
*
* A null strategy falls back to the instance's own embedding strategy on
* Multi, or to the global default set via
* Multi::setDefaultEmbeddingStrategy() otherwise.
*
* @throws \Darsyn\IP\Exception\WrongVersionException when no IPv4 address
* is embedded according to the strategy in effect.
*/
public function getEmbeddedIp(?EmbeddingStrategyInterface $strategy = null): IPv4;
}
8 changes: 8 additions & 0 deletions src/IpInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,33 @@ public static function factory(string $ip);
/**
* Whether the IP is an IPv4-mapped IPv6 address, according to
* RFC 4291 § 2.5.5.2 (eg, "::ffff:7f00:1").
*
* @deprecated in favour of Contracts\StrategyDetectionInterface.
*/
public function isMapped(): bool;

/**
* Whether the IP is a 6to4-derived address, according to RFC 3056 § 2. Any
* address within the 6to4 block `2002::/16` (eg, "2002:7f00:1::"), all of
* which embed an IPv4 address in bits 16-47.
*
* @deprecated in favour of Contracts\StrategyDetectionInterface.
*/
public function isDerived(): bool;

/**
* Whether the IP is an IPv4-compatible IPv6 address, according to
* RFC 4291 § 2.5.5.1 (eg, `::7f00:1`); deprecated by that same RFC.
*
* @deprecated in favour of Contracts\StrategyDetectionInterface.
*/
public function isCompatible(): bool;

/**
* Whether the IP is an IPv4-embedded IPv6 address (according to the
* embedding strategy used).
*
* @deprecated in favour of Contracts\StrategyDetectionInterface.
*/
public function isEmbedded(): bool;

Expand Down
54 changes: 50 additions & 4 deletions src/Version/IPv6.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

use Darsyn\IP\AbstractIP;
use Darsyn\IP\Exception;
use Darsyn\IP\Strategy;
use Darsyn\IP\Strategy\EmbeddingStrategyInterface;
use Darsyn\IP\Strategy\Nat64;
use Darsyn\IP\Util\Binary;
use Darsyn\IP\Util\MbString;

Expand Down Expand Up @@ -138,6 +138,52 @@ public static function fromEmbedded(string $ip, ?EmbeddingStrategyInterface $str
return new static($multi->getBinary());
}

public function isEmbeddedAccordingToStrategy(EmbeddingStrategyInterface $strategy): bool
{
return $strategy->isEmbedded($this->getBinary());
}

/** @not-deprecated IpInterface deprecated in favour of Contracts\StrategyDetectionInterface. */
public function isMapped(): bool
{
return $this->isEmbeddedAccordingToStrategy(new Strategy\Mapped());
}

/** @not-deprecated IpInterface deprecated in favour of Contracts\StrategyDetectionInterface. */
public function isDerived(): bool
{
return $this->isEmbeddedAccordingToStrategy(new Strategy\Derived());
}

/** @not-deprecated IpInterface deprecated in favour of Contracts\StrategyDetectionInterface. */
public function isCompatible(): bool
{
return $this->isEmbeddedAccordingToStrategy(new Strategy\Compatible());
}

public function isNat64WellKnown(): bool
{
return $this->isEmbeddedAccordingToStrategy(Strategy\Nat64::wellKnown());
}

public function isNat64LocalUse(): bool
{
return $this->isEmbeddedAccordingToStrategy(Strategy\Nat64::localUse());
}

public function isTeredo(): bool
{
return $this->isEmbeddedAccordingToStrategy(new Strategy\Teredo());
}

public function getEmbeddedIp(?EmbeddingStrategyInterface $strategy = null): IPv4
{
// A null strategy falls back to Multi's global default; route through
// Multi (whose constructor resolves it) rather than widening the
// private default-strategy accessor.
return Multi::fromBinary($this->getBinary(), $strategy)->getEmbeddedIp();
}

public function getExpandedAddress(): string
{
// Convert the 16-byte binary sequence into a hexadecimal-string
Expand Down Expand Up @@ -249,8 +295,8 @@ public function isUnicastGlobal(): bool
// § 3.1 forbids embedding non-global addresses within the Well-Known
// Prefix, but a received address is not guaranteed to obey that, so
// classify by the embedded address rather than trusting the prefix.
if (($wellKnown = Nat64::wellKnown())->isEmbedded($this->getBinary())) {
return (new IPv4($wellKnown->extract($this->getBinary())))->isGloballyReachable();
if ($this->isNat64WellKnown()) {
return $this->getEmbeddedIp(Strategy\Nat64::wellKnown())->isGloballyReachable();
}
return $this->isUnicast()
&& !$this->isLoopback()
Expand All @@ -270,7 +316,7 @@ public function isUnicastGlobal(): bool
// 8215) as not globally reachable. Detection is by prefix membership
// alone, covering every Network-Specific Prefix operators subdivide
// the /48 into.
&& !Nat64::localUse()->isEmbedded($this->getBinary())
&& !$this->isNat64LocalUse()
&& !$this->isDocumentation()
&& !$this->isBenchmarking()
&& !$this->isIetfProtocolAssignment()
Expand Down
Loading