diff --git a/src/shadowrocket/Bin/Launcher.php b/src/shadowrocket/Bin/Launcher.php index aea0564..9b14f76 100644 --- a/src/shadowrocket/Bin/Launcher.php +++ b/src/shadowrocket/Bin/Launcher.php @@ -36,7 +36,9 @@ class Launcher /* 2nd */ array('guarder'), /* 3rd */ - array('server', 'local', 'manager'), + array('server', 'local'), + /* 4th */ + array('manager'), ); /** @@ -370,8 +372,11 @@ public static function isModuleReady($module_name) return false; } - public static function getModule($module_name) + public static function getModule($module_name = '') { + if (empty($module_name)) { + return self::$_modules; + } $module_name = strtolower($module_name); return isset(self::$_modules[$module_name]) ? self::$_modules[$module_name] : null; } diff --git a/src/shadowrocket/Exception/ConfigException.php b/src/shadowrocket/Exception/ConfigException.php new file mode 100644 index 0000000..c76de68 --- /dev/null +++ b/src/shadowrocket/Exception/ConfigException.php @@ -0,0 +1,24 @@ + + * @license MIT License + */ + +/** + * Author: ycgambo + * Date: 15/5/2018 + * Time: 8:25 AM + */ + +namespace ShadowRocket\Exception; + + +class ConfigException extends \UnexpectedValueException +{ + +} \ No newline at end of file diff --git a/src/shadowrocket/Module/Base/ManagerInterface.php b/src/shadowrocket/Module/Base/ManagerInterface.php new file mode 100644 index 0000000..93bf920 --- /dev/null +++ b/src/shadowrocket/Module/Base/ManagerInterface.php @@ -0,0 +1,20 @@ + + * @license MIT License + */ + +namespace ShadowRocket\Module\Base; + +interface ManagerInterface +{ + + public function preBootCommands(); + + public function denyCommand($command, $full_command = ''); +} \ No newline at end of file diff --git a/src/shadowrocket/Module/Guarder.php b/src/shadowrocket/Module/Guarder.php index 76732c6..4b91d74 100644 --- a/src/shadowrocket/Module/Guarder.php +++ b/src/shadowrocket/Module/Guarder.php @@ -12,6 +12,7 @@ namespace ShadowRocket\Module; +use ShadowRocket\Exception\ConfigException; use ShadowRocket\Module\Base\ConfigRequired; use ShadowRocket\Module\Base\GuarderInterface; use ShadowRocket\Module\Base\LauncherModuleInterface; @@ -30,7 +31,7 @@ public function getReady() $instance = $this->getConfig('instance'); if (!$instance instanceof GuarderInterface) { - throw new \Exception('A Guarder should implements ShadowRocket\Module\Base\GuarderInterface'); + throw new ConfigException('A Guarder should implements ShadowRocket\Module\Base\GuarderInterface'); } } diff --git a/src/shadowrocket/Module/Helper/ManagerCommandParser.php b/src/shadowrocket/Module/Helper/ManagerCommandParser.php index 17824ef..3e1763e 100644 --- a/src/shadowrocket/Module/Helper/ManagerCommandParser.php +++ b/src/shadowrocket/Module/Helper/ManagerCommandParser.php @@ -29,11 +29,11 @@ public function __construct() $getopt->setHelp(new CustomGetOptHelp()); $getopt->addCommands(array( Command::create('server:add', '') - ->setShortDescription('Add server on one or more port.') - ->setDescription('Create server named as prefix_port on each port. ') + ->setShortDescription('Add server on a port.') + ->setDescription('Create server named as prefix_port. ') ->addOperands(array( Operand::create('password', Operand::REQUIRED), - Operand::create('ports', Operand::MULTIPLE + Operand::REQUIRED), + Operand::create('port', Operand::REQUIRED), )) ->addOptions(array( Option::create('n', 'name', GetOpt::OPTIONAL_ARGUMENT) @@ -43,7 +43,15 @@ public function __construct() ->setDescription('process number.') ->setDefaultValue(4), )), - Command::create('server:del', ''), + Command::create('server:del', '') + ->setShortDescription('Delete added server according to their name.') + ->addOperands(array( + Operand::create('names', Operand::MULTIPLE + Operand::REQUIRED), + )), + Command::create('server:list', '') + ->setShortDescription('Simple server list.'), + Command::create('server:detail', '') + ->setShortDescription('List server detail.'), )); $this->_getopt = $getopt; diff --git a/src/shadowrocket/Module/Logger.php b/src/shadowrocket/Module/Logger.php index 362be34..0fe308a 100644 --- a/src/shadowrocket/Module/Logger.php +++ b/src/shadowrocket/Module/Logger.php @@ -13,6 +13,7 @@ use Monolog\Handler\HandlerInterface; use Monolog\Registry; +use ShadowRocket\Exception\ConfigException; use ShadowRocket\Module\Base\ConfigRequired; use ShadowRocket\Module\Base\LauncherModuleInterface; @@ -33,7 +34,7 @@ public function getReady() $handlers = $this->getConfig('handlers'); foreach ($handlers as $handler) { if (!($handler instanceof HandlerInterface)) { - throw new \Exception( + throw new ConfigException( 'Logger handlers should be an instance array of Monolog\Handler\HandlerInterface.' ); } diff --git a/src/shadowrocket/Module/Manager.php b/src/shadowrocket/Module/Manager.php index 757e1c2..412c83b 100644 --- a/src/shadowrocket/Module/Manager.php +++ b/src/shadowrocket/Module/Manager.php @@ -12,52 +12,74 @@ namespace ShadowRocket\Module; use ShadowRocket\Bin\Launcher; +use ShadowRocket\Exception\ConfigException; use ShadowRocket\Module\Base\ConfigRequired; use ShadowRocket\Module\Base\LauncherModuleInterface; +use ShadowRocket\Module\Base\ManagerInterface; use ShadowRocket\Module\Helper\CustomGetOptHelp; use ShadowRocket\Module\Helper\ManagerCommandParser; use ShadowRocket\Net\Connection; use Workerman\Worker; use GetOpt\ArgumentException; -class Manager extends ConfigRequired implements LauncherModuleInterface +class Manager extends ConfigRequired implements LauncherModuleInterface, ManagerInterface { + protected static $servers = array(); + public function init() { $this->declareRequiredConfig(array( 'port', 'token', - 'process_num' => 1, + 'instance' => new self(), )); } public function getReady() + { + $instance = $this->getConfig('instance'); + + if (!$instance instanceof ManagerInterface) { + throw new ConfigException('A Manager should implements ShadowRocket\Module\Base\ManagerInterface'); + } + + $this->preBoot(); + + $this->listen(); + } + + protected function listen() { $config = $this->getConfig(); $worker = new Worker('tcp://0.0.0.0:' . $config['port']); - $worker->count = $config['process_num']; + $worker->count = 1; $worker->name = 'shadowsocks-manager'; $worker->onConnect = function ($client) use ($config) { $client->stage = Connection::STAGE_INIT; }; - $worker->onMessage = function ($client, $buffer) use ($config) { - $parser = new ManagerCommandParser(); + $manager = $this; + $worker->onMessage = function ($client, $buffer) use ($config, $manager) { switch ($client->stage) { case Connection::STAGE_INIT: if ($buffer == $config['token']) { + $parser = new ManagerCommandParser(); $client->stage = Connection::VERIFIED; $client->send($parser->getHelpText()); } break; case Connection::VERIFIED: + $parser = new ManagerCommandParser(); try { if ($command = $parser->parseCommand($buffer)) { - Manager::handle($command, $parser); - $client->send(PHP_EOL . 'success' . PHP_EOL); + if ($manager->_denyCommand($command, $buffer)) { + $client->send(PHP_EOL . 'Failed: Illegal Command' . PHP_EOL); + } else { + $client->send(PHP_EOL . $manager->handle($command, $parser) . PHP_EOL); + } } else { $client->send($parser->getHelpText()); } @@ -68,24 +90,96 @@ public function getReady() }; } - /** - * @param $command - * @param $parser - * @throws \Exception - */ - protected static function handle($command, $parser) + protected function handle($command, $parser) { switch ($command) { case 'server:add': - foreach ($parser->getOperand('ports') as $port) { - Launcher::superaddModule('server', array( - 'name' => $parser->getOption('name') . '_' . $port, - 'port' => $port, - 'password' => $parser->getOperand('password'), - 'process_num' => $parser->getOption('process'), - )); + $port = $parser->getOperand('port'); + Manager::serverAdd(array( + 'name' => $parser->getOption('name') . '_' . $port, + 'port' => $port, + 'password' => $parser->getOperand('password'), + 'process_num' => $parser->getOption('process'), + )); + return 'Done' . PHP_EOL; + break; + case 'server:del': + foreach ($parser->getOperand('names') as $name) { + Manager::serverDel($name); } + return 'Done' . PHP_EOL; + break; + case 'server:list': + return Manager::serverList(); + break; + case 'server:detail': + return Manager::serverList(true); break; } } + + public static function serverAdd(array $config) + { + Launcher::superaddModule('server', $config); + self::$servers[$config['name']] = $config; + } + + public static function serverDel($server_name) + { + try { + Launcher::removeModule($server_name); + unset(self::$servers[$server_name]); + } catch (\Exception $e) { + } + } + + public static function serverList($detail = false) + { + $rtn = 'Total: ' . count(self::$servers) . PHP_EOL; + + foreach (self::$servers as $name => $config) { + if ($detail) { + $rtn .= "{$config['port']}: $name with password {$config['password']}, {$config['process_num']} process" . PHP_EOL; + } else { + $rtn .= "{$config['port']}: $name" . PHP_EOL; + } + } + + return $rtn; + } + + protected function preBoot() + { + $commands = $this->getConfig('instance')->preBootCommands(); + if ($commands && is_array($commands)) { + $parser = new ManagerCommandParser(); + + foreach ($commands as $buffer) { + try { + if ($command = $parser->parseCommand($buffer)) { + $this->handle($command, $parser); + } + } catch (\Exception $exception) { + throw new ConfigException('Manager preBoot Failed: ' . $exception->getMessage()); + } + } + } + } + + public function _denyCommand($command, $buffer = '') + { + return $this->getConfig('instance')->denyCommand($command, $buffer); + + } + + public function preBootCommands() + { + return array(); + } + + public function denyCommand($command, $full_command = '') + { + return false; + } + } diff --git a/src/shadowrocket/Module/Server.php b/src/shadowrocket/Module/Server.php index b486b59..ce80cd2 100644 --- a/src/shadowrocket/Module/Server.php +++ b/src/shadowrocket/Module/Server.php @@ -97,7 +97,11 @@ protected function createWorker($protocol, $superadd = false) if ($guarder = Launcher::getModuleIfReady('guarder')) { if ($guarder->_deny($request, $config['port'])) { - $worker->stop(); + if ($manager = Launcher::getModuleIfReady('manager')) { + $manager::serverDel($config['name']); + } else { + $worker->stop(); + } } if ($guarder->_block($request, $config['port'])) { @@ -109,7 +113,7 @@ protected function createWorker($protocol, $superadd = false) // build tunnel to actual server $address = "{$protocol}://{$request['dst_addr']}:{$request['dst_port']}"; $remote = ($protocol == 'udp') - ? new UdpConnection(socket_create( AF_INET, SOCK_DGRAM, SOL_UDP ), $address) + ? new UdpConnection(socket_create(AF_INET, SOCK_DGRAM, SOL_UDP), $address) : new AsyncTcpConnection($address); Connection::bind($client, $remote);