mirror of
https://github.com/danog/stun.git
synced 2024-11-29 20:19:14 +01:00
Publish first version
This commit is contained in:
parent
e0137fcb17
commit
26c23dc14b
48
.vscode/launch.json
vendored
Normal file
48
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Listen for Xdebug",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"port": 9003
|
||||
},
|
||||
{
|
||||
"name": "Launch currently open script",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"cwd": "${fileDirname}",
|
||||
"port": 0,
|
||||
"runtimeArgs": [
|
||||
"-dxdebug.start_with_request=yes"
|
||||
],
|
||||
"env": {
|
||||
"XDEBUG_MODE": "debug,develop",
|
||||
"XDEBUG_CONFIG": "client_port=${port}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Launch Built-in web server",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"-dxdebug.mode=debug",
|
||||
"-dxdebug.start_with_request=yes",
|
||||
"-S",
|
||||
"localhost:0"
|
||||
],
|
||||
"program": "",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"port": 9003,
|
||||
"serverReadyAction": {
|
||||
"pattern": "Development Server \\(http://localhost:([0-9]+)\\) started",
|
||||
"uriFormat": "http://localhost:%s",
|
||||
"action": "openExternally"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
11
README.md
Normal file
11
README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Stun - A pure PHP async STUN implementation
|
||||
|
||||
Created by Daniil Gentili ([@danog](https://github.com/danog)).
|
||||
|
||||
This is a pure PHP async STUN implementation.
|
||||
|
||||
Usage:
|
||||
|
||||
```bash
|
||||
composer require danog/stun
|
||||
```
|
8
bind.php
Normal file
8
bind.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
use danog\Stun\StunClient;
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
$stun = new StunClient("udp://stun.l.google.com:19302");
|
||||
var_dump($stun->bind());
|
@ -10,7 +10,7 @@
|
||||
"license": "AGPLv3",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Danog\\Stun\\": "src/"
|
||||
"danog\\Stun\\": "src/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
|
@ -15,6 +15,15 @@ use danog\Stun\Attributes\XorMappedAddress;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
abstract class Attribute {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function posmod(int $a, int $b): int
|
||||
{
|
||||
$resto = $a % $b;
|
||||
return $resto < 0 ? $resto + \abs($b) : $resto;
|
||||
}
|
||||
|
||||
public function write(string $transactionId): string {
|
||||
$data = $this->writeAttr($transactionId);
|
||||
return pack('n', $this::TYPE).strlen($data).$data.str_repeat("\0", 4 - (strlen($data) % 4));
|
||||
@ -23,7 +32,7 @@ abstract class Attribute {
|
||||
$totalLength -= 4;
|
||||
Assert::true($totalLength >= 0);
|
||||
|
||||
$r = unpack('n', $reader->readLength(4, $cancellation));
|
||||
$r = unpack('n*', $reader->readLength(4, $cancellation));
|
||||
$type = $r[1];
|
||||
$length = $r[2];
|
||||
$result = match ($type) {
|
||||
@ -37,7 +46,8 @@ abstract class Attribute {
|
||||
default => null,
|
||||
};
|
||||
|
||||
$totalLength -= $length + (4 - ($length%4));
|
||||
$left = self::posmod($length, 4);
|
||||
$totalLength -= $length + $left;
|
||||
Assert::true($totalLength >= 0);
|
||||
|
||||
if ($result) {
|
||||
@ -45,7 +55,8 @@ abstract class Attribute {
|
||||
} else {
|
||||
$reader->readLength($length, $cancellation);
|
||||
}
|
||||
$reader->readLength(4 - ($length % 4), $cancellation);
|
||||
if ($left)
|
||||
$reader->readLength($left, $cancellation);
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
@ -27,12 +27,13 @@ final class MappedAddress extends Attribute {
|
||||
{
|
||||
Assert::true($length >= 8, "Wrong length!");
|
||||
$reader->readLength(1, $cancellation);
|
||||
$ip = $reader->readLength($len = match (ord($reader->readLength(1, $cancellation))) {
|
||||
$len = match (ord($reader->readLength(1, $cancellation))) {
|
||||
1 => 4,
|
||||
2 => 16
|
||||
}, $cancellation);
|
||||
};
|
||||
Assert::eq($len+4, $length, "Wrong length!");
|
||||
$port = unpack('n', $reader->readLength(2, $cancellation))[1];
|
||||
$ip = $reader->readLength($len);
|
||||
return new self(new InternetAddress(
|
||||
inet_ntop($ip),
|
||||
$port
|
||||
@ -41,6 +42,6 @@ final class MappedAddress extends Attribute {
|
||||
|
||||
protected function writeAttr(string $_): string {
|
||||
$addr = $this->address->getAddressBytes();
|
||||
return "\0".(strlen($addr) === 4 ? 1 : 16).$addr.pack('n', $this->address->getPort());
|
||||
return "\0".(strlen($addr) === 4 ? 1 : 16).pack('n', $this->address->getPort()).$addr;
|
||||
}
|
||||
}
|
@ -27,14 +27,15 @@ final class XorMappedAddress extends Attribute {
|
||||
|
||||
protected static function readAttr(BufferedReader $reader, string $transactionId, int $length, ?Cancellation $cancellation = null): self
|
||||
{
|
||||
Assert::true($length >= 8, "Wrong length!");
|
||||
Assert::greaterThanEq($length, 8);
|
||||
$reader->readLength(1, $cancellation);
|
||||
$ip = match ($len = ord($reader->readLength(1, $cancellation))) {
|
||||
1 => $reader->readLength(4, $cancellation) ^ Message::MAGIC_COOKIE,
|
||||
2 => $reader->readLength(16, $cancellation) ^ (Message::MAGIC_COOKIE.$transactionId)
|
||||
};
|
||||
$type = ord($reader->readLength(1, $cancellation));
|
||||
$port = unpack('n', $reader->readLength(2, $cancellation) ^ substr(Message::MAGIC_COOKIE, 2))[1];
|
||||
Assert::eq($len+4, $length, "Wrong length!");
|
||||
$ip = match ($type) {
|
||||
1 => $reader->readLength($len = 4, $cancellation) ^ Message::MAGIC_COOKIE,
|
||||
2 => $reader->readLength($len = 16, $cancellation) ^ (Message::MAGIC_COOKIE.$transactionId)
|
||||
};
|
||||
Assert::eq($len+4, $length);
|
||||
return new self(new InternetAddress(
|
||||
inet_ntop($ip),
|
||||
$port
|
||||
@ -48,6 +49,6 @@ final class XorMappedAddress extends Attribute {
|
||||
} else {
|
||||
$addr ^= Message::MAGIC_COOKIE.$transactionId;
|
||||
}
|
||||
return "\0".(strlen($addr) === 4 ? 1 : 16).$addr.(pack('n', $this->address->getPort()) ^ substr(Message::MAGIC_COOKIE, 2));
|
||||
return "\0".(strlen($addr) === 4 ? 1 : 16).(pack('n', $this->address->getPort()) ^ substr(Message::MAGIC_COOKIE, 2)).$addr;
|
||||
}
|
||||
}
|
@ -2,6 +2,9 @@
|
||||
|
||||
namespace danog\Stun;
|
||||
|
||||
use Amp\ByteStream\BufferedReader;
|
||||
use Amp\ByteStream\ReadableBuffer;
|
||||
use Amp\Socket\InternetAddress;
|
||||
use Amp\Socket\Socket;
|
||||
|
||||
use function Amp\Socket\connect;
|
||||
@ -25,7 +28,10 @@ final class StunClient {
|
||||
/**
|
||||
* @return list<Attribute>
|
||||
*/
|
||||
public function bind(Attribute ...$attributes): array {
|
||||
|
||||
public function bind(Attribute ...$attributes): Message {
|
||||
$msg = new Message(MessageMethod::BINDING, MessageClass::REQUEST, $attributes, random_bytes(12));
|
||||
$msg->write($this->socket);
|
||||
$read = new ReadableBuffer($this->socket->read());
|
||||
return Message::read(new BufferedReader($read));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user