首发于若水斋。
在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
至此读者应该可以明白网段的查询要如何实现了,直接用SQL
的like
就可以了,这也是要在末尾加“%”的原因。
为了文章的完整性,我觉得有必要进行简单的说明:
- 若网段A属于网段B,则在数据库中存储的数据,A like B一定成立;
- 若网段A包含网段B,则在数据库中存储的数据,B like A一定成立。
编程实现
阅读官方文档《编写自定义模型字段(model fields)》可以学习如何编写自定义的模型字段。完整的代码见Github,长度很短,有兴趣的读者可自行阅读。