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,长度很短,有兴趣的读者可自行阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值