django-cidrfield开发小记

在Django中GenericIPAddressField可以存储一个IPv4或IPv6地址,但没有专门用来存储网段的字段。用字符串存储网段会丢失语义,如无法按包含的IP地址过滤网段。Django插件django-netfields实现了存储网段的功能,但只支持PostgreSQL,它直接使用了PostgreSQL提供的网段相关字段。我设计实现了一个支持所有数据库的专用于存储网段的字段IPNetworkField。这篇文章将介绍IPNetworkField的使用方法和实现原理。

后文中IP段和网段为同义词,视行文方便使用。

使用方法

首先使用pip安装django-cidrfield

pip install django-cidrfield

在定义model时模仿下面的示例定义存储网段的字段:

from django.db import models
from cidrfield.models import IPNetworkField

class MyModel(models.Model):

    # the regular params should work well enough here
    ip_network = IPNetworkField()
    # ... and so on

创建model后按如下的示例存储一个网段:

MyModel(ip_network='192.168.1.0/24').save()

可以用__contains查询包含特定IP或IP段的网段:

MyModel.objects.filter(ip_network__contains='192.168.1.1')
MyModel.objects.filter(ip_network__contains='192.168.1.250/30')

__icontains效果是一样的。

可以用__in查询属于特定网段的网段:

MyModel.objects.filter(ip_network__in='192.168.0.0/16')

实现原理

设计目标

在开始论述实现原理前先捋一捋需求。我们的目标是:

  • 可以存储一个网段
  • 可以查询包含特定IP地址或地址段的网段
  • 可以查询属于特定IP地址段的网段

如何存储

并不是所有数据库都像PostgreSQL一样原生支持网段的存储和查询。所以我只能选择使用整数或字符串来存储网段。若想用一个数据库字段存储网段,使用整数有些力不从心,因为一个IP地址就是一个整数,而网段是IP地址加上一个额外的数据(掩码或CIDR)。但若用字符串来存储网段。又会遇到运算困难的问题,为了解决这些问题,我设计了一种实现简单,但效率不高的数据结构:用字符串存储二进制数。我们以一个IPv4地址段为例:

192.168.56.0/24

这个地址段由一个IP地址和“/24”(CIDR)组成。这种表示IP地址的方式为点分十进制,便于人类阅读,但不便于计算。最便于计算的无疑为二进制形式,因此我们把上述的地址段转换为:

11000000101010000011100000000000/24

但我们把上面的值以字符串形式保存在数据库中依旧无法计算,问题主要出在“/24”上。直接丢掉“/24”会丢失一部分信息,我的方法是丢掉“/24”的同时只保留字符串的前24位,网段变成了:

110000001010100000111000

如果知道转换规则,我们是否可以将其还原为最初的网段呢?答案是肯定的。因为我们没有丢失信息。“24”这个信息体现在字符串长度上,而字符串被丢掉的部分为全0。

但最终存储的数据还需要在字符串前面加上“IPv4”或“IPv6”,以区别两种IP协议的网段,还需要在字符串后边加上“%”,以便于进行数据库字符串搜索。

总结一下,网段:

192.168.56.0/24

存储在数据库中的实际是:

IPv4110000001010100000111000%

IPv6地址的存储同理。

如何查询

网段:

192.168.0.0/16

存储在数据库中长什么样呢?这是很容易计算的。为便于读者对比,将两个网段实际存储的数据写在一起:

IPv41100000010101000%                # 192.168.0.0/16
IPv4110000001010100000111000%        # 192.168.56.0/24

至此读者应该可以明白网段的查询要如何实现了,直接用SQLlike就可以了,这也是要在末尾加“%”的原因。

为了文章的完整性,我觉得有必要进行简单的说明:

  • 若网段A属于网段B,则在数据库中存储的数据,A like B一定成立;
  • 若网段A包含网段B,则在数据库中存储的数据,B like A一定成立。

编程实现

阅读官方文档《编写自定义模型字段(model fields)》可以学习如何编写自定义的模型字段。完整的代码见Github,长度很短,有兴趣的读者可自行阅读。

8 Replies to “django-cidrfield开发小记”

  1. 你好,想请问一个题外的问题,我怎么在自己的代码中调用其他的搜索引擎为我搜索?小白一个,网上的方法不太适合我的想法。

      1. 就是通过我的代码调用百度百科的搜索功能,或者是其他的百科搜索,然后将我输入的关键字让它帮我搜索,返回给我数据。我用的python,十分感谢

        1. 这个功能相当于写一个爬虫。如搜索“博客”,则爬取“https://baike.baidu.com/item/%E5%8D%9A%E5%AE%A2”的内容(“%E5%8D%9A%E5%AE%A2”是“博客”的URL编码)。

          1. 思路是这样的,但具体实现起来还有许多细节要处理。如:要如何处理具有多种含义的词条,如果有反爬虫机制要如何绕过等。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

4 × 5 =