Skip to content

Commit

Permalink
Make happy eyeballs available in Connector with a flag
Browse files Browse the repository at this point in the history
We'll set it to off until version 2.0.0 to ensure backwards
compatibility.
  • Loading branch information
WyriHaximus committed Oct 28, 2019
1 parent ca817ad commit 770727f
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 3 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
},
"require-dev": {
"clue/block-react": "^1.2",
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35",
"react/promise-stream": "^1.2"
},
"autoload": {
"psr-4": {
Expand Down
7 changes: 6 additions & 1 deletion src/Connector.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function __construct(LoopInterface $loop, array $options = array())

'dns' => true,
'timeout' => true,
'happy_eyeballs' => false,
);

if ($options['timeout'] === true) {
Expand Down Expand Up @@ -70,7 +71,11 @@ public function __construct(LoopInterface $loop, array $options = array())
);
}

$tcp = new DnsConnector($tcp, $resolver);
if ($options['happy_eyeballs'] === true) {
$tcp = new HappyEyeBallsConnector($loop, $tcp, $resolver);
} else {
$tcp = new DnsConnector($tcp, $resolver);
}
}

if ($options['tcp'] !== false) {
Expand Down
156 changes: 155 additions & 1 deletion tests/FunctionalConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@

use Clue\React\Block;
use React\EventLoop\Factory;
use React\Socket\ConnectionInterface;
use React\Socket\Connector;
use React\Socket\ConnectorInterface;
use React\Socket\TcpServer;

class FunctionalConnectorTest extends TestCase
{
const TIMEOUT = 1.0;
const TIMEOUT = 30.0;

private $ipv4 = false;
private $ipv6 = false;

public function __construct()
{
parent::__construct();
$this->ipv4 = !!@file_get_contents('http://ipv4.tlund.se/');
$this->ipv6 = !!@file_get_contents('http://ipv6.tlund.se/');
}

/** @test */
public function connectionToTcpServerShouldSucceedWithLocalhost()
Expand All @@ -29,4 +41,146 @@ public function connectionToTcpServerShouldSucceedWithLocalhost()
$connection->close();
$server->close();
}

/**
* @test
*/
public function connectionToRemoteTCP4n6ServerShouldResultInOurIP()
{
$loop = Factory::create();

$connector = new Connector($loop, array('happy_eyeballs' => true));

$ip = Block\await($this->request('dual.tlund.se', $connector), $loop, self::TIMEOUT);

$this->assertSame($ip, filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6), $ip);
}

/**
* @test
*/
public function connectionToRemoteTCP4ServerShouldResultInOurIP()
{
if ($this->ipv4 === false) {
$this->markTestSkipped('IPv4 not supported on this system');
}

$loop = Factory::create();

$connector = new Connector($loop, array('happy_eyeballs' => true));

$ip = Block\await($this->request('ipv4.tlund.se', $connector), $loop, self::TIMEOUT);

$this->assertSame($ip, filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4), $ip);
$this->assertFalse(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6), $ip);
}

/**
* @test
*/
public function connectionToRemoteTCP6ServerShouldResultInOurIP()
{
if ($this->ipv6 === false) {
$this->markTestSkipped('IPv6 not supported on this system');
}

$loop = Factory::create();

$connector = new Connector($loop, array('happy_eyeballs' => true));

$ip = Block\await($this->request('ipv6.tlund.se', $connector), $loop, self::TIMEOUT);

$this->assertFalse(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4), $ip);
$this->assertSame($ip, filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6), $ip);
}

/**
* @test
*
* @expectedException \RuntimeException
* @expectedExceptionMessageRegExp /Connection to ipv6.tlund.se:80 failed/
*/
public function tryingToConnectToAnIPv6OnlyHostWithOutHappyEyeBallsShouldResultInFailure()
{
$loop = Factory::create();

$connector = new Connector($loop);

$ip = Block\await($this->request('ipv6.tlund.se', $connector), $loop, self::TIMEOUT);

$this->assertFalse(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4), $ip);
$this->assertSame($ip, filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6), $ip);
}

/**
* @test
*
* @expectedException \RuntimeException
* @expectedExceptionMessageRegExp /Connection to tcp:\/\/193.15.228.195:80 failed:/
*/
public function connectingDirectlyToAnIPv4AddressShouldFailWhenIPv4IsntAvailable()
{
if ($this->ipv4 === true) {
$this->markTestSkipped('IPv4 supported on this system');
}

$loop = Factory::create();

$connector = new Connector($loop);

$host = current(dns_get_record('ipv4.tlund.se', DNS_A));
$host = $host['ip'];
$ip = Block\await($this->request($host, $connector), $loop, self::TIMEOUT);

$this->assertSame($ip, filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4), $ip);
$this->assertFalse(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6), $ip);
}

/**
* @test
*
* @expectedException \RuntimeException
* @expectedExceptionMessageRegExp /Connection to tcp:\/\/\[2a00:801:f::195\]:80 failed:/
*/
public function connectingDirectlyToAnIPv6AddressShouldFailWhenIPv6IsntAvailable()
{
if ($this->ipv6 === true) {
$this->markTestSkipped('IPv6 supported on this system');
}

$loop = Factory::create();

$connector = new Connector($loop);

$host = current(dns_get_record('ipv6.tlund.se', DNS_AAAA));
$host = $host['ipv6'];
$host = '[' . $host . ']';
$ip = Block\await($this->request($host, $connector), $loop, self::TIMEOUT);

$this->assertFalse(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4), $ip);
$this->assertSame($ip, filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6), $ip);
}

/**
* @internal
*/
public function parseIpFromPage($body)
{
$ex = explode('title="Look up on bgp.he.net">', $body);
$ex = explode('<', $ex[1]);

return $ex[0];
}

private function request($host, ConnectorInterface $connector)
{
$that = $this;
return $connector->connect($host . ':80')->then(function (ConnectionInterface $connection) use ($host) {
$connection->write("GET / HTTP/1.1\r\nHost: " . $host . "\r\n\r\n");

return \React\Promise\Stream\buffer($connection);
})->then(function ($response) use ($that) {
return $that->parseIpFromPage($response);
});
}
}

0 comments on commit 770727f

Please sign in to comment.