- 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.
458 lines
14 KiB
PHP
458 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* BirdConfig Class
|
|
*
|
|
* Helper class for BIRD 3 configuration parsing and generation
|
|
*/
|
|
|
|
class BirdConfig {
|
|
|
|
/**
|
|
* Parse BIRD protocol status output
|
|
*
|
|
* @param string $output
|
|
* @return array
|
|
*/
|
|
public static function parseProtocolStatus(string $output): array {
|
|
$protocols = [];
|
|
$lines = explode("\n", trim($output));
|
|
|
|
// Skip header lines
|
|
$dataStarted = false;
|
|
|
|
foreach ($lines as $line) {
|
|
$line = trim($line);
|
|
|
|
if (empty($line)) {
|
|
continue;
|
|
}
|
|
|
|
// Skip BIRD header
|
|
if (strpos($line, 'BIRD') === 0 || strpos($line, 'Name') === 0) {
|
|
$dataStarted = true;
|
|
continue;
|
|
}
|
|
|
|
if (!$dataStarted) {
|
|
continue;
|
|
}
|
|
|
|
// Parse protocol line
|
|
// Format: Name Proto Table State Since Info
|
|
$parts = preg_split('/\s+/', $line, 6);
|
|
|
|
if (count($parts) >= 5) {
|
|
$protocols[] = [
|
|
'name' => $parts[0],
|
|
'proto' => $parts[1],
|
|
'table' => $parts[2],
|
|
'state' => $parts[3],
|
|
'since' => $parts[4],
|
|
'info' => $parts[5] ?? '',
|
|
];
|
|
}
|
|
}
|
|
|
|
return $protocols;
|
|
}
|
|
|
|
/**
|
|
* Parse BIRD route count output
|
|
*
|
|
* @param string $output
|
|
* @return array
|
|
*/
|
|
public static function parseRouteCount(string $output): array {
|
|
$counts = [
|
|
'total' => 0,
|
|
'primary' => 0,
|
|
'filtered' => 0,
|
|
];
|
|
|
|
// Format: X of Y routes for Z networks
|
|
if (preg_match('/(\d+)\s+of\s+(\d+)\s+routes\s+for\s+(\d+)\s+networks/', $output, $matches)) {
|
|
$counts['primary'] = (int) $matches[1];
|
|
$counts['total'] = (int) $matches[2];
|
|
$counts['networks'] = (int) $matches[3];
|
|
}
|
|
|
|
// Filtered routes
|
|
if (preg_match('/(\d+)\s+routes\s+\((\d+)\s+filtered\)/', $output, $matches)) {
|
|
$counts['total'] = (int) $matches[1];
|
|
$counts['filtered'] = (int) $matches[2];
|
|
}
|
|
|
|
return $counts;
|
|
}
|
|
|
|
/**
|
|
* Parse BIRD status output
|
|
*
|
|
* @param string $output
|
|
* @return array
|
|
*/
|
|
public static function parseStatus(string $output): array {
|
|
$status = [
|
|
'version' => '',
|
|
'router_id' => '',
|
|
'hostname' => '',
|
|
'server_time' => '',
|
|
'last_reboot' => '',
|
|
'last_reconfiguration' => '',
|
|
];
|
|
|
|
$lines = explode("\n", trim($output));
|
|
|
|
foreach ($lines as $line) {
|
|
$line = trim($line);
|
|
|
|
if (preg_match('/^BIRD\s+(.+)$/', $line, $matches)) {
|
|
$status['version'] = trim($matches[1]);
|
|
} elseif (preg_match('/^Router ID is\s+(.+)$/', $line, $matches)) {
|
|
$status['router_id'] = trim($matches[1]);
|
|
} elseif (preg_match('/^Hostname is\s+(.+)$/', $line, $matches)) {
|
|
$status['hostname'] = trim($matches[1]);
|
|
} elseif (preg_match('/^Current server time is\s+(.+)$/', $line, $matches)) {
|
|
$status['server_time'] = trim($matches[1]);
|
|
} elseif (preg_match('/^Last reboot on\s+(.+)$/', $line, $matches)) {
|
|
$status['last_reboot'] = trim($matches[1]);
|
|
} elseif (preg_match('/^Last reconfiguration on\s+(.+)$/', $line, $matches)) {
|
|
$status['last_reconfiguration'] = trim($matches[1]);
|
|
}
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
/**
|
|
* Parse detailed protocol info
|
|
*
|
|
* @param string $output
|
|
* @return array
|
|
*/
|
|
public static function parseProtocolDetail(string $output): array {
|
|
$info = [
|
|
'name' => '',
|
|
'description' => '',
|
|
'state' => '',
|
|
'table' => '',
|
|
'neighbor_address' => '',
|
|
'neighbor_as' => '',
|
|
'local_as' => '',
|
|
'neighbor_id' => '',
|
|
'routes' => [
|
|
'imported' => 0,
|
|
'exported' => 0,
|
|
'preferred' => 0,
|
|
'filtered' => 0,
|
|
],
|
|
'uptime' => '',
|
|
'hold_timer' => '',
|
|
'keepalive_timer' => '',
|
|
'last_error' => '',
|
|
'channel' => [],
|
|
];
|
|
|
|
$lines = explode("\n", trim($output));
|
|
$currentChannel = null;
|
|
|
|
foreach ($lines as $line) {
|
|
$line = trim($line);
|
|
|
|
// Protocol name
|
|
if (preg_match('/^(\S+)\s+BGP\s+/', $line, $matches)) {
|
|
$info['name'] = $matches[1];
|
|
}
|
|
|
|
// Description
|
|
if (preg_match('/Description:\s+(.+)$/', $line, $matches)) {
|
|
$info['description'] = trim($matches[1]);
|
|
}
|
|
|
|
// State
|
|
if (preg_match('/BGP state:\s+(\S+)/', $line, $matches)) {
|
|
$info['state'] = $matches[1];
|
|
}
|
|
|
|
// Neighbor address and AS
|
|
if (preg_match('/Neighbor address:\s+(\S+)/', $line, $matches)) {
|
|
$info['neighbor_address'] = $matches[1];
|
|
}
|
|
|
|
if (preg_match('/Neighbor AS:\s+(\d+)/', $line, $matches)) {
|
|
$info['neighbor_as'] = (int) $matches[1];
|
|
}
|
|
|
|
if (preg_match('/Local AS:\s+(\d+)/', $line, $matches)) {
|
|
$info['local_as'] = (int) $matches[1];
|
|
}
|
|
|
|
if (preg_match('/Neighbor ID:\s+(\S+)/', $line, $matches)) {
|
|
$info['neighbor_id'] = $matches[1];
|
|
}
|
|
|
|
// Routes
|
|
if (preg_match('/Routes:\s+(\d+)\s+imported,\s+(\d+)\s+exported,\s+(\d+)\s+preferred/', $line, $matches)) {
|
|
$info['routes']['imported'] = (int) $matches[1];
|
|
$info['routes']['exported'] = (int) $matches[2];
|
|
$info['routes']['preferred'] = (int) $matches[3];
|
|
}
|
|
|
|
if (preg_match('/(\d+)\s+filtered/', $line, $matches)) {
|
|
$info['routes']['filtered'] = (int) $matches[1];
|
|
}
|
|
|
|
// Hold timer
|
|
if (preg_match('/Hold timer:\s+(\S+)/', $line, $matches)) {
|
|
$info['hold_timer'] = $matches[1];
|
|
}
|
|
|
|
// Keepalive timer
|
|
if (preg_match('/Keepalive timer:\s+(\S+)/', $line, $matches)) {
|
|
$info['keepalive_timer'] = $matches[1];
|
|
}
|
|
|
|
// Last error
|
|
if (preg_match('/Last error:\s+(.+)$/', $line, $matches)) {
|
|
$info['last_error'] = trim($matches[1]);
|
|
}
|
|
|
|
// Channel info
|
|
if (preg_match('/Channel (ipv[46])/', $line, $matches)) {
|
|
$currentChannel = $matches[1];
|
|
$info['channel'][$currentChannel] = [];
|
|
}
|
|
|
|
if ($currentChannel && preg_match('/State:\s+(\S+)/', $line, $matches)) {
|
|
$info['channel'][$currentChannel]['state'] = $matches[1];
|
|
}
|
|
|
|
if ($currentChannel && preg_match('/Table:\s+(\S+)/', $line, $matches)) {
|
|
$info['channel'][$currentChannel]['table'] = $matches[1];
|
|
}
|
|
}
|
|
|
|
return $info;
|
|
}
|
|
|
|
/**
|
|
* Get state color class for UI
|
|
*
|
|
* @param string $state
|
|
* @return string
|
|
*/
|
|
public static function getStateColorClass(string $state): string {
|
|
return match (strtolower($state)) {
|
|
'established' => 'color-fg-success',
|
|
'up' => 'color-fg-success',
|
|
'start', 'connect', 'active', 'opensent', 'openconfirm' => 'color-fg-attention',
|
|
'idle' => 'color-fg-muted',
|
|
'down' => 'color-fg-danger',
|
|
default => 'color-fg-default',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get state label for UI
|
|
*
|
|
* @param string $state
|
|
* @return string
|
|
*/
|
|
public static function getStateLabel(string $state): string {
|
|
return match (strtolower($state)) {
|
|
'established' => 'Label--success',
|
|
'up' => 'Label--success',
|
|
'start', 'connect', 'active', 'opensent', 'openconfirm' => 'Label--attention',
|
|
'idle' => 'Label--secondary',
|
|
'down' => 'Label--danger',
|
|
default => 'Label--primary',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate basic BIRD 3 configuration
|
|
*
|
|
* @param array $config
|
|
* @return string
|
|
*/
|
|
public static function generateBasicConfig(array $config): string {
|
|
$bird = "# BIRD 3 Configuration\n";
|
|
$bird .= "# Generated by Pathvector Admin\n\n";
|
|
|
|
// Router ID
|
|
if (!empty($config['router_id'])) {
|
|
$bird .= "router id {$config['router_id']};\n\n";
|
|
}
|
|
|
|
// Log
|
|
$bird .= "log syslog all;\n\n";
|
|
|
|
// Debug
|
|
$bird .= "debug protocols all;\n\n";
|
|
|
|
// Timeformat
|
|
$bird .= "timeformat protocol iso long;\n\n";
|
|
|
|
// Protocol device
|
|
$bird .= "protocol device {\n";
|
|
$bird .= " scan time 10;\n";
|
|
$bird .= "}\n\n";
|
|
|
|
// Protocol direct
|
|
$bird .= "protocol direct {\n";
|
|
$bird .= " ipv4;\n";
|
|
$bird .= " ipv6;\n";
|
|
$bird .= "}\n\n";
|
|
|
|
// Protocol kernel
|
|
$bird .= "protocol kernel {\n";
|
|
$bird .= " ipv4 {\n";
|
|
$bird .= " export all;\n";
|
|
$bird .= " import all;\n";
|
|
$bird .= " };\n";
|
|
$bird .= "}\n\n";
|
|
|
|
$bird .= "protocol kernel {\n";
|
|
$bird .= " ipv6 {\n";
|
|
$bird .= " export all;\n";
|
|
$bird .= " import all;\n";
|
|
$bird .= " };\n";
|
|
$bird .= "}\n\n";
|
|
|
|
return $bird;
|
|
}
|
|
|
|
/**
|
|
* Validate BIRD config syntax (basic)
|
|
*
|
|
* @param string $config
|
|
* @return array
|
|
*/
|
|
public static function validateSyntax(string $config): array {
|
|
$errors = [];
|
|
$warnings = [];
|
|
|
|
// Check for common issues
|
|
$lines = explode("\n", $config);
|
|
$braceCount = 0;
|
|
$inString = false;
|
|
|
|
foreach ($lines as $lineNum => $line) {
|
|
$lineNum++; // 1-indexed
|
|
|
|
// Skip comments
|
|
if (preg_match('/^\s*#/', $line)) {
|
|
continue;
|
|
}
|
|
|
|
// Count braces
|
|
for ($i = 0; $i < strlen($line); $i++) {
|
|
$char = $line[$i];
|
|
|
|
if ($char === '"' && ($i === 0 || $line[$i - 1] !== '\\')) {
|
|
$inString = !$inString;
|
|
}
|
|
|
|
if (!$inString) {
|
|
if ($char === '{') {
|
|
$braceCount++;
|
|
} elseif ($char === '}') {
|
|
$braceCount--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for missing semicolons (basic check)
|
|
$trimmed = trim($line);
|
|
if (!empty($trimmed) &&
|
|
!preg_match('/[{};#]$/', $trimmed) &&
|
|
!preg_match('/^\s*(protocol|filter|function|table|if|else|for|case|switch)/', $trimmed)) {
|
|
$warnings[] = "Line $lineNum: Possible missing semicolon";
|
|
}
|
|
}
|
|
|
|
if ($braceCount !== 0) {
|
|
$errors[] = "Unbalanced braces: " . ($braceCount > 0 ? "missing $braceCount closing braces" : "extra " . abs($braceCount) . " closing braces");
|
|
}
|
|
|
|
return [
|
|
'valid' => empty($errors),
|
|
'errors' => $errors,
|
|
'warnings' => $warnings,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Format BIRD configuration
|
|
*
|
|
* @param string $config
|
|
* @return string
|
|
*/
|
|
public static function formatConfig(string $config): string {
|
|
$lines = explode("\n", $config);
|
|
$output = [];
|
|
$indent = 0;
|
|
|
|
foreach ($lines as $line) {
|
|
$trimmed = trim($line);
|
|
|
|
// Decrease indent for closing braces
|
|
if (preg_match('/^}/', $trimmed)) {
|
|
$indent = max(0, $indent - 1);
|
|
}
|
|
|
|
// Add line with proper indentation
|
|
if (!empty($trimmed)) {
|
|
$output[] = str_repeat("\t", $indent) . $trimmed;
|
|
} else {
|
|
$output[] = '';
|
|
}
|
|
|
|
// Increase indent for opening braces
|
|
if (preg_match('/{\s*$/', $trimmed)) {
|
|
$indent++;
|
|
}
|
|
}
|
|
|
|
return implode("\n", $output);
|
|
}
|
|
|
|
/**
|
|
* Get list of BIRD 3 protocol types
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function getProtocolTypes(): array {
|
|
return [
|
|
'bgp' => 'Border Gateway Protocol',
|
|
'ospf' => 'Open Shortest Path First',
|
|
'rip' => 'Routing Information Protocol',
|
|
'babel' => 'Babel Routing Protocol',
|
|
'static' => 'Static Routes',
|
|
'kernel' => 'Kernel Route Table',
|
|
'device' => 'Device Protocol',
|
|
'direct' => 'Direct Interfaces',
|
|
'pipe' => 'Table Pipe',
|
|
'bfd' => 'Bidirectional Forwarding Detection',
|
|
'rpki' => 'Resource Public Key Infrastructure',
|
|
'mrt' => 'MRT Table Dump',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get BGP session states
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function getBgpStates(): array {
|
|
return [
|
|
'Idle' => 'Not running',
|
|
'Connect' => 'Connecting to peer',
|
|
'Active' => 'Trying to connect',
|
|
'OpenSent' => 'Open message sent',
|
|
'OpenConfirm' => 'Waiting for keepalive',
|
|
'Established' => 'Session established',
|
|
];
|
|
}
|
|
}
|