如何在C#中将IP范围转换为CIDR?

7

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

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


1
请展示一些源代码...你已经尝试了什么?究竟哪里出现了问题? - Yahia
6个回答

10

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

1
这段代码很好地生成了一个IP范围的CIDRs。在我看来,这是正确的答案。 - Steven
@Steven Wolfe:什么鬼?问题是如何将IP范围转换为CIDR,而不是CIDR转换为IP范围,所以这不是正确的答案... - Stefan Steiger
3
@StefanSteiger,是的,这个类还包含提取CIDR范围第一个(NetworkAddress)和最后一个地址的代码,但示例split的“主要”功能确实将IP范围转换为CIDR块列表。您真的阅读了顶部的描述吗? - Stefan

8

很难确定这里到底在问什么(您提供的CIDR列表似乎与给定的输入地址不对应),但是以下代码将允许您查找包含指定起始和结束地址的最小单个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

1
这个计算出包含两个给定IPv4地址的最小CIDR块,但我认为问题是关于一个完全覆盖范围的CIDR列表。 - Stefan
@Stefan,很难确定OP想要什么,因为问题中的示例输出似乎与给定的起始/结束地址不对应。由于OP自己的答案(据称是)所需的输出是在一天后发布的,而在此之前,许多人认为我的回答非常有用并给予了赞成票,因此我将其保留为原样。这似乎不值得(我假设)您的反对票,但每个人都有自己的看法。 - Iridium
嗯,你能编辑一下你的回答吗?让它清晰明了地表述出你是如何理解这个问题的?不得不读源代码才能弄清楚实际操作很令人烦恼。 - Stefan
1
@Stefan,我已经编辑了答案,以澄清代码的意图。希望现在更清晰了。 - Iridium

1

死灵法师。
不,没有这个答案,我不明白为什么人们会一直点赞错误的答案。

这是IP范围转换为CIDR和反向转换的代码:

// https://dev.maxmind.com/geoip/
// https://dev59.com/7XRB5IYBdhLWcg3w77kn
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();
}

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

1

我仅针对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;
    }
}

用法 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

0
我建议使用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

玩得愉快!


-1

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


提供转换后的代码会更有帮助,因为他要求使用C#编写代码。 - kzfabi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接