将cisco通配符掩码转换为Python中的IPNetworks列表

问题描述 投票:1回答:5

因此,cisco路由器allow使用所谓的通配符掩码来指定ACL。通配符掩码使用32位长整数指定一组主机,但它们与典型的网络掩码不同,因为当通配符掩码中的位为0时,它表示强制匹配,当它为1时,则它是通配符。有些人(甚至是cisco)将其称为反向掩码,事实是所有网络掩码都可以通过简单的反转转换为通配符掩码,但不是相反。因为您可以使用通配符掩码,如下所示:

0.0.255.0 0.0.255.254 0.255.0.254等。

它实际上没有相应的网络掩码

我想做的事情(并希望有人已经做过这样的事情),就是将通用通配符转换为等效CIDR块的集合。如果解决方案是在Python中(即输出生成臭名昭着的netaddr IPNetwork对象),那将是非常好的,但如果你真的只有一个有效的算法来解决这个问题,我会接受它。

python ip wildcard mask cisco
5个回答
1
投票

在Python 2.7中:

import ipaddress

mask = u"0.1.2.7"
address = u"172.18.161.2"

def wildcard_mask_test (test_octet, acl_octet, acl_wildcard_octet):
    #Test one number against acl address and mask
    #Bitwise OR of test_octet and acl_octet against the octet of the wildcard mask
    test_result = test_octet | acl_wildcard_octet
    acl_result  = acl_octet | acl_wildcard_octet
    #Return value is whether they match
    return (acl_result == test_result)

def test_octet (acl_octet, acl_wildcard_octet):
    matches = []
    #Test all possible numbers in an octet (0..255) against octet of acl and mask
    #Short circuit here for a mask value of 0 since it matches only the $acl_octet
    if (acl_wildcard_octet == 0):
        matches.append(acl_octet)
        return matches
    else:
        for test_octet in range(0,256):
            if (wildcard_mask_test(test_octet, acl_octet, acl_wildcard_octet)):
                matches.append(test_octet)
        return matches

def list_of_matches_acl (acl_address, acl_mask):
    #Pass in the variables of ACL network and wildcard mask
    #eg 10.200.128.0 0.0.0.255
    potential_matches=[]
    #Split the incoming parameters into 4 octets
    acl_address_octets = acl_address.split('.')
    for n in range(0,4):
        acl_address_octets[n] = int(acl_address_octets[n])

    acl_mask_octets = acl_mask.split('.')
    for n in range(0,4):
        acl_mask_octets[n] = int(acl_mask_octets[n])

    #Test the 1st octet
    matches_octet_1_ref = test_octet(acl_address_octets[0], acl_mask_octets[0])
    #Test the 2nd octet
    matches_octet_2_ref = test_octet(acl_address_octets[1], acl_mask_octets[1])
    #Test the 3rd octet
    matches_octet_3_ref = test_octet(acl_address_octets[2], acl_mask_octets[2])
    #Test the 4th octet
    matches_octet_4_ref = test_octet(acl_address_octets[3], acl_mask_octets[3])

    #Assemble the list of possible matches
    #Iterating over all options for each octet
    for n1 in matches_octet_1_ref:
        for n2 in matches_octet_2_ref:
            for n3 in matches_octet_3_ref:
                for n4 in matches_octet_4_ref:
                    potential_matches.append(str(n1)+'.'+str(n2)+'.'+str(n3)+'.'+str(n4))
    ip = []
    for m in potential_matches:
        ip.append(ipaddress.ip_address(unicode(m)))
    out = list(ipaddress.collapse_addresses(ip))
    return out



a = list_of_matches_acl (address, mask)
print a

0
投票

首先计算右端有多少连续1。这将决定CIDR块的大小。

每个剩余的通配符将使您拥有的CIDR块数量翻倍。


0
投票

我很确定最常见的情况是“通配符掩码正好与子网掩码相反”,而像0.0.255.0这样的例子并不典型。但是,我认为这会做你要求的。使用Python 3.3的ipaddress module

import ipaddress

mask = "0.0.255.0"
address = "172.18.161.1"

mask_int = int.from_bytes((ipaddress.IPv4Address(mask).packed), "big")
address_int = int.from_bytes(ipaddress.IPv4Address(address).packed, "big")

lower = ipaddress.IPv4Address((2 ** 32 - 1 - mask_int) & address_int)
upper = ipaddress.IPv4Address(mask_int | address_int)

subnet_range = list(ipaddress.summarize_address_range(lower, upper))

在这个例子中,我们用通配符替换172.18.161.1的第三个八位字节,即172.18.x.1,x范围从0到255.所以我们的范围是172.18.0.1172.18.255.1。这些摘要块位于subnet_range列表中:

172.18.0.1/32
172.18.0.2/31
172.18.0.4/30
172.18.0.8/29
172.18.0.16/28
172.18.0.32/27
172.18.0.64/26
172.18.0.128/25
172.18.1.0/24
172.18.2.0/23
172.18.4.0/22
172.18.8.0/21
172.18.16.0/20
172.18.32.0/19
172.18.64.0/18
172.18.128.0/18
172.18.192.0/19
172.18.224.0/20
172.18.240.0/21
172.18.248.0/22
172.18.252.0/23
172.18.254.0/24
172.18.255.0/31

0
投票

我在尝试自己解决这个问题时遇到了这个问题,并最终编写了一些新的perl代码来实现它,所以我想我会分享

它没有优化或优雅(但)它的工作原理,应该非常清楚它正在做什么。

list_of_matches('10.48.140.82' , '0.0.0.1')

收益:

10.48.140.82 10.48.140.83

list_of_matches('10.48.140.82' , '0.1.0.1')

收益:

10.48.140.82 10.48.140.83 10.49.140.82 10.49.140.83

list_of_matches('10.48.140.82' , '0.1.5.1')

收益:

10.48.136.82 10.48.136.83 10.48.137.82 10.48.137.83 10.48.140.82 10.48.140.83 10.48.141.82 10.48.141.83 10.49.136.82 10.49.136.83 10.49.137.82 10.49.137.83 10.49.140.82 10.49.140.83 10.49.141.82 10.49.141.83

码:

sub list_of_matches_acl {

    #Pass in the variables of ACL network and wildcard mask
    #eg 10.200.128.0 0.0.0.255
    my ( $acl_address, $acl_mask ) = @_;

    #The array of possible matches
    my @potential_matches;

    #Split the incoming parameters into 4 octets
    my @acl_address_octets = split /\./, $acl_address;
    my @acl_mask_octets    = split /\./, $acl_mask;

    #Test the 1st octet
    my $matches_octet_1_ref
        = test_octet( $acl_address_octets[0], $acl_mask_octets[0] );

    #Copy the referenced array into a new one
    my @one = @{$matches_octet_1_ref};

    #Test the 2nd octet
    my $matches_octet_2_ref
        = test_octet( $acl_address_octets[1], $acl_mask_octets[1] );

    #Copy the referenced array into a new one
    my @two = @{$matches_octet_2_ref};

    #Test the 3rd octet
    my $matches_octet_3_ref
        = test_octet( $acl_address_octets[2], $acl_mask_octets[2] );

    #Copy the referenced array into a new one
    my @three = @{$matches_octet_3_ref};

    #Test the 4th octet
    my $matches_octet_4_ref
        = test_octet( $acl_address_octets[3], $acl_mask_octets[3] );

    #Copy the referenced array into a new one
    my @four = @{$matches_octet_4_ref};

    #Assemble the list of possible matches
    #Iterating over all options for each octet
    foreach my $octet1 (@one) {
        foreach my $octet2 (@two) {
            foreach my $octet3 (@three) {
                foreach my $octet4 (@four) {

                    #Save this potential match to the array of matches
                    #say "$octet1.$octet2.$octet3.$octet4"
                    push( @potential_matches,
                        "$octet1.$octet2.$octet3.$octet4" );
                }
            }
        }
    }
    return \@potential_matches;
}

sub test_octet {

    #Test all possible numbers in an octet (0..255) against octet of acl and mask
    my ( $acl_octet, $acl_wildcard_octet ) = @_;

    my @matches;

    #Short circuit here for a mask value of 0 since it matches only the $acl_octet
    if ( $acl_wildcard_octet eq 0 ) {
        push @matches, $acl_octet;
    }
    else {
        for my $test_octet ( 0 .. 255 ) {
            if (wildcard_mask_test(
                    $test_octet, $acl_octet, $acl_wildcard_octet
                )
                )
            {
                #If this value is a match, save it
                push @matches, $test_octet;
            }

        }
    }

    #Return which values from 0..255 match
    return \@matches;
}

sub wildcard_mask_test {

    #Test one number against acl address and mask
    my ( $test_octet, $acl_octet, $acl_wildcard_octet ) = @_;

    #Bitwise OR of test_octet and acl_octet against the octet of the wildcard mask
    my $test_result = $test_octet | $acl_wildcard_octet;
    my $acl_result  = $acl_octet | $acl_wildcard_octet;

    #Return value is whether they match
    return ( $acl_result eq $test_result );
}

0
投票

Python 3通用实现(v4和v6),使用内置的ipaddress模块​​:

import itertools
from ipaddress import NetmaskValueError
import ipaddress


MAX_DONT_CARE_BITS = 8


def acl_with_wildcard_to_netmasks(address_str: str, wildcard_str: str):
    """
    Translates an ACL (address, wildcard) to a list of ip_network objects (address, netmask)
    :param address_str: IP address string (v4 or v6)
    :param wildcard_str: wildcard mask (v4 or v6)
    :return: list of IP networks

    E.G for address="172.18.161.2" and wildcard "0.1.2.7"
    it returns:
        [IPv4Network('172.18.161.0/29'), IPv4Network('172.18.163.0/29'),
        IPv4Network('172.19.161.0/29'), IPv4Network('172.19.163.0/29')]
    """

    ip_addr = ipaddress.ip_address(address_str)
    wildcard = ipaddress.ip_address(wildcard_str)

    if wildcard.version != ip_addr.version:
        raise ValueError(f"IP version mismtach: address_str({address_str}), wildcard_str({wildcard_str})")

    # default values for v4
    _length = ipaddress.IPV4LENGTH
    _net_cls = ipaddress.IPv4Network
    if wildcard.version == 6:
        # values for v6
        _length = ipaddress.IPV6LENGTH
        _net_cls = ipaddress.IPv6Network

    mask_bits = [int(b) for b in format(int(wildcard), F"0{_length}b")]

    # We keep count of zero bits position (left-most is 0)
    dont_care_bits_index = [i for i, e in enumerate(reversed(mask_bits)) if e == 1]

    # We count how many contiguous zeros are from left-most bit, and we will mask them with a netmask
    hostmask_length = 0
    for (pos, bit) in enumerate(dont_care_bits_index):
        if pos != bit:
            break
        hostmask_length += 1

    # We only keep the bits that can't be dealt with by a netmask and need to be expanded to cartesian product
    dont_care_to_expand_index = dont_care_bits_index[hostmask_length:]

    # reverse in order to have the final loop iterate last through high order bits
    dont_care_to_expand_index.reverse()

    if len(dont_care_to_expand_index) > MAX_DONT_CARE_BITS:
        raise NetmaskValueError(f"{wildcard_str} contains more than {MAX_DONT_CARE_BITS} non-contiguous wildcard bits")

    ip_int_original = int(ip_addr)
    subnets = []
    for bits_values in itertools.product((0,1), repeat=len(dont_care_to_expand_index)):
        # enforce the bits_values in the IP address
        ip_int = ip_int_original
        for (index, val) in zip(dont_care_to_expand_index, bits_values):
            sb_mask = 1 << index
            if val:
                ip_int |= sb_mask
            else:
                ip_int &= ~sb_mask

        subnets.append(_net_cls((ip_int, _length-hostmask_length), strict=False))

    return subnets


if __name__ == '__main__':
    # IPv4
    a4 = [ipaddress.IPv4Network('172.18.161.0/29'), ipaddress.IPv4Network('172.18.163.0/29'),
         ipaddress.IPv4Network('172.19.161.0/29'), ipaddress.IPv4Network('172.19.163.0/29')]
    b4 = acl_with_wildcard_to_netmasks('172.18.161.2', '0.1.2.7')
    print(b4)
    print(f"Lists are equal: {a4 == b4} (length {len(a4)})")

    # IPv6
    a6 = [ipaddress.IPv6Network('eed0:feff::/64'), ipaddress.IPv6Network('eed0:ffff::/64'),
          ipaddress.IPv6Network('fed0:feff::/64'), ipaddress.IPv6Network('fed0:ffff::/64')]
    b6 = acl_with_wildcard_to_netmasks('fed0:ffff::1', '1000:0100:0000:0000:ffff:ffff:ffff:ffff')
    print(b6)
    print(f"Lists are equal: {a6 == b6} (length {len(a6)})")

也许还有一个评论对这有用:一个具体的用例是将通用ACL(使用通配符语法)转换为Juniper QFX10k配置(硬件仅支持netmasks)。

© www.soinside.com 2019 - 2024. All rights reserved.