Пример #1
0
        private void LoadOSInfo()
        {
            tempFolder = Path.GetTempPath();
            if (string.IsNullOrWhiteSpace(tempFolder))
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    tempFolder = "c:\\temp";
                }
                else
                {
                    tempFolder = "/tmp";
                }
            }
            Directory.CreateDirectory(tempFolder);

            // start off with built in version info, this is not as detailed or nice as we like,
            //  so we try some other ways to get more detailed information
            CpuArchitecture = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();
            Version         = Environment.OSVersion.Version.ToString();
            Description     = RuntimeInformation.OSDescription;

            // attempt to get detailed version info
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                Name    = FriendlyName = OSUtility.Linux;
                isLinux = true;
                string tempFile = GetTempFileName();
                Process.Start("/bin/bash", "-c \"cat /etc/*release* > " + tempFile + "\"").WaitForExit();
                System.Threading.Tasks.Task.Delay(100); // wait a small bit for file to really be closed
                string versionText = File.ReadAllText(tempFile).Trim();
                ExtensionMethods.FileDeleteWithRetry(tempFile);
                if (string.IsNullOrWhiteSpace(versionText))
                {
                    Logger.Error(new IOException("Unable to load os version from /etc/*release* ..."));
                }
                else
                {
                    FriendlyName = ExtractRegex(versionText, "^(Id|Distrib_Id)=(?<value>.*?)$", string.Empty);
                    if (FriendlyName.Length != 0)
                    {
                        string codeName = ExtractRegex(versionText, "^(Name|Distrib_CodeName)=(?<value>.+)$", string.Empty);
                        if (codeName.Length != 0)
                        {
                            FriendlyName += " - " + codeName;
                        }
                        Version = ExtractRegex(versionText, "^Version_Id=(?<value>.+)$", Version);
                    }
                }
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                Name        = FriendlyName = OSUtility.Windows;
                isWindows   = true;
                processVerb = "runas";
                try
                {
                    LoadVersionFromWmic();
                }
                catch (Exception ex)
                {
                    Logger.Error(ex, "Failed to load os info from wmic");

                    // last resort, try wmi api
                    LoadVersionFromWmiApi();
                }

                // Windows loves to add a trailing .0 for some reason
                Version = Regex.Replace(Version, "\\.0$", string.Empty);
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
            {
                // TODO: Implement better for MAC
                Name         = FriendlyName = OSUtility.Mac;
                FriendlyName = "OSX";
                isMac        = true;
            }
            else
            {
                Name         = OSUtility.Unknown;
                FriendlyName = "Unknown";
            }
        }
Пример #2
0
        /// <summary>
        /// Create a test IPBanService
        /// </summary>
        /// <param name="directory">Root directory</param>
        /// <param name="configFileName">Config file name</param>
        /// <param name="defaultBannedIPAddressHandlerUrl">Url for banned ip handling or null to not handle banned ip</param>
        /// <param name="configFileModifier">Change config file (param are file text, returns new file text)</param>
        /// <returns>Service</returns>
        public static T CreateAndStartIPBanTestService <T>(string directory = null, string configFileName = null, string defaultBannedIPAddressHandlerUrl = null,
                                                           Func <string, string> configFileModifier = null) where T : IPBanService
        {
            NLog.Time.TimeSource.Current = new TestTimeSource();
            string defaultNLogConfig = $@"<?xml version=""1.0""?>
<nlog xmlns=""http://www.nlog-project.org/schemas/NLog.xsd"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" throwExceptions=""false"" internalLogToConsole=""false"" internalLogToConsoleError=""false"" internalLogLevel=""Trace"">
  <targets>
    <target name=""logfile"" xsi:type=""File"" fileName=""${{basedir}}/logfile.txt"" encoding=""UTF-8""/>
    <target name=""console"" xsi:type=""Console""/>
  </targets>
  <rules>
    <logger name=""*"" minlevel=""Debug"" writeTo=""logfile""/>
    <logger name=""*"" minlevel=""Debug"" writeTo=""console""/>
  </rules>
</nlog>";

            File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "nlog.config"), defaultNLogConfig);

            string logFilePath = Path.Combine(AppContext.BaseDirectory, "logfile.txt");

            if (File.Exists(logFilePath))
            {
                File.Delete(logFilePath);
            }

            ExtensionMethods.RemoveDatabaseFiles();
            DefaultHttpRequestMaker.DisableLiveRequests = true;
            if (string.IsNullOrWhiteSpace(directory))
            {
                directory = AppContext.BaseDirectory;
            }
            if (string.IsNullOrWhiteSpace(configFileName))
            {
                configFileName = IPBanConfig.DefaultFileName;
            }
            string configFilePath = Path.Combine(directory, configFileName);
            string configFileText = File.ReadAllText(configFilePath);

            configFilePath += ".tmp";
            if (configFileModifier != null)
            {
                configFileText = configFileModifier(configFileText);
            }
            ExtensionMethods.FileWriteAllTextWithRetry(configFilePath, configFileText);
            T service = IPBanService.CreateService <T>() as T;

            service.ExternalIPAddressLookup = LocalMachineExternalIPAddressLookupTest.Instance;
            service.ConfigFilePath          = configFilePath;
            service.MultiThreaded           = false;
            service.ManualCycle             = true;
            service.DnsList = null; // too slow for tests, turn off
            if (defaultBannedIPAddressHandlerUrl is null)
            {
                service.BannedIPAddressHandler = NullBannedIPAddressHandler.Instance;
            }
            else
            {
                service.BannedIPAddressHandler = new DefaultBannedIPAddressHandler {
                    BaseUrl = defaultBannedIPAddressHandlerUrl
                };
            }
            service.Version = "1.1.1.1";
            service.RunAsync(CancellationToken.None).Sync();
            service.RunCycleAsync().Sync();
            service.DB.Truncate(true);
            service.Firewall.Truncate();
            return(service);
        }
Пример #3
0
        /// <summary>
        /// Static constructor
        /// </summary>
        static OSUtility()
        {
            try
            {
                tempFolder = Path.GetTempPath();
                if (string.IsNullOrWhiteSpace(tempFolder))
                {
                    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                    {
                        tempFolder = "c:\\temp";
                    }
                    else
                    {
                        tempFolder = "/tmp";
                    }
                }
                Directory.CreateDirectory(tempFolder);

                // start off with built in version info, this is not as detailed or nice as we like,
                //  so we try some other ways to get more detailed information
                Version     = Environment.OSVersion.VersionString;
                Description = RuntimeInformation.OSDescription;

                // attempt to get detailed version info
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
                {
                    isLinux = true;
                    string tempFile = OSUtility.GetTempFileName();
                    Process.Start("/bin/bash", "-c \"cat /etc/*release* > " + tempFile + "\"").WaitForExit();
                    System.Threading.Tasks.Task.Delay(100); // wait a small bit for file to really be closed
                    string versionText = File.ReadAllText(tempFile).Trim();
                    ExtensionMethods.FileDeleteWithRetry(tempFile);
                    if (string.IsNullOrWhiteSpace(versionText))
                    {
                        Logger.Error(new IOException("Unable to load os version from /etc/*release* ..."));
                    }
                    else
                    {
                        Name         = OSUtility.Linux;
                        FriendlyName = ExtractRegex(versionText, "^(Id|Distrib_Id)=(?<value>.*?)$", string.Empty);
                        if (FriendlyName.Length != 0)
                        {
                            string codeName = ExtractRegex(versionText, "^(Name|Distrib_CodeName)=(?<value>.+)$", string.Empty);
                            if (codeName.Length != 0)
                            {
                                FriendlyName += " - " + codeName;
                            }
                            Version = ExtractRegex(versionText, "^Version_Id=(?<value>.+)$", Version);
                        }
                    }
                }
                else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    isWindows   = true;
                    processVerb = "runas";
                    Name        = OSUtility.Windows;
                    string tempFile = OSUtility.GetTempFileName();

                    // .net core WMI has a strange bug where WMI will not initialize on some systems
                    // since this is the only place where WMI is used, we can just work-around it
                    // with the wmic executable, which exists (as of 2018) on all supported Windows.
                    StartProcessAndWait("cmd", "/C wmic path Win32_OperatingSystem get Caption,Version /format:table > \"" + tempFile + "\"");
                    if (File.Exists(tempFile))
                    {
                        // try up to 10 times to read the file
                        for (int i = 0; i < 10; i++)
                        {
                            try
                            {
                                string[] lines = File.ReadAllLines(tempFile);
                                ExtensionMethods.FileDeleteWithRetry(tempFile);
                                if (lines.Length > 1)
                                {
                                    int versionIndex = lines[0].IndexOf("Version");
                                    if (versionIndex >= 0)
                                    {
                                        FriendlyName = lines[1].Substring(0, versionIndex - 1).Trim();
                                        Version      = lines[1].Substring(versionIndex).Trim();
                                        break;
                                    }
                                }
                                throw new IOException("Invalid file generated from wmic");
                            }
                            catch (Exception ex)
                            {
                                if (i < 9)
                                {
                                    System.Threading.Tasks.Task.Delay(200).Wait();
                                }
                                else
                                {
                                    Logger.Error(ex, "Unable to load os version using wmic, trying wmi api...");

                                    // last resort, try wmi api
                                    LoadVersionFromWmiApi();
                                }
                            }
                        }
                    }
                    else
                    {
                        // last resort, try wmi api
                        LoadVersionFromWmiApi();
                    }
                }
                else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                {
                    // TODO: Implement better for MAC
                    isMac        = true;
                    Name         = OSUtility.Mac;
                    FriendlyName = "OSX";
                }
                else
                {
                    Name         = OSUtility.Unknown;
                    FriendlyName = "Unknown";
                }
            }
            catch (Exception ex)
            {
                Logger.Error("Error determining platform info", ex);
            }
        }
Пример #4
0
        // deleteRule will drop the rule and matching set before creating the rule and set, use this is you don't care to update the rule and set in place
        protected bool UpdateRule(string ruleName, string action, IEnumerable <string> ipAddresses, string hashType, int maxCount,
                                  IEnumerable <PortRange> allowPorts, CancellationToken cancelToken)
        {
            string ipFileTemp = OSUtility.Instance.GetTempFileName();

            try
            {
                // add and remove the appropriate ip addresses from the set
                using (StreamWriter writer = File.CreateText(ipFileTemp))
                {
                    if (cancelToken.IsCancellationRequested)
                    {
                        throw new OperationCanceledException(cancelToken);
                    }
                    RunProcess("ipset", true, out IReadOnlyList <string> sets, "-L -n");
                    if (sets.Contains(ruleName))
                    {
                        writer.WriteLine($"flush {ruleName}");// hash:{hashType} family {INetFamily} hashsize {hashSize} maxelem {maxCount} -exist");
                    }
                    writer.WriteLine($"create {ruleName} hash:{hashType} family {INetFamily} hashsize {hashSize} maxelem {maxCount} -exist");
                    foreach (string ipAddress in ipAddresses)
                    {
                        if (cancelToken.IsCancellationRequested)
                        {
                            throw new OperationCanceledException(cancelToken);
                        }

                        if (IPAddressRange.TryParse(ipAddress, out IPAddressRange range) &&
                            range.Begin.AddressFamily == addressFamily && range.End.AddressFamily == addressFamily)
                        {
                            try
                            {
                                if (hashType != hashTypeCidrMask || range.Begin.Equals(range.End))
                                {
                                    writer.WriteLine($"add {ruleName} {range.Begin} -exist");
                                }
                                else
                                {
                                    writer.WriteLine($"add {ruleName} {range.ToCidrString()} -exist");
                                }
                            }
                            catch
                            {
                                // ignore invalid cidr ranges
                            }
                        }
                    }
                }

                if (cancelToken.IsCancellationRequested)
                {
                    throw new OperationCanceledException(cancelToken);
                }
                else
                {
                    // restore the set
                    bool result = (RunProcess("ipset", true, $"restore < \"{ipFileTemp}\"") == 0);
                    CreateOrUpdateRule(ruleName, action, hashType, maxCount, allowPorts, cancelToken);
                    return(result);
                }
            }
            finally
            {
                ExtensionMethods.FileDeleteWithRetry(ipFileTemp);
            }
        }
Пример #5
0
        // deleteRule will drop the rule and matching set before creating the rule and set, use this is you don't care to update the rule and set in place
        protected bool UpdateRuleDelta(string ruleName, string action, IEnumerable <IPBanFirewallIPAddressDelta> deltas, string hashType,
                                       int maxCount, bool deleteRule, IEnumerable <PortRange> allowPorts, CancellationToken cancelToken)
        {
#if ENABLE_FIREWALL_PROFILING
            Stopwatch timer = Stopwatch.StartNew();
#endif

            string ipFileTemp = OSUtility.GetTempFileName();
            try
            {
                // add and remove the appropriate ip addresses from the set
                using (StreamWriter writer = File.CreateText(ipFileTemp))
                {
                    if (cancelToken.IsCancellationRequested)
                    {
                        throw new OperationCanceledException(cancelToken);
                    }
                    writer.WriteLine($"create {ruleName} hash:{hashType} family {INetFamily} hashsize {hashSize} maxelem {maxCount} -exist");
                    foreach (IPBanFirewallIPAddressDelta delta in deltas)
                    {
                        if (cancelToken.IsCancellationRequested)
                        {
                            throw new OperationCanceledException(cancelToken);
                        }

                        if (IPAddressRange.TryParse(delta.IPAddress, out IPAddressRange range) &&
                            range.Begin.AddressFamily == addressFamily && range.End.AddressFamily == addressFamily)
                        {
                            try
                            {
                                if (delta.Added)
                                {
                                    if (range.Begin.Equals(range.End))
                                    {
                                        writer.WriteLine($"add {ruleName} {range.Begin} -exist");
                                    }
                                    else
                                    {
                                        writer.WriteLine($"add {ruleName} {range.ToCidrString()} -exist");
                                    }
                                }
                                else
                                {
                                    if (range.Begin.Equals(range.End))
                                    {
                                        writer.WriteLine($"del {ruleName} {range.Begin} -exist");
                                    }
                                    else
                                    {
                                        writer.WriteLine($"del {ruleName} {range.ToCidrString()} -exist");
                                    }
                                }
                            }
                            catch
                            {
                                // ignore invalid cidr ranges
                            }
                        }
                    }
                }

                if (cancelToken.IsCancellationRequested)
                {
                    throw new OperationCanceledException(cancelToken);
                }
                else
                {
                    // restore the deltas into the existing set
                    bool result = (RunProcess("ipset", true, $"restore < \"{ipFileTemp}\"") == 0);
                    CreateOrUpdateRule(ruleName, action, hashType, maxCount, allowPorts, cancelToken);
                    return(result);
                }
            }
            finally
            {
                ExtensionMethods.FileDeleteWithRetry(ipFileTemp);

#if ENABLE_FIREWALL_PROFILING
                timer.Stop();
                Logger.Warn("BlockIPAddressesDelta rule '{0}' took {1:0.00}ms with {2} ips",
                            ruleName, timer.Elapsed.TotalMilliseconds, deltas.Count());
#endif
            }
        }
Пример #6
0
        /* // makes nlog go haywire, revisit later
         * private static readonly CustomTimeSource timeSource = new CustomTimeSource();
         *
         * private class CustomTimeSource : NLog.Time.TimeSource
         * {
         *  private TimeZoneInfo zoneInfo = TimeZoneInfo.Utc;
         *
         *  [Required]
         *  public string Zone
         *  {
         *      get { return zoneInfo.DisplayName; }
         *      set { zoneInfo = TimeZoneInfo.FindSystemTimeZoneById(value); }
         *  }
         *
         *  public override DateTime Time => IPBanService.UtcNow;
         *
         *  public override DateTime FromSystemTime(DateTime systemTime)
         *  {
         *      return systemTime.ToUniversalTime();
         *  }
         *
         *  public DateTime CurrentTime { get; set; } = IPBanService.UtcNow;
         * }
         */

        static Logger()
        {
            try
            {
                LogFactory factory = null;
                try
                {
                    factory = LogManager.LoadConfiguration(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath);
                }
                catch
                {
                    // if no config, exception is thrown that is OK
                }
                if (factory is null || factory.Configuration.AllTargets.Count == 0)
                {
                    string nlogConfigPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nlog.config");
                    if (!File.Exists(nlogConfigPath))
                    {
                        string logLevel = "Warn";

                        Console.WriteLine("Creating default nlog.config file");

                        // storing this as a resource fails to use correct string in precompiled .exe with .net core, bug with Microsoft I think
                        string defaultNLogConfig = $@"<?xml version=""1.0""?>
<nlog xmlns=""http://www.nlog-project.org/schemas/NLog.xsd"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" throwExceptions=""false"" internalLogToConsole=""false"" internalLogToConsoleError=""false"" internalLogLevel=""Trace"">
  <targets>
    <target name=""logfile"" xsi:type=""File"" fileName=""${{basedir}}/logfile.txt"" archiveNumbering=""Sequence"" archiveEvery=""Day"" maxArchiveFiles=""28"" encoding=""UTF-8""/>
    <target name=""console"" xsi:type=""Console""/>
  </targets>
  <rules>
    <logger name=""*"" minlevel=""{logLevel}"" writeTo=""logfile""/>
    <logger name=""*"" minlevel=""{logLevel}"" writeTo=""console""/>
  </rules>
</nlog>";
                        ExtensionMethods.FileWriteAllTextWithRetry(nlogConfigPath, defaultNLogConfig);
                    }
                    if (File.Exists(nlogConfigPath))
                    {
                        factory = LogManager.LoadConfiguration(nlogConfigPath);
                    }
                    else
                    {
                        throw new IOException("Unable to create nlog configuration file, nlog.config file failed to write default config.");
                    }
                }
                nlogInstance = factory.GetCurrentClassLogger();
                instance     = new NLogWrapper(nlogInstance);

                if (UnitTestDetector.Running)
                {
                    foreach (LoggingRule rule in LogManager.Configuration.LoggingRules)
                    {
                        rule.EnableLoggingForLevels(NLog.LogLevel.Trace, NLog.LogLevel.Fatal);
                    }
                    LogManager.ReconfigExistingLoggers();
                }
                //NLog.Time.TimeSource.Current = timeSource;
            }
            catch (Exception ex)
            {
                // log to console as no other logger is available
                Console.WriteLine("Failed to initialize logger: {0}", ex);
            }
        }
Пример #7
0
        private void PopulateList(HashSet <System.Net.IPAddress> set,
                                  HashSet <IPAddressRange> ranges,
                                  HashSet <string> others,
                                  ref Regex regex,
                                  string setValue,
                                  string regexValue)
        {
            setValue   = (setValue ?? string.Empty).Trim();
            regexValue = (regexValue ?? string.Empty).Replace("*", @"[0-9A-Fa-f:]+?").Trim();
            set.Clear();
            regex = null;

            if (!string.IsNullOrWhiteSpace(setValue))
            {
                foreach (string entry in setValue.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(e => e.Trim()))
                {
                    string entryWithoutComment = entry;
                    int    pos = entryWithoutComment.IndexOf('?');
                    if (pos >= 0)
                    {
                        entryWithoutComment = entryWithoutComment.Substring(0, pos);
                    }
                    entryWithoutComment = entryWithoutComment.Trim();
                    if (!ignoreListEntries.Contains(entryWithoutComment))
                    {
                        if (IPAddressRange.TryParse(entryWithoutComment, out IPAddressRange range))
                        {
                            if (range.Begin.Equals(range.End))
                            {
                                set.Add(range.Begin);
                            }
                            else
                            {
                                ranges.Add(range);
                            }
                        }
                        else if (Uri.CheckHostName(entryWithoutComment) != UriHostNameType.Unknown)
                        {
                            try
                            {
                                // add entries for each ip address that matches the dns entry
                                IPAddress[] addresses = null;
                                ExtensionMethods.Retry(() => addresses = dns.GetHostAddressesAsync(entryWithoutComment).Sync());
                                foreach (IPAddress adr in addresses)
                                {
                                    set.Add(adr);
                                }
                            }
                            catch (Exception ex)
                            {
                                Logger.Error(ex, "Unable to resolve dns for {0}", entryWithoutComment);

                                // eat exception, nothing we can do
                                others.Add(entryWithoutComment);
                            }
                        }
                        else
                        {
                            others.Add(entryWithoutComment);
                        }
                    }
                }
            }

            if (!string.IsNullOrWhiteSpace(regexValue))
            {
                regex = ParseRegex(regexValue);
            }
        }
Пример #8
0
 /// <summary>
 /// Create a firewall
 /// </summary>
 /// <param name="osAndFirewall">Dictionary of string operating system name (Windows, Linux, OSX) and firewall class</param>
 /// <param name="rulePrefix">Rule prefix or null for default</param>
 /// <returns>Firewall</returns>
 public static IIPBanFirewall CreateFirewall(IReadOnlyDictionary <string, string> osAndFirewall, string rulePrefix = null, IIPBanFirewall existing = null)
 {
     try
     {
         bool        foundFirewallType = false;
         int         priority          = int.MinValue;
         Type        firewallType      = typeof(IIPBanFirewall);
         List <Type> allTypes          = ExtensionMethods.GetAllTypes();
         var         q =
             from fwType in allTypes
             where fwType.IsPublic &&
             fwType != firewallType &&
             firewallType.IsAssignableFrom(fwType) &&
             fwType.GetCustomAttribute <RequiredOperatingSystemAttribute>() != null &&
             fwType.GetCustomAttribute <RequiredOperatingSystemAttribute>().IsValid
             select new { FirewallType = fwType, OS = fwType.GetCustomAttribute <RequiredOperatingSystemAttribute>(), Name = fwType.GetCustomAttribute <CustomNameAttribute>() };
         var array = q.ToArray();
         foreach (var result in array)
         {
             // look up the requested firewall by os name
             bool matchPriority = priority < result.OS.Priority;
             if (matchPriority)
             {
                 bool matchName = true;
                 if (osAndFirewall != null && osAndFirewall.Count != 0 &&
                     (osAndFirewall.TryGetValue(OSUtility.Instance.Name, out string firewallToUse) || osAndFirewall.TryGetValue("*", out firewallToUse)))
                 {
                     matchName = result.Name.Name.Equals(firewallToUse, StringComparison.OrdinalIgnoreCase);
                 }
                 if (matchName)
                 {
                     // if IsAvailable method is provided, attempt to call
                     MethodInfo available = result.FirewallType.GetMethod("IsAvailable", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                     if (available != null)
                     {
                         try
                         {
                             if (!Convert.ToBoolean(available.Invoke(null, null)))
                             {
                                 continue;
                             }
                         }
                         catch
                         {
                             continue;
                         }
                     }
                     firewallType      = result.FirewallType;
                     priority          = result.OS.Priority;
                     foundFirewallType = true;
                 }
             }
         }
         if (firewallType is null)
         {
             throw new ArgumentException("Firewall is null, at least one type should implement IIPBanFirewall");
         }
         else if (osAndFirewall.Count != 0 && !foundFirewallType)
         {
             string typeString = string.Join(',', osAndFirewall.Select(kv => kv.Key + ":" + kv.Value));
             throw new ArgumentException("Unable to find firewalls of types: " + typeString + ", osname: " + OSUtility.Instance.Name);
         }
         if (existing != null && existing.GetType().Equals(firewallType))
         {
             return(existing);
         }
         return(Activator.CreateInstance(firewallType, new object[] { rulePrefix }) as IIPBanFirewall);
     }
     catch (Exception ex)
     {
         throw new ArgumentException("Unable to create firewall, please double check your Firewall configuration property", ex);
     }
 }
Пример #9
0
        /// <summary>
        /// Create a firewall
        /// </summary>
        /// <param name="rulePrefix">Rule prefix or null for default</param>
        /// <returns>Firewall</returns>
        public static IIPBanFirewall CreateFirewall(string rulePrefix = null, IIPBanFirewall existing = null)
        {
            try
            {
                int  priority     = int.MinValue;
                Type firewallType = typeof(IIPBanFirewall);
                Type fallbackType = null;
                IReadOnlyCollection <Type> allTypes = ExtensionMethods.GetAllTypes();

                var q =
                    from fwType in allTypes
                    where fwType.IsPublic &&
                    fwType != firewallType &&
                    firewallType.IsAssignableFrom(fwType) &&
                    fwType.GetCustomAttribute <RequiredOperatingSystemAttribute>() != null &&
                    fwType.GetCustomAttribute <RequiredOperatingSystemAttribute>().IsMatch
                    select new { FirewallType = fwType, OS = fwType.GetCustomAttribute <RequiredOperatingSystemAttribute>(), Name = fwType.GetCustomAttribute <CustomNameAttribute>() };
                var array = q.OrderBy(f => f.OS.Priority).ToArray();
                foreach (var result in array)
                {
                    bool matchPriority = priority < result.OS.Priority;
                    if (matchPriority)
                    {
                        // if IsAvailable method is provided, attempt to call
                        MethodInfo available = result.FirewallType.GetMethod("IsAvailable", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                        if (available != null)
                        {
                            try
                            {
                                if (!Convert.ToBoolean(available.Invoke(null, null)))
                                {
                                    continue;
                                }
                            }
                            catch
                            {
                                continue;
                            }
                        }
                        firewallType = result.FirewallType;
                        priority     = result.OS.Priority;
                        fallbackType = result.OS.FallbackFirewallType;
                    }
                }
                if (firewallType is null || firewallType == typeof(IIPBanFirewall))
                {
                    throw new ArgumentException("Firewall is null, at least one type should implement IIPBanFirewall");
                }
                RequiredOperatingSystemAttribute fallbackAttr = fallbackType?.GetCustomAttribute <RequiredOperatingSystemAttribute>();
                Type existingType = existing?.GetType();
                if (existingType != null &&                      // if we have an existing firewall and
                    (
                        firewallType.Equals(existingType)) ||    // if the existing firewall is the desired type or
                    (
                        fallbackType != null &&                  // we have a fallback type and
                        (
                            fallbackType.Equals(existingType) || // the existing firewall is the fallback type or
                            (
                                // the fallback firewall has another fallback firewall and it matches the existing type
                                fallbackAttr?.FallbackFirewallType != null && fallbackAttr.FallbackFirewallType.Equals(existingType)
                            )
                        )
                    )
                    )
                {
                    return(existing);
                }
                try
                {
                    return(Activator.CreateInstance(firewallType, new object[] { rulePrefix }) as IIPBanFirewall);
                }
                catch (Exception ex)
                {
                    // see if there's a fallback
                    if (fallbackType is null)
                    {
                        throw;
                    }
                    Logger.Error(ex, "Failed to create firewall of type {0}, falling back to firewall type {1}", firewallType, fallbackType);
                    try
                    {
                        return(Activator.CreateInstance(fallbackType, new object[] { rulePrefix }) as IIPBanFirewall);
                    }
                    catch (Exception ex2)
                    {
                        // last fallback attempt
                        if (fallbackAttr?.FallbackFirewallType is null)
                        {
                            throw;
                        }
                        Logger.Error(ex2, "Failed to create firewall of type {0}, falling back to final attempt with firewall type {1}", fallbackType, fallbackAttr.FallbackFirewallType);
                        return(Activator.CreateInstance(fallbackAttr.FallbackFirewallType, new object[] { rulePrefix }) as IIPBanFirewall);
                    }
                }
            }
            catch (Exception ex)
            {
                throw new ArgumentException("Unable to create firewall, please double check your Firewall configuration property", ex);
            }
        }
        private void PopulateList(HashSet <System.Net.IPAddress> set,
                                  HashSet <IPAddressRange> ranges,
                                  HashSet <string> others,
                                  ref Regex regex,
                                  string setValue,
                                  string regexValue)
        {
            setValue   = (setValue ?? string.Empty).Trim();
            regexValue = (regexValue ?? string.Empty).Replace("*", @"[0-9A-Fa-f]+?").Trim();
            set.Clear();
            regex = null;

            void AddIPAddressRange(IPAddressRange range)
            {
                if (range.Begin.Equals(range.End))
                {
                    lock (set)
                    {
                        set.Add(range.Begin);
                    }
                }
                else
                {
                    lock (ranges)
                    {
                        ranges.Add(range);
                    }
                }
            }

            if (!string.IsNullOrWhiteSpace(setValue))
            {
                List <string> entries = new List <string>();
                foreach (string entry in setValue.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(e => e.Trim()))
                {
                    string entryWithoutComment = entry;
                    int    pos = entryWithoutComment.IndexOf('?');
                    if (pos >= 0)
                    {
                        entryWithoutComment = entryWithoutComment.Substring(0, pos);
                    }
                    entryWithoutComment = entryWithoutComment.Trim();
                    entries.Add(entryWithoutComment);
                }
                List <Task> entryTasks = new List <Task>();

                // iterate in parallel for performance
                foreach (string entry in entries)
                {
                    string entryWithoutComment = entry;
                    entryTasks.Add(Task.Run(async() =>
                    {
                        bool isUserName;
                        if (entryWithoutComment.StartsWith("user:"******"user:"******"https://", StringComparison.OrdinalIgnoreCase) ||
                                      entryWithoutComment.StartsWith("http://", StringComparison.OrdinalIgnoreCase)))
                            {
                                try
                                {
                                    if (httpRequestMaker != null)
                                    {
                                        // assume url list of ips, newline delimited
                                        byte[] ipListBytes = null;
                                        Uri uri            = new Uri(entryWithoutComment);
                                        await ExtensionMethods.RetryAsync(async() => ipListBytes = await httpRequestMaker.MakeRequestAsync(uri, null, ipListHeaders));
                                        string ipList = Encoding.UTF8.GetString(ipListBytes);
                                        if (!string.IsNullOrWhiteSpace(ipList))
                                        {
                                            foreach (string item in ipList.Split('\n'))
                                            {
                                                if (IPAddressRange.TryParse(item.Trim(), out IPAddressRange ipRangeFromUrl))
                                                {
                                                    AddIPAddressRange(ipRangeFromUrl);
                                                }
                                            }
                                        }
                                    }
                                }
                                catch (Exception ex)
                                {
                                    Logger.Error(ex, "Failed to get ip list from url {0}", entryWithoutComment);
                                }
                            }
                            else if (!isUserName && Uri.CheckHostName(entryWithoutComment) != UriHostNameType.Unknown)
                            {
                                try
                                {
                                    // add entries for each ip address that matches the dns entry
                                    IPAddress[] addresses = null;
                                    await ExtensionMethods.RetryAsync(async() => addresses = await dns.GetHostAddressesAsync(entryWithoutComment),
                                                                      exceptionRetry: _ex =>
                                    {
                                        // ignore host not found errors
                                        return(!(_ex is System.Net.Sockets.SocketException socketEx) ||
                                               socketEx.SocketErrorCode != System.Net.Sockets.SocketError.HostNotFound);
                                    });

                                    lock (set)
                                    {
                                        foreach (IPAddress adr in addresses)
                                        {
                                            set.Add(adr);
                                        }
                                    }
                                }
                                catch (Exception ex)
                                {
                                    Logger.Debug("Unable to resolve dns for {0}: {1}", entryWithoutComment, ex.Message);

                                    lock (others)
                                    {
                                        // eat exception, nothing we can do
                                        others.Add(entryWithoutComment);
                                    }
                                }
                            }
                            else
                            {
                                lock (others)
                                {
                                    others.Add(entryWithoutComment);
                                }
                            }
                        }