$value) { if (property_exists(get_called_class(), $field)) { $this->$field = $value; } } } public static function getIpNetworks($filters, $limit = null, $offset = 0, $order = null): array { $db = FronkDB::singleton(); $sql = "SELECT *, CONCAT(INET_NTOA(network_address), '/', cidr) AS network_address_str FROM `IpNetwork` WHERE 1 "; $sql .= isset($filters['network_address_str']) ? " AND CONCAT(INET_NTOA(network_address), '/', cidr) LIKE '%" . $filters['network_address_str'] . "%'" : ""; $sql .= self::getSqlFilter($filters); $sql .= $order === null || $order['key'] === null ? " ORDER BY `network_address` ASC" : " ORDER BY `" . $order['key'] . "` " . $order['order']; $sql .= $limit === null ? "" : " LIMIT " . $limit . " OFFSET " . $offset; $result = $db->query($sql); $rows = []; while ($row = $result->fetch_assoc()) { $rows[] = new IpNetworkModel($row); } return $rows; } public static function getSqlFilter($filters): string { $sql = isset($filters['name']) ? Helper::generateFilterCondition($filters['name'], 'name') : ""; $sql .= isset($filters['description']) ? Helper::generateFilterCondition($filters['description'], 'description') : ""; $sql .= isset($filters['location']) ? Helper::generateFilterCondition($filters['location'], 'location') : ""; $sql .= isset($filters['status']) ? Helper::generateFilterCondition($filters['status'], 'status') : ""; $sql .= empty($filters['parent_network_id']) ? " AND `parent_network_id` IS NULL" : " AND `parent_network_id` = " . $filters['parent_network_id']; return $sql; } public static function countIpNetworks($filters) { $db = FronkDB::singleton(); $sql = "SELECT COUNT(*) as `total_rows` FROM `IpNetwork` WHERE 1 " . self::getSqlFilter($filters); $result = $db->query($sql); return $result->fetch_assoc()['total_rows']; } public static function countChildren($id) { $db = FronkDB::singleton(); $sql = "SELECT COUNT(*) as `total_rows` FROM `IpNetwork` WHERE `parent_network_id` = $id"; $result = $db->query($sql); return $result->fetch_assoc()['total_rows']; } public static function getChildren($id): array { $db = FronkDB::singleton(); $sql = "SELECT * FROM `IpNetwork` WHERE `parent_network_id` = $id"; $result = $db->query($sql); $rows = []; while ($row = $result->fetch_assoc()) { $rows[] = new IpNetworkModel($row); } return $rows; } /** * @throws Exception */ public static function createIpNetwork($data): void { $db = FronkDB::singleton(); $network_address = $data['network_address']; $cidr = $data['cidr']; $parent_network_id = $data['parent_network_id'] ?? 'NULL'; $status = $data['status']; $name = $data['name']; $description = $data['description']; $location = $data['location']; // Convert network address to integer $network_address_int = ip2long($network_address); // Define query to check for overlapping networks $check_sql = " SELECT `id`, `network_address`, `cidr` FROM `IpNetwork` WHERE ( (INET_ATON('$network_address') & ~((1 << (32 - `cidr`)) - 1)) = `network_address` OR (`network_address` & ~((1 << (32 - $cidr)) - 1)) = INET_ATON('$network_address') )"; // die($check_sql); $result = $db->query($check_sql); $parentFound = false; $parentFoundId = null; while ($row = $result->fetch_assoc()) { $existing_network_address_int = $row['network_address']; $existing_cidr = $row['cidr']; // Check if the new network is within an existing network if ($network_address_int >= $existing_network_address_int && $network_address_int < ($existing_network_address_int + pow(2, (32 - $existing_cidr)))) { if ($cidr <= $existing_cidr) { // The new network is larger or equal, which is invalid if ($cidr == 32) { throw new Exception("Address $network_address/32 already exists"); } else { throw new Exception("Network $network_address/$cidr conflicts with existing network " . long2ip($existing_network_address_int) . "/$existing_cidr"); } } // Check if the new network is a correct subnetwork if ($parent_network_id != 'NULL') { $result->data_seek(0); while ($row = $result->fetch_assoc()) { if ($row['id'] == $parent_network_id) { $parentFoundId = $row['id']; $parentFound = true; break; } } if (!$parentFound) { throw new Exception("Parent network ID $parent_network_id does not match the actual parent network ID {$row['id']}"); } // New check for conflicts with child networks $check_child_sql = " SELECT `id`, `network_address`, `cidr` FROM `IpNetwork` WHERE `parent_network_id` = $parent_network_id AND ( INET_ATON('$network_address') BETWEEN `network_address` AND (`network_address` + POW(2, (32 - `cidr`)) - 1) OR `network_address` BETWEEN INET_ATON('$network_address') AND (INET_ATON('$network_address') + POW(2, (32 - $cidr)) - 1) )"; ; $child_result = $db->query($check_child_sql); while ($child_row = $child_result->fetch_assoc()) { $existing_child_network_address_int = $child_row['network_address']; $existing_child_cidr = $child_row['cidr']; // Check if the new network overlaps any existing child networks if ($network_address_int < $existing_child_network_address_int && ($network_address_int + pow(2, (32 - $cidr))) > $existing_child_network_address_int) { throw new Exception("Network $network_address/$cidr conflicts with child network " . long2ip($existing_child_network_address_int) . "/$existing_child_cidr"); } } } else { // If no parent ID provided and the new network is within an existing network, throw an error throw new Exception("Network $network_address/$cidr must be a child of " . long2ip($existing_network_address_int) . "/$existing_cidr."); } // For CIDR 32, check if it already exists but also check if $parent_network_id is same as $parentFoundId but if $parentFoundId is null, throw an error if ($cidr == 32 && $parent_network_id != 'NULL' && $parent_network_id != $parentFoundId) { throw new Exception("CIDR 32 address $network_address already exists within " . long2ip($existing_network_address_int) . "/$existing_cidr"); } } // Check if the new network overlaps any existing networks if ($network_address_int < $existing_network_address_int && ($network_address_int + pow(2, (32 - $cidr))) > $existing_network_address_int) { throw new Exception("Network $network_address/$cidr overlaps with existing network " . long2ip($existing_network_address_int) . "/$existing_cidr"); } } if (!$parentFound && $parent_network_id === 'NULL' && intval($cidr) >= 32) { throw new Exception("Root Networks cannot be single IPs"); } // Proceed with insertion if no conflicts are found $sql = "INSERT INTO `IpNetwork` (`network_address`, `cidr`, `parent_network_id`, `status`, `name`, `description`, `location`, `create`, `edit`) VALUES (INET_ATON('$network_address'), $cidr, $parent_network_id, '$status', '$name', '$description', '$location', UNIX_TIMESTAMP(), UNIX_TIMESTAMP())"; $result = $db->query($sql); if (!$result) { throw new Exception("Failed to insert network"); } } public static function updateIpNetwork($data): void { $db = FronkDB::singleton(); $sqlSetStr = ""; $sqlSetStr .= isset($data['status']) ? "`status` = '" . $data['status'] . "', " : ""; $sqlSetStr .= isset($data['name']) ? "`name` = '" . $data['name'] . "', " : ""; $sqlSetStr .= isset($data['description']) ? "`description` = '" . $data['description'] . "', " : ""; $sqlSetStr .= isset($data['location']) ? "`location` = '" . $data['location'] . "', " : ""; $sqlSetStr .= "`edit` = UNIX_TIMESTAMP()"; $sql = "UPDATE `IpNetwork` SET $sqlSetStr WHERE `id` = " . $data['id']; $result = $db->query($sql); if (!$result) { throw new Exception("Failed to update network"); } } public static function getById($id) { $db = FronkDB::singleton(); $sql = "SELECT *, INET_NTOA(network_address) as network_address_str FROM `IpNetwork` WHERE `id` = $id"; $result = $db->query($sql); $row = $result->fetch_assoc(); return $row ? new IpNetworkModel($row) : null; } public static function deleteIpNetwork($id) { // delete this id and all children and children of children until no more children $db = FronkDB::singleton(); $sql = "SELECT `id` FROM `IpNetwork` WHERE `parent_network_id` = $id"; $result = $db->query($sql); while ($row = $result->fetch_assoc()) { self::deleteIpNetwork($row['id']); } $sql = "DELETE FROM `IpNetwork` WHERE `id` = $id"; $result = $db->query($sql); if (!$result) { throw new Exception("Failed to delete network"); } } }