CIDR - deel 2

Ingediend door Dirk Hornstra op 15-jan-2024 21:29

Scott Hanselman heeft tijdens zijn podcasts eens gezegd: "ik blog, zodat als ik een probleem heb, ik via Google terug kan vinden dat ik het probleem eerder gehad heb en opgelost heb en ook hoe ik dat opgelost heb".

Dat was bij mij nu ook het geval. Want bij TRES hebben we websites, we hebben webservers en we hebben IP-adressen. En voor onze monitoring wilde ik een controle toevoegen dat als website A volgens onze gegevens op server A staat deze niet "blijkt te staan op webserver B".  Met Zabbix kun je DNS gegevens monitoren, dus dat is wel te doen. Maar goed, we hebben iets van 2 x 255 ip-adressen beschikbaar, 2 ranges en die kan ik op zich nog "redelijk via code" in een lijst plaatsen per server. Dus de controle "zit deze site in de pool van ip-adressen van server A of in de pool van ip-adressen van server B" dat was nog wel te doen.

En toen kwam ik dus die site tegen die achter CloudFlare zit. CloudFlare is een mooie tool om je site "wereldwijd beschikbaar te houden", dus in de toekomst komen er mogelijk meer van dit type sites. Maar goed, Zabbix gaat hier dus het IP-adres van een CloudFlare server detecteren. En dan zouden we "ten onrechte" een melding krijgen dat de site niet op server A of B staat. Hoewel "achter de schermen" dit site nog steeds op server A of server B staat en ik zou willen controleren of dat nog juist is, ga ik eerst de CloudFlare websites "eruit filteren". Dat betekent dat als een site een IP-adres van CloudFlare heeft ik zeg: prima, die skippen we.

Als je zoekt kom je op deze pagina van CloudFlare zelf uit: link. En daarbij wordt ook verwezen naar de IP-v4 lijst: link. Ook omdat ik valideer op basis van IP-v4 sla ik de IP-v6 even over. Je ziet in deze lijst de CIDR-weergave.

In 2018 heb ik nog eens naar CIDR gekeken en de vraag is altijd: als je jouw eigen teksten terug leest, snap je het? Eerlijk gezegd moest ik het nog wel een paar keer doorlezen om te snappen hoe het ook alweer zat.

Een IP-v4 adres heeft standaard de notatie a.b.c.d

Met de CIDR geef je aan welk deel "ongewijzigd" blijft. Dus doe je een /8, dan blijft deel a altijd gelijk. Doe je een /16, dan blijft deel a en b gelijk. Doe je een /24, dan blijft deel a, b en c gelijk. Dat zijn de "makkelijke scenario's". Het gaat om de gevallen waarbij je een /13 en een /14 range krijgt, dan moet je gaan rekenen.

Ik las dat in .NET 8 we als VB cn C# developers daar vanaf zijn, omdat er dan een IPNetwork Struct waaarmee je de CIDR kunt aangeven en vervolgens kun checken of een IP-adres binnen de range valt. Yes!

Maar goed, we werken (nog) met .NET 6, dus we moeten zelf wat bedenken. Er zijn wel implementaties van "anderen" via Github, maar ik wil de code eigenlijk zo klein mogelijk houden. En dat heb ik gedaan. Gewoon 1 class, je gooit er de CIDR notatie in en vervolgens krijg je de lijst met alle IP-v4 adressen in die lijst terug.

De oplossing die hieronder staat zal zeker niet de snelste oplossing zijn. En ook niet de "meest intelligente".
De oplettende kijker ziet dat er nogal ruige try-catches omheen staan die zorgen dat je een leeg resultaat krijgt bij "een fout". En ook dat de data heen en weer gaat als een IPAddress en de string-variant. Boxing / unboxing, dat zijn acties die je zo weinig mogelijk moet doen.
De code doet wat het moet doen, er staan nog 100-en andere zaken op mijn TODO-lijst en straks in .NET 8 kunnen we een kant en klare oplossing van Microsoft gebruiken. Dus daar ga ik nu dus niet meer tijd in steken. Mocht ik later nog wel eens tijd over hebben, misschien komt er dan nog eens een post met titel CIDR - deel 3 en krijgen we dan wel een "oplossing zoals het hoort". :)

Mocht je zelf zoiets nodig hebben en mijn onderstaande implementatie voldoet aan je eisen, je mag 'm van mij copy-pasten. Het kan ook zijn dat je "het principe" nodig hebt in een andere programmeertaal, Python, Ruby, Bash, Shell-script of wat dan ook en mocht het daar niet standaard in ondersteund worden, dan kun je waarschijnlijk onderstaande code herschrijven naar code voor jouw platform en zou het ook moeten werken!

 

 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
 

namespace Helpers
{
    /// <summary>
    /// Default notation of IP-ranges are x.x.x.x/n
    /// </summary>

    public static class CidrHelper
    {
        /// <summary>
        /// IP-addresses have A CIDR notation. To validate if our IP-address is "part of that range" use this Helper
        /// to retrieve all valid IP-addresses in this range
        /// </summary>
        /// <param name="cidrString"></param>
        /// <returns></returns>
        public static List<IPAddress> GetIp4AddressesOfCidr(string cidrString)
        {
            var result = new List<IPAddress>();
            try
            {
                var totalRange = GetFirstAndLastIp4Address(cidrString);
                var ipParts = new List<int>();
                var lastParts = new List<int>();
                totalRange.Key.ToString().Split('.').ToList().ForEach(rec => ipParts.Add(Convert.ToInt32(rec)));
                totalRange.Value.ToString().Split('.').ToList().ForEach(rec => lastParts.Add(Convert.ToInt32(rec)));
                while (string.Join(".", ipParts) != string.Join(".", lastParts))
                {
                    ipParts = IncrementIp4Address(ipParts, ipParts.Count() - 1);
                    result.Add(IPAddress.Parse(string.Join(".", ipParts)));
                }
                return result;
            }
            catch (Exception) { }
            return result;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="cidrString"></param>
        /// <returns></returns>
        private static KeyValuePair<IPAddress, IPAddress> GetFirstAndLastIp4Address(string cidrString)
        {
            try
            {
                var parts = cidrString.Split('/');
                var ipPart = parts[0];
                var ipRange = Convert.ToInt32(parts[1]);
                return new KeyValuePair<IPAddress, IPAddress>(IPAddress.Parse(ipPart), GetLastIpAddress(IPAddress.Parse(ipPart), ipRange));
            }
            catch (Exception) { }
            return new KeyValuePair<IPAddress, IPAddress>();
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="startIpAddress"></param>
        /// <param name="range"></param>
        /// <returns></returns>
        private static IPAddress GetLastIpAddress(IPAddress startIpAddress, int range)
        {
            try
            {
                var startIp = startIpAddress.ToString().Split('.');
                var buildIp = string.Empty;
                var partCounter = 0;
                while (range > 0)
                {
                    range -= 8;
                    if (range < 0)
                    {
                        break;
                    }
                    buildIp += $"{startIp[partCounter]}.";
                    partCounter++;
                }

                if (range == 0)
                {
                    foreach (var s in startIp.Skip(partCounter))
                    {
                        buildIp += $"255.";
                    }
                    return IPAddress.Parse(buildIp.TrimEnd('.'));
                }

                var margin = 8 - (range + 8);
                var additionCount = Math.Pow(2, margin);
                var original = (int.Parse(startIp[partCounter++]) + Convert.ToInt32(additionCount)) - 1;
                buildIp += $"{original}.";
                foreach (var s in startIp.Skip(partCounter))
                {
                    buildIp += "255.";
                }
                return IPAddress.Parse(buildIp.TrimEnd('.'));
            }
            catch (Exception) { }
            return startIpAddress;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="source"></param>
        /// <param name="ipPosition"></param>
        /// <returns></returns>
        private static List<int> IncrementIp4Address(List<int> source, int ipPosition)
        {
            source[ipPosition] = source[ipPosition] + 1;
            if (source[ipPosition] > 255)
            {
                source[ipPosition] = 0;
                if (ipPosition > 0)
                {
                    IncrementIp4Address(source, ipPosition - 1);
                }
            }
            return source;
        }
    }

}