如何在 C# List 中存储 IP 地址列表以使其也可搜索子网?

发布于 2024-08-04 13:15:03 字数 236 浏览 11 评论 0原文

我应该如何正确存储包含子网地址的 IP 地址列表以使其可搜索?

有两个示例:

  1. 我的 IP 地址为 1.2.3.4,并且在我的 C# 列表中存在 1.2.3.4 条目,因此这里没有问题。

  2. 我的 IP 地址为 3.4.5.6,在我的 C# 列表中我有子网 3.4.0.0/24。这是我的问题。

如何在列表中存储 IP 子网以覆盖第二个示例?

How should I correctly store IP address list with addresses which are subnets to make it searchable?

There are two examples:

  1. I have IP address 1.2.3.4 and in my C# List there is 1.2.3.4 entry so here we have no problems.

  2. I have IP address 3.4.5.6 and in my C# List I have subnet 3.4.0.0/24. Here is my problem.

How to store IP subnet in List to cover second example?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

栀梦 2024-08-11 13:15:03

在本答案的最后,您将找到表示 IPV4 地址的结构的完整实现。

这是非常简单的用法示例:-

List<IPV4Address> list = new List<IPV4Address>();
list.Add(IPV4Address.FromString("3.4.0.0", 24));
var x = IPV4Address.FromString("3.4.0.6");
foreach (var addr in list.Where(a => a.Contains(x)))
  Console.WriteLine(addr);

值“3.4.0.0/255.255.255.0”显示在控制台中,因为在 3.4.0.0/24 子网中找到了 3.4.0.6。假设 list 充满了各种子网,并且 x 可以包含任何地址,那么:-

var result = list.Where(a => a.Contains(x))
    .OrderByDescending(a => a.Mask)
    .FirstOrDefault();

将选择包含 x 的最具体的子网。

public struct IPV4Address
{
  private UInt32 _Value;
  private UInt32 _Mask;

  public UInt32 Value
  {
    get { return _Value; }
    private set { _Value = value; }
  }

  public UInt32 Mask
  {
    get { return _Mask; }
    private set { _Mask = value; }
  }

  public static IPV4Address FromString(string address)
  {
    return FromString(address, 32);
  }

  public static IPV4Address FromString(string address, int maskLength)
  {
    string[] parts = address.Split('.');
    UInt32 value = ((UInt32.Parse(parts[0]) << 24) +
      ((UInt32.Parse(parts[1])) << 16) +
      ((UInt32.Parse(parts[2])) << 8) +
      UInt32.Parse(parts[3]));

    return new IPV4Address(value, maskLength);
  }

  public IPV4Address(UInt32 value)
  {
    _Value = value;
    _Mask = int.MaxValue;
  }

  public IPV4Address(UInt32 value, int maskLength)
  {
    if (maskLength < 0 || maskLength > 32)
      throw new ArgumentOutOfRangeException("maskLength", "Must be 0 to 32");

    _Value = value;
    if (maskLength == 32)
      _Mask = UInt32.MaxValue;
    else
      _Mask = ~(UInt32)((1 << (32 - maskLength))-1);

    if ((_Value & _Mask) != _Value)
      throw new ArgumentException("Address value must be contained in mask");
  }

  public bool Contains(IPV4Address address)
  {
    if ((Mask & address.Mask) == Mask)
    {
      return (address.Value & Mask) == Value;
    }
    return false;
  }

  public override string ToString()
  {
    string result = String.Format("{0}.{1}.{2}.{3}", (_Value >> 24), 
      (_Value >> 16) & 0xFF, 
      (_Value >> 8) & 0xFF, 
      _Value & 0xFF);

    if (_Mask != UInt32.MaxValue)
      result += "/" + String.Format("{0}.{1}.{2}.{3}", (_Mask >> 24),
      (_Mask >> 16) & 0xFF,
      (_Mask >> 8) & 0xFF,
      _Mask & 0xFF);

    return result;
  }
}

At the end of this answer you will find a complete implementation of a structure to represent a IPV4 address.

Here is really simple example of usage:-

List<IPV4Address> list = new List<IPV4Address>();
list.Add(IPV4Address.FromString("3.4.0.0", 24));
var x = IPV4Address.FromString("3.4.0.6");
foreach (var addr in list.Where(a => a.Contains(x)))
  Console.WriteLine(addr);

The value "3.4.0.0/255.255.255.0" is displayed inthe console since 3.4.0.6 is found in the 3.4.0.0/24 subnet. Assuming list is full of various subnets and x could contain any address then this:-

var result = list.Where(a => a.Contains(x))
    .OrderByDescending(a => a.Mask)
    .FirstOrDefault();

will select the most specific subnet for that contains x.

public struct IPV4Address
{
  private UInt32 _Value;
  private UInt32 _Mask;

  public UInt32 Value
  {
    get { return _Value; }
    private set { _Value = value; }
  }

  public UInt32 Mask
  {
    get { return _Mask; }
    private set { _Mask = value; }
  }

  public static IPV4Address FromString(string address)
  {
    return FromString(address, 32);
  }

  public static IPV4Address FromString(string address, int maskLength)
  {
    string[] parts = address.Split('.');
    UInt32 value = ((UInt32.Parse(parts[0]) << 24) +
      ((UInt32.Parse(parts[1])) << 16) +
      ((UInt32.Parse(parts[2])) << 8) +
      UInt32.Parse(parts[3]));

    return new IPV4Address(value, maskLength);
  }

  public IPV4Address(UInt32 value)
  {
    _Value = value;
    _Mask = int.MaxValue;
  }

  public IPV4Address(UInt32 value, int maskLength)
  {
    if (maskLength < 0 || maskLength > 32)
      throw new ArgumentOutOfRangeException("maskLength", "Must be 0 to 32");

    _Value = value;
    if (maskLength == 32)
      _Mask = UInt32.MaxValue;
    else
      _Mask = ~(UInt32)((1 << (32 - maskLength))-1);

    if ((_Value & _Mask) != _Value)
      throw new ArgumentException("Address value must be contained in mask");
  }

  public bool Contains(IPV4Address address)
  {
    if ((Mask & address.Mask) == Mask)
    {
      return (address.Value & Mask) == Value;
    }
    return false;
  }

  public override string ToString()
  {
    string result = String.Format("{0}.{1}.{2}.{3}", (_Value >> 24), 
      (_Value >> 16) & 0xFF, 
      (_Value >> 8) & 0xFF, 
      _Value & 0xFF);

    if (_Mask != UInt32.MaxValue)
      result += "/" + String.Format("{0}.{1}.{2}.{3}", (_Mask >> 24),
      (_Mask >> 16) & 0xFF,
      (_Mask >> 8) & 0xFF,
      _Mask & 0xFF);

    return result;
  }
}
断念 2024-08-11 13:15:03

定义一个存储 IPAddress 和前缀长度的类:

public class IPAddressWithPrefixLength
{
    public IPAddress IPAddress { get; }
    public int PrefixLength { get; }
}

然后重写 EqualsGetHashCode,这样只有第一个 PrefixLength > IPAddress.GetAddressBytes() 的位被考虑在内(当然还有 IPAddress 类型)。

然后,您可以使用此类将子网前缀存储在 List 中,或将它们用作 Dictionary 的键:

var subnets = new List<IPAddressWithPrefixLength>
{
    new IPAddressWithPrefixLength(IPAddress.Parse("1.2.3.4"), 32),
    new IPAddressWithPrefixLength(IPAddress.Parse("3.4.0.0"), 16),
};

var ipawpl = new IPAddressWithPrefixLength(IPAddress.Parse("3.4.5.6"), 16);

Console.WriteLine(subnets.Contains(ipawpl)); // prints "True"

这适用于 IPv6 地址,也。

Define a class that stores an IPAddress and the prefix length:

public class IPAddressWithPrefixLength
{
    public IPAddress IPAddress { get; }
    public int PrefixLength { get; }
}

Then override Equals and GetHashCode such that only the first PrefixLength bits of IPAddress.GetAddressBytes() are taken into consideration (and, of course, the IPAddress type).

You can then use this class to store subnet prefixes in a List<T> or use them as keys of a Dictionary<K,V>:

var subnets = new List<IPAddressWithPrefixLength>
{
    new IPAddressWithPrefixLength(IPAddress.Parse("1.2.3.4"), 32),
    new IPAddressWithPrefixLength(IPAddress.Parse("3.4.0.0"), 16),
};

var ipawpl = new IPAddressWithPrefixLength(IPAddress.Parse("3.4.5.6"), 16);

Console.WriteLine(subnets.Contains(ipawpl)); // prints "True"

This works with IPv6 addresses, too.

野の 2024-08-11 13:15:03

我更愿意创建一个专门的结构(类)来将所有这些信息存储在一起。
也许在不久的将来,您希望将其扩展为除了 ipv4 之外还存储 ipv6,也许还可以存储更多数据(指标、网关等)。

I would prefer to create a specialized structure (class) to store all these information together.
Probably in near future you would like to extend it to store ipv6 beside ipv4, and maybe some more data (metric, gateway, etc).

辞旧 2024-08-11 13:15:03

我可以使用节点上带有布尔标签的二叉树。使用标准表示法,其中 0 是左子节点,1 是右子节点,1.2.3.4 将通过将 true 放置在 00000001000000100000001100000100(该地址的二进制表示形式)来存储在树中 - 在根和此之间的所有节点处为 false。相反,3.4.0.0/16 将使用 true 存储在 0000001100000100(3.4.0.0 二进制表示的前 16 位)。

当给你一个要测试的地址时,只需根据该地址的位沿着树向下走:如果到达一个 true 节点,则该地址位于列表中。如果您到达分支的末尾,则该地址不在列表中。

例如,如果查找 3.4.123.48,则需要在树中向下查找 16 层才能到达 true,这意味着该地址位于列表中。但是查找 129.199.195.13,您可以从该地址的前 1 位知道它不属于列表的一部分。

我不确定使用 List 类型来存储这些地址对您来说有多重要,因此这可能没有帮助; OTOH,一旦您实现了带有标签的基本二叉树,它应该具有比 .Net List 更好的渐近性能特征。

I could use a binary tree with boolean labels on the nodes. Using the standard notation in which 0 is the left child and 1 the right child, 1.2.3.4 would be stored by putting true at 00000001000000100000001100000100 (the binary representation of that address) in the tree - and false at all nodes between the root and this. Conversely, 3.4.0.0/16 would be stored with a true at 0000001100000100 (the first 16 bits of the binary representation of 3.4.0.0).

When you are given an address to test, just go down the tree according to the bits of that address : if you reach a node a true, the address is in the list. If you reach the end of a branch, the address is not in the list.

For instance, if looking up 3.4.123.48, you'd go down 16 levels in the tree before reaching true, meaning that this address is in the list. But looking up 129.199.195.13, you'd know from the first 1 bit in that address that it is not part of the list.

I'm not sure how important it is to you to use the List type to store those addresses, so this may not help ; OTOH, once you've implemented a basic binary tree with labels, this should have better asymptotic performance characteristics than a .Net List.

も让我眼熟你 2024-08-11 13:15:03

不要将其存储在列表中 - 将其存储在字典等结构中,其中键是 IP 地址,值是子网地址。

Don't store it in a list - store it in a structure such as a Dictionary instead, where the key is the IP address, and the value is the subnet address.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文