Publish first version

This commit is contained in:
Daniil Gentili 2023-09-01 22:01:39 +02:00
parent e0137fcb17
commit 26c23dc14b
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
8 changed files with 102 additions and 16 deletions

48
.vscode/launch.json vendored Normal file
View 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
View 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
View 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());

View File

@ -10,7 +10,7 @@
"license": "AGPLv3",
"autoload": {
"psr-4": {
"Danog\\Stun\\": "src/"
"danog\\Stun\\": "src/"
}
},
"authors": [

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}