pythonでIPアドレスを扱う方法

## 概要

- pythonでIPアドレスを扱う方法を記載します

## TL;DR

- ipアドレス型は、str型やint型に変換できる
- ipアドレスの計算はint型の方が**約2倍程度**早かった


## pythonのIP-Addressモジュールでできること

- ip addressの形になっているかチェック
- str型やint型に変換
- 2つのip addressの計算
  - subnet_of
  - supernet_of
- 内包しているipの確認

## IP-Address型に変換

- ``ipaddress モジュールは簡単に IP アドレス、ネットワーク、インターフェースを生成する``

- 例

  ```console
  >>> import ipaddress
  >>> ipaddress.ip_network('192.168.0.0/24')
  IPv4Network('192.168.0.0/24')
  >>> ipaddress.ip_address('192.168.0.1')
  IPv4Address('192.168.0.1')
  ```

- エラーになる例
  - ``ip_address``に``192.168.0.1/24``を与えると「IPv4またはIPv6アドレスではない」と怒られます
    - 対策：prefix(``/24``)を削除するもしくは``/32``にする
  - ``ip_network``に``192.168.0.255/24``を与えると「192.168.0.1/24はホストビットが設定されている」と怒られます
    - 対策：第4オクテットを０にする

  ```console
  >>> ipaddress.ip_address('192.168.0.1/24')
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/ipaddress.py", line 54, in ip_address
          raise ValueError('%r does not appear to be an IPv4 or IPv6 address' %
      ValueError: '192.168.0.1/24' does not appear to be an IPv4 or IPv6 address
  >>> ipaddress.ip_network('192.168.0.255/24')
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/ipaddress.py", line 75, in ip_network
          return IPv4Network(address, strict)
        File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/ipaddress.py", line 1510, in __init__
          raise ValueError('%s has host bits set' % self)
      ValueError: 192.168.0.255/24 has host bits set
  ```

## str型やint型に変換

- 変換例
  - str型
    ```console
    >>> str(ipaddress.ip_address('192.168.0.1'))
    '192.168.0.1'
    >>> type(str(ipaddress.ip_address('192.168.0.1')))
    <class 'str'>
    >>> type(ipaddress.ip_address('192.168.0.1'))
    <class 'ipaddress.IPv4Address'>
    ```

  - int型
    ```console
    >>> int(ipaddress.ip_address('192.168.0.1'))
    3232235521
    >>> type(int(ipaddress.ip_address('192.168.0.1')))
    <class 'int'>
    >>> type(ipaddress.ip_address('192.168.0.1'))
    <class 'ipaddress.IPv4Address'>
    ```


## 2つのip addressの計算

### IPアドレス型通しの計算

#### subnet_of

- サブネットを簡単に説明すると  
  ``大きなネットワークを分割した際の、それぞれの小さいネットワーク``
- ``例のip b``が``例のip a``のサブネットだった場合にTrueとなる
  - 構文: ``b_ip.subnet_of(a_ip)``
  - b_ipがa_ipのサブネット(小さいネットワーク)であればTrueを返す

```console
>>> a_ip = ipaddress.ip_network('192.168.0.0/24')
>>> b_ip = ipaddress.ip_network('192.168.0.16/28')
>>> b_ip.subnet_of(a_ip)
True
>>> a_ip.subnet_of(b_ip)
False
```

#### supernet_of

- スーパネットを簡単に説明すると  
  ``小さなネットワークを合体させて1つの大きなネットワークにすること``
  - サブネットの逆です

```console
>>> a_ip = ipaddress.ip_network('192.168.0.0/24')
>>> b_ip = ipaddress.ip_network('192.168.0.16/28')
>>> b_ip.subnet_of(a_ip)
True
>>> a_ip.subnet_of(b_ip)
False
```

### IPアドレスをintにして計算

### 手順

1. ipアドレス型に変換し、netowork_addressを取得する
2. ``1``のipをint型に変換する
3. ipアドレス型に変換し、broadcast_addressを取得する
4. ``3``ののipをint型に変換する
5. ``2``と``4``でintに変換したipをlistに格納する
6. 比較したいipにも1-5の動作を行う
7. 2つのlistを比較しipのsubnetを確認する

```console
>>> a_int = [int(ipaddress.ip_network('192.168.0.0/24').network_address),int(ipaddress.ip_network('192.168.0.0/24').broadcast_address)]
>>> a_int
[3232235520, 3232235775]
>>> b_int = [int(ipaddress.ip_network('192.168.0.16/28').network_address),int(ipaddress.ip_network('192.168.0.16/28').broadcast_address)]
>>> b_int
[3232235536, 3232235551]
>>> c_int = [int(ipaddress.ip_network('192.168.0.0/16').network_address),int(ipaddress.ip_network('192.168.0.0/16').broadcast_address)]
>>> c_int
[3232235520, 3232301055]
>>> a_int[0] <= b_int[0] and b_int[1] <= a_int[1] # a="192.168.0.0/24" b="192.168.0.16/28"
True
>>> c_int[0] <= a_int[0] and a_int[1] <= c_int[1] # a="192.168.0.0/24" c="192.168.0.0/16"
True
>>> b_int[0] <= a_int[0] and a_int[1] <= b_int[1]
False
>>> a_int[0] <= c_int[0] and c_int[1] <= a_int[1]
False
```

#### ポイント

- int型にすると処理が早い
  - **約2.26倍で処理している!!**

  - int型にすると**8.11 µs**

    ```python
    >>> %%time
    >>> a_int[0] <= b_int[0] and b_int[1] <= a_int[1]
    CPU times: user 5 µs, sys: 1 µs, total: 6 µs
    Wall time: 8.11 µs
    ```

  - ipアドレスで実行すると**18.4µs**

    ```python
    >>> %%time
    >>> b_ip.subnet_of(a_ip)
    CPU times: user 14 µs, sys: 1e+03 ns, total: 15 µs
    Wall time: 18.4 µs
    ```

## 内包しているipの確認

- イテレーション
  ```python
  >>> for addr in ipaddress.IPv4Network('192.0.2.0/28'):
        print(addr)
  192.0.2.0
  192.0.2.1
  192.0.2.2
  192.0.2.3
  192.0.2.4
  192.0.2.5
  192.0.2.6
  192.0.2.7
  192.0.2.8
  192.0.2.9
  192.0.2.10
  192.0.2.11
  192.0.2.12
  192.0.2.13
  192.0.2.14
  192.0.2.15
  ```

## 参考

- https://docs.python.org/ja/3/library/ipaddress.html