ipaddress 模組介紹
******************

作者:
   Peter Moody

作者:
   Nick Coghlan


總攬
^^^^

這份文件旨在為 "ipaddress" 模組提供一個初步介紹。文件主要針對那些不熟
悉 IP 網路術語的使用者，但對想要了解 "ipaddress" 模組如何表示 IP 網址
概念的網路工程師也可能有用。


建立 Address/Network/Interface 物件
===================================

由於 "ipaddress" 是一個用於檢查和操作 IP 位址的模組，你首先會想要做的
事情是建立一些物件。你可以使用 "ipaddress" 從字串和整數建立物件。


關於 IP 版本的說明
------------------

對於不太熟悉 IP 位址的讀者來說，重要的是要知道網際網路協定 (Internet
Protocol, IP) 目前正在從協定的第 4 版過渡到第 6 版。這個轉換主要是因為
協定的第 4 版無法提供足夠的位址來處理全世界的需求，特別是考慮到直接連
接到網際網路的裝置數量不斷增加。

解釋這兩個版本協定之間差異的細節超出了本文介紹的範圍，但讀者至少需要知
道這兩個版本的存在，而且有時候會需要強制使用其中一個版本。


IP 主機位址
-----------

位址，通常被稱為「主機位址」，是處理 IP 位址時最基本的單位。建立位址最
簡單的方法是使用 "ipaddress.ip_address()" 工廠函式，它會根據傳入的值自
動判斷要建立 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 網路物件是從定義該網路所包含之主機位址範圍
的字串建構而成。這些資訊最簡單的形式是「網路位址/網路前綴」配對，其中
前綴定義了用於比較的前導位元數量，以判斷位址是否屬於該網路的一部分，而
網路位址則定義了這些位元的預期值。

至於位址，提供了一個工廠函式可以自動判斷正確的 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')

與位址一樣，可以透過直接呼叫類別建構子而非使用工廠函式，來強制建立特定
類型的網路。


主機介面
--------

如上所述，如果你需要描述特定網路上的位址，位址類別和網路類別都不足以滿
足需求。像 "192.0.2.1/24" 這樣的標記法通常被網路工程師以及編寫防火牆和
路由器工具的人用作「網路 "192.0.2.0/24" 上的主機 "192.0.2.1"」的簡寫。
因此，"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 版本。


檢視 Address/Network/Interface 物件
===================================

你費心建立了 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:
       # 做某些事情

msgstr "可以根據網路前綴有效率地檢查是否包含： ::"

   >>> 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

如果你嘗試比較不同版本或不同類型的物件，會引發 "TypeError" 例外。


與其他模組一起使用 IP 位址
==========================

其他使用 IP 位址的模組（例如 "socket"）通常不會直接接受來自此模組的物
件。相反地，它們必須被強制轉換為其他模組能接受的整數或字串：

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


在實例建立失敗時取得更多細節
============================

當使用版本無關的工廠函式建立位址/網路/介面物件時，任何錯誤都會以
"ValueError" 回報，並附帶一個通用的錯誤訊息，簡單地說明傳入的值未被識
別為該類型的物件。缺乏具體錯誤的原因是，為了提供更多關於為何被拒絕的細
節，需要知道該值應該是 IPv4 還是 IPv6。

為了支援需要存取這些額外細節的使用情境，個別類別建構子實際上會引發
"ValueError" 的子類別 "ipaddress.AddressValueError" 和
"ipaddress.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)
