使用 Python 的 ipaddress 模块学习 IP 地址概念

网友投稿 1412 2022-05-30

目录

理论与实践中的 IP 地址

IP地址的机制

Python ipaddress 模块

IP 网络和接口

CIDR 表示法

循环网络

子网

主机接口

特殊地址范围

引擎盖下的 Python ipaddress 模块

组合的核心作用

扩展 IPv4 地址

结论

进一步阅读

Python 的ipaddress模块是 Python 标准库中被低估的珍宝。您不必是一个成熟的网络工程师,就可以在野外暴露于 IP 地址。IP 地址和网络在软件开发和基础设施中无处不在。它们是计算机如何相互寻址的基础。

边做边学是掌握IP地址的有效方法。该ipaddress模块允许您通过将 IP 地址作为 Python 对象查看和操作来实现这一点。在本教程中,您将通过使用 Pythonipaddress模块的一些功能更好地掌握 IP 地址。

在本教程中,您将学习:

如何IP地址的工作,无论是在理论上还是在Python代码

IP 网络如何表示 IP 地址组以及如何检查两者之间的关系

Python 的ipaddress模块如何巧妙地使用经典的设计模式,让你事半功倍

要继续,您只需要 Python 3.3 或更高版本,因为它ipaddress已添加到该版本的 Python 标准库中。本教程中的示例是使用Python 3.8生成的。

理论与实践中的 IP 地址

如果您只记得有关 IP 地址的一个概念,那么请记住这一点:IP 地址是一个整数。这条信息将帮助您更好地理解 IP 地址的功能以及它们如何表示为 Python 对象。

在你跳入任何 Python 代码之前,看看这个概念在数学上充实是有帮助的。如果您在这里只是为了了解如何使用ipaddress模块的一些示例,那么您可以跳到下一部分,关于使用模块本身。

IP地址的机制

您在上面看到 IP 地址归结为一个整数。更完整的定义是IPv4 地址是一个 32 位整数,用于表示网络上的主机。术语主机有时与地址同义。

因此,有 2 32 个可能的 IPv4 地址,从 0 到 4,294,967,295(其中上限为 2 32 - 1)。但这是人类的教程,而不是机器人。没有人想 ping IP 地址0xdc0e0925。

表达 IPv4 地址的更常见方法是使用四点表示法,它由四个点分隔的十进制整数组成:

220.14.9.37

220.14.9.37不过,地址代表的底层整数并不是很明显。从公式上讲,您可以将 IP 地址220.14.9.37分成四个八位字节组成部分:

>>> ( ... 220 * (256 ** 3) + ... 14 * (256 ** 2) + ... 9 * (256 ** 1) + ... 37 * (256 ** 0) ... ) 3691907365

如上所示,地址220.14.9.37代表整数 3,691,907,365。每个八位字节是一个byte,或一个从 0 到 255 的数字。鉴于此,您可以推断出 IPv4 地址的最大值为255.255.255.255(或FF.FF.FF.FF十六进制表示法),而最小值为0.0.0.0。

接下来,您将看到 Python 的ipaddress模块如何为您执行此计算,从而允许您使用人类可读的形式并让地址算术在视线之外发生。

Pythonipaddress模块

接下来,您可以获取计算机的外部 IP 地址以在命令行中使用:

$ curl -sS ifconfig.me/ip 220.14.9.37

这会从站点ifconfig.me请求您的 IP 地址,该地址可用于显示有关您的连接和网络的一系列详细信息。

注意:为了技术上的正确性,这很可能不是您计算机自己的公共 IP 地址。如果您的连接位于NATed路由器后面,那么最好将其视为您访问 Internet 的“代理”IP。

现在打开一个 Python REPL。您可以使用IPv4Address该类来构建一个封装地址的 Python 对象:

>>>

>>> from ipaddress import IPv4Address >>> addr = IPv4Address("220.14.9.37") >>> addr IPv4Address('220.14.9.37')

将str诸如此类传递"220.14.9.37"给IPv4Address构造函数是最常见的方法。但是,该类也可以接受其他类型:

>>> IPv4Address(3691907365) # From an int IPv4Address('220.14.9.37') >>> IPv4Address(b"\xdc\x0e\t%") # From bytes (packed form) IPv4Address('220.14.9.37')

虽然从人类可读的构造str可能是更常见的方式,但bytes如果您正在处理诸如TCP packet data 之类的东西,您可能会看到输入。

上面的转换也可以在另一个方向上进行:

>>>

>>> int(addr) 3691907365 >>> addr.packed b'\xdc\x0e\t%'

除了允许往返输入和输出到不同的 Python 类型之外, 的实例IPv4Address也是可散列的。这意味着您可以将它们用作映射数据类型(例如字典)中的键:

>>> hash(IPv4Address("220.14.9.37")) 4035855712965130587 >>> num_connections = { ... IPv4Address("220.14.9.37"): 2, ... IPv4Address("100.201.0.4"): 16, ... IPv4Address("8.240.12.2"): 4, ... }

最重要的是,IPv4Address还实现的方法,其允许使用底层整数比较:

>>> IPv4Address("220.14.9.37") > IPv4Address("8.240.12.2") True >>> addrs = ( ... IPv4Address("220.14.9.37"), ... IPv4Address("8.240.12.2"), ... IPv4Address("100.201.0.4"), ... ) >>> for a in sorted(addrs): ... print(a) ... 8.240.12.2 100.201.0.4 220.14.9.37

您可以使用任何标准比较运算符来比较地址对象的整数值。

注意:本教程重点介绍 Internet 协议版本 4 (IPv4) 地址。还有一些 IPv6 地址,它们是 128 位而不是 32 位,并以标题形式表示,例如2001:0:3238:dfe1:63::fefb. 由于地址的算术基本相同,因此本教程从等式中删除了一个变量,重点关注 IPv4 地址。

该ipaddress模块具有更灵活的工厂函数, ip_address(),它接受一个表示 IPv4 或 IPv6 地址的参数,并尽力分别返回一个IPv4Address或一个IPv6Address实例。

在本教程中,您将切入正题并IPv4Address直接使用它构建地址对象。

正如你在上面看到的,构造函数本身IPv4Address是简短而甜蜜的。当您开始将地址归入组或网络时,事情就会变得更有趣。

IP 网络和接口

甲网络是一组IP地址。网络被描述和显示为连续的地址范围。例如,一个网络可以由地址的192.4.2.0通过192.4.2.255,含有256个地址的网络。

您可以通过上下 IP 地址识别网络,但是如何以更简洁的约定来显示它呢?这就是 CIDR 表示法的用武之地。

CIDR 表示法

网络是使用所定义的网络地址加一个前缀在无类别域间路由(CIDR)表示法:

>>>

>>> from ipaddress import IPv4Network >>> net = IPv4Network("192.4.2.0/24") >>> net.num_addresses 256

CIDR 表示法将网络表示为/。该路由前缀(或前缀长度,或者只是前缀),这是在这种情况下,24岁,是用来回答问题前几位,如某个地址是否是网络的一部分,或者有多少地址驻留在网络中的计数。(这里的前导位是指从二进制整数左侧开始计数的前N位。)

您可以通过以下.prefixlen属性找到路由前缀:

>>> net.prefixlen 24

让我们直接进入一个例子。地址192.4.2.12在网络中192.4.2.0/24吗?在这种情况下,答案是肯定的,因为 的前 24 位192.4.2.12是前三个八位字节 ( 192.4.2)。使用/24前缀,您可以简单地切掉最后一个八位字节并查看192.4.2.xxx部分匹配。

换个角度看,/24前缀转换为网络掩码,顾名思义,该掩码用于屏蔽正在比较的地址中的位:

>>>

>>> net.netmask IPv4Address('255.255.255.0')

您可以比较前导位以确定地址是否属于网络的一部分。如果前导位匹配,则该地址是网络的一部分:

11000000 00000100 00000010 00001100 # 192.4.2.12 # Host IP address 11000000 00000100 00000010 00000000 # 192.4.2.0 # Network address | ^ 24th bit (stop here!) |_________________________| | These bits match

上面,最后 8 位192.4.2.12被屏蔽(用0)并在比较中被忽略。再一次,Python 为ipaddress您节省了数学体操并支持惯用的成员资格测试:

>>>

>>> net = IPv4Network("192.4.2.0/24") >>> IPv4Address("192.4.2.12") in net True >>> IPv4Address("192.4.20.2") in net False

这是由运算符重载的宝藏实现的,它IPv4Network定义__contains__()了允许使用in运算符进行成员资格测试。

在 CIDR 表示法中192.4.2.0/24,该192.4.2.0部分是网络地址,用于标识网络:

>>> net.network_address IPv4Address('192.4.2.0')

正如您在上面192.4.2.0看到的,当掩码应用于主机 IP 地址时,网络地址可以看作是预期的结果:

11000000 00000100 00000010 00001100 # Host IP address 11111111 11111111 11111111 00000000 # Netmask, 255.255.255.0 or /24 11000000 00000100 00000010 00000000 # Result (compared to network address)

当您这样思考时,您可以看到/24前缀实际上是如何转换为 true 的IPv4Address:

>>>

>>> net.prefixlen 24 >>> net.netmask IPv4Address('255.255.255.0') # 11111111 11111111 11111111 00000000

事实上,如果你喜欢它,你可以IPv4Network直接从两个地址构造一个:

>>> IPv4Network("192.4.2.0/255.255.255.0") IPv4Network('192.4.2.0/24')

上面,192.4.2.0是网络地址,255.255.255.0而是网络掩码。

在网络频谱的另一端是其最终地址或广播地址,这是一个可用于与其网络上的所有主机通信的地址:

>>>

>>> net.broadcast_address IPv4Address('192.4.2.255')

关于网络掩码还有一点值得一提。您最常看到的前缀长度是 8 的倍数:

但是,0 到 32 之间的任何整数都是有效的,尽管不太常见:

>>>

>>> net = IPv4Network("100.64.0.0/10") >>> net.num_addresses 4194304 >>> net.netmask IPv4Address('255.192.0.0')

在本节中,您了解了如何构建IPv4Network实例并测试某个 IP 地址是否位于其中。在下一节中,您将学习如何遍历网络中的地址。

循环网络

本IPv4Network类支持迭代,这意味着你可以在其个人地址迭代for循环:

>>>

>>> net = IPv4Network("192.4.2.0/28") >>> for addr in net: ... print(addr) ... 192.4.2.0 192.4.2.1 192.4.2.2 ... 192.4.2.13 192.4.2.14 192.4.2.15

类似地,net.hosts()返回一个生成器,该生成器将生成上面显示的地址,不包括网络和广播地址:

>>>

>>> h = net.hosts() >>> type(h) >>> next(h) IPv4Address('192.4.2.1') >>> next(h) IPv4Address('192.4.2.2')

在下一节中,您将深入研究一个与网络密切相关的概念:子网。

子网

一个子网是一个IP网络的细分:

>>>

>>> small_net = IPv4Network("192.0.2.0/28") >>> big_net = IPv4Network("192.0.0.0/16") >>> small_net.subnet_of(big_net) True >>> big_net.supernet_of(small_net) True

上面,small_net只包含16个地址,足够你和你周围的几个小隔间了。相反,big_net包含 65,536 个地址。

实现子网划分的一种常见方法是取一个网络并将其前缀长度增加 1。让我们以维基百科中的这个例子为例:

IPv4 网络子网划分

这个例子从一个/24网络开始:

net = IPv4Network("200.100.10.0/24")

通过将前缀长度从 24 增加到 25 来划分子网涉及移动位以将网络分成更小的部分。这在数学上有点毛茸茸的。幸运的是,IPv4Network这很简单,因为.subnets()在子网上返回一个迭代器:

>>>

>>> for sn in net.subnets(): ... print(sn) ... 200.100.10.0/25 200.100.10.128/25

您还可以告诉.subnets()新前缀应该是什么。更高的前缀意味着更多和更小的子网:

>>> for sn in net.subnets(new_prefix=28): ... print(sn) ... 200.100.10.0/28 200.100.10.16/28 200.100.10.32/28 ... 200.100.10.208/28 200.100.10.224/28 200.100.10.240/28

除了地址和网络之外,ipaddress接下来您将看到该模块的第三个核心部分。

主机接口

最后但同样重要的是,Python 的ipaddress模块导出一个IPv4Interface类来表示主机接口。一个主机接口是描述在一个紧凑的形式的方式,在主机IP地址,它坐落在一个网络:

>>>

>>> from ipaddress import IPv4Interface >>> ifc = IPv4Interface("192.168.1.6/24") >>> ifc.ip # The host IP address IPv4Address('192.168.1.6') >>> ifc.network # Network in which the host IP resides IPv4Network('192.168.1.0/24')

上面的192.168.1.6/24意思是“192.168.1.6网络中的 IP 地址192.168.1.0/24”。

注意:在计算机网络的上下文中,接口也可以指网络接口,最常见的是网络接口卡 (NIC)。如果你曾经使用过的ifconfig工具(* nix中)或ipconfig(Windows)中,那么你可能知道一个名字你如eth0,en0或ens3。这两种类型的接口是不相关的。

换句话说,单独的 IP 地址并不能告诉您该地址位于哪个或哪些网络,并且网络地址是一组 IP 地址而不是单个 IP 地址。这IPv4Interface为您提供了一种通过 CIDR 表示法同时表示单个主机 IP 地址及其网络的方法。

特殊地址范围

既然您对 IP 地址和网络都有较高的了解,那么了解并非所有 IP 地址都是平等的——有些是特殊的,了解这一点也很重要。

互联网号码分配机构 (IANA) 与互联网工程任务组 (IETF) 协同监督不同地址范围的分配。IANA 的IPv4 特殊用途地址注册表是一个非常重要的表格,规定某些地址范围应具有特殊含义。

一个常见的例子是私有地址。专用 IP 地址用于网络上不需要连接到公共 Internet 的设备之间的内部通信。以下范围保留供私人使用:

一个随机选择的例子是10.243.156.214。那么,你怎么知道这个地址是私人的呢?您可以确认它在10.0.0.0/8范围内:

使用 Python 的 ipaddress 模块学习 IP 地址概念

>>> IPv4Address("10.243.156.214") in IPv4Network("10.0.0.0/8") True

第二种特殊地址类型是链路本地地址,它只能从给定的子网内访问。一个例子是Amazon Time Sync Service,它可用于链接本地 IP 上的AWS EC2实例169.254.169.123。如果您的 EC2 实例位于Virtual Private Cloud (VPC) 中,那么您不需要 Internet 连接来告诉您的实例现在是什么时间。块 169.254.0.0/16 保留用于链路本地地址:

>>>

>>> timesync_addr = IPv4Address("169.254.169.123") >>> timesync_addr.is_link_local True

在上面,您可以看到确认这10.243.156.214是一个私有地址的一种方法是测试它是否位于该10.0.0.0/8范围内。但是 Python 的ipaddress模块也提供了一组用于测试地址是否为特殊类型的属性:

>>> IPv4Address("10.243.156.214").is_private True >>> IPv4Address("127.0.0.1").is_loopback True >>> [i for i in dir(IPv4Address) if i.startswith("is_")] # "is_X" properties ['is_global', 'is_link_local', 'is_loopback', 'is_multicast', 'is_private', 'is_reserved', 'is_unspecified']

.is_private不过,需要注意的一件事是,它使用了比上表中显示的三个 IANA 范围更广泛的专用网络定义。Python 的ipaddress模块还包含为专用网络分配的其他地址:

0.0.0.0/8用于“此网络上的此主机”。

127.0.0.0/8用于环回地址。

169.254.0.0/16 用于如上所述的链路本地地址。

198.18.0.0/15用于对网络性能进行基准测试。

这不是一个详尽的列表,但它涵盖了最常见的情况。

ipaddress引擎盖下的Python模块

除了其文档化的 API 之外,模块及其类的CPython 源代码还ipaddressIPv4Address提供了一些关于如何使用称为组合的模式为您自己的代码提供惯用 API 的深刻见解。

组合的核心作用

该ipaddress模块利用了一种称为组合的面向对象模式。它的IPv4Address类是一个复合的,它包装了一个普通的 Python 整数。毕竟,IP 地址基本上是整数。

注意:公平地说,该ipaddress模块还使用了健康剂量的继承,主要是为了减少代码重复。

每个IPv4Address实例都有一个准私有._ip属性,它本身就是一个int. 该类的许多其他属性和方法都由该属性的值驱动:

>>>

>>> addr = IPv4Address("220.14.9.37") >>> addr._ip 3691907365

该._ip属性实际上是负责生产int(addr). 调用链是int(my_addr)调用my_addr.__int__(),它IPv4Address实现为my_addr._ip:

如果您向CPython开发人员询问了这一点,那么他们可能会告诉您这._ip是一个实现细节。虽然 Python 中没有真正私有的,但前导下划线表示它._ip是准私有的,不是公共ipaddressAPI 的一部分,如有更改,恕不另行通知。这就是为什么用 提取底层整数更稳定的原因int(addr)。

尽管如此,正是底层._ip赋予了IPv4Address和IPv4Network类它们的魔力。

扩展 IPv4Address

您可以._ip通过扩展IPv4 地址类来展示底层整数的强大功能:

from ipaddress import IPv4Address class MyIPv4(IPv4Address): def __and__(self, other: IPv4Address): if not isinstance(other, (int, IPv4Address)): raise NotImplementedError return self.__class__(int(self) & int(other))

添加.__and__()允许您使用二元 AND ( &) 运算符。现在您可以直接将网络掩码应用于主机 IP:

>>>

>>> addr = MyIPv4("100.127.40.32") >>> mask = MyIPv4("255.192.0.0") # A /10 prefix >>> addr & mask MyIPv4('100.64.0.0') >>> addr & 0xffc00000 # Hex literal for 255.192.0.0 MyIPv4('100.64.0.0')

上面,.__and__()允许您使用 anotherIPv4Address或 anint直接作为掩码。因为MyIPv4是 的子类IPv4Address,在这种情况下isinstance()检查将返回True。

除了运算符重载之外,您还可以添加全新的属性:

import re 2from ipaddress import IPv4Address 3 4class MyIPv4(IPv4Address): 5 @property 6 def binary_repr(self, sep=".") -> str: 7 """Represent IPv4 as 4 blocks of 8 bits.""" 8 return sep.join(f"{i:08b}" for i in self.packed) 9 10 @classmethod 11 def from_binary_repr(cls, binary_repr: str): 12 """Construct IPv4 from binary representation.""" 13 # Remove anything that's not a 0 or 1 14 i = int(re.sub(r"[^01]", "", binary_repr), 2) 15 return cls(i)

在.binary_repr(第 8 行)中, using.packed将 IP 地址转换为字节数组,然后将其格式化为其二进制形式的字符串表示形式。

在 中.from_binary_repr,int(re.sub(r"[^01]", "", binary_repr), 2)对第 14 行的调用有两个部分:

它从输入字符串中删除除 0 和 1 之外的任何内容。

它解析结果,假设基数为 2,int(, 2).

使用.binary_repr()和.from_binary_repr()允许您以str二进制表示法转换为1 和 0 的a 并从中构造:

>>> MyIPv4("220.14.9.37").binary_repr '11011100.00001110.00001001.00100101' >>> MyIPv4("255.255.0.0").binary_repr # A /16 netmask '11111111.11111111.00000000.00000000' >>> MyIPv4.from_binary_repr("11011100 00001110 00001001 00100101") MyIPv4('220.14.9.37')

这些只是展示如何利用 IP-as-integer 模式可以帮助您IPv4Address使用少量附加代码扩展功能的几种方法。

结论

在本教程中,您了解了 Python 的ipaddress模块如何允许您使用常见的 Python 构造来处理 IP 地址和网络。

以下是您可以带走的一些要点:

IP 地址基本上是一个integer,这是如何使用地址进行手动算术以及如何ipaddress使用组合设计Python 类的基础。

该ipaddress模块利用运算符重载来推断地址和网络之间的关系。

该ipaddress模块使用组合,您可以根据需要扩展该功能以添加行为。

与往常一样,如果您想深入了解,那么阅读模块源代码是一个很好的方法。

进一步阅读

以下是一些深入的资源,您可以查看这些资源以了解有关该ipaddress模块的更多信息:

Pythonipaddress模块文档

ipaddress模块简介

Python 的“ipaddress”模块概述

Python TCP/IP 网络

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Spring Cloud Consul 之Greenwich版本全攻略
下一篇:excel使用图表分析指数级变化的方法是什么
相关文章