3

我在我一直在开发的这个 IDS/IPS 流量监控控制台应用程序中发现了内存泄漏。我以为是实体框架,原来是防火墙代码。以下代码在 .net 4.6.2 中运行良好,并且在 .net 核心中运行良好。但是,它有内存泄漏(特别是第二firewallRule行):

INetFwPolicy2 firewallPolicy = (INetFwPolicy2)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FwPolicy2"));
INetFwRule firewallRule = firewallPolicy.Rules.OfType<INetFwRule>().Where(x => x.Name == fwRuleName).FirstOrDefault();

我必须每天重新启动应用程序,因为它会消耗尽可能多的内存。当它应该运行在 35MB 左右时,我已经看到它高达 12GB。也只需要一天左右的时间就可以达到如此高的水平。所以基本上每个电话都firewallRule在吃〜5MB-10MB。似乎很疯狂,一条或一条空记录会占用那么多内存。

我相信是那一firewallRule行,因为我已经在被调用的方法(以及与此相关的应用程序)中注释掉了所有其他行,当我最终注释掉该行时,泄漏就停止了。如果我的逻辑有缺陷,请告诉我。

如果要对其进行测试,则必须引用 以NetFwTypeLib使该代码正常工作。

有谁知道为什么会发生这种情况以及如何解决?

更新:

以下是那些相信我没有全部评论的人的原始代码:

public static void FirewallSetup(string ip, string countryCode, bool isIpsString)
        {

        //    FirewallControl fwCtrl = new FirewallControl();
        //    fwCtrl.Block(ip, countryCode, isIpsString);
        //}

        //private void Block(string ip, string countryCode, bool isIpsString)
        //{
            var remoteAddresses = "*";
            string fwRuleName;
            bool createNewFwRule = false;

            //var fwRuleSuffix = SQLControl.GetFirewallRuleSuffix(countryCode).ToString();
            //if (string.IsNullOrEmpty(fwRuleSuffix) || fwRuleSuffix == "0")
            //{
                fwRuleName = fwRuleNamePrefix + countryCode;
            //}
            //else
            //{
            //    fwRuleName = fwRuleNamePrefix + countryCode + fwRuleSuffix;
            //}

            INetFwPolicy2 firewallPolicy = (INetFwPolicy2)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FwPolicy2"));
            INetFwRule firewallRule = firewallPolicy.Rules.OfType<INetFwRule>().Where(x => x.Name == fwRuleName).FirstOrDefault();

            //if (firewallRule == null)
            //{
            //    createNewFwRule = true;
            //}
            //else
            //{
            //    //We need the following for creating the remoteAddresses string below
            //    //but, also need to count as Windows has a 5000 ip limit per rule
            //    remoteAddresses = firewallRule.RemoteAddresses;
            //    string[] aRemoteAddresses = remoteAddresses.Split(",");
            //    int remoteAddressesCount = aRemoteAddresses.Length;
            //    //Log.Debug(">>>Firewall remote addresses scope=" + remoteAddresses);
            //    Log.Warning(">>>" + fwRuleName + " Firewall remote addresses count:" + remoteAddressesCount);

            //    //5000 would be 0 to 4999 I think?
            //    if (remoteAddressesCount >= 4999)
            //    {
            //        //If remote ip scope is 5000, create a new fw rule
            //        var newFwRuleNameSuffix = SQLControl.GetNewFirewallRuleSuffix(countryCode).ToString();
            //        fwRuleName = fwRuleNamePrefix + countryCode + newFwRuleNameSuffix;

            //        createNewFwRule = true;
            //    }
            //}

            //If necessary, we create a new rule
            //TODO: Create another method for this?
            //if (createNewFwRule)
            //{
            //    firewallRule = (INetFwRule)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FWRule"));
            //    firewallRule.Name = fwRuleName;
            //    firewallPolicy.Rules.Add(firewallRule);
            //    firewallRule.Description = "Block inbound traffic from " + countryCode;
            //    firewallRule.Profiles = (int)NET_FW_PROFILE_TYPE2_.NET_FW_PROFILE2_ALL;
            //    firewallRule.Protocol = (int)NET_FW_IP_PROTOCOL_.NET_FW_IP_PROTOCOL_TCP;
            //    firewallRule.Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN;
            //    firewallRule.Action = NET_FW_ACTION_.NET_FW_ACTION_BLOCK;
            //    firewallRule.Enabled = true;
            //    //firewallRule.RemoteAddresses = ip;
            //    //firewallPolicy.Rules.Add(firewallRule); //throws error and not needed anyway
            //    //firewallRule.LocalPorts = "4000";
            //    //firewallRule.Grouping = "@firewallapi.dll,-23255";
            //    //firewallRule.Profiles = firewallPolicy.CurrentProfileTypes;
            //}

            //if (isIpsString || remoteAddresses == "*")
            //{
            //    firewallRule.RemoteAddresses = ip;
            //}
            //else
            //{
            //    firewallRule.RemoteAddresses = remoteAddresses + "," + ip;
            //}
        }

在生产中,它只是“按原样”执行此方法,只是吃掉内存。同样,它特定于 .Net Core,因为我在 .Net 4.6.1(未注释)中运行相同的代码一年多没有问题。

现在我想了想并把它注释掉了,我也可以把它通过一些循环并在内部进行故障排除。这是一场比赛...

它当前运行的服务器是 Windows Server 2016。

更新 2:它也可以在 Windows 10 中重现。这是重现错误的完整代码:

using System;
using System.Linq;
using NetFwTypeLib;

namespace FirewallLeakTesterCore
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = 0;
            while (count < 1000)
            {
                Console.WriteLine(count += 1);
                INetFwPolicy2 firewallPolicy = (INetFwPolicy2)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FwPolicy2"));
                INetFwRule firewallRule = firewallPolicy.Rules.OfType<INetFwRule>().Where(x => x.Name == "firewallRuleName").FirstOrDefault();

            }
            Console.ReadKey();
        }
    }
}

结果如下:

Visual Studio 诊断工具:

Visual Studio 诊断工具

看起来像微软的错误?我想我会尝试解决这些内存分析器之一,但即使我发现了问题,我能做什么?这是对基本 Windows 库的基本调用,我认为这就是为什么评论中的某些人没有抓住重点。

我没有要求任何人记忆我的应用程序或追踪问题(从来没有......我已经这样做了)。我正在寻求替代解决方案。是否有可能完全可以使用的结构或 linq 问题或其他语句?

如果您想关闭这个问题,那就这样吧,但我很难相信没有其他人会遇到这个问题。

4

1 回答 1

1

我看到很多关于您的内存分析的评论,而不是很多关于解决您的问题的评论......这是一篇您可以用来测试 JetBrains 的内存泄漏的文章https://blog.jetbrains.com/dotnet/2018 /10/04/unit-testing-memory-leaks-using-dotmemory-unit/

至于你的问题,我想你可能会看看 Marshal.ReleaseComObject

我会释放任何类似这样的 COM 调用:

if (_netFwPolicy is not null && OperatingSystem.IsWindows())
{
    Marshal.ReleaseComObject(_netFwPolicy);
}

但是您真正需要考虑的是将实例包装在处理 IDispose 的类中,这样任何 foreach 也会释放该项目,这样使用时,循环遍历策略中的项目将成为一个问题......

我知道有很多示例在互联网上做你正在做的事情,从定义上讲这并不是一个好主意,也许考虑使用这个包装器。

public interface IFireWallRule :INetFwRule, IDisposable
{

}

class FireWallRule: IFireWallRule
{
    private INetFwRule? _data;
    private bool _disposedValue;

    public FireWallRule(INetFwRule data)
    {
        _data=data;
    }



    internal INetFwRule Root => _data?? throw new ObjectDisposedException("This firewall rule is already disposed and can't be accessed");
    public string Name { get => Root.Name; set => Root.Name=value; }
    public string Description { get => Root.Description; set => Root.Description=value; }
    public string ApplicationName { get => Root.ApplicationName; set => Root.ApplicationName=value; }
    public string serviceName { get => Root.serviceName; set => Root.serviceName=value; }
    public int Protocol { get => Root.Protocol; set => Root.Protocol=value; }
    public string LocalPorts { get => Root.LocalPorts; set => Root.LocalPorts=value; }
    public string RemotePorts { get => Root.RemotePorts; set => Root.RemotePorts=value; }
    public string LocalAddresses { get => Root.LocalAddresses; set => Root.LocalAddresses=value; }
    public string RemoteAddresses { get => Root.RemoteAddresses; set => Root.RemoteAddresses=value; }
    public string IcmpTypesAndCodes { get => Root.IcmpTypesAndCodes; set => Root.IcmpTypesAndCodes=value; }
    public NET_FW_RULE_DIRECTION_ Direction { get => Root.Direction; set => Root.Direction=value; }
    public dynamic Interfaces { get => Root.Interfaces; set => Root.Interfaces=value; }
    public string InterfaceTypes { get => Root.InterfaceTypes; set => Root.InterfaceTypes=value; }
    public bool Enabled { get => Root.Enabled; set => _data.Enabled=value; }
    public string Grouping { get => Root.Grouping; set => _data.Grouping=value; }
    public int Profiles { get => Root.Profiles; set => _data.Profiles=value; }
    public bool EdgeTraversal { get => Root.EdgeTraversal; set => Root.EdgeTraversal=value; }
    public NET_FW_ACTION_ Action { get => Root.Action; set => Root.Action=value; }




    protected virtual void Dispose(bool _)
    {
        if (!_disposedValue)
        {
            if (_data is not null && OperatingSystem.IsWindows())
            {
                Marshal.ReleaseComObject(_data);
                _data=null;
            }


            _disposedValue=true;
        }
    }


    ~FireWallRule()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

为了让生活“轻松”,我会考虑一些扩展方法来更轻松地使用 Wrapper 类

internal static class FireWallExcensionMethods
{
    public static void Add(this INetFwPolicy2 policy, FireWallRule rule)
    {
        policy.Rules.Add(rule.Root);
    }
    public static void Add(this INetFwRules rules, FireWallRule rule)
    {
        rules.Add(rule.Root);
    }
    public static IEnumerable<FireWallRule> GetRules(this INetFwPolicy2 policy)
    {
        foreach (INetFwRule rule in policy.Rules)
        {
            yield return new FireWallRule(rule);
        }

    }
}
于 2021-12-14T07:40:53.620 回答