网站首页 网站源码
website
站点相关全部源代码,隐藏了一些关于服务器的信息
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using ZiggyCreatures.Caching.Fusion;

namespace Dpz.Core.Infrastructure.RateLimiting;

/// <summary>
/// 基于行为的IP限流服务实现
/// 专注于限制可疑行为,不限制正常请求频率
/// </summary>
public class IpRateLimitService(
    IFusionCache fusionCache,
    ILogger<IpRateLimitService> logger,
    IConfiguration configuration,
    RateLimitConfig? config = null
) : IIpRateLimitService
{
    private readonly RateLimitConfig _config = config ?? new RateLimitConfig();

    private const string CacheKeyPrefix = "RateLimit:Block:";
    private const string WhiteListConfigurationKey = "IpRateLimitWhiteList";

    // 缓存白名单,避免每次都读取配置
    private HashSet<string>? _cachedWhiteList;
    private DateTime _whiteListCacheTime = DateTime.MinValue;
    private readonly TimeSpan _whiteListCacheExpiry = TimeSpan.FromMinutes(5); // 5分钟缓存

    /// <summary>
    /// 检查IP是否被限流
    /// 只检查状态,不记录任何访问信息
    /// </summary>
    public async Task<RateLimitResult> CheckLimitStatusAsync(
        string ip,
        CancellationToken cancellationToken = default
    )
    {
        if (string.IsNullOrEmpty(ip))
        {
            return RateLimitResult.Allow();
        }

        // 检查白名单
        if (IsWhitelisted(ip))
        {
            return RateLimitResult.Allow();
        }

        var record = await GetBlockRecordAsync(ip);
        if (record == null)
        {
            // 没有拦截记录,允许通过
            return RateLimitResult.Allow();
        }

        var now = DateTime.UtcNow;

        // 检查是否在限流期内
        if (record.BlockedUntil.HasValue && now < record.BlockedUntil.Value)
        {
            var remainingTime = record.BlockedUntil.Value - now;
            var delayMs = CalculateBlockedDelay(record);

            // 根据配置决定是否记录详细日志
            if (_config.EnableVerboseLogging)
            {
                logger.LogInformation(
                    "IP {Ip} 仍在限流期内,剩余时间: {RemainingTime},延迟: {DelayMs}ms",
                    ip,
                    remainingTime,
                    delayMs
                );
            }

            return RateLimitResult.Block(
                $"IP blocked for suspicious behavior, remaining time: {remainingTime:mm\\:ss}",
                delayMs
            );
        }

        // 限流期已过,允许通过
        return RateLimitResult.Allow();
    }

    /// <summary>
    /// 记录IP被拦截事件
    /// 当安全中间件检测到可疑行为时调用
    /// </summary>
    public async Task RecordBlockAsync(string ip, string? reason = null)
    {
        if (string.IsNullOrEmpty(ip))
        {
            return;
        }

        // 检查白名单
        if (IsWhitelisted(ip))
        {
            return;
        }

        var record = await GetOrCreateBlockRecordAsync(ip);
        var now = DateTime.UtcNow;

        // 检查是否需要重置窗口
        if (now - record.WindowStart > TimeSpan.FromMinutes(_config.WindowSizeMinutes))
        {
            ResetWindow(record, now);
        }

        // 记录拦截事件
        record.BlockCount++;
        record.LastBlockTime = now;

        logger.LogWarning(
            "IP {Ip} 被拦截,原因: {Reason},当前窗口拦截次数: {BlockCount}/{MaxBlocks}",
            ip,
            reason ?? "未指定",
            record.BlockCount,
            _config.MaxBlocksPerWindow
        );

        // 检查是否需要限流
        if (record.BlockCount >= _config.MaxBlocksPerWindow)
        {
            record.BlockedUntil = now.AddMinutes(_config.BlockDurationMinutes);

            logger.LogWarning(
                "IP {Ip} 因频繁可疑行为被限流 {Duration} 分钟,拦截次数: {BlockCount}",
                ip,
                _config.BlockDurationMinutes,
                record.BlockCount
            );
        }

        // 更新缓存
        await UpdateBlockRecordAsync(ip, record);
    }

    /// <summary>
    /// 获取IP拦截记录
    /// </summary>
    private async Task<IpAccessRecord?> GetBlockRecordAsync(string ip)
    {
        var cacheKey = GetCacheKey(ip);
        var result = await fusionCache.TryGetAsync<IpAccessRecord>(cacheKey);

        return result.HasValue ? result.Value : null;
    }

    /// <summary>
    /// 获取或创建IP拦截记录
    /// </summary>
    private async Task<IpAccessRecord> GetOrCreateBlockRecordAsync(string ip)
    {
        var cacheKey = GetCacheKey(ip);
        var expiration = TimeSpan.FromMinutes(
            _config.WindowSizeMinutes + _config.CleanupIntervalMinutes
        );

        var record = await fusionCache.GetOrSetAsync<IpAccessRecord>(
            cacheKey,
            (_, _) =>
                Task.FromResult(
                    new IpAccessRecord
                    {
                        WindowStart = DateTime.UtcNow,
                        LastBlockTime = DateTime.UtcNow,
                        BlockCount = 0,
                    }
                ),
            options =>
            {
                options.SetDuration(expiration);
                // 针对限流服务优化:适中的抖动时间,平衡性能和容错
                options.JitterMaxDuration = TimeSpan.FromMilliseconds(500);
            }
        );

        return record;
    }

    /// <summary>
    /// 更新IP拦截记录到缓存
    /// </summary>
    private async Task UpdateBlockRecordAsync(string ip, IpAccessRecord record)
    {
        var cacheKey = GetCacheKey(ip);
        var expiration = TimeSpan.FromMinutes(
            _config.WindowSizeMinutes + _config.CleanupIntervalMinutes
        );

        await fusionCache.SetAsync(
            cacheKey,
            record,
            options =>
            {
                options.SetDuration(expiration);
                // 针对限流服务优化:适中的抖动时间,平衡性能和容错
                options.JitterMaxDuration = TimeSpan.FromMilliseconds(500);
            }
        );
    }

    /// <summary>
    /// 构建缓存键
    /// </summary>
    private static string GetCacheKey(string ip)
    {
        return $"{CacheKeyPrefix}{ip}";
    }

    /// <summary>
    /// 重置时间窗口
    /// </summary>
    private static void ResetWindow(IpAccessRecord record, DateTime now)
    {
        record.WindowStart = now;
        record.BlockCount = 0;
        // 保留限流状态 BlockedUntil
    }

    /// <summary>
    /// 计算被限流时的延迟时间
    /// 基于拦截次数进行渐进式延迟,让攻击者"不爽"
    /// </summary>
    private int CalculateBlockedDelay(IpAccessRecord record)
    {
        // 基础延迟 + 根据拦截次数递增
        var baseDelay = _config.BlockedDelayBaseMs;
        var extraDelay = Math.Min(
            record.BlockCount * 1000, // 每次拦截增加1秒
            _config.BlockedDelayMaxMs - baseDelay
        );

        return Math.Min(baseDelay + extraDelay, _config.BlockedDelayMaxMs);
    }

    /// <summary>
    /// 获取IP白名单(带缓存)
    /// </summary>
    private HashSet<string> GetWhiteList()
    {
        var now = DateTime.UtcNow;

        // 检查缓存是否有效
        if (_cachedWhiteList != null && now - _whiteListCacheTime < _whiteListCacheExpiry)
        {
            return _cachedWhiteList;
        }

        try
        {
            // 尝试从配置中读取白名单
            var configSection = configuration.GetSection(WhiteListConfigurationKey);
            var whiteListArray = configSection.Get<string[]>() ?? [];
            var whiteList = new HashSet<string>(whiteListArray, StringComparer.OrdinalIgnoreCase);

            // 只在白名单条目数量变化时记录,避免重复日志
            if (_cachedWhiteList == null || _cachedWhiteList.Count != whiteList.Count)
            {
                logger.LogInformation("IP白名单已更新,共 {Count} 个条目", whiteList.Count);
            }

            // 更新缓存
            _cachedWhiteList = whiteList;
            _whiteListCacheTime = now;
            return whiteList;
        }
        catch (Exception e)
        {
            logger.LogError(e, "获取IP白名单失败,使用空白名单");

            // 失败时使用空集合,但不缓存(下次继续尝试)
            return [];
        }
    }

    /// <summary>
    /// 检查IP是否在白名单中(支持CIDR)
    /// </summary>
    private bool IsWhitelisted(string ip)
    {
        if (string.IsNullOrEmpty(ip))
        {
            return false;
        }

        var whiteList = GetWhiteList();
        if (whiteList.Count == 0)
        {
            return false;
        }

        // 先检查精确匹配(最快)
        if (whiteList.Contains(ip))
        {
            logger.LogDebug("IP {Ip} 匹配白名单精确条目", ip);
            return true;
        }

        // 解析IP地址(只解析一次)
        if (!IPAddress.TryParse(ip, out var ipAddress))
        {
            logger.LogWarning("无效的IP地址格式: {Ip}", ip);
            return false;
        }

        // 检查CIDR匹配(按地址族分组优化)
        return whiteList.Any(entry => entry.Contains('/') && IsIpInRange(ipAddress, entry));
    }

    /// <summary>
    /// 检查IP地址是否在指定的CIDR范围内
    /// </summary>
    private static bool IsIpInRange(IPAddress ip, string cidr)
    {
        try
        {
            // 如果不是CIDR格式,直接返回false
            if (string.IsNullOrEmpty(cidr) || !cidr.Contains('/'))
            {
                return false;
            }

            var parts = cidr.Split('/', StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length != 2)
            {
                return false;
            }

            var networkAddress = parts[0].Trim();
            if (!int.TryParse(parts[1].Trim(), out var prefixLength))
            {
                return false;
            }

            if (!IPAddress.TryParse(networkAddress, out var networkIp))
            {
                return false;
            }

            // 确保两个IP地址是同一类型(IPv4或IPv6)
            if (networkIp.AddressFamily != ip.AddressFamily)
            {
                return false;
            }

            // 验证前缀长度的有效性
            var maxPrefixLength =
                networkIp.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128;
            if (prefixLength < 0 || prefixLength > maxPrefixLength)
            {
                return false;
            }

            var networkBytes = networkIp.GetAddressBytes();
            var targetBytes = ip.GetAddressBytes();
            var maskBytes = GetNetworkMask(prefixLength, networkBytes.Length);

            // 比较网络部分是否匹配
            return !networkBytes
                .Where((x, y) => (x & maskBytes[y]) != (targetBytes[y] & maskBytes[y]))
                .Any();
        }
        catch
        {
            // 任何异常都认为不匹配
            return false;
        }
    }

    /// <summary>
    /// 根据前缀长度生成网络掩码
    /// </summary>
    private static byte[] GetNetworkMask(int prefixLength, int addressBytesLength)
    {
        var maskBytes = new byte[addressBytesLength];
        var fullBytes = prefixLength / 8;
        var remainingBits = prefixLength % 8;

        // 设置完整的字节
        for (var i = 0; i < fullBytes && i < maskBytes.Length; i++)
        {
            maskBytes[i] = 0xFF;
        }

        // 设置剩余的位
        if (fullBytes < maskBytes.Length && remainingBits > 0)
        {
            maskBytes[fullBytes] = (byte)(0xFF << (8 - remainingBits));
        }

        return maskBytes;
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

上述代码实现了一个基于行为的IP限流服务,主要用于限制可疑行为的IP地址,而不限制正常请求的频率。以下是代码的主要功能和结构的详细解释:

主要功能

  1. IP限流检查

    • CheckLimitStatusAsync 方法用于检查给定IP地址是否被限流。如果IP在白名单中,或者没有拦截记录,则允许通过;如果在限流期内,则返回被限流的状态和剩余时间。
  2. 记录拦截事件

    • RecordBlockAsync 方法用于记录IP地址的拦截事件。当检测到可疑行为时调用此方法。它会更新拦截记录,包括拦截次数和最后拦截时间,并在达到最大拦截次数时设置限流状态。
  3. 缓存管理

    • 使用 IFusionCache 进行缓存管理,存储IP的拦截记录,以提高性能并减少对存储的频繁访问。
  4. 白名单管理

    • 通过 GetWhiteList 方法获取IP白名单,并使用缓存来避免频繁读取配置。白名单中的IP地址不会受到限流。
  5. CIDR支持

    • 支持CIDR格式的IP地址白名单检查,允许用户指定IP范围。
  6. 限流策略

    • 通过配置文件中的参数(如最大拦截次数、限流持续时间等)来控制限流策略。
  7. 日志记录

    • 使用 ILogger 记录相关的操作和状态,包括拦截事件、白名单更新等,便于后续的审计和调试。

代码结构

  • 构造函数

    • 接收缓存、日志记录器、配置和限流配置作为参数,初始化服务。
  • 私有方法

    • GetBlockRecordAsync:获取指定IP的拦截记录。
    • GetOrCreateBlockRecordAsync:获取或创建IP的拦截记录。
    • UpdateBlockRecordAsync:更新IP的拦截记录到缓存。
    • GetCacheKey:生成缓存键。
    • ResetWindow:重置时间窗口。
    • CalculateBlockedDelay:计算被限流时的延迟时间。
    • GetWhiteList:获取IP白名单并进行缓存。
    • IsWhitelisted:检查IP是否在白名单中。
    • IsIpInRange:检查IP地址是否在指定的CIDR范围内。
    • GetNetworkMask:根据前缀长度生成网络掩码。

总结

这个IP限流服务的设计旨在保护系统免受可疑行为的影响,同时允许正常用户的请求不受限制。通过使用缓存和白名单机制,服务能够高效地处理请求并减少对存储的依赖。

loading