LoginSignup
2
1

More than 5 years have passed since last update.

IPアドレスをCIDR表記にまとめる

Last updated at Posted at 2019-02-11

「IPv4 AddressとCIDRリストを可能な限りCIDRでまとめる君」

やりたいこと

ホスト表記のIPアドレス、またはCIDR表記のIPアドレスを、可能な限りCIDR表記でまとめたい。
※IPv6は対象外

動作環境

python3系であれば動作する想定。
主は3.6.4で実行

入出力

IPv4アドレスが記載されているファイルを読み込んで、可能な限りまとめたCIDR表記の一覧を別ファイルに出力する。
※動作確認は「/23」まで。「/10」とかでも動作はする想定。

入力

ip_list.txt
172.30.43.190
172.30.43.191
172.30.43.192
172.30.43.193
172.30.43.194/31
172.30.43.196/30

出力

out_file.txt
172.30.43.190/31
172.30.43.192/29

スクリプト

大まかな流れ

以下main内の処理

  1. ファイル読み込み
      ついでに空行と改行コードの削除

  2. CIDRをIPAddressのリストに変換する
      ”/”があればCIDR表記認識して処理

  3. IPをBitに変換
      0≦オクテット値≦255 じゃなければエラーで終了

  4. 重複カット
      入力に重複があっても、ホスト表記のIPアドレスとCIDR表記内のIPアドレスに重複があってもOK

  5. bitでソート

  6. IPアドレスが連続しているか判定し、2次元配列を作成

  7. 連続するIPの判定し、連続するIP群をリスト化
      bitの右から0が何個連続しているかで最大サブネットをチェック
      最大サブネットでまとめられるIP数と、IPが連続している数を比較
      小さい方でまとめる。
      連続するIPが残っていれば再帰処理

  8. ファイル書き出し
      7で貯めたリストを出力

コード

to_cidr.py
import ipaddress
import math


HOST_CIDR = False     # True :「/32を記載する」
IN_FILE = "ip_list.txt"
OUT_FILE = "out_file.txt"

# 出力用
OUT_LIST = []


# ファイル読み込み
def read_file(file):
    try:
        with open(file, "r", encoding="utf-8") as f:
            ip_list = f.readlines()
            # 改行コードの処理
            ip_list = [line.rstrip('\r\n') for line in ip_list]
            ip_list = [line.rstrip('\n') for line in ip_list]

            # 空行の削除処理
            ip_list = [line for line in ip_list if line]
        return ip_list

    except IOError:
        print("Error : {}が開けない".format(IN_FILE))

    except UnicodeDecodeError:
        print("Error : {}が読み込めない".format(IN_FILE))


# ファイル書き込み
def write_file(file):
    try:
        with open(file, "w", encoding="utf-8") as f:
            f.write('\n'.join(OUT_LIST))
    except IOError:
        print("Error : {}が開けない".format(OUT_FILE))


# IPAddressをbit文字列へ変換
def ip_to_bit(ip):

    # octetごとに分ける
    octet_1 = int(ip.split(".")[0])
    octet_2 = int(ip.split(".")[1])
    octet_3 = int(ip.split(".")[2])
    octet_4 = int(ip.split(".")[3])

    # それぞれのオクテットの値が0~255の範囲でなければError
    if 0 <= octet_1 <= 255 and \
            0 <= octet_2 <= 255 and \
            0 <= octet_3 <= 255 and \
            0 <= octet_4 <= 255:

        bit_str = format(octet_1, "08b")
        bit_str += format(octet_2, "08b")
        bit_str += format(octet_3, "08b")
        bit_str += format(octet_4, "08b")
    else:
        print("Func : ip_to_bit")
        print("Error : {}".format(ip))
        exit()

    return bit_str


# bit文字列をIPAddressへ変換
def bit_to_ip(bit_str):
    octet_1 = int(bit_str[0:8], 2)
    octet_2 = int(bit_str[8:16], 2)
    octet_3 = int(bit_str[16:24], 2)
    octet_4 = int(bit_str[24:32], 2)
    return "{}.{}.{}.{}".format(octet_1, octet_2, octet_3, octet_4)


# 連続したIPAddressをリストにする
def make_serial_list(bit_list):
    serial_list = []
    tmp_list = []

    for i in range(0, len(bit_list)-1):
        if len(tmp_list) == 0:
            tmp_list.append(bit_list[i])

        # 1足したら次の値と一致するか確認
        if int(bit_list[i], 2) + 1 == int(bit_list[i + 1], 2):
            tmp_list.append(bit_list[i + 1])
        else:
            serial_list.append(tmp_list[:])
            tmp_list.clear()
            tmp_list.append(bit_list[i+1])

    serial_list.append(tmp_list[:])
    return serial_list


# 最大でIPを何個含むマスクでまとめられるかを返す
def check_ip_num_in_subnet(bit_str):
    count = 0
    for s in bit_str[::-1]:
        if s == "0":
            count += 1
        else:
            return 2 ** count
    return 2 ** count


# num以下で最大の2のべき乗を返す
def the_largest_power_of_2_less_than_or_equal_to_num(num):
    count = 0
    while True:
        if num < 2 ** count:
            return 2 ** (count - 1)
        elif num == 2 ** count:
            return num
        else:
            count += 1


# 連続するIPAddressからCIDR表記を作成して出力準備する
def iplist_to_cidr(bit_list):

    # IPAddressが連続している数 serial_num >= 1
    serial_num = len(bit_list)

    # リスト中の一つ目のIPAddressを渡し、
    # 最大何個のIPAddressをまとめることができるかを返す max_ip_num >= 1
    max_ip_num = check_ip_num_in_subnet(bit_list[0])

    bit_str = ""

    # サブネットを切る範囲の決定
    if serial_num >= max_ip_num:
        # 連続するIPアドレスの数より、まとめることが可能なIPAddress数が小さい時
        summary_num = max_ip_num
    else:
        # 上記以外の場合、連続するIPアドレスの数の中で最大何個にまとめられるかを返す
        summary_num = the_largest_power_of_2_less_than_or_equal_to_num(serial_num)

    # 最初のbitを取り出して、残りは捨てる
    for i in range(summary_num):
        if i == 0:
            bit_str = bit_list.pop(0)
        else:
            bit_list.pop(0)

    # 出力処理 
    # /32の時、/32と表記するかどうかの判定を行う
    if int(math.log2(summary_num)) == 0 and not HOST_CIDR:
        print("{}".format(bit_to_ip(bit_str)))
        OUT_LIST.append("{}".format(bit_to_ip(bit_str)))
    else:
        print("{}/{}".format(
            bit_to_ip(bit_str), 32 - int(math.log2(summary_num))
        ))
        OUT_LIST.append("{}/{}".format(
            bit_to_ip(bit_str), 32 - int(math.log2(summary_num))
        ))

    # bitのListがなくなれば終了
    if len(bit_list) == 0:
        return
    else:
        # 再起させる
        iplist_to_cidr(bit_list)


# CIDR表記をIPAddressのリストに変換し、IPAddressの一覧を完成させる。
def cidr_to_ip(read_ip_list):
    cidr_to_ip_list = []
    try:
        for ip_or_cidr in read_ip_list:
            if "/" in ip_or_cidr:
                for ip in ipaddress.IPv4Network(ip_or_cidr):
                    cidr_to_ip_list.append(str(ip))
            # CIDR表記でないIPAddressはそのままリストに追加
            else:
                cidr_to_ip_list.append(ip_or_cidr)
    except ipaddress.AddressValueError:
        print("Func : cidr_to_ip")
        print("Error : {}".format(ip_or_cidr))

    return cidr_to_ip_list


def main():
    # ファイル読み込み
    read_ip_list = read_file(IN_FILE)

    # CIDRをIPAddressのリストに変換する
    cidr_to_ip_list = cidr_to_ip(read_ip_list)

    # IPをBitに変換
    bit_list = []
    for ip in cidr_to_ip_list:
        bit_list.append(ip_to_bit(ip))

    # 重複カット
    bit_list = list(set(bit_list[:]))

    # Bitでソート
    bit_list.sort()

    # 連続するIPの判定し、連続するIP群をリスト化
    serial_list = make_serial_list(bit_list)

    # IPのサブネットの決定と出力リスト作成
    for bit_list in serial_list:
        iplist_to_cidr(bit_list)

    # ファイル書き出し
    write_file(OUT_FILE)


if __name__ == '__main__':
    main()

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1