|
1 | 1 | import ipaddress |
2 | 2 |
|
3 | 3 | try: |
4 | | - import pytricia |
| 4 | + from ipset_c import IPSet |
5 | 5 |
|
6 | | - PYTRICIA_AVAILABLE = True |
| 6 | + IPSET_C_AVAILABLE = True |
7 | 7 | except ImportError: |
8 | | - PYTRICIA_AVAILABLE = False |
| 8 | + IPSET_C_AVAILABLE = False |
9 | 9 | from aikido_zen.helpers.logging import logger |
10 | 10 |
|
11 | 11 | logger.warning( |
12 | | - "pytricia is not available. This happens on windows devices where pytricia is not supported yet." |
| 12 | + "ipset_c is not available on this platform/architecture." |
13 | 13 | "Using fallback, this may result in slower performance." |
14 | | - "You can try to install pytricia for better performance: pip install pytricia" |
| 14 | + "You can try to install ipset_c for better performance: pip install ipset_c" |
15 | 15 | ) |
16 | 16 |
|
17 | 17 |
|
18 | | -def preparse(network: str) -> str: |
19 | | - # Remove the brackets around IPv6 addresses if they are there. |
| 18 | +IPV4_MAPPED_IPV6_BASE = ipaddress.ip_network("::ffff:0:0/96") |
| 19 | + |
| 20 | + |
| 21 | +def preparse(network: str): |
| 22 | + """ |
| 23 | + Strips the brackets around IPv6 addresses if they are there and parses the |
| 24 | + network into an ipaddress network object. IPv4-mapped IPv6 networks (e.g. |
| 25 | + ::ffff:127.0.0.1) are converted to their plain IPv4 equivalent. |
| 26 | + Returns None if the network is invalid. |
| 27 | + """ |
20 | 28 | network = network.strip("[]") |
21 | 29 | try: |
22 | | - ip = ipaddress.IPv6Address(network) |
23 | | - if ip.ipv4_mapped: |
24 | | - return str(ip.ipv4_mapped) |
| 30 | + net = ipaddress.ip_network(network, strict=False) |
25 | 31 | except ValueError: |
26 | | - pass |
27 | | - return network |
| 32 | + return None |
| 33 | + if net.version == 6 and net.subnet_of(IPV4_MAPPED_IPV6_BASE): |
| 34 | + ipv4_addr = net.network_address.ipv4_mapped |
| 35 | + return ipaddress.ip_network(f"{ipv4_addr}/{net.prefixlen - 96}", strict=False) |
| 36 | + return net |
28 | 37 |
|
29 | 38 |
|
30 | | -if PYTRICIA_AVAILABLE: |
| 39 | +if IPSET_C_AVAILABLE: |
31 | 40 |
|
32 | 41 | class IPMatcher: |
33 | 42 | def __init__(self, networks=None): |
34 | | - self.trie = pytricia.PyTricia(128) |
| 43 | + v4_cidrs = [] |
| 44 | + v6_cidrs = [] |
35 | 45 | if networks is not None: |
36 | 46 | for s in networks: |
37 | | - self._add(s) |
38 | | - # We freeze in constructor ensuring that after initialization the IPMatcher is always frozen. |
39 | | - self.trie.freeze() |
| 47 | + net = preparse(s) |
| 48 | + if net is None: |
| 49 | + continue |
| 50 | + (v4_cidrs if net.version == 4 else v6_cidrs).append(str(net)) |
| 51 | + self.v4 = IPSet(v4_cidrs) |
| 52 | + self.v6 = IPSet(v6_cidrs) |
40 | 53 |
|
41 | 54 | def has(self, network): |
42 | | - try: |
43 | | - return self.trie.get(preparse(network)) is not None |
44 | | - except ValueError: |
| 55 | + net = preparse(network) |
| 56 | + if net is None: |
45 | 57 | return False |
46 | | - |
47 | | - def _add(self, network): |
48 | | - try: |
49 | | - self.trie[preparse(network)] = True |
50 | | - except ValueError: |
51 | | - pass |
52 | | - except SystemError: |
53 | | - # SystemError's have been known to occur in the PyTricia library (see issue #34 e.g.), |
54 | | - # best to play it safe and catch these errors. |
55 | | - pass |
56 | | - return self |
| 58 | + ipset = self.v4 if net.version == 4 else self.v6 |
| 59 | + return ipset.isContainsCidr(str(net)) |
57 | 60 |
|
58 | 61 | def is_empty(self): |
59 | | - return len(self.trie) == 0 |
| 62 | + return self.v4.size == 0 and self.v6.size == 0 |
60 | 63 |
|
61 | 64 | else: |
62 | | - # Fallback to pure Python implementation - this happens on windows machines since pytricia is not |
63 | | - # fully supported there. |
| 65 | + # Fallback to pure Python implementation - this happens when ipset_c is not |
| 66 | + # available for the current platform/architecture. |
64 | 67 | from aikido_zen.helpers.ip_matcher_fallback import IPMatcher # noqa: F401 |
0 commit comments