ネットワークエンジニアのためのIPアドレス表現 for Python
Pythonとipaddress
ライブラリで、
ネットワークエンジニアがよく使うIPアドレスを表現する方法を例示します。
from ipaddress import ip_interface >>> ip = ip_interface("192.168.1.124/24") >>> ip IPv4Interface('192.168.1.124/24') >>> str(ip) '192.168.1.124/24'
はじめに
PythonでIPアドレスを取り扱う方法を調査してみました。 過去に、Rubyで調査したので、ほぼ同じことをできるか、Pythonで調査しました。
Python3.3から標準モジュールとしてipaddress
ライブラリが追加となっています。
より詳細な解説は、ライブラリのドキュメントを参照してください。
ここでは、Python3.7.4の環境で調査しました。
IPアドレスの表現方法
ipaddress
ライブラリには、IPアドレスを表現するために、3つの関数(ファクトリ関数)が定義されています。
ファクトリ関数 | 生成されるクラス | 用途 |
---|---|---|
ip_address() |
IPv4Address , IPv6Address |
IPv4/IPv6のホストアドレスを表現する。マスク情報は付加できない。 |
ip_network() |
IPv4Network, IPv6Network |
IPv4/IPv6のネットワークアドレスを表現する。IPプレフィックスとしてよく使う表現。ホストアドレスは表現できない。 |
ip_interface() |
IPv4Interface , IPv6Interface |
IPv4/IPv4のインタフェースアドレスを表現する。ルータのIPアドレスを扱うのならこっち。 |
ここでは、取り扱いやすいip_interface()
関数を主で利用します。必要に応じて、他の関数を利用してください。
ip_interface()
関数から生成されるIPv4Interface
クラスから、IPv4Address
クラスとIPv4Network
クラスが生成できます。
from ipaddress import ( ip_network, ip_address, ip_interface ) # ホストアドレス(IPv4Address) >>> ip_address("192.168.1.124") IPv4Address('192.168.1.124') # ネットワークアドレス(IPv4Network) >>> ip_network("192.168.1.0/24") IPv4Network('192.168.1.0/24') # インタフェースアドレス(IPv4Interface) >>> ip_interface("192.168.1.124/24") IPv4Interface('192.168.1.124/24') # ip_network()はネットワークアドレスを指定しないといけない # ホストアドレスを指定すると、デフォルトはエラーとなる >>> ip_network("192.168.1.124/24") ValueError: 192.168.1.124/24 has host bits set # strict=Falseを指定すると、ネットワークアドレスに変換する >>> ip_network("192.168.1.124/24", strict=False) IPv4Network('192.168.1.0/24') # IPv4Interfaceクラスから、IPv4Addressクラスを生成 # ip_interface() => ip_address()に変換 >>> ip = ip_interface("192.168.1.124/24") >>> ip.ip IPv4Address('192.168.1.124') # IPv4Interfaceクラスから、IPv4Networkクラスを生成 # ip_interface() => ip_network()に変換 >>> ip = ip_interface("192.168.1.124/24") >>> ip.network IPv4Network('192.168.1.0/24')
色々なIPアドレスの読み込み
ip_interface()
関数を前提として、IPアドレスを読み込みます。
from ipaddress import ip_interface # プレフィックス長を指定したIPアドレス >>> ip = ip_interface("192.168.1.124/24") >>> ip IPv4Interface('192.168.1.124/24') >>> str(ip) '192.168.1.124/24' # プレフィックス長を省略すると/32になる >>> ip = ip_interface("192.168.1.124") >>> ip IPv4Interface('192.168.1.124/32') >>> str(ip) '192.168.1.124/32' # サブネットマスクで指定も可能 >>> ip = ip_interface("192.168.1.124/255.255.255.0") >>> ip IPv4Interface('192.168.1.124/24') >>> str(ip) '192.168.1.124/24' # 10進数からIPアドレスに変換 >>> ip = ip_interface(0) >>> ip IPv4Interface('0.0.0.0/32') >>> str(ip) '0.0.0.0/32' # 16進数からIPアドレスに変換 >>> ip = ip_interface(0xffffffff) >>> ip IPv4Interface('255.255.255.255/32') >>> str(ip) '255.255.255.255/32'
よく使うIPアドレスの表現
from ipaddress import ip_interface # IPアドレス指定 >>> ip = ip_interface("192.168.1.124/24") >>> ip IPv4Interface('192.168.1.124/24') # プレフィックス表記(1) >>> str(ip) '192.168.1.124/24' # プレフィックス表記(2) >>> ip.with_prefixlen '192.168.1.124/24' # サブネットマスク表記 >>> ip.with_netmask '192.168.1.124/255.255.255.0' # ホストアドレス >>> ip.ip IPv4Address('192.168.1.124') >>> str(ip.ip) '192.168.1.124' # サブネットマスク >>> ip.netmask IPv4Address('255.255.255.0') >>> str(ip.netmask) '255.255.255.0' # プレフィックス長 >>> ip.network.prefixlen 24 # ネットワークアドレス >>> ip.network IPv4Network('192.168.1.0/24') >>> str(ip.network) '192.168.1.0/24' >>> ip.network.network_address IPv4Address('192.168.1.0') >>> str(ip.network.network_address) '192.168.1.0' # ホストマスク/ワイルドカードマスク >>> ip.hostmask IPv4Address('0.0.0.255') >>> str(ip.hostmask) '0.0.0.255' # ブロードキャストアドレス(1) >>> ip.network.broadcast_address IPv4Address('192.168.1.255') >>> str(ip.network.broadcast_address) '192.168.1.255' # ブロードキャストアドレス(2) >>> ip_interface("0.0.0.0/0").network.broadcast_address IPv4Address('255.255.255.255') >>> str(ip_interface("0.0.0.0/0").network.broadcast_address) '255.255.255.255'
その他の表現
from ipaddress import ip_interface # IPアドレス指定 >>> ip = ip_interface("192.168.1.124/24") >>> ip IPv4Interface('192.168.1.124/24') # 数値 >>> int(ip) 3232235900 # DNS逆引きレコード >>> ip.reverse_pointer '124/24.1.168.192.in-addr.arpa' # ホストアドレスの配列(ネットワークアドレスとブロードキャストを除く) >>> ip.network.hosts() <generator object _BaseNetwork.hosts at 0x7f87841d2430> >>> list(ip.network.hosts()) [IPv4Address('192.168.1.1'), IPv4Address('192.168.1.2'), ~略~, IPv4Address('192.168.1.254')] # 192.168.1.254~192.168.2.2までを表現 >>> ip = ip_interface("192.168.1.254/24") >>> ip + 1 IPv4Interface('192.168.1.255/32') >>> ip + 2 IPv4Interface('192.168.2.0/32') >>> for i in range(5): ... ip + i ... IPv4Interface('192.168.1.254/32') IPv4Interface('192.168.1.255/32') IPv4Interface('192.168.2.0/32') IPv4Interface('192.168.2.1/32') IPv4Interface('192.168.2.2/32')
プレフィックスの表記
プレフィックスの計算には、ip_network()
関数が便利なため、
ここでは、ip_network()
を利用します。
サマライズする関数はcollapse_addresses()
関数です。
collapse_addresses
に、IPv4Network形式のアドレスリストを渡すことで、
適時サマライズしてくれます。
プレフィックスの分割は、IPv4Network.subnets()
関数です。
現在のプレフィックス長に値を足す場合はprefixlen_diff=
パラメータを使います。
新しいプレフィックス長にする場合はnew_prefix=
パラメータを使います。
from ipaddress import ( ip_network, # ★ip_network()関数を利用 collapse_addresses, ) # 複数のプレフィックスをサマライズする >>> prefixes = map(ip_network, [ ... "192.168.0.0/25", ... "192.168.0.128/25", ... "192.168.1.0/24", ... "192.168.3.0/24", ... "192.168.4.0/24", ... "192.168.5.0/26", ... "192.168.128.0/22", ... "192.168.132.0/22", ... "192.168.128.0/21", ... ]) >>> summarized_networks = collapse_addresses(prefixes) >>> list(summarized_networks) [IPv4Network('192.168.0.0/23'), IPv4Network('192.168.3.0/24'), IPv4Network('192.168.4.0/24'), IPv4Network('192.168.5.0/26'), IPv4Network('192.168.128.0/21')] # プレフィックスを分割する(プレフィックス長を増やす) >>> prefix = ip_network("192.168.0.0/24") # プレフィックス長に+1して、プレフィックスを分割(/24 => /25) >>> list(prefix.subnets(prefixlen_diff=1)) [IPv4Network('192.168.0.0/25'), IPv4Network('192.168.0.128/25')] # プレフィックス長に+2して、プレフィックスを分割(/24 => /26) >>> list(prefix.subnets(prefixlen_diff=2)) [IPv4Network('192.168.0.0/26'), IPv4Network('192.168.0.64/26'), IPv4Network('192.168.0.128/26'), IPv4Network('192.168.0.192/26')] # 新しいプレフィックス長を27にして、プレフィックスを分割(/24 => /27) >>> list(prefix.subnets(new_prefix=27)) [IPv4Network('192.168.0.0/27'), IPv4Network('192.168.0.32/27'), IPv4Network('192.168.0.64/27'), IPv4Network('192.168.0.96/27'), IPv4Network('192.168.0.128/27'), IPv4Network('192.168.0.160/27'), IPv4Network('192.168.0.192/27'), IPv4Network('192.168.0.224/27')] # プレフィックスのスーパーネットを求める(プレフィックス長を減らす) >>> prefix = ip_network("192.168.0.0/24") # プレフィックス長を-1にする(/24 => /23) >>> prefix.supernet(prefixlen_diff=1) IPv4Network('192.168.0.0/23') # プレフィックス長を-2にする(/24 => /22) >>> prefix.supernet(prefixlen_diff=2) IPv4Network('192.168.0.0/22') # プレフィックス長を/21にする(/24 => /21) >>> prefix.supernet(new_prefix=21) IPv4Network('192.168.0.0/21') # プレフィックスから、特定のプレフィックスを除外する >>> prefix1 = ip_network("192.168.0.0/24") >>> prefix2 = ip_network("192.168.0.32/27") >>> prefix1.address_exclude(prefix2) <generator object _BaseNetwork.address_exclude at 0x00000286C6AD4BC8> >>> list(prefix1.address_exclude(prefix2)) [IPv4Network('192.168.0.128/25'), IPv4Network('192.168.0.64/26'), IPv4Network('192.168.0.0/27')]
IPアドレスの判定
from ipaddress import ip_network # プレフィックスが、サブネットの範囲内か判定(右辺に左辺が含まれるか?) >>> ip1 = ip_network("192.168.0.0/24") >>> ip2 = ip_network("192.168.0.32/27") >>> ip1.subnet_of(ip2) False >>> ip2.subnet_of(ip1) True # プレフィックスが、スパーネットの範囲内か判定(左辺に右辺が含まれるか?) >>> ip1 = ip_network("192.168.0.0/24") >>> ip2 = ip_network("192.168.0.32/27") >>> ip1.supernet_of(ip2) True >>> ip2.supernet_of(ip1) False
各クラスで使える関数一覧
各クラス(IPv4Interface、IPv4Network、IPv4Address)に定義されている変数と関数一覧です。
from ipaddress import ( ip_network, ip_address, ip_interface ) # IPv4Interface >>> ip_interface("192.168.1.124/24") IPv4Interface('192.168.1.124/24') >>> ip = ip_interface("192.168.1.124/24") >>> ip IPv4Interface('192.168.1.124/24') >>> ip. ip.compressed ip.is_global ip.is_private ip.netmask ip.version ip.exploded ip.is_link_local ip.is_reserved ip.network ip.with_hostmask ip.hostmask ip.is_loopback ip.is_unspecified ip.packed ip.with_netmask ip.ip ip.is_multicast ip.max_prefixlen ip.reverse_pointer ip.with_prefixlen # IPv4Network >>> ip = ip_network("192.168.1.124/24", strict=False) >>> ip IPv4Network('192.168.1.0/24') >>> ip. ip.address_exclude( ip.hosts( ip.is_reserved ip.overlaps( ip.supernet_of( ip.broadcast_address ip.is_global ip.is_unspecified ip.prefixlen ip.version ip.compare_networks( ip.is_link_local ip.max_prefixlen ip.reverse_pointer ip.with_hostmask ip.compressed ip.is_loopback ip.netmask ip.subnet_of( ip.with_netmask ip.exploded ip.is_multicast ip.network_address ip.subnets( ip.with_prefixlen ip.hostmask ip.is_private ip.num_addresses ip.supernet( # IPv4Address >>> ip = ip_address("192.168.1.124") >>> ip IPv4Address('192.168.1.124') >>> ip. ip.compressed ip.is_link_local ip.is_private ip.max_prefixlen ip.version ip.exploded ip.is_loopback ip.is_reserved ip.packed ip.is_global ip.is_multicast ip.is_unspecified ip.reverse_pointer
付録:シスコルータのコンフィグを生成する
CSVファイルからコンフィグのパラメータを読み取り、 Jinja2テンプレートで、コンフィグを生成します。 今回は、インタフェースの設定と、OSPFの設定用のシスコルータのコンフィグを生成します。
入力CSVファイル:ospf_interfaces.csv
CSVのカラムは、下記の表のとおりです。
カラム名 | 値 |
---|---|
interface |
インタフェース名、GigabitEthernet0/0 |
ipaddress |
インタフェースに割り当てるIPアドレス(プレフィックス長付き)、192.168.0.1/24 |
ospfarea |
インタフェースが所属するOSPFエリア番号、0 |
interface,ipaddress,ospfarea Loopback0,192.168.0.1/32,0 GigabitEthernet0/0,192.168.101.1/24,0 GigabitEthernet0/1,192.168.102.1/24,0 GigabitEthernet1/0,192.168.201.1/24,100 GigabitEthernet1/1,192.168.202.1/24,100
プログラム:ospf_template.py
import csv from ipaddress import ip_interface from jinja2 import Template # CSVからパラメータ読み込み # Excelで作成したCSVの場合、BOM付きのUTF-8で出力されるため、encoding="utf_8_sig"にする params = [] with open("ospf_interfaces.csv", "r", encoding="utf_8_sig", newline="") as f: reader = csv.DictReader(f) for row in reader: ip = ip_interface(row["ipaddress"]) print(row) param = { "ip": ip, "interface": row["interface"], "ospfarea": row["ospfarea"] } params.append(param) # コンフィグのテンプレート CONFIG_TEMPLATE = """ {%- for p in params %} interface {{ p["interface"] }} ip address {{ p["ip"].ip }} {{ p["ip"].netmask }} {% endfor %} router ospf 1 passive-interface default {%- for p in params %} no passive-interface {{ p["interface"] }} {%- endfor %} {%- for p in params %} network {{ p["ip"].network.network_address }} {{ p["ip"].hostmask }} area {{ p["ospfarea"] }} {%- endfor %} """ # テンプレートにパラメータを渡して、コンフィグに変換する template = Template(CONFIG_TEMPLATE) output = template.render({"params": params}) print("=" * 60) print(output) print("=" * 60)
実行結果
CSVファイルから各パラメータを読み取り、コンフィグが生成されました。
>python ospf_template.py OrderedDict([('interface', 'Loopback0'), ('ipaddress', '192.168.0.1/32'), ('ospfarea', '0')]) OrderedDict([('interface', 'GigabitEthernet0/0'), ('ipaddress', '192.168.101.1/24'), ('ospfarea', '0')]) OrderedDict([('interface', 'GigabitEthernet0/1'), ('ipaddress', '192.168.102.1/24'), ('ospfarea', '0')]) OrderedDict([('interface', 'GigabitEthernet1/0'), ('ipaddress', '192.168.201.1/24'), ('ospfarea', '100')]) OrderedDict([('interface', 'GigabitEthernet1/1'), ('ipaddress', '192.168.202.1/24'), ('ospfarea', '100')]) ============================================================ interface Loopback0 ip address 192.168.0.1 255.255.255.255 interface GigabitEthernet0/0 ip address 192.168.101.1 255.255.255.0 interface GigabitEthernet0/1 ip address 192.168.102.1 255.255.255.0 interface GigabitEthernet1/0 ip address 192.168.201.1 255.255.255.0 interface GigabitEthernet1/1 ip address 192.168.202.1 255.255.255.0 router ospf 1 passive-interface default no passive-interface Loopback0 no passive-interface GigabitEthernet0/0 no passive-interface GigabitEthernet0/1 no passive-interface GigabitEthernet1/0 no passive-interface GigabitEthernet1/1 network 192.168.0.1 0.0.0.0 area 0 network 192.168.101.0 0.0.0.255 area 0 network 192.168.102.0 0.0.0.255 area 0 network 192.168.201.0 0.0.0.255 area 100 network 192.168.202.0 0.0.0.255 area 100 ============================================================
おわりに
ipaddress
ライブラリは、Python3.3から標準ライブラリとなっているので、非常に使いやすいと思います。
下記の参考文献の通り、便利でした!