【问题标题】:How can I convert IP range to Cidr in C#?如何在 C# 中将 IP 范围转换为 Cidr?
【发布时间】:2012-11-10 14:06:26
【问题描述】:

有很多将 CIDR 转换为 ip 范围的示例。但我想知道如何使用开始/结束 ip 地址在 C# 中生成一个/一些 cidr?

例如: 我有起始IP地址(192.168.0.1)和结束IP地址(192.168.0.254)。所以使用这两个地址生成 cidr 列表 {192.168.0.0/31, 192.168.0.2/32}。有C#代码示例吗?

【问题讨论】:

  • 请展示一些源代码...你试过什么?究竟是什么不工作?

标签: c# ip cidr


【解决方案1】:

很难确定这里到底要问什么(您提供的 CIDR 列表似乎与给定的输入地址不对应),但是下面的代码将允许您找到包含指定的开始和结束地址。

您需要先将开始和结束 IP 地址转换为 32 位整数(例如 192.168.0.1 变为 0xc0a80001),然后应用以下算法:

var startAddr = 0xc0a80001; // 192.168.0.1
var endAddr = 0xc0a800fe;   // 192.168.0.254

// Determine all bits that are different between the two IPs
var diffs = startAddr ^ endAddr;

// Now count the number of consecutive zero bits starting at the most significant
var bits = 32;
var mask = 0;
while (diffs != 0)
{
    // We keep shifting diffs right until it's zero (i.e. we've shifted all the non-zero bits off)
    diffs >>= 1;
    // Every time we shift, that's one fewer consecutive zero bits in the prefix
    bits--;
    // Accumulate a mask which will have zeros in the consecutive zeros of the prefix and ones elsewhere
    mask = (mask << 1) | 1;
}

// Construct the root of the range by inverting the mask and ANDing it with the start address
var root = startAddr & ~mask;
// Finally, output the range
Console.WriteLine("{0}.{1}.{2}.{3}/{4}", root >> 24, (root >> 16) & 0xff, (root >> 8) & 0xff, root & 0xff, bits);

在您问题中的两个地址上运行它会给出:

192.168.0.0/24

【讨论】:

  • 这会计算包含两个给定 IPv4 地址的最小 CIDR 块,但我认为问题是关于完全覆盖该范围的 CIDR 列表。
  • @Stefan 很难确定 OP 想要什么,因为问题中的示例输出似乎与给定的开始/结束地址不对应。由于 OP 自己对(据称)所需输出的回答在一天后发布,在许多人认为我的足够有用以对其进行投票之后,我将其保留原样。这似乎不能保证(我假设)你的反对票,但对每个人来说都是他自己的。
  • 嗯。如何编辑你的答案,以便清楚你是如何解释这个问题的?必须阅读源代码才能了解它的实际作用,这很烦人。
  • @Stefan 我已经编辑了答案以阐明代码的意图。希望现在更清楚了。
【解决方案2】:

CIDR 类使用静态方法将 IP 范围拆分为一组最小的不相交的 CIDR 范围,这些范围完全覆盖原始 IP 范围。

拆分方法(在 BigIntegers 上进行实际工作的“真正”方法,以及 IP 地址和 CIDR 创建的包装器)位于底部。

foreach (IPRangeToCidr.CIDR c in IPRangeToCidr.CIDR.split(first, last)) ...一起使用

在引用中需要 System.Numerics.dll。

using System;
using System.Numerics;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;

namespace IPRangeToCidr {
    public struct CIDR {
        private IPAddress address;
        private uint network_length, bits;

        public CIDR(IPAddress address, uint network_length) {
            this.address = address;
            this.network_length = network_length;
            this.bits = AddressFamilyBits(address.AddressFamily);
            if (network_length > bits) {
                throw new ArgumentException("Invalid network length " + network_length + " for " + address.AddressFamily);
            }
        }

        public IPAddress NetworkAddress {
            get { return address; }
        }
        public IPAddress LastAddress {
            get { return IPAddressAdd(address, (new BigInteger(1) << (int) HostLength) - 1); }
        }
        public uint NetworkLength {
            get { return network_length; }
        }
        public uint AddressBits {
            get { return bits; }
        }
        public uint HostLength {
            get { return bits - network_length; }
        }

        override public String ToString() {
            return address.ToString() + "/" + NetworkLength.ToString();
        }

        public String ToShortString() {
            if (network_length == bits) return address.ToString();
            return address.ToString() + "/" + NetworkLength.ToString();
        }

        /* static helpers */
        public static IPAddress IPAddressAdd(IPAddress address, BigInteger i) {
            return IPFromUnsigned(IPToUnsigned(address) + i, address.AddressFamily);
        }

        public static uint AddressFamilyBits(AddressFamily family) {
            switch (family) {
            case AddressFamily.InterNetwork:
                return 32;
            case AddressFamily.InterNetworkV6:
                return 128;
            default:
                throw new ArgumentException("Invalid address family " + family);
            }
        }

        private static BigInteger IPToUnsigned(IPAddress addr) {
            /* Need to reverse addr bytes for BigInteger; prefix with 0 byte to force unsigned BigInteger
             * read BigInteger bytes as: bytes[n] bytes[n-1] ... bytes[0], address is bytes[0] bytes[1] .. bytes[n] */
            byte[] b = addr.GetAddressBytes();
            byte[] unsigned = new byte[b.Length + 1];
            for (int i = 0; i < b.Length; ++i) {
                unsigned[i] = b[(b.Length - 1) - i];
            }
            unsigned[b.Length] = 0;
            return new BigInteger(unsigned);
        }

        private static byte[] GetUnsignedBytes(BigInteger unsigned, uint bytes) {
            /* reverse bytes again. check that now higher bytes are actually used */
            if (unsigned.Sign < 0) throw new ArgumentException("argument must be >= 0");
            byte[] data = unsigned.ToByteArray();
            byte[] result = new byte[bytes];
            for (int i = 0; i < bytes && i < data.Length; ++i) {
                result[bytes - 1 - i] = data[i];
            }
            for (uint i = bytes; i < data.Length; ++i) {
                if (data[i] != 0) throw new ArgumentException("argument doesn't fit in requested number of bytes");
            }
            return result;
        }

        private static IPAddress IPFromUnsigned(BigInteger unsigned, System.Net.Sockets.AddressFamily family) {
            /* IPAddress(byte[]) constructor picks family from array size */
            switch (family) {
            case System.Net.Sockets.AddressFamily.InterNetwork:
                return new IPAddress(GetUnsignedBytes(unsigned, 4));
            case System.Net.Sockets.AddressFamily.InterNetworkV6:
                return new IPAddress(GetUnsignedBytes(unsigned, 16));
            default:
                throw new ArgumentException("AddressFamily " + family.ToString() + " not supported");
            }
        }

        /* splits set [first..last] of unsigned integers into disjoint slices { x,..., x + 2^k - 1 | x mod 2^k == 0 }
         *  covering exaclty the given set.
         * yields the slices ordered by x as tuples (x, k)
         * This code relies on the fact that BigInteger can't overflow; temporary results may need more bits than last is using.
         */
        public static IEnumerable<Tuple<BigInteger, uint>> split(BigInteger first, BigInteger last) {
            if (first > last) yield break;
            if (first < 0) throw new ArgumentException();
            last += 1;
            /* mask == 1 << len */
            BigInteger mask = 1;
            uint len = 0;
            while (first + mask <= last) {
                if ((first & mask) != 0) {
                    yield return new Tuple<BigInteger, uint>(first, len);
                    first += mask;
                }
                mask <<= 1;
                ++len;
            }
            while (first < last) {
                mask >>= 1;
                --len;
                if ((last & mask) != 0) {
                    yield return new Tuple<BigInteger, uint>(first, len);
                    first += mask;
                }
            }
        }

        public static IEnumerable<CIDR> split(IPAddress first, IPAddress last) {
            if (first.AddressFamily != last.AddressFamily) {
                throw new ArgumentException("AddressFamilies don't match");
            }
            AddressFamily family = first.AddressFamily;
            uint bits = AddressFamilyBits(family); /* split on numbers returns host length, CIDR takes network length */
            foreach (Tuple<BigInteger, uint> slice in split(IPToUnsigned(first), IPToUnsigned(last))) {
                yield return new CIDR(IPFromUnsigned(slice.Item1, family), bits - slice.Item2);
            }
        }
    }
}

【讨论】:

  • 此代码可以很好地为 IP 范围生成 CIDR。这是国际海事组织的正确答案。
  • @Steven Wolfe:WTF?问题是如何将 IP 范围转换为 CIDR,而不是将 CIDR 转换为 IP 范围,所以这不是正确的答案......
  • @StefanSteiger 是的,这个类包含提取CIDR范围的第一个(NetworkAddress)和最后一个地址的代码,但是示例的“main”函数split 确实将 IP 范围转换为 CIDR 块列表。您真的阅读了顶部的说明吗?
【解决方案3】:

我建议使用 IPNetwork 库https://github.com/lduchosal/ipnetwork。 从版本 2 开始,它也支持 IPv4 和 IPv6。

超级网

  IPNetwork network = IPNetwork.Parse("192.168.0.1");
  IPNetwork network2 = IPNetwork.Parse("192.168.0.254");

  IPNetwork ipnetwork = IPNetwork.Supernet(network, network2);

  Console.WriteLine("Network : {0}", ipnetwork.Network);
  Console.WriteLine("Netmask : {0}", ipnetwork.Netmask);
  Console.WriteLine("Broadcast : {0}", ipnetwork.Broadcast);
  Console.WriteLine("FirstUsable : {0}", ipnetwork.FirstUsable);
  Console.WriteLine("LastUsable : {0}", ipnetwork.LastUsable);
  Console.WriteLine("Usable : {0}", ipnetwork.Usable);
  Console.WriteLine("Cidr : {0}", ipnetwork.Cidr);

输出

Network : 192.168.0.0
Netmask : 255.255.255.0
Broadcast : 192.168.0.255
FirstUsable : 192.168.0.1
LastUsable : 192.168.0.254
Usable : 254
Cidr : 24

玩得开心!

【讨论】:

    【解决方案4】:

    我将它用于 IpV4,如果其中有问题,请告诉我。 您可以从以下链接中找到提取源代码: https://blog.ip2location.com/knowledge-base/how-to-convert-ip-address-range-into-cidr/

    using System;
    using System.Collections.Generic;
    using System.Net;
    
    namespace ConsoleApp
    {
        public class IPNetwork
        {
            private readonly long _firstIpAddress;
            private readonly long _lastIpAddress;
    
            public static IPNetwork[] FromIpRange(IPAddress firstIpAddress, IPAddress lastIpAddress)
                => FromIpRange(IpAddressToLong(firstIpAddress), IpAddressToLong(lastIpAddress));
    
            public static IPNetwork[] FromIpRange(long firstIpAddress, long lastIpAddress)
            {
                var result = new List<IPNetwork>();
                while (lastIpAddress >= firstIpAddress)
                {
                    byte maxSize = 32;
                    while (maxSize > 0)
                    {
                        long mask = IMask(maxSize - 1);
                        long maskBase = firstIpAddress & mask;
    
                        if (maskBase != firstIpAddress)
                            break;
    
                        maxSize--;
                    }
                    double x = Math.Log(lastIpAddress - firstIpAddress + 1) / Math.Log(2);
                    byte maxDiff = (byte)(32 - Math.Floor(x));
                    if (maxSize < maxDiff)
                    {
                        maxSize = maxDiff;
                    }
                    var ipAddress = IpAddressFromLong(firstIpAddress);
                    result.Add(new IPNetwork(ipAddress, maxSize));
                    firstIpAddress += (long)Math.Pow(2, 32 - maxSize);
                }
                return result.ToArray();
            }
    
            private static long IMask(int s)
            {
                return (long)(Math.Pow(2, 32) - Math.Pow(2, 32 - s));
            }
    
            public static long IpAddressToLong(IPAddress ipAddress)
            {
                var bytes = ipAddress.GetAddressBytes();
                return ((long)bytes[0] << 24) | ((long)bytes[1] << 16) | ((long)bytes[2] << 8) | bytes[3];
            }
    
            public static IPAddress IpAddressFromLong(long ipAddress)
                => new IPAddress((uint)IPAddress.NetworkToHostOrder((int)ipAddress));
    
            public IPNetwork(IPAddress prefix, int prefixLength = 32)
            {
                if (prefix.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
                    throw new NotSupportedException("IPv6 is not supported");
    
                Prefix = prefix;
                PrefixLength = prefixLength;
    
                var mask = (uint)~(0xFFFFFFFFL >> prefixLength);
                _firstIpAddress = IpAddressToLong(Prefix) & mask;
                _lastIpAddress = _firstIpAddress | ~mask;
            }
    
            public static IPNetwork Parse(string value)
            {
                try
                {
                    var parts = value.Split('/');
                    return new IPNetwork(IPAddress.Parse(parts[0]), int.Parse(parts[1]));
                }
                catch
                {
                    throw new FormatException($"Could not parse IPNetwork from {value}");
                }
            }
    
            public override string ToString() => $"{Prefix}/{PrefixLength}";
    
            public IPAddress Prefix { get; }
            public int PrefixLength { get; }
            public IPAddress LastAddress => IpAddressFromLong(_lastIpAddress);
            public IPAddress FirstAddress => IpAddressFromLong(_firstIpAddress);
            public long Total => _lastIpAddress - _firstIpAddress + 1;
        }
    }
    

    用法一:

    var startAddress = IPAddress.Parse("192.168.0.0");
    var endAddress = IPAddress.Parse("192.168.0.255");
    foreach (var item in IPNetwork.FromIpRange(startAddress, endAddress))
            Console.WriteLine(item);
    

    结果

    192.168.0.0/24
    

    用法2:

    var startAddress = IPAddress.Parse("192.168.0.1");
    var endAddress = IPAddress.Parse("192.168.0.254");
    foreach (var item in IPNetwork.FromIpRange(startAddress, endAddress))
            Console.WriteLine(item);
    

    结果:

    192.168.0.1/32
    192.168.0.2/31
    192.168.0.4/30
    192.168.0.8/29
    192.168.0.16/28
    192.168.0.32/27
    192.168.0.64/26
    192.168.0.128/26
    192.168.0.192/27
    192.168.0.224/28
    192.168.0.240/29
    192.168.0.248/30
    192.168.0.252/31
    192.168.0.254/32
    

    【讨论】:

      【解决方案5】:

      我找到了这个 C code 并将其转换为 C#,它现在可以工作了。

      【讨论】:

      • 提供转换后的代码会更有帮助,因为他用 C# 询问代码。
      【解决方案6】:

      死灵术。
      不,没有,我不明白为什么人们一直支持错误的答案。

      这是 IP 范围到 CIDR 的代码,反之亦然:

      // https://dev.maxmind.com/geoip/
      // https://*.com/questions/461742/how-to-convert-an-ipv4-address-into-a-integer-in-c
      public static string IPrange2CIDR(string ip1, string ip2)
      {
          uint startAddr = IP2num(ip1);
          uint endAddr = IP2num(ip2);
      
          // uint startAddr = 0xc0a80001; // 192.168.0.1
          // uint endAddr = 0xc0a800fe;   // 192.168.0.254
          // uint startAddr = System.BitConverter.ToUInt32(System.Net.IPAddress.Parse(ip1).GetAddressBytes(), 0);
          // uint endAddr = System.BitConverter.ToUInt32(System.Net.IPAddress.Parse(ip2).GetAddressBytes(), 0);
      
          if (startAddr > endAddr)
          {
              uint temp = startAddr;
              startAddr = endAddr;
              endAddr = temp;
          }
      
          // uint diff = endAddr - startAddr -1;
          // int bits =  32 - (int)System.Math.Ceiling(System.Math.Log10(diff) / System.Math.Log10(2));
          // return ip1 + "/" + bits;
      
          uint diffs = startAddr ^ endAddr;
      
          // Now count the number of consecutive zero bits starting at the most significant
          int bits = 32;
          // int mask = 0;
      
          // We keep shifting diffs right until it's zero (i.e. we've shifted all the non-zero bits off)
          while (diffs != 0)
          {
              diffs >>= 1;
              bits--; // Every time we shift, that's one fewer consecutive zero bits in the prefix
              // Accumulate a mask which will have zeros in the consecutive zeros of the prefix and ones elsewhere
              // mask = (mask << 1) | 1;
          }
      
          string res = ip1 + "/" + bits;
          System.Console.WriteLine(res);
          return res;
      }
      
      
      
      
      // https://www.digitalocean.com/community/tutorials/understanding-ip-addresses-subnets-and-cidr-notation-for-networking
      public static void CIDR2IP(string IP)
      {   
          string[] parts = IP.Split('.', '/');
      
          uint ipnum = (System.Convert.ToUInt32(parts[0]) << 24) |
              (System.Convert.ToUInt32(parts[1]) << 16) |
              (System.Convert.ToUInt32(parts[2]) << 8) |
              System.Convert.ToUInt32(parts[3]);
      
          int maskbits = System.Convert.ToInt32(parts[4]);
          uint mask = 0xffffffff;
          mask <<= (32 - maskbits);
      
          uint ipstart = ipnum & mask;
          uint ipend = ipnum | (mask ^ 0xffffffff);
      
          string fromRange = string.Format("{0}.{1}.{2}.{3}", ipstart >> 24, (ipstart >> 16) & 0xff, (ipstart >> 8) & 0xff, ipstart & 0xff);
          string toRange = string.Format("{0}.{1}.{2}.{3}", ipend >> 24, (ipend >> 16) & 0xff, (ipend >> 8) & 0xff, ipend & 0xff);
      
          System.Console.WriteLine(fromRange + " - " + toRange);
      }
      
      
      
      public static uint IP2num(string ip)
      {
          string[] nums = ip.Split('.');
          uint first = System.UInt32.Parse(nums[0]);
          uint second = System.UInt32.Parse(nums[1]);
          uint third = System.UInt32.Parse(nums[2]);
          uint fourth = System.UInt32.Parse(nums[3]);
      
          return (first << 24) | (second << 16) | (third << 8) | (fourth);
      }
      
      public static void Test()
      {
          string IP = "5.39.40.96/27";
          // IP = "88.84.128.0/19";
          CIDR2IP(IP);
      
          // IPrange2CIDR("88.84.128.0", "88.84.159.255");
          IPrange2CIDR("5.39.40.96", "5.39.40.127");
      
          System.Console.WriteLine(System.Environment.NewLine);
          System.Console.WriteLine(" --- Press any key to continue --- ");
          System.Console.ReadKey();
      }
      

      【讨论】:

      • 这与 Iridium 的回答基本相同:“这将计算包含两个给定 IPv4 地址的最小 CIDR 块,...”,但您未能记录。
      • @Stefan:基本一样,只是结果是正确的。另外,计算一个非最小 CIDR 块有什么意义?否则你还不如静态返回“0.0.0.0/0”;这将永远是正确的 - 对于 IPv4。
      • 只是为了澄清@Stefan 在说什么。 CIDR 通常是“块”。当转换一个朴素的 ipstart->ipend 时,上面的代码将返回一个块,但它可能会与相邻的 CIDR 重叠 - 所以你需要有多个块来正确匹配范围。如果您知道这是有意的,那就可以了。但是很可能您需要比单个重叠跨度更准确的输出。