using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; namespace PSProxmox.IPAM { /// /// Manages IP address pools for Proxmox VE. /// public class IPAMManager { private readonly Dictionary _pools = new Dictionary(); /// /// Creates a new IP pool from a CIDR notation. /// /// The name of the pool. /// The CIDR notation (e.g., 192.168.1.0/24). /// IPs to exclude from the pool. /// The created IP pool. public IPPool CreatePool(string name, string cidr, IEnumerable excludeIPs = null) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } if (string.IsNullOrEmpty(cidr)) { throw new ArgumentNullException(nameof(cidr)); } if (_pools.ContainsKey(name)) { throw new ArgumentException($"Pool with name '{name}' already exists"); } var pool = new IPPool(name, cidr, excludeIPs); _pools[name] = pool; return pool; } /// /// Gets an IP pool by name. /// /// The name of the pool. /// The IP pool. public IPPool GetPool(string name) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } if (!_pools.TryGetValue(name, out var pool)) { throw new KeyNotFoundException($"Pool with name '{name}' not found"); } return pool; } /// /// Gets all IP pools. /// /// All IP pools. public IEnumerable GetPools() { return _pools.Values; } /// /// Removes an IP pool. /// /// The name of the pool. public void RemovePool(string name) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } if (!_pools.ContainsKey(name)) { throw new KeyNotFoundException($"Pool with name '{name}' not found"); } _pools.Remove(name); } /// /// Clears all IP pools. /// public void ClearPools() { _pools.Clear(); } } /// /// Represents an IP address pool. /// public class IPPool { private readonly Queue _availableIPs = new Queue(); private readonly HashSet _usedIPs = new HashSet(); private readonly HashSet _excludedIPs = new HashSet(); /// /// Gets the name of the pool. /// public string Name { get; } /// /// Gets the CIDR notation of the pool. /// public string CIDR { get; } /// /// Gets the network address of the pool. /// public IPAddress NetworkAddress { get; } /// /// Gets the subnet mask of the pool. /// public IPAddress SubnetMask { get; } /// /// Gets the prefix length of the pool. /// public int PrefixLength { get; } /// /// Gets the total number of IPs in the pool. /// public int TotalIPs { get; } /// /// Gets the number of available IPs in the pool. /// public int AvailableIPs => _availableIPs.Count; /// /// Gets the number of used IPs in the pool. /// public int UsedIPs => _usedIPs.Count; /// /// Gets the number of excluded IPs in the pool. /// public int ExcludedIPs => _excludedIPs.Count; /// /// Initializes a new instance of the class. /// /// The name of the pool. /// The CIDR notation (e.g., 192.168.1.0/24). /// IPs to exclude from the pool. public IPPool(string name, string cidr, IEnumerable excludeIPs = null) { Name = name ?? throw new ArgumentNullException(nameof(name)); CIDR = cidr ?? throw new ArgumentNullException(nameof(cidr)); // Parse CIDR string[] parts = cidr.Split('/'); if (parts.Length != 2) { throw new ArgumentException("Invalid CIDR format", nameof(cidr)); } if (!IPAddress.TryParse(parts[0], out var ipAddress)) { throw new ArgumentException("Invalid IP address", nameof(cidr)); } if (!int.TryParse(parts[1], out var prefixLength) || prefixLength < 0 || prefixLength > 32) { throw new ArgumentException("Invalid prefix length", nameof(cidr)); } PrefixLength = prefixLength; SubnetMask = GetSubnetMask(prefixLength); NetworkAddress = GetNetworkAddress(ipAddress, SubnetMask); TotalIPs = (int)Math.Pow(2, 32 - prefixLength) - 2; // Exclude network and broadcast addresses // Initialize available IPs var broadcastAddress = GetBroadcastAddress(NetworkAddress, SubnetMask); var currentIP = IncrementIP(NetworkAddress); // Skip network address // Add excluded IPs if (excludeIPs != null) { foreach (var ip in excludeIPs) { if (IPAddress.TryParse(ip, out var excludeIP)) { _excludedIPs.Add(excludeIP); } } } // Add available IPs while (!currentIP.Equals(broadcastAddress)) { if (!_excludedIPs.Contains(currentIP)) { _availableIPs.Enqueue(currentIP); } currentIP = IncrementIP(currentIP); } } /// /// Gets the next available IP address from the pool. /// /// The next available IP address. public IPAddress GetNextIP() { if (_availableIPs.Count == 0) { throw new InvalidOperationException("No more IPs available in the pool"); } var ip = _availableIPs.Dequeue(); _usedIPs.Add(ip); return ip; } /// /// Releases an IP address back to the pool. /// /// The IP address to release. public void ReleaseIP(IPAddress ip) { if (ip == null) { throw new ArgumentNullException(nameof(ip)); } if (!_usedIPs.Contains(ip)) { throw new ArgumentException("IP is not in use", nameof(ip)); } _usedIPs.Remove(ip); _availableIPs.Enqueue(ip); } /// /// Releases an IP address back to the pool. /// /// The IP address to release. public void ReleaseIP(string ip) { if (string.IsNullOrEmpty(ip)) { throw new ArgumentNullException(nameof(ip)); } if (!IPAddress.TryParse(ip, out var ipAddress)) { throw new ArgumentException("Invalid IP address", nameof(ip)); } ReleaseIP(ipAddress); } /// /// Gets all used IP addresses in the pool. /// /// All used IP addresses. public IEnumerable GetUsedIPs() { return _usedIPs; } /// /// Gets all available IP addresses in the pool. /// /// All available IP addresses. public IEnumerable GetAvailableIPs() { return _availableIPs; } /// /// Gets all excluded IP addresses in the pool. /// /// All excluded IP addresses. public IEnumerable GetExcludedIPs() { return _excludedIPs; } /// /// Clears all used IPs and returns them to the available pool. /// public void Clear() { foreach (var ip in _usedIPs.ToList()) { _availableIPs.Enqueue(ip); } _usedIPs.Clear(); } private static IPAddress GetSubnetMask(int prefixLength) { uint mask = 0xffffffff; mask <<= (32 - prefixLength); return new IPAddress(BitConverter.GetBytes(mask).Reverse().ToArray()); } private static IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask) { byte[] ipBytes = address.GetAddressBytes(); byte[] maskBytes = subnetMask.GetAddressBytes(); byte[] networkBytes = new byte[4]; for (int i = 0; i < 4; i++) { networkBytes[i] = (byte)(ipBytes[i] & maskBytes[i]); } return new IPAddress(networkBytes); } private static IPAddress GetBroadcastAddress(IPAddress networkAddress, IPAddress subnetMask) { byte[] ipBytes = networkAddress.GetAddressBytes(); byte[] maskBytes = subnetMask.GetAddressBytes(); byte[] broadcastBytes = new byte[4]; for (int i = 0; i < 4; i++) { broadcastBytes[i] = (byte)(ipBytes[i] | ~maskBytes[i]); } return new IPAddress(broadcastBytes); } private static IPAddress IncrementIP(IPAddress address) { byte[] bytes = address.GetAddressBytes(); for (int i = bytes.Length - 1; i >= 0; i--) { if (bytes[i] == 255) { bytes[i] = 0; } else { bytes[i]++; break; } } return new IPAddress(bytes); } } }