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