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