【算法】CIDR集合的算法

    CIDR(Classless Inter-Domain Routing)是“无类别域间路由”的缩写。是当前用来表示路由的一种方式。由于在做某产品的时候需要用到CIDR集合的一些算法,例如集合的交集、并集、补集运算。下面给出相应的实现代码。

    首先是CIDR的结构:

    public sealed class CIDR : IXmlSerializable, IComparable<CIDR>
    {
        private byte[] bytes = null;

        public IPAddress IPAddress { get; private set; }
        public int Subnet { get; private set; }

        public override bool Equals(object obj)
        {
            CIDR cidr = obj as CIDR;
            if (cidr == null)
                return false;
            return this.Subnet == cidr.Subnet && this.IPAddress.Equals(cidr.IPAddress);
        }
        public override int GetHashCode()
        {
            return this.IPAddress.GetHashCode() ^ this.Subnet.GetHashCode();
        }
        public override string ToString()
        {
            return string.Format("{0}/{1}", IPAddress, Subnet);
        }

        public static CIDR Parse(string str)
        {
            CIDR cidr = new CIDR();
            cidr.SetFromString(str);
            return cidr;
        }
        private void SetFromString(string str)
        {
            int i = str.IndexOf('/');
            this.IPAddress = IPAddress.Parse(str.Substring(0, i));
            this.Subnet = int.Parse(str.Substring(i + 1));
            this.bytes = this.IPAddress.GetAddressBytes();

            int length = this.bytes.Length << 3;
            if (this.Subnet < 0 || this.Subnet > length)
                throw new ArgumentException("", "str");
            // can optimize
            for (i = this.Subnet + 1; i <= length; i++)
            {
                int bit = (bytes[(i - 1) >> 3] >> ((8 - i) & 7)) & 1;
                if (bit != 0)
                    throw new ArgumentException("", "str");
            }
        }

        public bool IsIPv6
        {
            get { return this.IPAddress.AddressFamily == AddressFamily.InterNetworkV6; }
        }

        #region IXmlSerializable
        XmlSchema IXmlSerializable.GetSchema()
        {
            return null;
        }

        void IXmlSerializable.ReadXml(XmlReader reader)
        {
            this.SetFromString(reader.ReadElementContentAsString());
        }

        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            writer.WriteString(this.ToString());
        }
        #endregion

        #region CIDR Algorithm
        public CIDR GetPaired()
        {
            if (this.Subnet == 0)
                throw new NotSupportedException("Subnet=0");
            CIDR cidr = new CIDR();
            cidr.Subnet = this.Subnet;
            cidr.bytes = new byte[this.bytes.Length];
            this.bytes.CopyTo(cidr.bytes, 0);

            int k = this.Subnet - 1;
            int i = k >> 3;
            int j = (7 - k) & 7;
            cidr.bytes[i] = (byte)(cidr.bytes[i] ^ (1 << j));
            cidr.IPAddress = new IPAddress(cidr.bytes);

            return cidr;
        }

        public CIDR GetBiggerSubnet()
        {
            if (this.Subnet == 0)
                throw new NotSupportedException("Subnet=0");
            CIDR cidr = new CIDR();
            cidr.Subnet = this.Subnet - 1;
            cidr.bytes = new byte[this.bytes.Length];
            this.bytes.CopyTo(cidr.bytes, 0);

            int k = this.Subnet - 1;
            int i = k >> 3;
            int j = (7 - k) & 7;
            cidr.bytes[i] = (byte)(cidr.bytes[i] & ~(1 << j));
            cidr.IPAddress = new IPAddress(cidr.bytes);

            return cidr;
        }

        public CIDR GetSmallerSubnet()
        {
            if (this.Subnet == this.bytes.Length << 3)
                throw new NotSupportedException("Subnet=max");
            CIDR cidr = new CIDR();
            cidr.Subnet = this.Subnet + 1;
            cidr.bytes = this.bytes;
            cidr.IPAddress = this.IPAddress;
            return cidr;
        }

        public bool ExistsIntersection(CIDR other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (this.IsIPv6 != other.IsIPv6)
                throw new NotSupportedException("Different address family");

            if (this.Subnet == 0 || other.Subnet == 0)
                return true;

            int min = Math.Min(this.Subnet, other.Subnet) - 1;
            int length = min >> 3;

            int k;
            for (k = 0; k < length; k++)
                if (this.bytes[k] != other.bytes[k])
                    return false;

            //int m = (0xff << (7 - min & 7)) & 0xff;
            int m = (0xff80 >> (min & 7));
            if (k < this.bytes.Length && (this.bytes[k] & m) != (other.bytes[k] & m))
                return false;

            return true;
        }

        public CIDR Intersect(CIDR other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (this.IsIPv6 != other.IsIPv6)
                throw new NotSupportedException("Different address family");

            if (this.ExistsIntersection(other))
                if (this.Subnet > other.Subnet)
                    return this;
                else
                    return other;
            else
                return null;
        }

        public CIDR[] Union(CIDR other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (this.IsIPv6 != other.IsIPv6)
                throw new NotSupportedException("Different address family");

            if (this.ExistsIntersection(other))
                if (this.Subnet < other.Subnet)
                    return new[] { this };
                else
                    return new[] { other };
            else
                if (this.Subnet == other.Subnet && this.Equals(other.GetPaired()))
                    return new[] { this.GetBiggerSubnet() };
                else
                    return new[] { this, other };
        }
        #endregion

        public int CompareTo(CIDR other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (this.IsIPv6 != other.IsIPv6)
                if (this.IsIPv6)
                    return 1;
                else
                    return -1;
            else if (this.Subnet > other.Subnet)
                return 1;
            else if (this.Subnet < other.Subnet)
                return -1;
            else
                for (int i = 0; i < this.bytes.Length; i++)
                    if (this.bytes[i] > other.bytes[i])
                        return 1;
                    else if (this.bytes[i] < other.bytes[i])
                        return -1;
            return 0;
        }
    }

    这个类实现了序列化接口(IXmlSerializable)可比较接口(IComparable<T>)。方便进行存储等操作。

    接下来是对CIDR集合的操作。由于在产品中叫做RouteGroup,所以在这里也就没改名字了:

    public class RouteGroup
    {
        public RouteGroup()
        {
            this.CIDRs = new HashSet<CIDR>();
        }

        [XmlAttribute]
        public string GroupName { get; set; }
        [XmlAttribute]
        public Guid GroupID { get; set; }
        public HashSet<CIDR> CIDRs { get; set; }

        public static RouteGroup Create(string groupName)
        {
            return new RouteGroup { GroupName = groupName, GroupID = Guid.NewGuid() };
        }

        public void Union(CIDR cidr)
        {
            if (cidr == null)
                throw new ArgumentNullException("cidr");

            var list = this.CIDRs
                .Where(s => s.IsIPv6 == cidr.IsIPv6 && s.ExistsIntersection(cidr))
                .ToList();
            if (list.Count == 0)
            {
                if (cidr.Subnet > 0 && this.CIDRs.Remove(cidr.GetPaired()))
                    this.Union(cidr.GetBiggerSubnet());
                else
                    this.CIDRs.Add(cidr);
            }
            else
            {
                foreach (var item in list)
                    this.CIDRs.Remove(item);

                var max = list.Max();
                if (max.Subnet < cidr.Subnet)
                    this.CIDRs.Add(max);
                else if (max.Subnet > cidr.Subnet)
                    this.CIDRs.Add(cidr);
            }
        }

        public void Subtract(CIDR cidr)
        {
            if (cidr == null)
                throw new ArgumentNullException("cidr");

            var list = this.CIDRs
               .Where(s => s.IsIPv6 == cidr.IsIPv6 && s.ExistsIntersection(cidr))
               .ToList();
            if (list.Count > 0)
            {
                foreach (var item in list)
                    this.CIDRs.Remove(item);

                var min = list.Min();
                CIDR other = cidr;
                for (int i = cidr.Subnet; i > min.Subnet; i--)
                {
                    this.CIDRs.Add(other.GetPaired());
                    other = other.GetBiggerSubnet();
                }
            }
        }

        public void Union(string cidr)
        {
            this.Union(CIDR.Parse(cidr));
        }

        public void Subtract(string cidr)
        {
            this.Subtract(CIDR.Parse(cidr));
        }

        public void Reverse()
        {
            var list = this.CIDRs.ToList();
            this.CIDRs.Clear();
            this.Union("0.0.0.0/0");
            foreach (var item in list)
                this.Subtract(item);
        }

        public void Sort()
        {
            var list = this.CIDRs.OrderBy(s => s).ToList();
            this.CIDRs.Clear();
            foreach (var item in list)
            {
                this.CIDRs.Add(item);
            }
        }

        public void Rebuild()
        {
            var list = this.CIDRs.OrderBy(s => s).ToList();
            this.CIDRs.Clear();
            foreach (var item in list)
            {
                this.Union(item);
            }
        }
    }

    样例代码:

   static void Main(string[] args)
        {
            var rg = RouteGroup.Create("test");
            rg.Union("1.2.0.0/16");
            rg.Union("1.3.0.0/16");
            rg.Union("2.2.0.0/16");
            rg.Union("3.2.0.0/16");
            rg.Union("4.2.5.0/24");
            rg.Union("5.2.8.0/21");
            rg.Union("4.2.12.0/22");
            rg.Sort();
            foreach (var item in rg.CIDRs)
                Console.WriteLine(item);
            Console.WriteLine();

            rg.Reverse();
            rg.Sort();
            foreach (var item in rg.CIDRs)
                Console.WriteLine(item);
            Console.WriteLine();

            rg.Subtract("0.0.0.0/2");
            rg.Sort();
            foreach (var item in rg.CIDRs)
                Console.WriteLine(item);

            /*
             * 1.2.0.0/15
             * 2.2.0.0/16
             * 3.2.0.0/16
             * 5.2.8.0/21
             * 4.2.12.0/22
             * 4.2.5.0/24
             * 
             * 128.0.0.0/1
             * 64.0.0.0/2
             * 32.0.0.0/3
             * 16.0.0.0/4
             * 8.0.0.0/5
             * 6.0.0.0/7
             * 0.0.0.0/8
             * 1.128.0.0/9
             * 2.128.0.0/9
             * 3.128.0.0/9
             * 4.128.0.0/9
             * 5.128.0.0/9
             * 1.64.0.0/10
             * 2.64.0.0/10
             * 3.64.0.0/10
             * 4.64.0.0/10
             * 5.64.0.0/10
             * 1.32.0.0/11
             * 2.32.0.0/11
             * 3.32.0.0/11
             * 4.32.0.0/11
             * 5.32.0.0/11
             * 1.16.0.0/12
             * 2.16.0.0/12
             * 3.16.0.0/12
             * 4.16.0.0/12
             * 5.16.0.0/12
             * 1.8.0.0/13
             * 2.8.0.0/13
             * 3.8.0.0/13
             * 4.8.0.0/13
             * 5.8.0.0/13
             * 1.4.0.0/14
             * 2.4.0.0/14
             * 3.4.0.0/14
             * 4.4.0.0/14
             * 5.4.0.0/14
             * 1.0.0.0/15
             * 2.0.0.0/15
             * 3.0.0.0/15
             * 4.0.0.0/15
             * 5.0.0.0/15
             * 2.3.0.0/16
             * 3.3.0.0/16
             * 4.3.0.0/16
             * 5.3.0.0/16
             * 4.2.128.0/17
             * 5.2.128.0/17
             * 4.2.64.0/18
             * 5.2.64.0/18
             * 4.2.32.0/19
             * 5.2.32.0/19
             * 4.2.16.0/20
             * 5.2.16.0/20
             * 5.2.0.0/21
             * 4.2.0.0/22
             * 4.2.8.0/22
             * 4.2.6.0/23
             * 4.2.4.0/24
             * 
             * 128.0.0.0/1
             * 64.0.0.0/2
             * Press any key to continue . . .
             */
        }

    欢迎拍砖讨论。

原文地址:https://www.cnblogs.com/Aimeast/p/2290841.html