lthn.io/app/Mod/Gateway/Controllers/GatewayController.php

172 lines
5.7 KiB
PHP
Raw Permalink Normal View History

<?php
declare(strict_types=1);
namespace Mod\Gateway\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Mod\Chain\Services\DaemonRpc;
use Mod\Gateway\Services\GatewayRegistry;
/**
* Gateway pairing API LetheanGateway nodes connect here.
*
* POST /v1/gateway/pair pair a gateway with lthn.io
* POST /v1/gateway/heartbeat report alive + stats
* GET /v1/gateway/live list live paired gateways
* POST /v1/gateway/dispatch assign a customer connection to a gateway
*/
class GatewayController extends Controller
{
public function __construct(
private readonly GatewayRegistry $registry,
private readonly DaemonRpc $rpc,
) {}
/**
* POST /v1/gateway/pair
* {
* "name": "charon",
* "signature": "...", // sign a challenge with alias wallet key
* "capabilities": ["vpn", "dns", "proxy"],
* "region": "eu-west",
* "bandwidth_mbps": 100,
* "max_connections": 50,
* "wireguard_endpoint": "10.69.69.165:51820",
* "proxy_endpoint": "10.69.69.165:3128"
* }
*/
public function pair(Request $request): JsonResponse
{
$name = strtolower(trim((string) $request->input('name')));
if (empty($name)) {
return response()->json(['error' => 'Name required'], 422);
}
// Verify the name exists on chain
$alias = $this->rpc->getAliasByName($name);
if (! $alias) {
return response()->json([
'error' => 'Name not registered on chain',
'name' => $name,
], 404);
}
// TODO: verify signature proves ownership of the alias wallet
// For now, trust the name exists on chain as proof of identity
$this->registry->register($name, [
'capabilities' => $request->input('capabilities', []),
'region' => $request->input('region', 'unknown'),
'bandwidth_mbps' => (int) $request->input('bandwidth_mbps', 0),
'max_connections' => (int) $request->input('max_connections', 0),
'wireguard_endpoint' => $request->input('wireguard_endpoint', ''),
'proxy_endpoint' => $request->input('proxy_endpoint', ''),
]);
return response()->json([
'status' => 'paired',
'name' => $name . '.lthn',
'heartbeat_interval' => 60,
'ttl' => 300,
'message' => 'Gateway paired. Send heartbeat every 60s to stay live.',
]);
}
/**
* POST /v1/gateway/heartbeat
* {"name": "charon", "connections": 12, "load": 45, "bytes_since_last": 104857600}
*/
public function heartbeat(Request $request): JsonResponse
{
$name = strtolower(trim((string) $request->input('name')));
$ok = $this->registry->heartbeat($name, [
'connections' => (int) $request->input('connections', 0),
'load' => (int) $request->input('load', 0),
'bytes_since_last' => (int) $request->input('bytes_since_last', 0),
]);
if (! $ok) {
return response()->json([
'error' => 'Gateway not paired. Call /pair first.',
'name' => $name,
], 404);
}
return response()->json([
'status' => 'ok',
'name' => $name . '.lthn',
'next_heartbeat' => 60,
]);
}
/**
* GET /v1/gateway/live?cap=proxy
*
* List all live paired gateways with real-time stats.
*/
public function live(Request $request): JsonResponse
{
$capability = strtolower(trim((string) $request->get('cap', '')));
$gateways = $this->registry->liveGateways($capability);
return response()->json([
'gateways' => array_map(fn ($gw) => [
'name' => $gw['name'] . '.lthn',
'region' => $gw['region'],
'capabilities' => $gw['capabilities'],
'load' => $gw['current_load'] ?? 0,
'connections' => $gw['current_connections'] ?? 0,
'bandwidth_mbps' => $gw['bandwidth_mbps'] ?? 0,
'wireguard' => ! empty($gw['wireguard_endpoint']),
'proxy' => ! empty($gw['proxy_endpoint']),
'status' => $gw['status'],
'last_heartbeat' => $gw['last_heartbeat'],
], $gateways),
'count' => count($gateways),
]);
}
/**
* POST /v1/gateway/dispatch
* {"type": "residential", "region": "eu"}
*
* Select the best live gateway for a customer connection.
*/
public function dispatch(Request $request): JsonResponse
{
$type = strtolower(trim((string) $request->input('type', 'proxy')));
$capMap = [
'mobile' => 'proxy',
'residential' => 'proxy',
'seo' => 'exit',
'vpn' => 'vpn',
];
$capability = $capMap[$type] ?? 'proxy';
$gateway = $this->registry->selectBest($capability);
if (! $gateway) {
// Fall back to chain-discovered nodes (unpaired but registered)
return response()->json([
'error' => 'No live paired gateways available. Try again later.',
'fallback' => 'chain',
], 503);
}
return response()->json([
'gateway' => $gateway['name'] . '.lthn',
'region' => $gateway['region'],
'wireguard_endpoint' => $gateway['wireguard_endpoint'] ?? '',
'proxy_endpoint' => $gateway['proxy_endpoint'] ?? '',
'load' => $gateway['current_load'],
'type' => $type,
]);
}
}