IP地址模块简介

作者

Peter Moody

作者

Nick Coghlan

概述

本文件旨在对 ipaddress 模块。它主要针对那些不熟悉IP网络术语的用户,但对于希望了解 ipaddress 表示IP网络寻址概念。

Creating Address/Network/Interface objects

自从 ipaddress 是一个用于检查和操作IP地址的模块,您首先要做的是创建一些对象。你可以使用 ipaddress 从字符串和整数创建对象。

关于IP版本的注释

对于不太熟悉IP地址的读者来说,重要的是要知道Internet协议目前正在从协议的版本4过渡到版本6。这种转变之所以发生,主要是因为第4版的协议没有提供足够的地址来满足全世界的需求,特别是考虑到越来越多的直接连接到互联网的设备。

解释协议两个版本之间差异的细节超出了本介绍的范围,但读者至少需要知道这两个版本存在,有时需要强制使用一个版本或另一个版本。

IP主机地址

地址,通常称为“主机地址”,是处理IP地址时最基本的单元。创建地址的最简单方法是使用 ipaddress.ip_address() factory函数,根据传入值自动确定是创建IPv4地址还是IPv6地址:

>>> ipaddress.ip_address('192.0.2.1')
IPv4Address('192.0.2.1')
>>> ipaddress.ip_address('2001:DB8::1')
IPv6Address('2001:db8::1')

地址也可以直接从整数创建。将适合32位的值假定为IPv4地址:

>>> ipaddress.ip_address(3221225985)
IPv4Address('192.0.2.1')
>>> ipaddress.ip_address(42540766411282592856903984951653826561)
IPv6Address('2001:db8::1')

为了强制使用IPv4或IPv6地址,可以直接调用相关类。这对于强制为小整数创建IPv6地址特别有用:

>>> ipaddress.ip_address(1)
IPv4Address('0.0.0.1')
>>> ipaddress.IPv4Address(1)
IPv4Address('0.0.0.1')
>>> ipaddress.IPv6Address(1)
IPv6Address('::1')

定义网络

主机地址通常分组到IP网络中,因此 ipaddress 提供创建、检查和操作网络定义的方法。IP网络对象是由定义作为该网络一部分的主机地址范围的字符串构成的。该信息最简单的形式是“网络地址/网络前缀”对,其中前缀定义用于确定地址是否为网络的一部分的前导位数,网络地址定义这些位数的预期值。

对于地址,提供了一个factory函数,自动确定正确的IP版本:

>>> ipaddress.ip_network('192.0.2.0/24')
IPv4Network('192.0.2.0/24')
>>> ipaddress.ip_network('2001:db8::0/96')
IPv6Network('2001:db8::/96')

网络对象不能设置任何主机位。实际效果是 192.0.2.1/24 不描述网络。这些定义被称为接口对象,因为IP-on-a-network表示法通常用于描述给定网络上计算机的网络接口,下一节将进一步介绍。

默认情况下,尝试创建具有主机位集的网络对象将导致 ValueError 正在被引发。要请求将附加位强制为零,标志 strict=False 可以传递给构造函数::

>>> ipaddress.ip_network('192.0.2.1/24')
Traceback (most recent call last):
   ...
ValueError: 192.0.2.1/24 has host bits set
>>> ipaddress.ip_network('192.0.2.1/24', strict=False)
IPv4Network('192.0.2.0/24')

虽然字符串形式提供了更大的灵活性,但是网络也可以用整数定义,就像主机地址一样。在这种情况下,网络被认为只包含由整数标识的单个地址,因此网络前缀包括整个网络地址:

>>> ipaddress.ip_network(3221225984)
IPv4Network('192.0.2.0/32')
>>> ipaddress.ip_network(42540766411282592856903984951653826560)
IPv6Network('2001:db8::/128')

与地址一样,可以通过直接调用类构造函数而不是使用factory函数来强制创建特定类型的网络。

主机接口

如上所述,如果您需要描述特定网络上的地址,那么地址和网络类都不够。类符号 192.0.2.1/24 通常被网络工程师和为防火墙和路由器编写工具的人用作“主机”的缩写。 192.0.2.1 在网络上 192.0.2.0/24 “,因此, ipaddress 提供一组将地址与特定网络关联的混合类。用于创建的接口与用于定义网络对象的接口相同,只是地址部分不限于网络地址。

>>> ipaddress.ip_interface('192.0.2.1/24')
IPv4Interface('192.0.2.1/24')
>>> ipaddress.ip_interface('2001:db8::1/96')
IPv6Interface('2001:db8::1/96')

接受整数输入(与网络一样),直接调用相关的构造函数可以强制使用特定的IP版本。

Inspecting Address/Network/Interface Objects

你已经陷入了创建 IPv(4|6)(Address|Network|Interface) 对象,因此您可能希望获取有关它的信息。 ipaddress 试着让这一切变得简单和直观。

正在提取IP版本:

>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> addr6 = ipaddress.ip_address('2001:db8::1')
>>> addr6.version
6
>>> addr4.version
4

从接口获取网络:

>>> host4 = ipaddress.ip_interface('192.0.2.1/24')
>>> host4.network
IPv4Network('192.0.2.0/24')
>>> host6 = ipaddress.ip_interface('2001:db8::1/96')
>>> host6.network
IPv6Network('2001:db8::/96')

找出网络中有多少个单独的地址:

>>> net4 = ipaddress.ip_network('192.0.2.0/24')
>>> net4.num_addresses
256
>>> net6 = ipaddress.ip_network('2001:db8::0/96')
>>> net6.num_addresses
4294967296

在网络上迭代“可用”地址:

>>> net4 = ipaddress.ip_network('192.0.2.0/24')
>>> for x in net4.hosts():
...     print(x)  
192.0.2.1
192.0.2.2
192.0.2.3
192.0.2.4
...
192.0.2.252
192.0.2.253
192.0.2.254

获取网络掩码(即设置网络前缀对应的位)或主机掩码(不属于网络掩码的任何位):

>>> net4 = ipaddress.ip_network('192.0.2.0/24')
>>> net4.netmask
IPv4Address('255.255.255.0')
>>> net4.hostmask
IPv4Address('0.0.0.255')
>>> net6 = ipaddress.ip_network('2001:db8::0/96')
>>> net6.netmask
IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff::')
>>> net6.hostmask
IPv6Address('::ffff:ffff')

分解或压缩地址:

>>> addr6.exploded
'2001:0db8:0000:0000:0000:0000:0000:0001'
>>> addr6.compressed
'2001:db8::1'
>>> net6.exploded
'2001:0db8:0000:0000:0000:0000:0000:0000/96'
>>> net6.compressed
'2001:db8::/96'

虽然IPv4不支持爆炸或压缩,但关联对象仍提供相关属性,以便版本无关代码可以轻松确保对IPv6地址使用最简洁或最详细的形式,同时仍然正确处理IPv4地址。

作为地址列表的网络

有时将网络视为列表很有用。这意味着可以这样索引它们:

>>> net4[1]
IPv4Address('192.0.2.1')
>>> net4[-1]
IPv4Address('192.0.2.255')
>>> net6[1]
IPv6Address('2001:db8::1')
>>> net6[-1]
IPv6Address('2001:db8::ffff:ffff')

它还意味着网络对象可以使用如下的列表成员资格测试语法:

if address in network:
    # do something

安全壳测试是根据网络前缀高效完成的:

>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> addr4 in ipaddress.ip_network('192.0.2.0/24')
True
>>> addr4 in ipaddress.ip_network('192.0.3.0/24')
False

比较

ipaddress 提供了一些简单、直观的比较对象的方法,这些方法很有意义:

>>> ipaddress.ip_address('192.0.2.1') < ipaddress.ip_address('192.0.2.2')
True

A TypeError 如果尝试比较不同版本或不同类型的对象,则会引发异常。

将IP地址与其他模块一起使用

使用IP地址的其他模块(例如 socket )通常不会直接接受来自这个模块的对象。相反,必须将它们强制为另一个模块将接受的整数或字符串::

>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> str(addr4)
'192.0.2.1'
>>> int(addr4)
3221225985

实例创建失败时获取更多详细信息

使用版本无关的factory函数创建地址/网络/接口对象时,任何错误都将报告为 ValueError 带有一条一般性错误消息,该消息简单地表示传入值未被识别为该类型的对象。缺少特定错误是因为需要知道值是否为 supposed 为IPv4或IPv6,以便提供有关拒绝原因的详细信息。

为了支持有助于访问这些附加细节的用例,各个类构造函数实际上会引发 ValueError 子类 ipaddress.AddressValueErroripaddress.NetmaskValueError 以准确指示定义的哪个部分未能正确分析。

当直接使用类构造函数时,错误消息会更详细。例如::

>>> ipaddress.ip_address("192.168.0.256")
Traceback (most recent call last):
  ...
ValueError: '192.168.0.256' does not appear to be an IPv4 or IPv6 address
>>> ipaddress.IPv4Address("192.168.0.256")
Traceback (most recent call last):
  ...
ipaddress.AddressValueError: Octet 256 (> 255) not permitted in '192.168.0.256'

>>> ipaddress.ip_network("192.168.0.1/64")
Traceback (most recent call last):
  ...
ValueError: '192.168.0.1/64' does not appear to be an IPv4 or IPv6 network
>>> ipaddress.IPv4Network("192.168.0.1/64")
Traceback (most recent call last):
  ...
ipaddress.NetmaskValueError: '64' is not a valid netmask

但是,两个模块特定的异常 ValueError 作为它们的父类,因此如果您不关心特定类型的错误,您仍然可以编写如下代码:

try:
    network = ipaddress.IPv4Network(address)
except ValueError:
    print('address/netmask is invalid for IPv4:', address)