Files
Pathvector.app/pathvector-admin/lib/ASN.php
Joseph.Rawlings d8b76233c0 Add nodes management and initial setup pages
- Implemented nodes management functionality in `nodes.php` including create, update, and delete actions.
- Added form validation and error handling for node operations.
- Created a new setup page in `setup.php` for initial administrator account creation.
- Included user feedback messages for successful operations and errors.
- Designed user interface for both nodes management and setup processes.
2025-12-14 01:33:12 -05:00

447 lines
14 KiB
PHP

<?php
/**
* ASN Class
*
* Manages ASN (Autonomous System Number) configurations for multi-ASN deployments
*/
require_once __DIR__ . '/FlatFileDB.php';
require_once __DIR__ . '/Logger.php';
require_once __DIR__ . '/Validator.php';
class ASN {
private FlatFileDB $db;
private Logger $logger;
private Validator $validator;
/**
* Constructor
*
* @param string $dataFile Path to ASNs JSON file
* @param Logger $logger Logger instance
*/
public function __construct(string $dataFile, Logger $logger) {
$this->db = new FlatFileDB($dataFile);
$this->logger = $logger;
$this->validator = new Validator();
// Initialize structure
if (!$this->db->exists('asns')) {
$this->db->set('asns', []);
}
}
/**
* Get all ASNs
*
* @return array
*/
public function getAll(): array {
return $this->db->get('asns') ?? [];
}
/**
* Get ASN by ID
*
* @param string $id ASN number as string key
* @return array|null
*/
public function get(string $id): ?array {
$asns = $this->db->get('asns') ?? [];
return $asns[$id] ?? null;
}
/**
* Create new ASN
*
* @param array $data ASN data
* @return array ['success' => bool, 'message' => string, 'errors' => array]
*/
public function create(array $data): array {
$this->validator->clear();
// Validate ASN number
if (!$this->validator->validateAsn($data['asn'] ?? null, 'asn')) {
return [
'success' => false,
'message' => 'Validation failed',
'errors' => $this->validator->getErrors(),
];
}
$asns = $this->db->get('asns') ?? [];
$id = (string) $data['asn'];
// Check if ASN already exists
if (isset($asns[$id])) {
return [
'success' => false,
'message' => "ASN $id already exists",
'errors' => ['asn' => 'ASN already exists'],
];
}
// Build ASN entry
$asnData = [
'asn' => (int) $data['asn'],
'name' => $data['name'] ?? "AS{$data['asn']}",
'description' => $data['description'] ?? '',
'pathvector_defaults' => $this->buildPathvectorDefaults($data['pathvector_defaults'] ?? []),
'templates' => $data['templates'] ?? [],
'contacts' => $data['contacts'] ?? [],
'metadata' => $data['metadata'] ?? [],
'created_at' => date('c'),
'updated_at' => date('c'),
];
$asns[$id] = $asnData;
if ($this->db->set('asns', $asns)) {
$this->logger->success('asn', "Created ASN: $id ({$asnData['name']})");
return [
'success' => true,
'message' => "ASN $id created successfully",
'data' => $asnData,
];
}
return [
'success' => false,
'message' => 'Failed to save ASN',
'errors' => ['database' => 'Failed to save ASN'],
];
}
/**
* Update ASN
*
* @param string $id ASN ID
* @param array $data Updated data
* @return array
*/
public function update(string $id, array $data): array {
$asns = $this->db->get('asns') ?? [];
if (!isset($asns[$id])) {
return [
'success' => false,
'message' => "ASN $id not found",
'errors' => ['asn' => 'ASN not found'],
];
}
// Merge with existing data
$asnData = $asns[$id];
if (isset($data['name'])) {
$asnData['name'] = $data['name'];
}
if (isset($data['description'])) {
$asnData['description'] = $data['description'];
}
if (isset($data['pathvector_defaults'])) {
$asnData['pathvector_defaults'] = $this->buildPathvectorDefaults(
array_merge($asnData['pathvector_defaults'], $data['pathvector_defaults'])
);
}
if (isset($data['templates'])) {
$asnData['templates'] = $data['templates'];
}
if (isset($data['contacts'])) {
$asnData['contacts'] = $data['contacts'];
}
if (isset($data['metadata'])) {
$asnData['metadata'] = array_merge($asnData['metadata'] ?? [], $data['metadata']);
}
$asnData['updated_at'] = date('c');
$asns[$id] = $asnData;
if ($this->db->set('asns', $asns)) {
$this->logger->info('asn', "Updated ASN: $id");
return [
'success' => true,
'message' => "ASN $id updated successfully",
'data' => $asnData,
];
}
return [
'success' => false,
'message' => 'Failed to update ASN',
'errors' => ['database' => 'Failed to save ASN'],
];
}
/**
* Delete ASN
*
* @param string $id ASN ID
* @return array
*/
public function delete(string $id): array {
$asns = $this->db->get('asns') ?? [];
if (!isset($asns[$id])) {
return [
'success' => false,
'message' => "ASN $id not found",
];
}
$asnName = $asns[$id]['name'];
unset($asns[$id]);
if ($this->db->set('asns', $asns)) {
$this->logger->warning('asn', "Deleted ASN: $id ($asnName)");
return [
'success' => true,
'message' => "ASN $id deleted successfully",
];
}
return [
'success' => false,
'message' => 'Failed to delete ASN',
];
}
/**
* Build Pathvector defaults structure
*
* @param array $data
* @return array
*/
private function buildPathvectorDefaults(array $data): array {
return [
// Global config
'router_id' => $data['router_id'] ?? '',
'source4' => $data['source4'] ?? '',
'source6' => $data['source6'] ?? '',
'prefixes' => $data['prefixes'] ?? [],
'hostname' => $data['hostname'] ?? '',
// BIRD/Pathvector paths
'bird_directory' => $data['bird_directory'] ?? '/etc/bird',
'bird_binary' => $data['bird_binary'] ?? '/usr/sbin/bird',
'bird_socket' => $data['bird_socket'] ?? '/var/run/bird.ctl',
'cache_directory' => $data['cache_directory'] ?? '/var/cache/pathvector',
// PeeringDB settings
'peeringdb_query_timeout' => $data['peeringdb_query_timeout'] ?? 10,
'peeringdb_api_key' => $data['peeringdb_api_key'] ?? '',
'peeringdb_cache' => $data['peeringdb_cache'] ?? true,
'peeringdb_url' => $data['peeringdb_url'] ?? 'https://peeringdb.com/api/',
// IRR settings
'irr_server' => $data['irr_server'] ?? 'rr.ntt.net',
'irr_query_timeout' => $data['irr_query_timeout'] ?? 30,
'bgpq_args' => $data['bgpq_args'] ?? '',
// RTR/RPKI settings
'rtr_server' => $data['rtr_server'] ?? '',
'rpki_enable' => $data['rpki_enable'] ?? true,
// Filtering defaults
'keep_filtered' => $data['keep_filtered'] ?? false,
'merge_paths' => $data['merge_paths'] ?? false,
'default_route' => $data['default_route'] ?? false,
'accept_default' => $data['accept_default'] ?? false,
// Communities
'origin_communities' => $data['origin_communities'] ?? [],
'local_communities' => $data['local_communities'] ?? [],
'add_on_import' => $data['add_on_import'] ?? [],
'add_on_export' => $data['add_on_export'] ?? [],
// Blocklist
'blocklist' => $data['blocklist'] ?? [],
'blocklist_urls' => $data['blocklist_urls'] ?? [],
'blocklist_files' => $data['blocklist_files'] ?? [],
// Bogons
'bogons4' => $data['bogons4'] ?? [],
'bogons6' => $data['bogons6'] ?? [],
'bogon_asns' => $data['bogon_asns'] ?? [],
'blackhole_bogon_asns' => $data['blackhole_bogon_asns'] ?? false,
// Transit ASNs
'transit_asns' => $data['transit_asns'] ?? [],
// Operation modes
'no_announce' => $data['no_announce'] ?? false,
'no_accept' => $data['no_accept'] ?? false,
'stun' => $data['stun'] ?? false,
// Global config
'global_config' => $data['global_config'] ?? '',
// Web UI
'web_ui_file' => $data['web_ui_file'] ?? '',
// Log
'log_file' => $data['log_file'] ?? 'syslog',
// Keepalived
'keepalived_config' => $data['keepalived_config'] ?? '/etc/keepalived.conf',
// Authorized providers (ASPA)
'authorized_providers' => $data['authorized_providers'] ?? [],
];
}
/**
* Count ASNs
*
* @return int
*/
public function count(): int {
$asns = $this->db->get('asns') ?? [];
return count($asns);
}
/**
* Search ASNs
*
* @param string $query
* @return array
*/
public function search(string $query): array {
$asns = $this->db->get('asns') ?? [];
$query = strtolower($query);
return array_filter($asns, function($asn) use ($query) {
return strpos(strtolower((string) $asn['asn']), $query) !== false ||
strpos(strtolower($asn['name']), $query) !== false ||
strpos(strtolower($asn['description'] ?? ''), $query) !== false;
});
}
/**
* Export ASN to YAML format for Pathvector
*
* @param string $id
* @return string|null
*/
public function exportYaml(string $id): ?string {
$asn = $this->get($id);
if (!$asn) {
return null;
}
$config = [];
$config['asn'] = $asn['asn'];
$defaults = $asn['pathvector_defaults'];
if (!empty($defaults['router_id'])) {
$config['router-id'] = $defaults['router_id'];
}
if (!empty($defaults['source4'])) {
$config['source4'] = $defaults['source4'];
}
if (!empty($defaults['source6'])) {
$config['source6'] = $defaults['source6'];
}
if (!empty($defaults['prefixes'])) {
$config['prefixes'] = $defaults['prefixes'];
}
if (!empty($defaults['hostname'])) {
$config['hostname'] = $defaults['hostname'];
}
// Add other non-empty defaults
foreach ($defaults as $key => $value) {
if ($value !== '' && $value !== [] && $value !== null && !isset($config[$key])) {
$yamlKey = str_replace('_', '-', $key);
$config[$yamlKey] = $value;
}
}
return $this->arrayToYaml($config);
}
/**
* Convert array to YAML string
*
* @param array $data
* @param int $indent
* @return string
*/
private function arrayToYaml(array $data, int $indent = 0): string {
$yaml = '';
$prefix = str_repeat(' ', $indent);
foreach ($data as $key => $value) {
if (is_array($value)) {
if (empty($value)) {
continue;
}
// Check if it's a sequential array
if (array_keys($value) === range(0, count($value) - 1)) {
$yaml .= "$prefix$key:\n";
foreach ($value as $item) {
if (is_array($item)) {
$yaml .= "$prefix -\n" . $this->arrayToYaml($item, $indent + 2);
} else {
$yaml .= "$prefix - " . $this->formatYamlValue($item) . "\n";
}
}
} else {
$yaml .= "$prefix$key:\n" . $this->arrayToYaml($value, $indent + 1);
}
} else {
if ($value === '' || $value === null) {
continue;
}
$yaml .= "$prefix$key: " . $this->formatYamlValue($value) . "\n";
}
}
return $yaml;
}
/**
* Format value for YAML output
*
* @param mixed $value
* @return string
*/
private function formatYamlValue($value): string {
if (is_bool($value)) {
return $value ? 'true' : 'false';
}
if (is_numeric($value)) {
return (string) $value;
}
if (is_string($value) && (strpos($value, ':') !== false || strpos($value, '#') !== false)) {
return '"' . addslashes($value) . '"';
}
return $value;
}
/**
* Backup ASN data
*
* @param string $backupDir
* @return string|false
*/
public function backup(string $backupDir): string|false {
return $this->db->backup($backupDir);
}
}