Hack it up good

This commit is contained in:
James Gilliland 2022-03-09 00:03:45 -06:00
parent b3d3728186
commit 0083bddfca
8 changed files with 184 additions and 224 deletions

View file

@ -3,5 +3,6 @@
"files": [ "files": [
"bin/pfatt", "bin/pfatt",
"cache/container.php" "cache/container.php"
] ],
"compression": "GZ"
} }

View file

@ -16,6 +16,7 @@
], ],
"require": { "require": {
"php": ">=7.4", "php": ">=7.4",
"monolog/monolog": "^2.3",
"psr/log": "^1.1", "psr/log": "^1.1",
"symfony/config": "^5.4", "symfony/config": "^5.4",
"symfony/console": "^5.4", "symfony/console": "^5.4",
@ -25,6 +26,7 @@
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.5", "bamarni/composer-bin-plugin": "^1.5",
"phpstan/phpstan": "^1.4", "phpstan/phpstan": "^1.4",
"squizlabs/php_codesniffer": "^3.6",
"vimeo/psalm": "^4.22" "vimeo/psalm": "^4.22"
}, },
"bin": [ "bin": [

View file

@ -5,8 +5,8 @@ declare(strict_types=1);
namespace Pfatt\Commands; namespace Pfatt\Commands;
use Pfatt\Service\Config; use Pfatt\Service\Config;
use Pfatt\Service\Logger;
use Pfatt\Service\NgController; use Pfatt\Service\NgController;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@ -17,12 +17,12 @@ final class Monitor extends Command
protected static $defaultName = 'monitor'; protected static $defaultName = 'monitor';
protected static $defaultDescription = 'Monitor connection to trigger 5268AC updates'; protected static $defaultDescription = 'Monitor connection to trigger 5268AC updates';
protected Config $config; protected Config $config;
protected Logger $logger; protected LoggerInterface $logger;
protected NgController $ngControl; protected NgController $ngControl;
public function __construct( public function __construct(
Config $config, Config $config,
Logger $logger, LoggerInterface $logger,
NgController $ngControl NgController $ngControl
) { ) {
parent::__construct('monitor'); parent::__construct('monitor');
@ -34,20 +34,31 @@ final class Monitor extends Command
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(
{ InputInterface $input,
$this->logger->setOutput($output); OutputInterface $output
): int {
$this->logger->info('Starting 5268AC ping monitor ...'); $this->logger->info('Starting 5268AC ping monitor ...');
register_shutdown_function(function () { $this->logger->info('Stopping 5268AC ping monitor ...'); }); register_shutdown_function(function () {
$this->logger->info('Stopping 5268AC ping monitor ...');
});
while ($input->isInteractive()) { while ($input->isInteractive()) {
if (!$this->ping()) { if (!$this->ping()) {
$this->ngControl->ngRmHook(); if ($this->ngControl->ngIsConnected()) {
} $this->logger->warning('Connection to ' . $this->config->getPingHost() . ' is up, but EAP is being bridged!');
elseif (!$this->ngControl->ngIsConnected()) { $this->ngControl->ngRmHook();
}
else {
$this->logger->info('Everything is going well.');
}
} elseif (!$this->ngControl->ngIsConnected()) {
$this->logger->warning('Connection to ' . $this->config->getPingHost() . ' is down, but EAP is not being bridged!');
$this->ngControl->ngConnect(); $this->ngControl->ngConnect();
} }
else {
$this->logger->error('Connection to ' . $this->config->getPingHost() . ' is down, and EAP is not being bridged! We are stuck!');
}
sleep(5); sleep(5);
} }
return Command::SUCCESS; return Command::SUCCESS;
@ -56,11 +67,11 @@ final class Monitor extends Command
private function ping(): bool private function ping(): bool
{ {
$process = new Process([ $process = new Process([
'/bin/ping', '/sbin/ping',
'-t2', '-t2',
'-q', '-q',
'-c1', '-c1',
$this->config->getPingHost() $this->config->getPingHost()
]); ]);
return (bool) $process->run(); return (bool) $process->run();
} }

View file

@ -5,8 +5,8 @@ declare(strict_types=1);
namespace Pfatt\Commands; namespace Pfatt\Commands;
use Pfatt\Service\Config; use Pfatt\Service\Config;
use Pfatt\Service\Logger;
use Pfatt\Service\NgController; use Pfatt\Service\NgController;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@ -17,12 +17,12 @@ final class Startup extends Command
protected static $defaultName = 'startup'; protected static $defaultName = 'startup';
protected static $defaultDescription = 'Setup initial connection'; protected static $defaultDescription = 'Setup initial connection';
protected Config $config; protected Config $config;
protected Logger $logger; protected LoggerInterface $logger;
protected NgController $ngControl; protected NgController $ngControl;
public function __construct( public function __construct(
Config $config, Config $config,
Logger $logger, LoggerInterface $logger,
NgController $ngControl NgController $ngControl
) { ) {
parent::__construct('startup'); parent::__construct('startup');
@ -38,8 +38,6 @@ final class Startup extends Command
InputInterface $input, InputInterface $input,
OutputInterface $output OutputInterface $output
): int { ): int {
$this->logger->setOutput($output);
switch ($this->getVeriant()) { switch ($this->getVeriant()) {
case 'opnsense': case 'opnsense':
$kldload = function (string $mod): Process { $kldload = function (string $mod): Process {

View file

@ -4,8 +4,51 @@ declare(strict_types=1);
namespace Pfatt; namespace Pfatt;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\PsrHandler;
use Monolog\Logger;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
final class PfattApplication extends Application final class PfattApplication extends Application
{ {
private Logger $logger;
public function __construct(Logger $logger)
{
parent::__construct('PfATT', '1.0');
$this->logger = $logger;
}
protected function getDefaultInputDefinition()
{
$definition = parent::getDefaultInputDefinition();
$definition->addOption(new InputOption(
'--debug',
'-d',
InputOption::VALUE_NONE,
'Trigger debug output'
));
return $definition;
}
protected function doRunCommand(
Command $command,
InputInterface $input,
OutputInterface $output
) {
// We don't want to interact with the command execution but this is the
// only place to interact with input after the definition is bound and
// before the command is run.
if ($input->hasOption('debug')) {
$handler = new PsrHandler(new ConsoleLogger($output));
$handler->setFormatter(new LineFormatter('[%datetime%] %channel%.%level_name%: %message%'));
$this->logger->pushHandler($handler);
}
return parent::doRunCommand($command, $input, $output);
}
} }

View file

@ -4,11 +4,13 @@ declare(strict_types=1);
namespace Pfatt; namespace Pfatt;
use Monolog\Handler\SyslogHandler;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Pfatt\Commands\GenDuid; use Pfatt\Commands\GenDuid;
use Pfatt\Commands\Monitor; use Pfatt\Commands\Monitor;
use Pfatt\Commands\Startup; use Pfatt\Commands\Startup;
use Pfatt\Service\Config; use Pfatt\Service\Config;
use Pfatt\Service\Logger;
use Pfatt\Service\NgController; use Pfatt\Service\NgController;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
@ -44,15 +46,20 @@ final class PfattKernel
if (!file_exists($file)) { if (!file_exists($file)) {
$containerBuilder = new ContainerBuilder(); $containerBuilder = new ContainerBuilder();
$containerBuilder->register(Logger::class, Logger::class)
->setPublic(true);
$containerBuilder->setAlias(LoggerInterface::class, Logger::class);
$containerBuilder->register('logger-5268', Logger::class)
->setPublic(true)
->setArgument('$channel', 'pfatt-5268AC');
$containerBuilder->register(Config::class, Config::class) $containerBuilder->register(Config::class, Config::class)
->setPublic(true) ->setPublic(true);
->setArguments(['', '', '']);
// Setup various logging handlers.
$containerBuilder->register('syslogger', SyslogHandler::class)
->setArguments(['pfatt'])
->setPublic(true);
$containerBuilder->register(LoggerInterface::class, Logger::class)
->setArguments(['pfatt', [new Reference('syslogger')]])
->setPublic(true);
$containerBuilder->setAlias(Logger::class, LoggerInterface::class);
// Utilities.
$containerBuilder->autowire(NgController::class, NgController::class) $containerBuilder->autowire(NgController::class, NgController::class)
->setPublic(true); ->setPublic(true);
@ -62,8 +69,7 @@ final class PfattKernel
$containerBuilder->autowire(GenDuid::class, GenDuid::class) $containerBuilder->autowire(GenDuid::class, GenDuid::class)
->setPublic(true); ->setPublic(true);
$containerBuilder->autowire(Monitor::class, Monitor::class) $containerBuilder->autowire(Monitor::class, Monitor::class)
->setPublic(true) ->setPublic(true);
->setArgument(Logger::class, new Reference('logger-5268'));
// Register application. // Register application.
$containerBuilder->register(ContainerCommandLoader::class, ContainerCommandLoader::class) $containerBuilder->register(ContainerCommandLoader::class, ContainerCommandLoader::class)
@ -72,8 +78,7 @@ final class PfattKernel
$this->commands $this->commands
]) ])
->setPublic(true); ->setPublic(true);
$containerBuilder->register(PfattApplication::class, PfattApplication::class) $containerBuilder->autowire(PfattApplication::class, PfattApplication::class)
->setArguments(['PfATT'])
->addMethodCall('setCommandLoader', [new Reference(ContainerCommandLoader::class)]) ->addMethodCall('setCommandLoader', [new Reference(ContainerCommandLoader::class)])
->setPublic(true); ->setPublic(true);
@ -96,5 +101,4 @@ final class PfattKernel
$container = new \ProjectServiceContainer(); $container = new \ProjectServiceContainer();
return $container; return $container;
} }
} }

View file

@ -1,63 +0,0 @@
<?php
declare(strict_types=1);
namespace Pfatt\Service;
use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
/**
* PSR-3 style logger.
*/
class Logger extends AbstractLogger
{
private string $channel;
/**
* @var \Psr\Log\LoggerInterface|null
*/
private ?LoggerInterface $logger;
public function __construct(string $channel = 'pfatt', LoggerInterface $logger = null)
{
$this->channel = $channel;
$this->logger = $logger;
}
/**
* {@inheritDoc}
*/
public function log($level, $message, array $context = array())
{
$full_message = $this->getTimestamp() . ' :: [' . $this->channel . '] :: ' . $message;
if (isset($this->logger)) {
$this->logger->log($level, $full_message, $context);
}
else {
echo $full_message;
}
}
/**
* Get the current timestamp formatted for logs.
*
* @return string
* ISO8601 formatted timestamp.
*/
private function getTimestamp(): string
{
return (new \DateTime())->format(\DateTimeInterface::ISO8601);
}
/**
* Add output to use for writing.
*
* @param \Symfony\Component\Console\Output\OutputInterface $output
* Output service.
*/
public function setOutput(OutputInterface $output): void {
$this->logger = new ConsoleLogger($output);
}
}

View file

@ -22,29 +22,29 @@ class NgController implements LoggerAwareInterface
*/ */
public function ngIsConnected(): bool public function ngIsConnected(): bool
{ {
return $this->ngCtlRun('show laneapfilter:eapout'); return $this->ngCtlRun(['show', 'laneapfilter:eapout']);
} }
/** /**
* Use ngctl remove connection. * Remove the eapout connection between laneapfilter and waneapfilter.
*/ */
public function ngRmHook(): void public function ngRmHook(): void
{ {
$this->logger->info('Disconnecting netgraph node ...'); $this->logger->info('Disconnecting netgraph node ...');
$this->ngCtlRun('rmhook laneapfilter: eapout') ? $this->ngCtlRun(['rmhook', 'laneapfilter: eapout']) ?
$this->logger->info('OK!') : $this->logger->info('OK!') :
$this->logger->error('ERROR!'); $this->logger->error('ERROR!');
} }
/** /**
* Use ngctl to establish connection. * Connect laneapfilter and waneapfilter with eapout.
*/ */
public function ngConnect(): void public function ngConnect(): void
{ {
$this->logger->info('Connecting netgraph node ...'); $this->logger->info('Connecting netgraph node ...');
$this->ngCtlRun('connect waneapfilter: laneapfilter: eapout eapout') ? $this->ngCtlRun(['connect', 'waneapfilter:', 'laneapfilter:', 'eapout', 'eapout']) ?
$this->logger->info('OK!') : $this->logger->info('OK!') :
$this->logger->error('ERROR!'); $this->logger->error('ERROR!');
} }
/** /**
@ -54,157 +54,121 @@ class NgController implements LoggerAwareInterface
* ONT network connection interface. * ONT network connection interface.
* @param string $rg * @param string $rg
* Residential Gateway network connection interface. * Residential Gateway network connection interface.
* @return int|void * @param string $rgMac
* ... * Residential Gateway MAC address.
* @todo are all the ifs reversed? * @return void
*
*/ */
public function createNodes(string $ont, string $rg, string $rgMac) public function createNodes(string $ont, string $rg, string $rgMac): void
{ {
$this->logger->info('Building netgraph nodes.'); $this->logger->info('Building netgraph nodes.');
$this->logger->info(' creating ng_one2many...'); $this->logger->info(' creating ng_one2many...');
if ( $this->ngCtlRun(['mkpeer', $ont . ':', 'one2many', 'lower', 'one']);
$this->ngCtlRun("mkpeer $ont: one2many lower one") $this->ngCtlRun(['name', $ont . ':lower', 'o2m']);
&& $this->ngCtlRun("name $ont:lower o2m") $this->logger->info('OK!');
) {
$this->logger->info('OK!');
} else {
$this->logger->error('ERROR!');
return 1;
}
$this->logger->info(' creating vlan node and interface...'); $this->logger->info(' creating vlan node and interface...');
if ( $this->ngCtlRun(['mkpeer', 'o2m:', 'vlan', 'many0', ' downstream']);
$this->ngCtlRun('mkpeer o2m: vlan many0 downstream') $this->ngCtlRun(['name', 'o2m:many0', 'vlan0']);
&& $this->ngCtlRun('name o2m:many0 vlan0') $this->ngCtlRun(['mkpeer', 'vlan0:', 'eiface', 'vlan0', 'ether']);
&& $this->ngCtlRun('mkpeer vlan0: eiface vlan0 ether')
&& $this->ngCtlRun('msg vlan0: \'addfilter { vlan=0 hook="vlan0" }\'') $this->ngCtlRun([
&& $this->ngCtlRun("msg ngeth0: set $rgMac") 'msg',
) { 'vlan0:',
$this->logger->info('OK!'); '\'addfilter { vlan=0 hook="vlan0" }\''
} else { ]);
$this->logger->error('ERROR!'); $this->ngCtlRun(['msg', 'ngeth0:', 'set', $rgMac]);
return 1; $this->logger->info('OK!');
}
$this->logger->info(" defining etf for $ont (ONT)..."); $this->logger->info(" defining etf for $ont (ONT)...");
if ( $this->ngCtlRun(['mkpeer', 'o2m:', 'etf', 'many1', 'downstream']);
$this->ngCtlRun('mkpeer o2m: etf many1 downstream') $this->ngCtlRun(['name', 'o2m:many1', 'waneapfilter']);
&& $this->ngCtlRun('name o2m:many1 waneapfilter') $this->ngCtlRun([
&& $this->ngCtlRun("connect waneapfilter: $ont: nomatch upper") 'connect',
) { 'waneapfilter:',
$this->logger->info('OK!'); $ont . ':',
} else { 'nomatch',
$this->logger->error('ERROR!'); 'upper'
return 1; ]);
} $this->logger->info('OK!');
$this->logger->info(" defining etf for $rg (RG)..."); $this->logger->info(" defining etf for $rg (RG)...");
if ( $this->ngCtlRun(['mkpeer', $rg . ':', 'etf', 'lower', 'downstream']);
$this->ngCtlRun("mkpeer $rg: etf lower downstream") $this->ngCtlRun(['name', $rg . ':', 'lower', 'laneapfilter']);
&& $this->ngCtlRun("name $rg:lower laneapfilter") $this->ngCtlRun([
&& $this->ngCtlRun("connect laneapfilter: $rg: nomatch upper") 'connect',
) { 'laneapfilter:',
$this->logger->info('OK!'); $rg . ':',
} else { 'nomatch',
$this->logger->error('ERROR!'); 'upper'
return 1; ]);
} $this->logger->info('OK!');
$this->logger->info(" bridging etf for $ont <-> $rg..."); $this->logger->info(" bridging etf for $ont <-> $rg...");
if ( $this->ngCtlRun([
$this->ngCtlRun('connect waneapfilter: laneapfilter: eapout eapout') 'connect',
) { 'waneapfilter:',
$this->logger->info('OK!'); 'laneapfilter:',
} else { 'eapout',
$this->logger->error('ERROR!'); 'eapout'
return 1; ]);
} $this->logger->info('OK!');
$this->logger->info(" defining filters for EAP traffic..."); $this->logger->info(" defining filters for EAP traffic...");
if ( $this->ngCtlRun([
$this->ngCtlRun('msg waneapfilter: \'setfilter { matchhook="eapout" ethertype=0x888e }\'') 'msg',
&& $this->ngCtlRun('msg laneapfilter: \'setfilter { matchhook="eapout" ethertype=0x888e }\'') 'waneapfilter:',
) { '\'setfilter { matchhook="eapout" ethertype=0x888e }\''
$this->logger->info('OK!'); ]);
} else { $this->ngCtlRun([
$this->logger->error('ERROR!'); 'msg',
return 1; 'laneapfilter:',
} '\'setfilter { matchhook="eapout" ethertype=0x888e }\''
]);
$this->logger->info('OK!');
$this->logger->info(" enabling one2many links..."); $this->logger->info(" enabling one2many links...");
if ( $this->ngCtlRun([
$this->ngCtlRun('msg o2m: setconfig "{ xmitAlg=2 failAlg=1 enabledLinks=[ 1 1 ] }"') 'msg',
) { 'o2m:',
$this->logger->info('OK!'); 'setconfig',
} else { '"{ xmitAlg=2 failAlg=1 enabledLinks=[ 1 1 ] }"'
$this->logger->error('ERROR!'); ]);
return 1; $this->logger->info('OK!');
}
$this->logger->info(" removing waneapfilter:nomatch hook..."); $this->logger->info(" removing waneapfilter:nomatch hook...");
if ( $this->ngCtlRun(['rmhook', 'waneapfilter:', 'nomatch']);
$this->ngCtlRun('rmhook waneapfilter: nomatch') $this->logger->info('OK!');
) {
$this->logger->info('OK!');
} else {
$this->logger->error('ERROR!');
return 1;
}
$this->logger->info(" enabling $rg interface..."); $this->logger->info(" enabling $rg interface...");
if ( $this->ngCtlRun([$rg, 'up']);
$this->ngCtlRun("$rg up") $this->logger->info('OK!');
) {
$this->logger->info('OK!');
} else {
$this->logger->error('ERROR!');
return 1;
}
$this->logger->info(" enabling $ont interface..."); $this->logger->info(" enabling $ont interface...");
if ( $this->ngCtlRun([$ont, 'up"']);
$this->ngCtlRun("$ont up") $this->logger->info('OK!');
) {
$this->logger->info('OK!');
} else {
$this->logger->error('ERROR!');
return 1;
}
$this->logger->info(" enabling promiscuous mode on $rg..."); $this->logger->info(" enabling promiscuous mode on $rg...");
if ( $this->ngCtlRun([$rg, 'promisc']);
$this->ngCtlRun("$rg promisc") $this->logger->info('OK!');
) {
$this->logger->info('OK!');
} else {
$this->logger->error('ERROR!');
return 1;
}
$this->logger->info("ngeth0 should now be available to configure as your pfSense WAN"); $this->logger->info("ngeth0 should now be available to configure as your pfSense WAN");
$this->logger->info("Done"); $this->logger->info("Done");
return 0;
} }
/** /**
* Check filter status. * Run a ngctl command.
* *
* @param string $arg * @param array<int, string> $args
* Additional arguments to pass to ngctl. * Additional arguments to pass to ngctl.
* *
* @return bool * @return bool
* Process return code. * True on success, false on failure.
*/ */
private function ngCtlRun(string $arg): bool private function ngCtlRun(array $args): bool
{ {
$process = new Process( $process = new Process(['/usr/sbin/ngctl', ...$args], '/tmp');
[ return !$process->run();
'/usr/sbin/ngctl',
$arg
],
'/tmp'
);
return (bool)$process->run();
} }
/** /**