Skip to content

Commit 72682e3

Browse files
committed
Initial commit
0 parents  commit 72682e3

File tree

12 files changed

+400
-0
lines changed

12 files changed

+400
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/vendor
2+
/composer.lock

.travis.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
language: php
2+
php:
3+
- 5.3
4+
- 5.6
5+
- hhvm
6+
install:
7+
- composer install --prefer-source --no-interaction
8+
script:
9+
- phpunit --coverage-text

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2015 Christian Lück
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is furnished
10+
to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# clue/mdns-react [![Build Status](https://travis-ci.org/clue/php-mdns-react.svg?branch=master)](https://travis-ci.org/clue/php-mdns-react)
2+
3+
Simple, async multicast DNS (mDNS) resolver library, built on top of [React PHP](http://reactphp.org/).
4+
5+
[Multicast DNS](http://www.multicastdns.org/) name resolution is commonly used
6+
as part of [zeroconf networking](http://en.wikipedia.org/wiki/Zero-configuration_networking)
7+
ala Bonjour/Avahi.
8+
It is defined in [RFC 6762](http://tools.ietf.org/html/rfc6762), in particular
9+
this specification also highlights the
10+
[differences to normal DNS operation](http://tools.ietf.org/html/rfc6762#section-19).
11+
12+
The mDNS protocol is related to, but independent of, DNS-Based Service Discovery (DNS-SD)
13+
as defined in [RFC 6763](http://tools.ietf.org/html/rfc6763).
14+
15+
> Note: This project is in early alpha stage! Feel free to report any issues you encounter.
16+
17+
## Quickstart example
18+
19+
Once [installed](#install), you can use the following code to look up the address of a local domain name:
20+
21+
```php
22+
$loop = React\EventLoop\Factory::create();
23+
$factory = new Factory($loop);
24+
$resolver = $factory->createResolver();
25+
26+
$resolver->lookup('hostname.local')->then(function ($ip) {
27+
echo 'Found: ' . $ip . PHP_EOL;
28+
});
29+
30+
$loop->run();
31+
```
32+
33+
See also the [examples](examples).
34+
35+
## Install
36+
37+
The recommended way to install this library is [through composer](http://getcomposer.org). [New to composer?](http://getcomposer.org/doc/00-intro.md)
38+
39+
```JSON
40+
{
41+
"require": {
42+
"clue/mdns-react": "dev-master"
43+
}
44+
}
45+
```
46+
47+
## License
48+
49+
MIT

composer.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "clue/mdns-react",
3+
"description": "Multicast DNS (mDNS) resolver for zeroconf networking ala Bonjour/Avahi",
4+
"keywords": ["Multicast DNS", "mDNS", "RFC 6762", "zeroconf", "Bonjour", "Avahi", "ReactPHP", "async"],
5+
"homepage": "https://github.com/clue/php-mdns-react",
6+
"license": "MIT",
7+
"authors": [
8+
{
9+
"name": "Christian Lück",
10+
"email": "[email protected]"
11+
}
12+
],
13+
"autoload": {
14+
"psr-4": { "Clue\\React\\Mdns\\": "src/" }
15+
},
16+
"require": {
17+
"php": ">=5.3",
18+
"clue/multicast-react": "~0.2.0",
19+
"react/event-loop": "~0.3.0|~0.4.0",
20+
"react/dns": "~0.3.0|~0.4.0",
21+
"react/promise": "~1.0|~2.0"
22+
}
23+
}

examples/lookup.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
require __DIR__ . '/../vendor/autoload.php';
4+
5+
$name = isset($argv[1]) ? $argv[1] : 'me.local';
6+
7+
$loop = React\EventLoop\Factory::create();
8+
$factory = new Clue\React\Mdns\Factory($loop);
9+
$mdns = $factory->createResolver();
10+
11+
$mdns->resolve($name)->then('e', 'e');
12+
13+
function e($v) {
14+
echo $v . PHP_EOL;
15+
}
16+
17+
$loop->run();

phpunit.xml.dist

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit bootstrap="tests/bootstrap.php"
4+
colors="true"
5+
convertErrorsToExceptions="true"
6+
convertNoticesToExceptions="true"
7+
convertWarningsToExceptions="true"
8+
>
9+
<testsuites>
10+
<testsuite name="mDNS React Test Suite">
11+
<directory>./tests/</directory>
12+
</testsuite>
13+
</testsuites>
14+
<filter>
15+
<whitelist>
16+
<directory>./src/</directory>
17+
</whitelist>
18+
</filter>
19+
</phpunit>

src/Factory.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Clue\React\Mdns;
4+
5+
use React\EventLoop\LoopInterface;
6+
use React\Dns\Query\ExecutorInterface;
7+
use React\Dns\Resolver\Resolver;
8+
9+
class Factory
10+
{
11+
const DNS = '224.0.0.251:5353';
12+
13+
private $loop;
14+
private $executor;
15+
16+
public function __construct(LoopInterface $loop, ExecutorInterface $executor = null)
17+
{
18+
if ($executor === null) {
19+
$executor = new MulticastExecutor($loop);
20+
}
21+
22+
$this->loop = $loop;
23+
$this->executor = $executor;
24+
}
25+
26+
public function createResolver()
27+
{
28+
return new Resolver(self::DNS, $this->executor);
29+
}
30+
}

src/MulticastExecutor.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
namespace Clue\React\Mdns;
4+
5+
use React\Dns\BadServerException;
6+
use React\Dns\Model\Message;
7+
use React\Dns\Protocol\Parser;
8+
use React\Dns\Protocol\BinaryDumper;
9+
use React\EventLoop\LoopInterface;
10+
use React\Promise\Deferred;
11+
use Clue\React\Multicast\Factory as DatagramFactory;
12+
use React\Dns\Query\ExecutorInterface;
13+
use React\Dns\Query\Query;
14+
use React\Dns\Query\TimeoutException;
15+
16+
/**
17+
* DNS executor that uses multicast sockets
18+
*
19+
* Based on React\Dns\Query\Executor which should eventually be split into
20+
* multiple smaller components..
21+
*/
22+
class MulticastExecutor implements ExecutorInterface
23+
{
24+
private $loop;
25+
private $parser;
26+
private $dumper;
27+
private $timeout;
28+
private $factory;
29+
30+
public function __construct(LoopInterface $loop, Parser $parser = null, BinaryDumper $dumper = null, $timeout = 5, DatagramFactory $factory = null)
31+
{
32+
if ($parser === null) {
33+
$parser = new Parser();
34+
}
35+
if ($dumper === null) {
36+
$dumper = new BinaryDumper();
37+
}
38+
if ($factory === null) {
39+
$factory = new DatagramFactory($loop);
40+
}
41+
42+
$this->loop = $loop;
43+
$this->parser = $parser;
44+
$this->dumper = $dumper;
45+
$this->timeout = $timeout;
46+
$this->factory = $factory;
47+
}
48+
49+
public function query($nameserver, Query $query)
50+
{
51+
$request = $this->prepareRequest($query);
52+
53+
$queryData = $this->dumper->toBinary($request);
54+
55+
return $this->doQuery($nameserver, $queryData, $query->name);
56+
}
57+
58+
public function prepareRequest(Query $query)
59+
{
60+
$request = new Message();
61+
$request->header->set('id', $this->generateId());
62+
$request->header->set('rd', 1);
63+
$request->questions[] = (array) $query;
64+
$request->prepare();
65+
66+
return $request;
67+
}
68+
69+
public function doQuery($nameserver, $queryData, $name)
70+
{
71+
$that = $this;
72+
$parser = $this->parser;
73+
$loop = $this->loop;
74+
75+
$deferred = new Deferred();
76+
77+
$timer = $this->loop->addTimer($this->timeout, function () use (&$conn, $name, $deferred) {
78+
$conn->close();
79+
$deferred->reject(new TimeoutException(sprintf("DNS query for %s timed out", $name)));
80+
});
81+
82+
$conn = $this->factory->createSender();
83+
84+
$conn->on('message', function ($data) use ($conn, $parser, $deferred, $timer) {
85+
$response = new Message();
86+
$responseReady = $parser->parseChunk($data, $response);
87+
88+
$conn->close();
89+
$timer->cancel();
90+
91+
if (!$responseReady) {
92+
$deferred->reject(new BadServerException('Invalid response received'));
93+
94+
return;
95+
}
96+
97+
if ($response->header->isTruncated()) {
98+
$deferred->reject(new BadServerException('The server set the truncated bit although we issued a TCP request'));
99+
100+
return;
101+
}
102+
103+
$deferred->resolve($response);
104+
});
105+
106+
$conn->send($queryData, $nameserver);
107+
108+
return $deferred->promise();
109+
}
110+
111+
protected function generateId()
112+
{
113+
return mt_rand(0, 0xffff);
114+
}
115+
}

tests/FactoryTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
use Clue\React\Mdns\Factory;
4+
5+
class FactoryTest extends TestCase
6+
{
7+
public function testCreateResolver()
8+
{
9+
$loop = $this->getMock('React\EventLoop\LoopInterface');
10+
$factory = new Factory($loop);
11+
12+
$resolver = $factory->createResolver();
13+
14+
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
15+
}
16+
}

0 commit comments

Comments
 (0)