-
Notifications
You must be signed in to change notification settings - Fork 20
Switch IP matcher implementation #701
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,64 +1,67 @@ | ||
| import ipaddress | ||
|
|
||
| try: | ||
| import pytricia | ||
| from ipset_c import IPSet | ||
|
|
||
| PYTRICIA_AVAILABLE = True | ||
| IPSET_C_AVAILABLE = True | ||
| except ImportError: | ||
| PYTRICIA_AVAILABLE = False | ||
| IPSET_C_AVAILABLE = False | ||
| from aikido_zen.helpers.logging import logger | ||
|
|
||
| logger.warning( | ||
| "pytricia is not available. This happens on windows devices where pytricia is not supported yet." | ||
| "ipset_c is not available on this platform/architecture." | ||
| "Using fallback, this may result in slower performance." | ||
| "You can try to install pytricia for better performance: pip install pytricia" | ||
| "You can try to install ipset_c for better performance: pip install ipset_c" | ||
| ) | ||
|
|
||
|
|
||
| def preparse(network: str) -> str: | ||
| # Remove the brackets around IPv6 addresses if they are there. | ||
| IPV4_MAPPED_IPV6_BASE = ipaddress.ip_network("::ffff:0:0/96") | ||
|
|
||
|
|
||
| def preparse(network: str): | ||
| """ | ||
| Strips the brackets around IPv6 addresses if they are there and parses the | ||
| network into an ipaddress network object. IPv4-mapped IPv6 networks (e.g. | ||
| ::ffff:127.0.0.1) are converted to their plain IPv4 equivalent. | ||
| Returns None if the network is invalid. | ||
| """ | ||
| network = network.strip("[]") | ||
| try: | ||
| ip = ipaddress.IPv6Address(network) | ||
| if ip.ipv4_mapped: | ||
| return str(ip.ipv4_mapped) | ||
| net = ipaddress.ip_network(network, strict=False) | ||
| except ValueError: | ||
| pass | ||
| return network | ||
| return None | ||
| if net.version == 6 and net.subnet_of(IPV4_MAPPED_IPV6_BASE): | ||
| ipv4_addr = net.network_address.ipv4_mapped | ||
| return ipaddress.ip_network(f"{ipv4_addr}/{net.prefixlen - 96}", strict=False) | ||
| return net | ||
|
|
||
|
|
||
| if PYTRICIA_AVAILABLE: | ||
| if IPSET_C_AVAILABLE: | ||
|
|
||
| class IPMatcher: | ||
| def __init__(self, networks=None): | ||
| self.trie = pytricia.PyTricia(128) | ||
| v4_cidrs = [] | ||
| v6_cidrs = [] | ||
| if networks is not None: | ||
| for s in networks: | ||
| self._add(s) | ||
| # We freeze in constructor ensuring that after initialization the IPMatcher is always frozen. | ||
| self.trie.freeze() | ||
| net = preparse(s) | ||
| if net is None: | ||
| continue | ||
| (v4_cidrs if net.version == 4 else v6_cidrs).append(str(net)) | ||
| self.v4 = IPSet(v4_cidrs) | ||
| self.v6 = IPSet(v6_cidrs) | ||
|
|
||
| def has(self, network): | ||
| try: | ||
| return self.trie.get(preparse(network)) is not None | ||
| except ValueError: | ||
| net = preparse(network) | ||
| if net is None: | ||
| return False | ||
|
|
||
| def _add(self, network): | ||
| try: | ||
| self.trie[preparse(network)] = True | ||
| except ValueError: | ||
| pass | ||
| except SystemError: | ||
| # SystemError's have been known to occur in the PyTricia library (see issue #34 e.g.), | ||
| # best to play it safe and catch these errors. | ||
| pass | ||
| return self | ||
| ipset = self.v4 if net.version == 4 else self.v6 | ||
| return ipset.isContainsCidr(str(net)) | ||
|
|
||
| def is_empty(self): | ||
| return len(self.trie) == 0 | ||
| return self.v4.size == 0 and self.v6.size == 0 | ||
|
|
||
| else: | ||
| # Fallback to pure Python implementation - this happens on windows machines since pytricia is not | ||
| # fully supported there. | ||
| # Fallback to pure Python implementation - this happens when ipset_c is not | ||
| # available for the current platform/architecture. | ||
| from aikido_zen.helpers.ip_matcher_fallback import IPMatcher # noqa: F401 |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2 Open source vulnerabilities detected - high severity DetailsRemediation:
Reply |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,7 +68,7 @@ regex = [ | |
| ] | ||
| packaging = "^24.1" | ||
| wrapt = "^1.17.2" | ||
| pytricia = { version = "^1.3.0", markers = "sys_platform != 'win32'" } | ||
| ipset_c = "0.2.1" | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Locked dependency because it is not really popular and has single maintainer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium - Mandatory ipset_c dependency breaks installs on supported platforms without matching wheels This unconditional runtime dependency is only published with CPython wheels for macOS 15 ARM64, manylinux_2_39 x86_64, and Windows amd64; the lock has no wheel for the repository's Linux ARM CI target, macOS x86_64, musllinux, or Linux systems with older glibc. Poetry/pip therefore falls back to the sdist and must compile the native extension, so ordinary production images without a C toolchain fail during dependency installation before the package can import, while the in-code fallback cannot help because resolution already requires this package. This regresses the documented Windows/Linux/macOS and Python 3.8-3.14 support rather than merely reducing performance on those environments. Show fixEither publish compatible ipset_c wheels for every supported interpreter/platform (including the minimum glibc and ARM/macOS targets), or make it an optional/platform-scoped dependency and arrange installation so unsupported environments can omit it and select the pure-Python matcher without a mandatory native build. More info - Reply on this comment to give feedback or ignore the issue.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but that is better than before? So it fallbacks to manual build on some systems but before it had to manually build on all systems as pytricia does not publish any prebuilt wheel? |
||
|
|
||
| [tool.poetry.group.dev.dependencies] | ||
| black = "^24.4.2" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was actually wrong before (the test is also False in Node.js).