├── .clang-format ├── .clang-format-ignore ├── .cmake-format.json ├── .github └── workflows │ ├── pre-commit.yml │ └── tests.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── .update-changes.cfg ├── CHANGES ├── CMakeLists.txt ├── COPYING ├── MANIFEST.in ├── Makefile ├── README ├── README.rst ├── SubnetTree.cc ├── SubnetTree.i ├── SubnetTree.py ├── SubnetTree_wrap.cc ├── VERSION ├── configure ├── include ├── SubnetTree.h └── patricia.h ├── patricia.c ├── pyproject.toml ├── setup.py └── testing ├── .gitignore ├── Makefile ├── Scripts └── testsetup.py ├── btest.cfg └── pysubnettree ├── binary-mode-toggle.test ├── errors.test ├── get-binary-lookup.test ├── ipv4-mapped.test ├── lookup.test ├── membership.test ├── prefixes.test ├── remove.test ├── search-all.test └── set-binary-lookup.test /.clang-format: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020-2023 by the Zeek Project. See LICENSE for details. 2 | 3 | --- 4 | Language: Cpp 5 | AccessModifierOffset: -4 6 | AlignAfterOpenBracket: Align 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Right 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | AllowAllParametersOfDeclarationOnNextLine: false 13 | AllowShortBlocksOnASingleLine: false 14 | AllowShortCaseLabelsOnASingleLine: true 15 | AllowShortFunctionsOnASingleLine: true 16 | AllowShortIfStatementsOnASingleLine: false 17 | AllowShortLoopsOnASingleLine: false 18 | AlwaysBreakAfterDefinitionReturnType: None 19 | AlwaysBreakAfterReturnType: None 20 | AlwaysBreakBeforeMultilineStrings: true 21 | AlwaysBreakTemplateDeclarations: Yes 22 | BinPackArguments: true 23 | BinPackParameters: true 24 | BraceWrapping: 25 | AfterClass: false 26 | AfterControlStatement: false 27 | AfterEnum: false 28 | AfterFunction: false 29 | AfterNamespace: false 30 | AfterObjCDeclaration: false 31 | AfterStruct: false 32 | AfterUnion: false 33 | AfterExternBlock: false 34 | BeforeCatch: false 35 | BeforeElse: true 36 | IndentBraces: false 37 | SplitEmptyFunction: false 38 | SplitEmptyRecord: false 39 | SplitEmptyNamespace: false 40 | BreakBeforeBinaryOperators: None 41 | BreakBeforeBraces: Custom 42 | BreakBeforeInheritanceComma: false 43 | BreakInheritanceList: BeforeColon 44 | BreakBeforeTernaryOperators: false 45 | BreakConstructorInitializersBeforeComma: false 46 | BreakConstructorInitializers: BeforeColon 47 | BreakAfterJavaFieldAnnotations: false 48 | BreakStringLiterals: true 49 | ColumnLimit: 120 50 | CommentPragmas: 'NOLINT' 51 | CompactNamespaces: false 52 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 53 | ConstructorInitializerIndentWidth: 4 54 | ContinuationIndentWidth: 4 55 | Cpp11BracedListStyle: true 56 | DerivePointerAlignment: false 57 | DisableFormat: false 58 | ExperimentalAutoDetectBinPacking: false 59 | FixNamespaceComments: true 60 | ForEachMacros: 61 | - foreach 62 | - Q_FOREACH 63 | - BOOST_FOREACH 64 | IncludeBlocks: Regroup 65 | 66 | # Include categories go like this: 67 | # 0: reserved, since this automatically is the primary header for any .cc file 68 | # 1: zeek-config.h 69 | # 2: any c-style header 70 | # 3: any c++-style header 71 | # 4: any header that starts with "zeek/" 72 | # 5: everything else, which should catch any of the auto-generated code from the 73 | # build directory as well 74 | # 6: third party doctest header 75 | # 76 | # Sections 0-1 and 2-3 get grouped together in their respective blocks 77 | IncludeCategories: 78 | - Regex: '^"zeek-config\.h"' 79 | Priority: 1 80 | SortPriority: 1 81 | - Regex: '^"zeek/zeek-config\.h"' 82 | Priority: 1 83 | SortPriority: 2 84 | - Regex: '^<[[:print:]]+\.(h|hh)>' 85 | Priority: 2 86 | SortPriority: 2 87 | - Regex: '^<[[:print:]]+>' 88 | Priority: 2 89 | SortPriority: 3 90 | - Regex: '^"zeek/3rdparty/doctest.h' 91 | Priority: 6 92 | - Regex: '^"zeek/' 93 | Priority: 4 94 | - Regex: '.*' 95 | Priority: 5 96 | 97 | IncludeIsMainRegex: '$' 98 | IndentCaseLabels: true 99 | IndentPPDirectives: None 100 | IndentWidth: 4 101 | IndentWrappedFunctionNames: false 102 | JavaScriptQuotes: Leave 103 | JavaScriptWrapImports: true 104 | KeepEmptyLinesAtTheStartOfBlocks: false 105 | MacroBlockBegin: '^BEGIN_' 106 | MacroBlockEnd: '^END_' 107 | MaxEmptyLinesToKeep: 2 108 | NamespaceIndentation: None 109 | ObjCBinPackProtocolList: Auto 110 | ObjCBlockIndentWidth: 2 111 | ObjCSpaceAfterProperty: false 112 | ObjCSpaceBeforeProtocolList: true 113 | PenaltyBreakAssignment: 2 114 | PenaltyBreakBeforeFirstCallParameter: 500 115 | PenaltyBreakComment: 300 116 | PenaltyBreakFirstLessLess: 120 117 | PenaltyBreakString: 1000 118 | PenaltyBreakTemplateDeclaration: 10 119 | PenaltyExcessCharacter: 1000000 120 | PenaltyReturnTypeOnItsOwnLine: 1000 121 | PointerAlignment: Left 122 | ReflowComments: true 123 | SortIncludes: true 124 | SortUsingDeclarations: true 125 | SpaceAfterCStyleCast: false 126 | SpaceAfterTemplateKeyword: false 127 | SpaceAfterLogicalNot: true 128 | SpaceBeforeAssignmentOperators: true 129 | SpaceBeforeCpp11BracedList: false 130 | SpaceBeforeCtorInitializerColon: true 131 | SpaceBeforeInheritanceColon: true 132 | SpaceBeforeParens: ControlStatements 133 | SpaceBeforeRangeBasedForLoopColon: true 134 | SpaceInEmptyParentheses: false 135 | SpacesBeforeTrailingComments: 1 136 | SpacesInAngles: false 137 | SpacesInContainerLiterals: true 138 | SpacesInCStyleCastParentheses: false 139 | SpacesInParentheses: false 140 | SpacesInSquareBrackets: false 141 | SpacesInConditionalStatement: true 142 | Standard: Cpp11 143 | StatementMacros: 144 | - STANDARD_OPERATOR_1 145 | TabWidth: 4 146 | UseTab: Never 147 | --- 148 | Language: Json 149 | ... 150 | -------------------------------------------------------------------------------- /.clang-format-ignore: -------------------------------------------------------------------------------- 1 | # patricia comes from the CAIDA/cc-common project and is formatted by them 2 | patricia.c 3 | include/patricia.h 4 | 5 | # This file is generated by SWIG and shouldn't be formatted 6 | SubnetTree_wrap.cc 7 | -------------------------------------------------------------------------------- /.cmake-format.json: -------------------------------------------------------------------------------- 1 | { 2 | "parse": { 3 | "additional_commands": { 4 | "CheckIPProto": { 5 | "kwargs": { 6 | "_proto": "*" 7 | } 8 | }, 9 | "CheckType": { 10 | "kwargs": { 11 | "_type": "*", 12 | "_alt_type": "*", 13 | "_var": "*" 14 | } 15 | }, 16 | "SetPackageVersion": { 17 | "kwargs": { 18 | "_version": "*" 19 | } 20 | }, 21 | "SetPackageFileName": { 22 | "kwargs": { 23 | "_version": "*" 24 | } 25 | }, 26 | "SetPackageInstallScripts": { 27 | "kwargs": { 28 | "VERSION": "*" 29 | } 30 | }, 31 | "ConfigurePackaging": { 32 | "kwargs": { 33 | "_version": "*" 34 | } 35 | }, 36 | "SetPackageGenerators": {}, 37 | "SetPackageMetadata": {}, 38 | "FindRequiredPackage": { 39 | "kwargs": { 40 | "packageName": "*" 41 | } 42 | }, 43 | "InstallClobberImmune": { 44 | "kwargs": { 45 | "_srcfile": "*", 46 | "_dstfile": "*" 47 | } 48 | }, 49 | "InstallPackageConfigFile": { 50 | "kwargs": { 51 | "_srcfile": "*", 52 | "_dstdir": "*", 53 | "_dstfilename": "*" 54 | } 55 | }, 56 | "InstallShellScript": { 57 | "kwargs": { 58 | "_srcfile": "*", 59 | "_dstfile": "*" 60 | } 61 | }, 62 | "InstallSymLink": { 63 | "kwargs": { 64 | "_filepath": "*", 65 | "_sympath": "*" 66 | } 67 | }, 68 | "spicy_add_analyzer": { 69 | "kwargs": { 70 | "NAME": "*", 71 | "PACKAGE_NAME": "*", 72 | "SOURCES": "*", 73 | "MODULES": "*" 74 | } 75 | } 76 | } 77 | }, 78 | "format": { 79 | "line_width": 100, 80 | "tab_size": 4, 81 | "separate_ctrl_name_with_space": true, 82 | "max_subgroups_hwrap": 3, 83 | "line_ending": "unix" 84 | }, 85 | "markup": { 86 | "enable_markup": false 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [master] 7 | 8 | jobs: 9 | pre-commit: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-python@v5 14 | - uses: pre-commit/action@v3.0.1 15 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: BTest 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [master] 7 | 8 | jobs: 9 | Run-BTest: 10 | strategy: 11 | matrix: 12 | python-version: 13 | - "3.9" 14 | - "3.10" 15 | - "3.11" 16 | - "3.12" 17 | - "3.13" 18 | os: [macos-latest, ubuntu-latest] 19 | 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | - name: Set up git config 23 | # Set autocrlf mode to false so that actions/checkout doesn't 24 | # modify the line endings in all of the files (mostly in the 25 | # test Baselines) to be \r\n on Windows. 26 | run: | 27 | git config --global core.autocrlf false 28 | git config --global core.eol lf 29 | - uses: actions/checkout@v3 30 | - name: Set up Python ${{ matrix.python-version }} 31 | uses: actions/setup-python@v4 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | - name: Install dependencies 35 | run: | 36 | python -m pip install btest 37 | - name: Install pysubnettree 38 | run: | 39 | python -m pip install --verbose -e . 40 | - run: | 41 | make test BTEST=`which btest` 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pysubnettree-*.tar.gz 2 | build 3 | pysubnettree.egg-info 4 | dist 5 | .cache 6 | _SubnetTree.cpython-*.so 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cmake"] 2 | path = cmake 3 | url = https://github.com/zeek/cmake 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: trailing-whitespace 8 | exclude: (^testing/Baseline|SubnetTree.py|SubnetTree_wrap.cc) 9 | - id: end-of-file-fixer 10 | exclude: (^testing/Baseline|SubnetTree.py|SubnetTree_wrap.cc) 11 | 12 | - repo: https://github.com/astral-sh/ruff-pre-commit 13 | rev: v0.8.1 14 | hooks: 15 | - id: ruff 16 | args: [--fix] 17 | - id: ruff-format 18 | 19 | - repo: https://github.com/cheshirekow/cmake-format-precommit 20 | rev: v0.6.13 21 | hooks: 22 | - id: cmake-format 23 | 24 | - repo: https://github.com/pre-commit/mirrors-clang-format 25 | rev: 'v19.1.4' 26 | hooks: 27 | - id: clang-format 28 | types_or: 29 | - "c" 30 | - "c++" 31 | - "json" 32 | -------------------------------------------------------------------------------- /.update-changes.cfg: -------------------------------------------------------------------------------- 1 | 2 | # Automatically adapt version in files. 3 | 4 | function new_version_hook 5 | { 6 | version=$1 7 | replace_version_in_rst README $version 8 | replace_version_in_setup_py setup.py $version 9 | } 10 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 0.37-22 | 2024-12-05 14:21:33 -0700 2 | 3 | * Fix references to Python 3.5 in README/CMakeLists (Tim Wojtulewicz, Corelight) 4 | 5 | * Add github workflow to run pre-commit (Tim Wojtulewicz, Corelight) 6 | 7 | * Ignore some generated build files via gitignore (Tim Wojtulewicz, Corelight) 8 | 9 | * Add pre-commit hook for clang-format, fix all of the findings (Tim Wojtulewicz, Corelight) 10 | 11 | * Add pre-commit hook for cmake-format, fix all of the findings (Tim Wojtulewicz, Corelight) 12 | 13 | * Add pre-commit hook for ruff, fix all of the findings (Tim Wojtulewicz, Corelight) 14 | 15 | * Add pre-commit hook for ruff-format, fix all of the findings (Tim Wojtulewicz, Corelight) 16 | 17 | * Add pre-commit for trailing whitespace, fix findings (Tim Wojtulewicz, Corelight) 18 | 19 | * Update pyproject.toml for consistency with other Zeek projects (Tim Wojtulewicz, Corelight) 20 | 21 | - Reorder fields 22 | - Require python 3.9 23 | - Set the correct license 24 | 25 | * Support binary lookup mode on `search_all` (Alejandro Rodriguez) 26 | 27 | * Expose patricia_search_all on SubnetTree (Alejandro Rodriguez) 28 | 29 | 0.37-5 | 2024-05-08 12:05:05 -0700 30 | 31 | * Do not assume that binary addresses are valid unicode (Benjamin Bannier, Corelight) 32 | 33 | On lookup failure we would previously assume that binary addresses are 34 | valid unicode when constructing exception messages. With 35 | python/cpython#105375 which appeared e.g., in python-3.11.9 this starts 36 | to cause failures as invalid unicode is now more consistently rejected; e.g., in 37 | the test `pysubnettree.lookup` we construct a binary address `1.3.3.255` 38 | which corresponds to `b'\x01\x03\x03\xff'` which is not valid unicode. 39 | 40 | With this patch we set messages for `KeyError` from a `bytes` object 41 | instead of a `str`, so that user's see e.g., 42 | 43 | KeyError: b'1:3:3::3' 44 | 45 | instead of the previous 46 | 47 | KeyError: '1:3:3::3' 48 | 49 | This should still provide all the information necessary while working 50 | with our interface which allows both `str` and `bytes` inputs. 51 | 52 | The changes to `SubnetTree_wrap.cc` are generated automatically with 53 | swig-3.0.12. 54 | 55 | * CI: Drop unsupported python-3.7, add python-3.12 (Benjamin Bannier, Corelight) 56 | 57 | * Add pyproject.toml (Benjamin Bannier, Corelight) 58 | 59 | This adds a top-level `pyproject.toml` and moves the minimally required 60 | settings there from `setup.py`. 61 | 62 | * Revert update to Python 3.7 (Tim Wojtulewicz, Corelight) 63 | 64 | 0.37 | 2023-08-04 13:05:19 +0200 65 | 66 | * Release 0.37. 67 | 68 | 0.36-24 | 2023-08-04 13:01:39 +0200 69 | 70 | * Use devN style format in setup.py (Arne Welzel, Corelight) 71 | 72 | Commit aa205cfa996d166ae54ce5fdcebaf4b310301a34 switched this to .postN 73 | format, but update-changes only recognizes .devN, so switch to that in 74 | order to make automatic updating of setup.py versions work again. 75 | 76 | 0.36-23 | 2023-08-04 12:42:03 +0200 77 | 78 | * GH-15: GH-32: GH-33: SubnetTree: Update swig generated files (Arne Welzel, Corelight) 79 | 80 | 0.36-21 | 2023-08-02 11:25:16 -0700 81 | 82 | * Update cmake submodule to deprecate FindRequiredPackage (Tim Wojtulewicz, Corelight) 83 | 84 | * Require CMake 3.15 for consistency with other Zeek projects (Tim Wojtulewicz, Corelight) 85 | 86 | * Fix python variable usage in configure (Tim Wojtulewicz, Corelight) 87 | 88 | * Use find_package instead of FindRequiredPackage (Tim Wojtulewicz, Corelight) 89 | 90 | 0.36-15 | 2023-06-01 10:49:13 +0200 91 | 92 | * Use new-style version numbers for setup.py (Tim Wojtulewicz, Corelight) 93 | 94 | * Remove old travis CI configuration, add new Github workflow instead (Tim Wojtulewicz, Corelight) 95 | 96 | * Update patricia files to match Zeek's newer version (Tim Wojtulewicz, Corelight) 97 | 98 | 0.36-5 | 2022-12-15 10:57:11 -0700 99 | 100 | * Makefile: Fix .cc and .cpp confusion (Arne Welzel, Corelight) 101 | 102 | 0.36 | 2022-06-01 09:30:15 -0700 103 | 104 | * Release 0.36 105 | 106 | * Update cmake submodule to latest master (Tim Wojtulewicz, Corelight) 107 | 108 | 0.35-4 | 2021-03-12 13:09:15 -0800 109 | 110 | * GH-25: Add description of supported platforms to README (Jon Siwek, Corelight) 111 | 112 | * Add trove classifiers to setup.py (Jon Siwek, Corelight) 113 | 114 | 0.35 | 2020-12-07 15:05:57 -0800 115 | 116 | * Release 0.35. 117 | 118 | 0.34-16 | 2020-12-07 15:05:32 -0800 119 | 120 | * Update CMake logic to prefer Python 3 over Python 2 (Jon Siwek, Corelight) 121 | 122 | 0.34-13 | 2020-12-02 11:09:48 -0800 123 | 124 | * Update minimum required CMake to 3.5 (Jon Siwek, Corelight) 125 | 126 | 0.34-11 | 2020-11-26 18:03:05 +0000 127 | 128 | * Update cmake submodule for changes related to Python 2 EOL (Jon Siwek, Corelight) 129 | 130 | * Update CI: test newer Python versions and drop older versions (Jon Siwek, Corelight) 131 | 132 | * Update Python invocations to use `python3` explicitly (Jon Siwek, Corelight) 133 | 134 | * Change CMakeLists.txt to enforce Python >= 3.5 (Jon Siwek, Corelight) 135 | 136 | * Update documentation for Python 2 EOL and new Python 3 requirements (Jon Siwek, Corelight) 137 | 138 | 0.34-2 | 2020-09-22 11:03:48 -0700 139 | 140 | * Fix undefined behavior of left-shifting a negative value (Jon Siwek, Corelight) 141 | 142 | 0.34 | 2020-07-23 10:12:30 -0700 143 | 144 | * Release 0.34 145 | 146 | * Update btest.cfg for aux/ to auxil/ rename (Jon Siwek, Corelight) 147 | 148 | 0.33 | 2020-02-15 11:06:27 -0800 149 | 150 | * Release 0.33. 151 | 152 | 0.32-2 | 2020-02-15 11:06:12 -0800 153 | 154 | * GH-18: Update Makefile to use swig -I option (Jon Siwek, Corelight) 155 | 156 | 0.32 | 2020-02-05 21:29:34 -0800 157 | 158 | * Release 0.32 159 | 160 | 0.31-16 | 2020-01-13 10:07:00 -0800 161 | 162 | * Fix use of CMake VERSION_GREATER_EQUAL (Jon Siwek, Corelight) 163 | 164 | 0.31-15 | 2020-01-13 09:29:26 -0800 165 | 166 | * Set CMake policies according to CMake version (Jon Siwek, Corelight) 167 | 168 | CMake versions earlier than when the policy was introduced otherwise 169 | emit an "unknown policy" error. 170 | 171 | 0.31-14 | 2020-01-13 11:34:17 +0000 172 | 173 | * Update CMake script for new SWIG policies. (Jon Siwek, Corelight) 174 | 175 | * Fix btest.cfg's PYTHONPATH. (Jon Siwek, Corelight) 176 | 177 | * Move header files into "include/ to avoid confusion on 178 | case-insensitive file systems. (Jon Siwek, Corelight) 179 | 180 | 0.31-7 | 2019-10-28 18:08:28 -0700 181 | 182 | * Move CMake project() after cmake_minimum_required() (Jon Siwek, Corelight) 183 | 184 | 0.31 | 2019-08-01 12:05:46 -0700 185 | 186 | * Release 0.31. 187 | 188 | 0.30-10 | 2019-08-01 12:05:12 -0700 189 | 190 | * Drop use of Python 2.6 for Travis CI tests (Jon Siwek, Corelight) 191 | 192 | 0.30-9 | 2019-06-17 20:15:55 -0700 193 | 194 | * Update Travis config for bro to zeek renaming (Daniel Thayer) 195 | 196 | 0.30-7 | 2019-06-12 15:06:22 -0700 197 | 198 | * Update for a directory name change (Daniel Thayer) 199 | 200 | Also a few other unrelated bro to zeek renaming changes. 201 | 202 | 0.30 | 2019-04-12 16:36:23 -0700 203 | 204 | * Release 0.30. 205 | 206 | 0.29-3 | 2019-04-12 16:34:55 -0700 207 | 208 | * Check for twine existence in `make upload` (Jon Siwek, Corelight) 209 | 210 | * GH-14: Fix segfault in lookup operations with Python3 (Jon Siwek, Corelight) 211 | 212 | Due to decrementing a reference count too early. 213 | 214 | 0.29-1 | 2019-03-22 16:33:21 -0700 215 | 216 | * Update the `make upload` target to use twine (Jon Siwek, Corelight) 217 | 218 | 0.29 | 2019-03-22 16:16:56 -0700 219 | 220 | * Release 0.29. 221 | 222 | 0.28-11 | 2019-03-22 16:15:24 -0700 223 | 224 | * Improve parsing/handling of invalid subnet strings (Shai Bentov) 225 | 226 | * GH-12: fix segfault on invalid prefixes (Jon Siwek, Corelight) 227 | 228 | * Fix python3 compatibility of ./configure script (Jon Siwek, Corelight) 229 | 230 | 0.28-2 | 2018-12-07 16:30:21 -0600 231 | 232 | * Update github/download link (Jon Siwek, Corelight) 233 | 234 | * Update submodules to use github.com/zeek (Jon Siwek, Corelight) 235 | 236 | 0.28 | 2018-08-22 11:55:29 -0500 237 | 238 | * Release v0.28. 239 | 240 | 0.27-18 | 2018-08-06 15:59:38 -0500 241 | 242 | * Fix deprecation warning for swig_add_module (Jon Siwek, Corelight) 243 | 244 | 0.27-12 | 2018-05-15 15:37:40 +0000 245 | 246 | * Updating submodule. 247 | 248 | 0.27-9 | 2018-03-29 12:57:25 -0700 249 | 250 | * Update README with correct Python version requirement. (Daniel Thayer) 251 | 252 | * Fix some issues reported by Coverity. (Daniel Thayer) 253 | 254 | 0.27-6 | 2018-03-15 14:56:13 -0700 255 | 256 | * Configure Travis CI email recipients and build branches. (Daniel 257 | Thayer) 258 | 259 | 0.27-3 | 2018-02-05 15:06:20 -0800 260 | 261 | * Add a .travis.yml file. (Daniel Thayer) 262 | 263 | 0.27 | 2017-05-26 08:28:10 -0500 264 | 265 | * Release 0.27. 266 | 267 | 0.26-2 | 2016-12-06 12:33:17 -0800 268 | 269 | * Fix compiler warnings on OpenBSD, add missing include. (Daniel Thayer) 270 | 271 | 0.26 | 2016-10-27 14:43:48 -0700 272 | 273 | * Release 0.26. 274 | 275 | 0.25-4 | 2016-10-06 09:08:48 -0700 276 | 277 | * Fix the prefixes() function to compile and work on Python 3. 278 | (Daniel Thayer) 279 | 280 | * Cleanup tests for the prefixes() function and improved comments 281 | and error messages. (Daniel Thayer) 282 | 283 | 0.25 | 2016-08-12 13:19:45 -0700 284 | 285 | * Release 0.25. 286 | 287 | 0.24-7 | 2016-01-25 14:22:14 -0800 288 | 289 | * Added prefixes() method to return all prefixes in the tree as a 290 | set of strings, with or without length. Also supports returning 291 | IPv4 addresses in their "native" format. (James Royalty) 292 | 293 | 0.24 | 2015-05-07 20:24:57 -0700 294 | 295 | * Release 0.24. 296 | 297 | * Update dist Makefile target (Johanna Amann) 298 | 299 | 0.23-23 | 2015-03-23 10:37:20 -0500 300 | 301 | * Update try..except syntax in one example in the README. 302 | (Daniel Thayer) 303 | 304 | * BIT-1303: Reorganize tests to use btest and add more test cases. 305 | (Daniel Thayer) 306 | 307 | 0.23-19 | 2015-03-23 09:36:00 -0500 308 | 309 | * Add IPv6 example in the docs (David Salisbury) 310 | 311 | 0.23-18 | 2015-03-17 09:27:14 -0700 312 | 313 | * Python 3 compatibility fixes. (Jon Siwek) 314 | 315 | 0.23-12 | 2014-12-12 10:44:38 -0800 316 | 317 | * Use IPv6 as canonical storage format (IPv4 -> IPv4-mapped IPv6). 318 | Addresses GitHub issues #4 and maybe #5. (Jon Siwek) 319 | 320 | * Update MANIFEST.in. Include SubnetTree.i in the distribution so 321 | the swig file (SubnetTree.cc) can be regenerated with the tarball 322 | if needed. . (Scott Kitterman) 323 | 324 | 0.23 | 2014-04-03 15:53:51 -0700 325 | 326 | * Release 0.23. 327 | 328 | 0.23 | 2014-03-31 18:05:31 -0700 329 | 330 | * Updated setup.py to work with setuptools. Uploaded to PyPI. (Henry 331 | Stern/Robin Sommer) 332 | 333 | 0.22 | 2013-10-14 09:47:15 -0700 334 | 335 | * Release. 336 | 337 | 0.21 | 2013-10-14 09:46:34 -0700 338 | 339 | * Fixing version number in setup.cfg. Addresses BIT-1088. (Robin 340 | Sommer) 341 | 342 | * Remove dead code. (Jon Siwek) 343 | 344 | * Fix allocator/deallocator mismatch. (Jon Siwek) 345 | 346 | 0.20 | 2013-09-23 11:53:17 -0700 347 | 348 | * Fix an error in README and improve the examples (Daniel Thayer) 349 | 350 | * Fix a broken link in the documentation. (Daniel Thayer) 351 | 352 | 0.19-9 | 2013-03-08 09:19:54 -0800 353 | 354 | * Fix a compiler warning. (John Siwek) 355 | 356 | * s/bro-ids.org/bro.org/g. (Robin Sommer) 357 | 358 | 0.19-3 | 2012-09-29 14:10:39 -0700 359 | 360 | * Fix compile error with Python C API. Addresses #887. (Matthias 361 | Vallentin) 362 | 363 | 0.19-1 | 2012-09-24 16:11:13 -0700 364 | 365 | * Fixing memory leak. When deleting a PySubnetTree, the values 366 | weren't unref'ed. (Simon Arlott) 367 | 368 | 0.19 | 2012-08-01 13:57:31 -0500 369 | 370 | * Fix configure script to exit with non-zero status on error (Jon Siwek) 371 | 372 | 0.18 | 2012-07-05 12:33:40 -0700 373 | 374 | * Improve check for SWIG/Python version incompatibility. Addresses 375 | #843. (Jon Siwek) 376 | 377 | 0.17-16 | 2012-07-02 14:53:05 -0700 378 | 379 | * Cleanup and update of SubnetTree_wrap.cc file. (Daniel Thayer) 380 | 381 | * Fix compile warnings and dependencies of swig-generated files. (Jon Siwek) 382 | 383 | * Fix typos. (Daniel Thayer) 384 | 385 | 0.17-8 | 2012-04-09 15:36:47 -0700 386 | 387 | * pysubnettree now supports IPv6 addresses and prefixes. (Henry 388 | Stern, updated by Daniel Thayer). 389 | 390 | * SubnetTree now have a binary mode as well in in which single 391 | addresses are passed in the form of packed binary strings as, 392 | e.g., returned by `socket.inet_aton. (Henry Stern, updated by 393 | Daniel Thayer). 394 | 395 | * Raise minimum required CMake version to 2.6.3 (Jon Siwek) 396 | 397 | 398 | 0.17 | 2012-01-09 16:11:02 -0800 399 | 400 | * Submodule README conformity changes. (Jon Siwek) 401 | 402 | * Simplify finding of Python headers/libraries. Addresses #666. (Jon 403 | Siwek). 404 | 405 | 0.16-3 | 2011-11-03 15:17:21 -0700 406 | 407 | * Fixing compiler warnings. Addresses #388. (Jon Siwek) 408 | 409 | 0.16 | 2011-10-26 13:50:28 -0700 410 | 411 | * Compile SWIG bindings with no-strict-aliasing (closes #644) (Jon Siwek) 412 | 413 | 0.15 | 2011-10-25 20:15:08 -0700 414 | 415 | * New make dist/distclean targets. (Jon Siwek) 416 | 417 | * Cleaning up the distribution. (Jon Siwek and Robin Sommer) 418 | 419 | 0.14-8 | 2011-09-04 09:19:08 -0700 420 | 421 | * Install binaries with an RPATH. (Jon Siwek) 422 | 423 | * Add check for incompatible swig+python versions. Addresses #562. 424 | (Jon Siwek) 425 | 426 | * Workaround for FreeBSD CMake port missing debug flags. (Jon Siwek) 427 | 428 | 0.14 | 2011-05-05 20:59:58 -0700 429 | 430 | * CMake build setup. (Jon Siwek) 431 | 432 | * Cleanup, and converting README to REST. (Robin Sommer) 433 | 434 | * Initial import to git. (Robin Sommer) 435 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15 FATAL_ERROR) 2 | project(PySubnetTree C CXX) 3 | include(cmake/CommonCMakeConfig.cmake) 4 | 5 | # ############################################################################## 6 | # Dependency Configuration 7 | 8 | find_package(SWIG 1.3.30 REQUIRED) 9 | list(APPEND Python_ADDITIONAL_VERSIONS 3) 10 | find_package(Python 3.9 REQUIRED COMPONENTS Interpreter Development) 11 | 12 | include_directories(BEFORE ${Python_INCLUDE_DIRS}) 13 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 14 | 15 | # ############################################################################## 16 | # Build Python Extension 17 | 18 | cmake_policy(SET CMP0078 NEW) 19 | cmake_policy(SET CMP0086 NEW) 20 | 21 | include(UseSWIG) 22 | 23 | set_source_files_properties(SubnetTree.i PROPERTIES CPLUSPLUS true) 24 | set(SWIG_MODULE_SubnetTree_EXTRA_DEPS include/SubnetTree.h) 25 | 26 | swig_add_library(SubnetTree LANGUAGE python SOURCES SubnetTree.i SubnetTree.cc patricia.c) 27 | 28 | swig_link_libraries(SubnetTree ${Python_LIBRARIES}) 29 | set_source_files_properties(${swig_generated_file_fullname} SubnetTree.cc 30 | PROPERTIES COMPILE_FLAGS -fno-strict-aliasing) 31 | 32 | # ############################################################################## 33 | # Install Files 34 | 35 | if (NOT PY_MOD_INSTALL_DIR) 36 | # the configure wrapper was not used, default to "home" style installation 37 | set(PY_MOD_INSTALL_DIR lib/python) 38 | endif () 39 | 40 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/SubnetTree.py DESTINATION ${PY_MOD_INSTALL_DIR}) 41 | 42 | install(TARGETS SubnetTree DESTINATION ${PY_MOD_INSTALL_DIR}) 43 | 44 | # ############################################################################## 45 | # Build Summary 46 | 47 | if (CMAKE_BUILD_TYPE) 48 | string(TOUPPER ${CMAKE_BUILD_TYPE} BuildType) 49 | endif () 50 | 51 | message( 52 | "\n===============| PySubnetTree Build Summary |=================" 53 | "\n" 54 | "\nInstall dir: ${PY_MOD_INSTALL_DIR}" 55 | "\nDebug mode: ${ENABLE_DEBUG}" 56 | "\n" 57 | "\nCC: ${CMAKE_C_COMPILER}" 58 | "\nCFLAGS: ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${BuildType}}" 59 | "\nCXX: ${CMAKE_CXX_COMPILER}" 60 | "\nCXXFLAGS: ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BuildType}}" 61 | "\nCPP: ${CMAKE_CXX_COMPILER}" 62 | "\n" 63 | "\n================================================================\n") 64 | 65 | include(UserChangedWarning) 66 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 1995-2013, The Regents of the University of California 2 | through the Lawrence Berkeley National Laboratory and the 3 | International Computer Science Institute. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | (1) Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | (2) Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | (3) Neither the name of the University of California, Lawrence Berkeley 16 | National Laboratory, U.S. Dept. of Energy, International Computer 17 | Science Institute, nor the names of contributors may be used to endorse 18 | or promote products derived from this software without specific prior 19 | written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 25 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | POSSIBILITY OF SUCH DAMAGE. 32 | 33 | Note that some files in the distribution may carry their own copyright 34 | notices. 35 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING 2 | include CHANGES 3 | include Makefile 4 | include VERSION 5 | include include/patricia.h 6 | include include/SubnetTree.h 7 | include SubnetTree.i 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile not needed to build module. Use "python3 setup.py install" instead. 2 | # 3 | # This Makefile generates the SWIG wrappers and the documentation. 4 | 5 | CLEAN=build SubnetTree_wrap.cc SubnetTree.py README.html *.pyc 6 | 7 | VERSION=`test -e VERSION && cat VERSION || cat ../VERSION` 8 | BUILD=build 9 | TGZ=pysubnettree-$(VERSION) 10 | 11 | all: SubnetTree_wrap.cc 12 | 13 | SubnetTree_wrap.cc SubnetTree.py: SubnetTree.i include/SubnetTree.h 14 | swig -c++ -python -Iinclude -o SubnetTree_wrap.cc SubnetTree.i 15 | 16 | clean: 17 | rm -rf $(CLEAN) 18 | 19 | .PHONY: dist 20 | dist: 21 | @python3 setup.py sdist 22 | @printf "Package: "; echo dist/*.tar.gz 23 | 24 | distclean: 25 | rm -rf pysubnettree.egg-info 26 | rm -rf dist 27 | rm -rf build 28 | 29 | .PHONY: upload 30 | upload: twine-check dist 31 | twine upload -u zeek dist/pysubnettree-$(VERSION).tar.gz 32 | 33 | .PHONY: twine-check 34 | twine-check: 35 | @type twine > /dev/null 2>&1 || \ 36 | { \ 37 | echo "Uploading to PyPi requires 'twine' and it's not found in PATH."; \ 38 | echo "Install it and/or make sure it is in PATH."; \ 39 | echo "E.g. you could use the following command to install it:"; \ 40 | echo "\tpip3 install twine"; \ 41 | echo ; \ 42 | exit 1; \ 43 | } 44 | 45 | .PHONY : test 46 | test: 47 | @make -C testing 48 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | .. -*- mode: rst-mode -*- 2 | .. 3 | .. Version number is filled in automatically. 4 | .. |version| replace:: 0.37-22 5 | 6 | =============================================== 7 | PySubnetTree - A Python Module for CIDR Lookups 8 | =============================================== 9 | 10 | .. rst-class:: opening 11 | 12 | The PySubnetTree package provides a Python data structure 13 | ``SubnetTree`` which maps subnets given in `CIDR 14 | `_ notation (incl. 15 | corresponding IPv6 versions) to Python objects. Lookups are 16 | performed by longest-prefix matching. 17 | 18 | PySubnetTree should generally work on Unix-like platforms such as Linux, 19 | macOS, and FreeBSD, but does not support Windows. 20 | 21 | Download 22 | -------- 23 | 24 | You can find the latest PySubnetTree release for download at 25 | https://www.zeek.org/download. 26 | 27 | PySubnetTree's git repository is located at 28 | https://github.com/zeek/pysubnettree 29 | 30 | This document describes PySubnetTree |version|. See the ``CHANGES`` 31 | file for version history. 32 | 33 | 34 | Example 35 | ------- 36 | 37 | A simple example which associates CIDR prefixes with strings:: 38 | 39 | >>> import SubnetTree 40 | >>> t = SubnetTree.SubnetTree() 41 | >>> t["10.1.0.0/16"] = "Network 1" 42 | >>> t["10.1.42.0/24"] = "Network 1, Subnet 42" 43 | >>> print("10.1.42.1" in t) 44 | True 45 | >>> print(t["10.1.42.1"]) 46 | Network 1, Subnet 42 47 | >>> print(t["10.1.43.1"]) 48 | Network 1 49 | >>> print("10.20.1.1" in t) 50 | False 51 | >>> t.search_all("10.1.42.1") 52 | ['Network 1, Subnet 42', 'Network 1'] 53 | >>> try: 54 | ... print(t["10.20.1.1"]) 55 | ... except KeyError as err: 56 | ... print("Error: %s not found" % err) 57 | Error: '10.20.1.1' not found 58 | 59 | 60 | PySubnetTree also supports IPv6 addresses and prefixes:: 61 | 62 | >>> import SubnetTree 63 | >>> t = SubnetTree.SubnetTree() 64 | >>> t["2001:db8::/32"] = "Company 1" 65 | >>> t["2001:db8:4000::/48"] = "Company 1, Site 1" 66 | >>> t["2001:db8:4000:abcd::"] 67 | Company 1, Site 1 68 | >>> t["2001:db8:fe:1234::"] 69 | Company 1 70 | >>> t.search_all("2001:db8:4000:abcd::1") 71 | ['Company 1, Site 1', 'Company 1'] 72 | 73 | 74 | By default, CIDR prefixes and IP addresses are given as strings. 75 | Alternatively, a ``SubnetTree`` object can be switched into *binary 76 | mode*, in which single addresses are passed in the form of packed 77 | binary strings as, e.g., returned by `socket.inet_aton 78 | `_:: 79 | 80 | 81 | >>> t.get_binary_lookup_mode() 82 | False 83 | >>> t.set_binary_lookup_mode(True) 84 | >>> t.get_binary_lookup_mode() 85 | True 86 | >>> import socket 87 | >>> print(t[socket.inet_aton("10.1.42.1")]) 88 | Network 1, Subnet 42 89 | 90 | A SubnetTree also provides methods ``insert(prefix,object=None)`` for insertion 91 | of prefixes (``object`` can be skipped to use the tree like a set), and 92 | ``remove(prefix)`` for removing entries (``remove`` performs an _exact_ match 93 | rather than longest-prefix). 94 | 95 | Internally, the CIDR prefixes of a ``SubnetTree`` are managed by a 96 | Patricia tree data structure and lookups are therefore efficient 97 | even with a large number of prefixes. 98 | 99 | PySubnetTree comes with a BSD license. 100 | 101 | 102 | Prerequisites 103 | ------------- 104 | 105 | This package requires Python 3.9 or newer. 106 | 107 | Installation 108 | ------------ 109 | 110 | Installation is pretty simple:: 111 | 112 | > python3 setup.py install 113 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | README -------------------------------------------------------------------------------- /SubnetTree.cc: -------------------------------------------------------------------------------- 1 | #include "SubnetTree.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static PyObject* dummy = Py_BuildValue("s", ""); 11 | 12 | const uint8_t v4_mapped_prefix[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}; 13 | 14 | inline static prefix_t* make_prefix() { 15 | prefix_t* rval = (prefix_t*)malloc(sizeof(prefix_t)); 16 | rval->ref_count = 1; 17 | return rval; 18 | } 19 | 20 | inline static bool set_prefix(prefix_t* subnet, int family, inx_addr* addr, unsigned int width) { 21 | if ( ! (family == AF_INET || family == AF_INET6) ) 22 | return false; 23 | 24 | if ( family == AF_INET && width > 32 ) 25 | return false; 26 | 27 | if ( family == AF_INET6 && width > 128 ) 28 | return false; 29 | 30 | if ( family == AF_INET ) { 31 | memcpy(&subnet->add.sin6, v4_mapped_prefix, sizeof(v4_mapped_prefix)); 32 | memcpy(&subnet->add.sin6.s6_addr[12], addr, sizeof(in_addr)); 33 | } 34 | 35 | else if ( family == AF_INET6 ) 36 | memcpy(&subnet->add.sin6, addr, sizeof(subnet->add.sin6)); 37 | 38 | subnet->family = AF_INET6; 39 | subnet->bitlen = (family == AF_INET ? width + 96 : width); 40 | 41 | return true; 42 | } 43 | 44 | inline static bool parse_cidr(const char* cidr, int* family, inx_addr* subnet, unsigned short* mask) { 45 | char buffer[40]; 46 | const char* addr_str = 0; 47 | const char* mask_str = 0; 48 | char* endptr; 49 | 50 | if ( ! cidr ) 51 | return false; 52 | 53 | const char* slash = strchr(cidr, '/'); 54 | 55 | if ( slash ) { 56 | int len = slash - cidr < 40 ? slash - cidr : 39; 57 | memcpy(buffer, cidr, len); 58 | buffer[len] = '\0'; 59 | addr_str = buffer; 60 | mask_str = slash + 1; 61 | } 62 | else { 63 | addr_str = cidr; 64 | mask_str = 0; 65 | } 66 | 67 | *family = AF_INET; 68 | 69 | if ( inet_pton(*family, addr_str, subnet) != 1 ) { 70 | *family = AF_INET6; 71 | 72 | if ( inet_pton(*family, addr_str, subnet) != 1 ) 73 | return false; 74 | } 75 | 76 | if ( mask_str ) { 77 | errno = 0; 78 | *mask = strtol(mask_str, &endptr, 10); 79 | 80 | if ( endptr == mask_str || errno != 0 ) 81 | return false; 82 | 83 | if ( *family == AF_INET && *mask > 32 ) 84 | return false; 85 | else if ( *mask > 128 ) 86 | return false; 87 | } 88 | else { 89 | if ( *family == AF_INET ) 90 | *mask = 32; 91 | else 92 | *mask = 128; 93 | } 94 | 95 | return true; 96 | } 97 | 98 | void SubnetTree::PatriciaDeleteFunction(void* data) { Py_DECREF(static_cast(data)); } 99 | 100 | SubnetTree::SubnetTree(bool arg_binary_lookup_mode) { 101 | tree = New_Patricia(128); 102 | binary_lookup_mode = arg_binary_lookup_mode; 103 | } 104 | 105 | SubnetTree::~SubnetTree() { Destroy_Patricia(tree, SubnetTree::PatriciaDeleteFunction); } 106 | 107 | PyObject* SubnetTree::insert(const char* cidr, PyObject* data) { 108 | int family; 109 | inx_addr subnet; 110 | unsigned short mask; 111 | 112 | if ( ! parse_cidr(cidr, &family, &subnet, &mask) ) { 113 | PyErr_SetString(PyExc_ValueError, "Invalid CIDR."); 114 | return 0; 115 | } 116 | 117 | return insert(family, subnet, mask, data); 118 | } 119 | 120 | PyObject* SubnetTree::insert(unsigned long subnet, unsigned short mask, PyObject* data) { 121 | inx_addr subnet_addr; 122 | memcpy(&subnet_addr, &subnet, sizeof(subnet)); 123 | 124 | return insert(AF_INET, subnet_addr, mask, data); 125 | } 126 | 127 | PyObject* SubnetTree::insert(int family, inx_addr subnet, unsigned short mask, PyObject* data) { 128 | prefix_t* sn = make_prefix(); 129 | 130 | if ( ! sn ) { 131 | PyErr_SetString(PyExc_MemoryError, "out of memory"); 132 | return 0; 133 | } 134 | 135 | bool res = set_prefix(sn, family, &subnet, mask); 136 | 137 | if ( ! res ) { 138 | Deref_Prefix(sn); 139 | PyErr_SetString(PyExc_RuntimeError, "invalid subnet/prefix"); 140 | return 0; 141 | } 142 | 143 | patricia_node_t* node = patricia_lookup(tree, sn); 144 | Deref_Prefix(sn); 145 | 146 | if ( ! node ) { 147 | PyErr_SetString(PyExc_RuntimeError, "patricia_lookup failed."); 148 | return 0; 149 | } 150 | 151 | if ( ! data ) 152 | data = dummy; 153 | 154 | Py_INCREF(data); 155 | node->data = data; 156 | 157 | Py_RETURN_TRUE; 158 | } 159 | 160 | PyObject* SubnetTree::remove(const char* cidr) { 161 | int family; 162 | inx_addr subnet; 163 | unsigned short mask; 164 | 165 | if ( ! parse_cidr(cidr, &family, &subnet, &mask) ) { 166 | PyErr_SetString(PyExc_ValueError, "Invalid CIDR."); 167 | return 0; 168 | } 169 | 170 | return remove(family, subnet, mask); 171 | } 172 | 173 | PyObject* SubnetTree::remove(unsigned long addr, unsigned short mask) { 174 | inx_addr subnet_addr; 175 | memcpy(&subnet_addr, &addr, sizeof(addr)); 176 | 177 | return remove(AF_INET, subnet_addr, mask); 178 | } 179 | 180 | PyObject* SubnetTree::remove(int family, inx_addr addr, unsigned short mask) { 181 | prefix_t* subnet = make_prefix(); 182 | 183 | if ( ! subnet ) { 184 | PyErr_SetString(PyExc_MemoryError, "out of memory"); 185 | return 0; 186 | } 187 | 188 | bool res = set_prefix(subnet, family, &addr, mask); 189 | 190 | if ( ! res ) { 191 | Deref_Prefix(subnet); 192 | PyErr_SetString(PyExc_RuntimeError, "invalid subnet/prefix"); 193 | return 0; 194 | } 195 | 196 | patricia_node_t* node = patricia_search_exact(tree, subnet); 197 | Deref_Prefix(subnet); 198 | 199 | if ( ! node ) { 200 | PyErr_SetString(PyExc_RuntimeError, "patricia_lookup failed."); 201 | return 0; 202 | } 203 | 204 | PyObject* data = (PyObject*)node->data; 205 | Py_DECREF(data); 206 | 207 | patricia_remove(tree, node); 208 | 209 | if ( data != dummy ) 210 | Py_RETURN_TRUE; 211 | else 212 | Py_RETURN_FALSE; 213 | } 214 | 215 | PyObject* SubnetTree::lookup(const char* cidr, int size) const { 216 | int family; 217 | inx_addr subnet; 218 | unsigned short mask; 219 | 220 | if ( binary_lookup_mode ) { 221 | if ( size == 4 ) 222 | family = AF_INET; 223 | 224 | else if ( size == 16 ) 225 | family = AF_INET6; 226 | 227 | else { 228 | PyErr_SetString(PyExc_ValueError, "Invalid binary address. Binary addresses are 4 or 16 bytes."); 229 | return 0; 230 | } 231 | 232 | memcpy(&subnet, cidr, size); 233 | return lookup(family, subnet); 234 | } 235 | 236 | else { 237 | if ( ! parse_cidr(cidr, &family, &subnet, &mask) ) { 238 | return 0; 239 | } 240 | 241 | return lookup(family, subnet); 242 | } 243 | } 244 | 245 | PyObject* SubnetTree::lookup(unsigned long addr) const { 246 | inx_addr addr_addr; 247 | memcpy(&addr_addr, &addr, sizeof(addr)); 248 | 249 | return lookup(AF_INET, addr_addr); 250 | } 251 | 252 | PyObject* SubnetTree::lookup(int family, inx_addr addr) const { 253 | prefix_t* subnet = make_prefix(); 254 | 255 | if ( ! subnet ) { 256 | PyErr_SetString(PyExc_RuntimeError, "invalid subnet/prefix"); 257 | return 0; 258 | } 259 | 260 | int mask = family == AF_INET ? 32 : 128; 261 | bool res = set_prefix(subnet, family, &addr, mask); 262 | 263 | if ( ! res ) { 264 | Deref_Prefix(subnet); 265 | PyErr_SetString(PyExc_MemoryError, "out of memory"); 266 | return 0; 267 | } 268 | 269 | patricia_node_t* node = patricia_search_best(tree, subnet); 270 | Deref_Prefix(subnet); 271 | 272 | if ( ! node ) 273 | return 0; 274 | 275 | PyObject* data = (PyObject*)node->data; 276 | Py_INCREF(data); 277 | 278 | return data; 279 | } 280 | 281 | PyObject* SubnetTree::search_all(const char* cidr, int size) const { 282 | int family; 283 | inx_addr subnet; 284 | unsigned short mask; 285 | 286 | if ( binary_lookup_mode ) { 287 | if ( size == 4 ) { 288 | family = AF_INET; 289 | mask = 32; 290 | } 291 | 292 | else if ( size == 16 ) { 293 | family = AF_INET6; 294 | mask = 128; 295 | } 296 | 297 | else { 298 | PyErr_SetString(PyExc_ValueError, "Invalid binary address. Binary addresses are 4 or 16 bytes."); 299 | return 0; 300 | } 301 | 302 | memcpy(&subnet, cidr, size); 303 | } 304 | 305 | else if ( ! parse_cidr(cidr, &family, &subnet, &mask) ) { 306 | PyErr_SetString(PyExc_ValueError, "Invalid CIDR."); 307 | return 0; 308 | } 309 | 310 | prefix_t* sn = make_prefix(); 311 | 312 | if ( ! sn ) { 313 | PyErr_SetString(PyExc_RuntimeError, "out of memory"); 314 | return 0; 315 | } 316 | 317 | bool res = set_prefix(sn, family, &subnet, mask); 318 | 319 | if ( ! res ) { 320 | Deref_Prefix(sn); 321 | PyErr_SetString(PyExc_RuntimeError, "invalid subnet/prefix"); 322 | return 0; 323 | } 324 | 325 | patricia_node_t** outlist = nullptr; 326 | int n; 327 | patricia_search_all(tree, sn, &outlist, &n); 328 | Deref_Prefix(sn); 329 | 330 | PyObject* list = PyList_New(n); 331 | for ( int i = 0; i < n; i++ ) { 332 | PyObject* data = (PyObject*)outlist[i]->data; 333 | Py_INCREF(data); 334 | PyList_SetItem(list, i, data); 335 | } 336 | 337 | free(outlist); 338 | 339 | return list; 340 | } 341 | 342 | PyObject* SubnetTree::prefixes(bool ipv4_native /*=false*/, bool with_len /*=true*/) const { 343 | char buf[INET6_ADDRSTRLEN]; 344 | char buffer[64]; 345 | bool wrote_buffer; 346 | PyObject* set = PySet_New(NULL); 347 | 348 | patricia_node_t* node; 349 | 350 | PATRICIA_WALK(tree->head, node) { 351 | prefix_t* pf = node->prefix; 352 | PyObject* pstr = NULL; 353 | 354 | wrote_buffer = false; 355 | 356 | if ( ipv4_native ) { 357 | // IPv4 addresses are stored mapped into the IPv6 space. (Xref: 358 | // https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses) 359 | // We'll check the first 12 bytes (96 bits) of the stored address 360 | // to see if they match v4_mapped_prefix. 361 | uint8_t* addrstart = (uint8_t*)&pf->add.sin6; 362 | 363 | if ( memcmp(&v4_mapped_prefix, addrstart, 12) == 0 ) { 364 | // Skip over the mapped-prefix to the IPV4 addr part. And we 365 | // need to correct the bitlen to make it valid for IPv4 (by 366 | // subtracting the 96 mapped-prefix bits). 367 | addrstart += 12; 368 | 369 | if ( with_len ) { 370 | snprintf(buffer, sizeof buffer, "%d.%d.%d.%d/%d", addrstart[0], addrstart[1], addrstart[2], 371 | addrstart[3], pf->bitlen - 96); 372 | } 373 | 374 | else { 375 | snprintf(buffer, sizeof buffer, "%d.%d.%d.%d", addrstart[0], addrstart[1], addrstart[2], 376 | addrstart[3]); 377 | } 378 | 379 | wrote_buffer = true; 380 | } 381 | } 382 | 383 | if ( ! wrote_buffer ) { 384 | // Format as IPv6 address. 385 | 386 | const char* addrstr = inet_ntop(AF_INET6, &pf->add.sin6, buf, INET6_ADDRSTRLEN); 387 | 388 | if ( ! addrstr ) { 389 | PyErr_SetString(PyExc_ValueError, "Unable to string-ify IPv6 address."); 390 | return NULL; 391 | } 392 | 393 | if ( with_len ) 394 | snprintf(buffer, sizeof buffer, "%s/%d", addrstr, pf->bitlen); 395 | else 396 | snprintf(buffer, sizeof buffer, "%s", addrstr); 397 | } 398 | 399 | #if PY_MAJOR_VERSION >= 3 400 | pstr = PyUnicode_FromString(buffer); 401 | #else 402 | pstr = PyString_FromString(buffer); 403 | #endif 404 | 405 | PySet_Add(set, pstr); 406 | Py_DECREF(pstr); 407 | } 408 | PATRICIA_WALK_END; 409 | 410 | return set; 411 | } 412 | 413 | bool SubnetTree::get_binary_lookup_mode() { return binary_lookup_mode; } 414 | 415 | void SubnetTree::set_binary_lookup_mode(bool arg_binary_lookup_mode) { binary_lookup_mode = arg_binary_lookup_mode; } 416 | -------------------------------------------------------------------------------- /SubnetTree.i: -------------------------------------------------------------------------------- 1 | 2 | %module SubnetTree 3 | %{ 4 | #include "SubnetTree.h" 5 | %} 6 | 7 | %include "SubnetTree.h" 8 | -------------------------------------------------------------------------------- /SubnetTree.py: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by SWIG (http://www.swig.org). 2 | # Version 3.0.12 3 | # 4 | # Do not make changes to this file unless you know what you are doing--modify 5 | # the SWIG interface file instead. 6 | 7 | from sys import version_info as _swig_python_version_info 8 | if _swig_python_version_info >= (2, 7, 0): 9 | def swig_import_helper(): 10 | import importlib 11 | pkg = __name__.rpartition('.')[0] 12 | mname = '.'.join((pkg, '_SubnetTree')).lstrip('.') 13 | try: 14 | return importlib.import_module(mname) 15 | except ImportError: 16 | return importlib.import_module('_SubnetTree') 17 | _SubnetTree = swig_import_helper() 18 | del swig_import_helper 19 | elif _swig_python_version_info >= (2, 6, 0): 20 | def swig_import_helper(): 21 | from os.path import dirname 22 | import imp 23 | fp = None 24 | try: 25 | fp, pathname, description = imp.find_module('_SubnetTree', [dirname(__file__)]) 26 | except ImportError: 27 | import _SubnetTree 28 | return _SubnetTree 29 | try: 30 | _mod = imp.load_module('_SubnetTree', fp, pathname, description) 31 | finally: 32 | if fp is not None: 33 | fp.close() 34 | return _mod 35 | _SubnetTree = swig_import_helper() 36 | del swig_import_helper 37 | else: 38 | import _SubnetTree 39 | del _swig_python_version_info 40 | 41 | try: 42 | _swig_property = property 43 | except NameError: 44 | pass # Python < 2.2 doesn't have 'property'. 45 | 46 | try: 47 | import builtins as __builtin__ 48 | except ImportError: 49 | import __builtin__ 50 | 51 | def _swig_setattr_nondynamic(self, class_type, name, value, static=1): 52 | if (name == "thisown"): 53 | return self.this.own(value) 54 | if (name == "this"): 55 | if type(value).__name__ == 'SwigPyObject': 56 | self.__dict__[name] = value 57 | return 58 | method = class_type.__swig_setmethods__.get(name, None) 59 | if method: 60 | return method(self, value) 61 | if (not static): 62 | if _newclass: 63 | object.__setattr__(self, name, value) 64 | else: 65 | self.__dict__[name] = value 66 | else: 67 | raise AttributeError("You cannot add attributes to %s" % self) 68 | 69 | 70 | def _swig_setattr(self, class_type, name, value): 71 | return _swig_setattr_nondynamic(self, class_type, name, value, 0) 72 | 73 | 74 | def _swig_getattr(self, class_type, name): 75 | if (name == "thisown"): 76 | return self.this.own() 77 | method = class_type.__swig_getmethods__.get(name, None) 78 | if method: 79 | return method(self) 80 | raise AttributeError("'%s' object has no attribute '%s'" % (class_type.__name__, name)) 81 | 82 | 83 | def _swig_repr(self): 84 | try: 85 | strthis = "proxy of " + self.this.__repr__() 86 | except __builtin__.Exception: 87 | strthis = "" 88 | return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,) 89 | 90 | try: 91 | _object = object 92 | _newclass = 1 93 | except __builtin__.Exception: 94 | class _object: 95 | pass 96 | _newclass = 0 97 | 98 | class inx_addr(_object): 99 | __swig_setmethods__ = {} 100 | __setattr__ = lambda self, name, value: _swig_setattr(self, inx_addr, name, value) 101 | __swig_getmethods__ = {} 102 | __getattr__ = lambda self, name: _swig_getattr(self, inx_addr, name) 103 | __repr__ = _swig_repr 104 | __swig_setmethods__["sin"] = _SubnetTree.inx_addr_sin_set 105 | __swig_getmethods__["sin"] = _SubnetTree.inx_addr_sin_get 106 | if _newclass: 107 | sin = _swig_property(_SubnetTree.inx_addr_sin_get, _SubnetTree.inx_addr_sin_set) 108 | __swig_setmethods__["sin6"] = _SubnetTree.inx_addr_sin6_set 109 | __swig_getmethods__["sin6"] = _SubnetTree.inx_addr_sin6_get 110 | if _newclass: 111 | sin6 = _swig_property(_SubnetTree.inx_addr_sin6_get, _SubnetTree.inx_addr_sin6_set) 112 | 113 | def __init__(self): 114 | this = _SubnetTree.new_inx_addr() 115 | try: 116 | self.this.append(this) 117 | except __builtin__.Exception: 118 | self.this = this 119 | __swig_destroy__ = _SubnetTree.delete_inx_addr 120 | __del__ = lambda self: None 121 | inx_addr_swigregister = _SubnetTree.inx_addr_swigregister 122 | inx_addr_swigregister(inx_addr) 123 | 124 | class SubnetTree(_object): 125 | __swig_setmethods__ = {} 126 | __setattr__ = lambda self, name, value: _swig_setattr(self, SubnetTree, name, value) 127 | __swig_getmethods__ = {} 128 | __getattr__ = lambda self, name: _swig_getattr(self, SubnetTree, name) 129 | __repr__ = _swig_repr 130 | 131 | def __init__(self, binary_lookup_mode=False): 132 | this = _SubnetTree.new_SubnetTree(binary_lookup_mode) 133 | try: 134 | self.this.append(this) 135 | except __builtin__.Exception: 136 | self.this = this 137 | __swig_destroy__ = _SubnetTree.delete_SubnetTree 138 | __del__ = lambda self: None 139 | 140 | def insert(self, *args): 141 | return _SubnetTree.SubnetTree_insert(self, *args) 142 | 143 | def remove(self, *args): 144 | return _SubnetTree.SubnetTree_remove(self, *args) 145 | 146 | def lookup(self, *args): 147 | return _SubnetTree.SubnetTree_lookup(self, *args) 148 | 149 | def search_all(self, cidr): 150 | return _SubnetTree.SubnetTree_search_all(self, cidr) 151 | 152 | def prefixes(self, ipv4_native=False, with_len=True): 153 | return _SubnetTree.SubnetTree_prefixes(self, ipv4_native, with_len) 154 | 155 | def get_binary_lookup_mode(self): 156 | return _SubnetTree.SubnetTree_get_binary_lookup_mode(self) 157 | 158 | def set_binary_lookup_mode(self, binary_lookup_mode=True): 159 | return _SubnetTree.SubnetTree_set_binary_lookup_mode(self, binary_lookup_mode) 160 | 161 | def __contains__(self, *args): 162 | return _SubnetTree.SubnetTree___contains__(self, *args) 163 | 164 | def __getitem__(self, cidr): 165 | return _SubnetTree.SubnetTree___getitem__(self, cidr) 166 | 167 | def __setitem__(self, cidr, data): 168 | return _SubnetTree.SubnetTree___setitem__(self, cidr, data) 169 | 170 | def __delitem__(self, cidr): 171 | return _SubnetTree.SubnetTree___delitem__(self, cidr) 172 | SubnetTree_swigregister = _SubnetTree.SubnetTree_swigregister 173 | SubnetTree_swigregister(SubnetTree) 174 | 175 | # This file is compatible with both classic and new-style classes. 176 | 177 | 178 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.37-22 2 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Convenience wrapper for easily viewing/setting options that 3 | # the project's CMake scripts will recognize 4 | set -e 5 | command="$0 $*" 6 | 7 | # check for `cmake` command 8 | type cmake > /dev/null 2>&1 || { 9 | echo "\ 10 | This package requires CMake, please install it first, then you may 11 | use this configure script to access CMake equivalent functionality.\ 12 | " >&2; 13 | exit 1; 14 | } 15 | 16 | # check for `python3` command 17 | type python3 > /dev/null 2>&1 || { 18 | echo "This package requires Python 3, please install it first." >&2; 19 | } 20 | 21 | site_packages=`python3 -c "from distutils.sysconfig import get_python_lib;\ 22 | print(get_python_lib(True))"` 23 | pyver=`python3 -c "import sys; print(\"%s.%s\" % (sys.version_info[0], sys.version_info[1]))"` 24 | 25 | usage="\ 26 | Usage: $0 [OPTION]... [VAR=VALUE]... 27 | 28 | Build Directory: 29 | --builddir=DIR place build files in directory [build] 30 | 31 | Installation Directories: 32 | --prefix=PREFIX installs to [PREFIX/lib/python${pyver}/site-packages] 33 | --home=PATH installs to [PATH/lib/python] 34 | 35 | Optional Features: 36 | --enable-debug compile in debugging mode 37 | 38 | Required Packages in Non-Standard Locations: 39 | --with-python=PATH path to Python interpreter 40 | --with-python-lib=PATH path to libpython 41 | --with-python-inc=PATH path to Python headers 42 | --with-swig=PATH path to SWIG executable 43 | 44 | Influential Environment Variables (only on first invocation 45 | per build directory): 46 | CC C compiler command 47 | CFLAGS C compiler flags 48 | CXX C++ compiler command 49 | CXXFLAGS C++ compiler flags 50 | " 51 | 52 | sourcedir="$( cd "$( dirname "$0" )" && pwd )" 53 | 54 | # Function to append a CMake cache entry definition to the 55 | # CMakeCacheEntries variable 56 | # $1 is the cache entry variable name 57 | # $2 is the cache entry variable type 58 | # $3 is the cache entry variable value 59 | append_cache_entry () { 60 | CMakeCacheEntries="$CMakeCacheEntries -D $1:$2=$3" 61 | } 62 | 63 | # set defaults 64 | builddir=build 65 | CMakeCacheEntries="" 66 | append_cache_entry PY_MOD_INSTALL_DIR PATH $site_packages 67 | append_cache_entry ENABLE_DEBUG BOOL false 68 | 69 | # parse arguments 70 | while [ $# -ne 0 ]; do 71 | case "$1" in 72 | -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; 73 | *) optarg= ;; 74 | esac 75 | 76 | case "$1" in 77 | --help|-h) 78 | echo "${usage}" 1>&2 79 | exit 1 80 | ;; 81 | --builddir=*) 82 | builddir=$optarg 83 | ;; 84 | --prefix=*) 85 | append_cache_entry PY_MOD_INSTALL_DIR PATH $optarg/lib/python${pyver}/site-packages 86 | ;; 87 | --home=*) 88 | append_cache_entry PY_MOD_INSTALL_DIR PATH $optarg/lib/python 89 | ;; 90 | --enable-debug) 91 | append_cache_entry ENABLE_DEBUG BOOL true 92 | ;; 93 | --with-python=*) 94 | append_cache_entry Python_EXECUTABLE PATH $optarg 95 | ;; 96 | --with-python-inc=*) 97 | append_cache_entry Python_INCLUDE_DIRS PATH $optarg 98 | ;; 99 | --with-swig=*) 100 | append_cache_entry SWIG_EXECUTABLE PATH $optarg 101 | ;; 102 | *) 103 | echo "Invalid option '$1'. Try $0 --help to see available options." 104 | exit 1 105 | ;; 106 | esac 107 | shift 108 | done 109 | 110 | if [ -d $builddir ]; then 111 | # If build directory exists, check if it has a CMake cache 112 | if [ -f $builddir/CMakeCache.txt ]; then 113 | # If the CMake cache exists, delete it so that this configuration 114 | # is not tainted by a previous one 115 | rm -f $builddir/CMakeCache.txt 116 | fi 117 | else 118 | # Create build directory 119 | mkdir -p $builddir 120 | fi 121 | 122 | echo "Build Directory : $builddir" 123 | echo "Source Directory: $sourcedir" 124 | cd $builddir 125 | cmake $CMakeCacheEntries $sourcedir 126 | 127 | echo "# This is the command used to configure this build" > config.status 128 | echo $command >> config.status 129 | chmod u+x config.status 130 | -------------------------------------------------------------------------------- /include/SubnetTree.h: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | #include "Python.h" 3 | #include "patricia.h" 4 | } 5 | 6 | #ifdef SWIG 7 | // If a function is supposed to accept 4-byte tuples as packet by 8 | // socket.inet_aton(), it needs to accept strings which contain 0s. 9 | // Therefore, we need a size parameter. 10 | %typemap(in) (const char* cidr, int size) (PyObject* ascii) 11 | %{ 12 | Py_ssize_t len; 13 | 14 | #if PY_MAJOR_VERSION >= 3 15 | if ( PyUnicode_Check($input) ) 16 | { 17 | ascii = PyUnicode_AsASCIIString($input); 18 | 19 | if ( ! ascii ) 20 | { 21 | PyErr_SetString(PyExc_TypeError, "Expected a ASCII encodable string or bytes"); 22 | return NULL; 23 | } 24 | 25 | PyBytes_AsStringAndSize(ascii, &$1, &len); 26 | $2 = len; 27 | } 28 | else if ( PyBytes_Check($input) ) 29 | { 30 | PyBytes_AsStringAndSize($input, &$1, &len); 31 | $2 = len; 32 | } 33 | else 34 | { 35 | PyErr_SetString(PyExc_TypeError, "Expected a string or bytes"); 36 | return NULL; 37 | } 38 | #else 39 | if ( ! PyString_Check($input) ) 40 | { 41 | PyErr_SetString(PyExc_TypeError, "Expected a string or bytes"); 42 | return NULL; 43 | } 44 | else 45 | { 46 | PyString_AsStringAndSize($input, &$1, &len); 47 | $2 = len; 48 | } 49 | #endif 50 | %} 51 | 52 | %typemap(arginit) (const char* cidr, int size) 53 | { 54 | ascii$argnum = NULL; 55 | } 56 | 57 | %typemap(freearg) (const char* cidr, int size) 58 | { 59 | Py_XDECREF(ascii$argnum); 60 | } 61 | 62 | %typecheck(SWIG_TYPECHECK_STRING) (const char* cidr, int size) 63 | { 64 | // The typemap above will check types and throw a type error when 65 | // needed, so just let everything through. 66 | $1 = 1; 67 | } 68 | 69 | #endif 70 | 71 | typedef union _inx_addr { 72 | struct in_addr sin; 73 | struct in6_addr sin6; 74 | } inx_addr; 75 | 76 | class SubnetTree { 77 | public: 78 | SubnetTree(bool binary_lookup_mode = false); 79 | ~SubnetTree(); 80 | 81 | PyObject* insert(const char* cidr, PyObject* data = 0); 82 | PyObject* insert(unsigned long subnet, unsigned short mask, PyObject* data = 0); 83 | 84 | PyObject* remove(const char* cidr); 85 | PyObject* remove(unsigned long subnet, unsigned short mask); 86 | 87 | PyObject* lookup(const char* cidr, int size) const; 88 | PyObject* lookup(unsigned long addr) const; 89 | 90 | PyObject* search_all(const char* cidr, int size) const; 91 | 92 | PyObject* prefixes(bool ipv4_native = false, bool with_len = true) const; 93 | 94 | bool get_binary_lookup_mode(); 95 | void set_binary_lookup_mode(bool binary_lookup_mode = true); 96 | 97 | #ifndef SWIG 98 | bool operator[](const char* addr) const { return lookup(addr, strlen(addr)); } 99 | bool operator[](unsigned long addr) const { return lookup(addr); } 100 | #else 101 | // clang-format wants to insert a space after the % on the next line, which breaks 102 | // SWIG's parsing of it. 103 | // clang-format off 104 | %extend { 105 | // clang-format on 106 | PyObject* __contains__(const char* cidr, int size) { 107 | if ( ! cidr ) { 108 | PyErr_SetString(PyExc_TypeError, "index must be string"); 109 | return 0; 110 | } 111 | 112 | PyObject* obj = self->lookup(cidr, size); 113 | if ( obj ) { 114 | Py_DECREF(obj); 115 | } 116 | 117 | if ( PyErr_Occurred() ) 118 | return 0; 119 | 120 | if ( obj != 0 ) 121 | Py_RETURN_TRUE; 122 | else 123 | Py_RETURN_FALSE; 124 | } 125 | 126 | PyObject* __contains__(unsigned long addr) { 127 | PyObject* obj = self->lookup(addr); 128 | 129 | if ( obj ) { 130 | Py_DECREF(obj); 131 | } 132 | 133 | if ( PyErr_Occurred() ) 134 | return 0; 135 | 136 | if ( obj != 0 ) 137 | Py_RETURN_TRUE; 138 | else 139 | Py_RETURN_FALSE; 140 | } 141 | 142 | PyObject* __getitem__(const char* cidr, int size) { 143 | if ( ! cidr ) { 144 | PyErr_SetString(PyExc_TypeError, "index must be string"); 145 | return 0; 146 | } 147 | 148 | PyObject* data = self->lookup(cidr, size); 149 | if ( ! data ) { 150 | PyErr_SetObject(PyExc_KeyError, PyBytes_FromStringAndSize(cidr, size)); 151 | return 0; 152 | } 153 | 154 | return data; 155 | } 156 | 157 | PyObject* __setitem__(const char* cidr, PyObject* data) { 158 | if ( ! cidr ) { 159 | PyErr_SetString(PyExc_TypeError, "index must be string"); 160 | return 0; 161 | } 162 | 163 | if ( self->insert(cidr, data) ) 164 | Py_RETURN_TRUE; 165 | else 166 | return 0; 167 | } 168 | 169 | PyObject* __delitem__(const char* cidr) { 170 | if ( ! cidr ) { 171 | PyErr_SetString(PyExc_TypeError, "index must be string"); 172 | return 0; 173 | } 174 | 175 | if ( self->remove(cidr) ) 176 | Py_RETURN_TRUE; 177 | else 178 | return 0; 179 | } 180 | } 181 | #endif 182 | 183 | private: 184 | PyObject* insert(int family, inx_addr subnet, unsigned short mask, PyObject* data); 185 | PyObject* remove(int family, inx_addr subnet, unsigned short mask); 186 | PyObject* lookup(int family, inx_addr subnet) const; 187 | 188 | static void PatriciaDeleteFunction(void* data); 189 | 190 | patricia_tree_t* tree; 191 | bool binary_lookup_mode; 192 | }; 193 | -------------------------------------------------------------------------------- /include/patricia.h: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id: patricia.h,v 1.6 2005/12/07 20:53:01 dplonka Exp $ 3 | * Dave Plonka 4 | * 5 | * This product includes software developed by the University of Michigan, 6 | * Merit Network, Inc., and their contributors. 7 | * 8 | * This file had been called "radix.h" in the MRT sources. 9 | * 10 | * I renamed it to "patricia.h" since it's not an implementation of a general 11 | * radix trie. Also, pulled in various requirements from "mrt.h" and added 12 | * some other things it could be used as a standalone API. 13 | */ 14 | 15 | /* 16 | * This code originates from Dave Plonka's Net::Security perl module. An 17 | * adaptation of it in C is kept at 18 | * https://github.com/CAIDA/cc-common/tree/master/libpatricia. That repository 19 | * is considered the upstream version for Zeek's fork. We make some custom 20 | * changes to this upstream: 21 | * - Replace void_fn_t with data_fn_t and prefix_data_fn_t 22 | * - Add patricia_search_all method 23 | * 24 | * The current version is based on commit 25 | * fd262ab5ac5bae8b0d4a8b5e2e723115b1846376 from that repo. 26 | */ 27 | 28 | /* From copyright.txt: 29 | * 30 | * Copyright (c) 1997, 1998, 1999 31 | * 32 | * 33 | * The Regents of the University of Michigan ("The Regents") and Merit Network, 34 | * Inc. All rights reserved. 35 | * Redistribution and use in source and binary forms, with or without 36 | * modification, are permitted provided that the following conditions are met: 37 | * 1. Redistributions of source code must retain the above 38 | * copyright notice, this list of conditions and the 39 | * following disclaimer. 40 | * 2. Redistributions in binary form must reproduce the above 41 | * copyright notice, this list of conditions and the 42 | * following disclaimer in the documentation and/or other 43 | * materials provided with the distribution. 44 | * 3. All advertising materials mentioning features or use of 45 | * this software must display the following acknowledgement: 46 | * This product includes software developed by the University of Michigan, Merit 47 | * Network, Inc., and their contributors. 48 | * 4. Neither the name of the University, Merit Network, nor the 49 | * names of their contributors may be used to endorse or 50 | * promote products derived from this software without 51 | * specific prior written permission. 52 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY 53 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 54 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 55 | * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY 56 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 57 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 58 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 59 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 60 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 61 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 62 | */ 63 | 64 | #ifndef _PATRICIA_H 65 | #define _PATRICIA_H 66 | 67 | #define HAVE_IPV6 68 | 69 | /* typedef unsigned int u_int; */ 70 | typedef void (*void_fn_t)(); 71 | /* { from defs.h */ 72 | #define prefix_touchar(prefix) ((u_char *)&(prefix)->add.sin) 73 | #define MAXLINE 1024 74 | #define BIT_TEST(f, b) ((f) & (b)) 75 | /* } */ 76 | 77 | #define addroute make_and_lookup 78 | 79 | #include /* for u_* definitions (on FreeBSD 5) */ 80 | 81 | #include /* for EAFNOSUPPORT */ 82 | #ifndef EAFNOSUPPORT 83 | #defined EAFNOSUPPORT WSAEAFNOSUPPORT 84 | #include 85 | #else 86 | #include /* for struct in_addr */ 87 | #endif 88 | 89 | #include /* for AF_INET */ 90 | 91 | /* { from mrt.h */ 92 | 93 | typedef struct _prefix4_t { 94 | u_short family; /* AF_INET | AF_INET6 */ 95 | u_short bitlen; /* same as mask? */ 96 | int ref_count; /* reference count */ 97 | struct in_addr sin; 98 | } prefix4_t; 99 | 100 | typedef struct _prefix_t { 101 | u_short family; /* AF_INET | AF_INET6 */ 102 | u_short bitlen; /* same as mask? */ 103 | int ref_count; /* reference count */ 104 | union { 105 | struct in_addr sin; 106 | #ifdef HAVE_IPV6 107 | struct in6_addr sin6; 108 | #endif /* IPV6 */ 109 | } add; 110 | } prefix_t; 111 | 112 | typedef void (*data_fn_t)(void *); 113 | typedef void (*prefix_data_fn_t)(prefix_t *, void *); 114 | 115 | /* } */ 116 | 117 | typedef struct _patricia_node_t { 118 | u_int bit; /* flag if this node used */ 119 | prefix_t *prefix; /* who we are in patricia tree */ 120 | struct _patricia_node_t *l, *r; /* left and right children */ 121 | struct _patricia_node_t *parent; /* may be used */ 122 | void *data; /* pointer to data */ 123 | void *user1; /* pointer to usr data (ex. route flap info) */ 124 | } patricia_node_t; 125 | 126 | typedef struct _patricia_tree_t { 127 | patricia_node_t *head; 128 | u_int maxbits; /* for IP, 32 bit addresses */ 129 | int num_active_node; /* for debug purpose */ 130 | } patricia_tree_t; 131 | 132 | patricia_node_t *patricia_search_exact(patricia_tree_t *patricia, 133 | prefix_t *prefix); 134 | int patricia_search_all(patricia_tree_t *patricia, prefix_t *prefix, 135 | patricia_node_t ***list, int *n); 136 | patricia_node_t *patricia_search_best(patricia_tree_t *patricia, 137 | prefix_t *prefix); 138 | patricia_node_t *patricia_search_best2(patricia_tree_t *patricia, 139 | prefix_t *prefix, int inclusive); 140 | patricia_node_t *patricia_lookup(patricia_tree_t *patricia, prefix_t *prefix); 141 | void patricia_remove(patricia_tree_t *patricia, patricia_node_t *node); 142 | patricia_tree_t *New_Patricia(int maxbits); 143 | void Clear_Patricia(patricia_tree_t *patricia, data_fn_t func); 144 | void Destroy_Patricia(patricia_tree_t *patricia, data_fn_t func); 145 | 146 | void patricia_process(patricia_tree_t *patricia, prefix_data_fn_t func); 147 | 148 | void Deref_Prefix(prefix_t *prefix); 149 | char *prefix_toa(prefix_t *prefix); 150 | 151 | /* { from demo.c */ 152 | 153 | prefix_t *ascii2prefix(int family, char *string); 154 | 155 | patricia_node_t *make_and_lookup(patricia_tree_t *tree, char *string); 156 | 157 | /* } */ 158 | 159 | #define PATRICIA_MAXBITS (sizeof(struct in6_addr) * 8) 160 | #define PATRICIA_NBIT(x) (0x80 >> ((x)&0x7f)) 161 | #define PATRICIA_NBYTE(x) ((x) >> 3) 162 | 163 | #define PATRICIA_DATA_GET(node, type) (type *)((node)->data) 164 | #define PATRICIA_DATA_SET(node, value) ((node)->data = (void *)(value)) 165 | 166 | #define PATRICIA_WALK(Xhead, Xnode) \ 167 | do { \ 168 | patricia_node_t *Xstack[PATRICIA_MAXBITS + 1]; \ 169 | patricia_node_t **Xsp = Xstack; \ 170 | patricia_node_t *Xrn = (Xhead); \ 171 | while ((Xnode = Xrn)) { \ 172 | if (Xnode->prefix) 173 | 174 | #define PATRICIA_WALK_ALL(Xhead, Xnode) \ 175 | do { \ 176 | patricia_node_t *Xstack[PATRICIA_MAXBITS + 1]; \ 177 | patricia_node_t **Xsp = Xstack; \ 178 | patricia_node_t *Xrn = (Xhead); \ 179 | while ((Xnode = Xrn)) { \ 180 | if (1) 181 | 182 | #define PATRICIA_WALK_BREAK \ 183 | { \ 184 | if (Xsp != Xstack) { \ 185 | Xrn = *(--Xsp); \ 186 | } else { \ 187 | Xrn = (patricia_node_t *)0; \ 188 | } \ 189 | continue; \ 190 | } 191 | 192 | #define PATRICIA_WALK_END \ 193 | if (Xrn->l) { \ 194 | if (Xrn->r) { \ 195 | *Xsp++ = Xrn->r; \ 196 | } \ 197 | Xrn = Xrn->l; \ 198 | } else if (Xrn->r) { \ 199 | Xrn = Xrn->r; \ 200 | } else if (Xsp != Xstack) { \ 201 | Xrn = *(--Xsp); \ 202 | } else { \ 203 | Xrn = (patricia_node_t *)0; \ 204 | } \ 205 | } \ 206 | } \ 207 | while (0) 208 | 209 | #endif /* _PATRICIA_H */ 210 | -------------------------------------------------------------------------------- /patricia.c: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id: patricia.c,v 1.7 2005/12/07 20:46:41 dplonka Exp $ 3 | * Dave Plonka 4 | * 5 | * This product includes software developed by the University of Michigan, 6 | * Merit Network, Inc., and their contributors. 7 | * 8 | * This file had been called "radix.c" in the MRT sources. 9 | * 10 | * I renamed it to "patricia.c" since it's not an implementation of a general 11 | * radix trie. Also I pulled in various requirements from "prefix.c" and 12 | * "demo.c" so that it could be used as a standalone API. 13 | */ 14 | 15 | /* 16 | * This code originates from Dave Plonka's Net::Security perl module. An 17 | * adaptation of it in C is kept at 18 | * https://github.com/CAIDA/cc-common/tree/master/libpatricia. That repository 19 | * is considered the upstream version for Zeek's fork. We make some custom 20 | * changes to this upstream: 21 | * - Replaces void_fn_t with data_fn_t and prefix_data_fn_t 22 | * - Adds patricia_search_all method 23 | * - One commented-out portion of an if statement that breaks one of our tests 24 | * 25 | * The current version is based on commit 26 | * fd262ab5ac5bae8b0d4a8b5e2e723115b1846376 from that repo. 27 | */ 28 | 29 | /* 30 | * Johanna Amann 31 | * 32 | * Added patricia_search_all function. 33 | */ 34 | 35 | #ifndef UNUSED 36 | #if __GNUC__ >= 3 37 | #define UNUSED __attribute__((unused)) 38 | #else 39 | #define UNUSED 40 | #endif 41 | #endif 42 | 43 | UNUSED static char copyright[] = "This product includes software developed by " 44 | "the University of Michigan, Merit" 45 | "Network, Inc., and their contributors."; 46 | 47 | #include /* BSD, Linux, Solaris: for inet_addr */ 48 | #include /* assert */ 49 | #include /* isdigit */ 50 | #include /* errno */ 51 | #include /* sin */ 52 | #include /* BSD, Linux: for inet_addr */ 53 | #include /* NULL */ 54 | #include /* snprintf, fprintf, stderr */ 55 | #include /* free, atol, calloc */ 56 | #include /* memcpy, strchr, strlen */ 57 | #include /* BSD, Linux: for inet_addr */ 58 | #include /* BSD: for inet_addr */ 59 | 60 | #include "patricia.h" 61 | 62 | #define Delete free 63 | 64 | static void out_of_memory(const char* where) 65 | { 66 | fprintf(stderr, "out of memory in %s.\n", where); 67 | abort(); 68 | } 69 | 70 | // From Zeek for reporting memory exhaustion. 71 | extern void out_of_memory(const char *where); 72 | 73 | /* { from prefix.c */ 74 | 75 | /* prefix_tochar 76 | * convert prefix information to bytes 77 | */ 78 | u_char *prefix_tochar(prefix_t *prefix) 79 | { 80 | if (prefix == NULL) 81 | return (NULL); 82 | 83 | return ((u_char *)&prefix->add.sin); 84 | } 85 | 86 | int comp_with_mask(void *addr, void *dest, u_int mask) 87 | { 88 | 89 | if (/* mask/8 == 0 || */ memcmp(addr, dest, mask / 8) == 0) { 90 | int n = mask / 8; 91 | int m = -(1 << (8 - (mask % 8))); 92 | 93 | if (mask % 8 == 0 || (((u_char *)addr)[n] & m) == (((u_char *)dest)[n] & m)) 94 | return (1); 95 | } 96 | return (0); 97 | } 98 | 99 | /* this allows imcomplete prefix */ 100 | int my_inet_pton(int af, const char *src, void *dst) 101 | { 102 | if (af == AF_INET) { 103 | int i, c, val; 104 | u_char xp[sizeof(struct in_addr)] = {0, 0, 0, 0}; 105 | 106 | for (i = 0;; i++) { 107 | c = *src++; 108 | if (!isdigit(c)) 109 | return (-1); 110 | val = 0; 111 | do { 112 | val = val * 10 + c - '0'; 113 | if (val > 255) 114 | return (0); 115 | c = *src++; 116 | } while (c && isdigit(c)); 117 | xp[i] = val; 118 | if (c == '\0') 119 | break; 120 | if (c != '.') 121 | return (0); 122 | if (i >= 3) 123 | return (0); 124 | } 125 | memcpy(dst, xp, sizeof(struct in_addr)); 126 | return (1); 127 | #ifdef HAVE_IPV6 128 | } else if (af == AF_INET6) { 129 | return (inet_pton(af, src, dst)); 130 | #endif /* HAVE_IPV6 */ 131 | } else { 132 | #ifndef NT 133 | errno = EAFNOSUPPORT; 134 | #endif /* NT */ 135 | return -1; 136 | } 137 | } 138 | 139 | #define PATRICIA_MAX_THREADS 16 140 | #define PATRICIA_PREFIX_BUF_LEN 53 141 | 142 | /* 143 | * convert prefix information to ascii string with length 144 | * thread safe and (almost) re-entrant implementation 145 | */ 146 | char *prefix_toa2x(prefix_t *prefix, char *buff, int with_len) 147 | { 148 | if (prefix == NULL) 149 | return ("(Null)"); 150 | assert(prefix->ref_count >= 0); 151 | if (buff == NULL) { 152 | 153 | struct buffer { 154 | char buffs[PATRICIA_MAX_THREADS][PATRICIA_PREFIX_BUF_LEN]; 155 | u_int i; 156 | } * buffp; 157 | 158 | #if 0 159 | THREAD_SPECIFIC_DATA (struct buffer, buffp, 1); 160 | #else 161 | { /* for scope only */ 162 | static struct buffer local_buff; 163 | buffp = &local_buff; 164 | } 165 | #endif 166 | if (buffp == NULL) { 167 | /* XXX should we report an error? */ 168 | return (NULL); 169 | } 170 | 171 | buff = buffp->buffs[buffp->i++ % PATRICIA_MAX_THREADS]; 172 | } 173 | if (prefix->family == AF_INET) { 174 | u_char *a; 175 | assert(prefix->bitlen <= sizeof(struct in_addr) * 8); 176 | a = prefix_touchar(prefix); 177 | if (with_len) { 178 | snprintf(buff, PATRICIA_PREFIX_BUF_LEN, "%d.%d.%d.%d/%d", a[0], a[1], a[2], a[3], prefix->bitlen); 179 | } else { 180 | snprintf(buff, PATRICIA_PREFIX_BUF_LEN, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]); 181 | } 182 | return (buff); 183 | } 184 | #ifdef HAVE_IPV6 185 | else if (prefix->family == AF_INET6) { 186 | char *r; 187 | r = (char *)inet_ntop(AF_INET6, &prefix->add.sin6, buff, 188 | 48 /* a guess value */); 189 | if (r && with_len) { 190 | assert(prefix->bitlen <= sizeof(struct in6_addr) * 8); 191 | snprintf(buff + strlen(buff), PATRICIA_PREFIX_BUF_LEN-strlen(buff), "/%d", prefix->bitlen); 192 | } 193 | return (buff); 194 | } 195 | #endif /* HAVE_IPV6 */ 196 | else 197 | return (NULL); 198 | } 199 | 200 | /* prefix_toa2 201 | * convert prefix information to ascii string 202 | */ 203 | char *prefix_toa2(prefix_t *prefix, char *buff) 204 | { 205 | return (prefix_toa2x(prefix, buff, 0)); 206 | } 207 | 208 | /* prefix_toa 209 | */ 210 | char *prefix_toa(prefix_t *prefix) 211 | { 212 | return (prefix_toa2(prefix, (char *)NULL)); 213 | } 214 | 215 | prefix_t *New_Prefix2(int family, void *dest, int bitlen, prefix_t *prefix) 216 | { 217 | int dynamic_allocated = 0; 218 | int default_bitlen = sizeof(struct in_addr) * 8; 219 | prefix4_t *p4 = NULL; 220 | 221 | #ifdef HAVE_IPV6 222 | if (family == AF_INET6) { 223 | default_bitlen = sizeof(struct in6_addr) * 8; 224 | if (prefix == NULL) { 225 | prefix = calloc(1, sizeof(prefix_t)); 226 | if (prefix == NULL) 227 | out_of_memory("patricia/new_prefix2: unable to allocate memory"); 228 | dynamic_allocated++; 229 | } 230 | memcpy(&prefix->add.sin6, dest, sizeof(struct in6_addr)); 231 | } else 232 | #endif /* HAVE_IPV6 */ 233 | if (family == AF_INET) { 234 | if (prefix == NULL) { 235 | #ifndef NT 236 | prefix = calloc(1, sizeof(prefix4_t)); 237 | if (prefix == NULL) 238 | out_of_memory("patricia/new_prefix2: unable to allocate memory"); 239 | #else 240 | // for some reason, compiler is getting 241 | // prefix4_t size incorrect on NT 242 | prefix = calloc(1, sizeof(prefix_t)); 243 | if (prefix == NULL) 244 | out_of_memory("patricia/new_prefix2: unable to allocate memory"); 245 | #endif /* NT */ 246 | 247 | dynamic_allocated++; 248 | } 249 | memcpy(&prefix->add.sin, dest, sizeof(struct in_addr)); 250 | } else { 251 | return (NULL); 252 | } 253 | 254 | p4 = (prefix4_t*) prefix; 255 | p4->bitlen = (bitlen >= 0) ? bitlen : default_bitlen; 256 | p4->family = family; 257 | p4->ref_count = 0; 258 | if (dynamic_allocated) { 259 | p4->ref_count++; 260 | } 261 | /* fprintf(stderr, "[C %s, %d]\n", prefix_toa (prefix), prefix->ref_count); 262 | */ 263 | return (prefix); 264 | } 265 | 266 | prefix_t *New_Prefix(int family, void *dest, int bitlen) 267 | { 268 | return (New_Prefix2(family, dest, bitlen, NULL)); 269 | } 270 | 271 | /* ascii2prefix 272 | */ 273 | prefix_t *ascii2prefix(int family, char *string) 274 | { 275 | u_long bitlen, maxbitlen = 0; 276 | char *cp; 277 | struct in_addr sin; 278 | #ifdef HAVE_IPV6 279 | struct in6_addr sin6; 280 | #endif /* HAVE_IPV6 */ 281 | int result; 282 | char save[MAXLINE]; 283 | 284 | if (string == NULL) 285 | return (NULL); 286 | 287 | /* easy way to handle both families */ 288 | if (family == 0) { 289 | family = AF_INET; 290 | #ifdef HAVE_IPV6 291 | if (strchr(string, ':')) 292 | family = AF_INET6; 293 | #endif /* HAVE_IPV6 */ 294 | } 295 | 296 | if (family == AF_INET) { 297 | maxbitlen = sizeof(struct in_addr) * 8; 298 | } 299 | #ifdef HAVE_IPV6 300 | else if (family == AF_INET6) { 301 | maxbitlen = sizeof(struct in6_addr) * 8; 302 | } 303 | #endif /* HAVE_IPV6 */ 304 | 305 | if ((cp = strchr(string, '/')) != NULL) { 306 | bitlen = atol(cp + 1); 307 | /* *cp = '\0'; */ 308 | /* copy the string to save. Avoid destroying the string */ 309 | assert(cp - string < MAXLINE); 310 | memcpy(save, string, cp - string); 311 | save[cp - string] = '\0'; 312 | string = save; 313 | if (/* bitlen < 0 || */ bitlen > maxbitlen) 314 | bitlen = maxbitlen; 315 | } else { 316 | bitlen = maxbitlen; 317 | } 318 | 319 | if (family == AF_INET) { 320 | if ((result = my_inet_pton(AF_INET, string, &sin)) <= 0) 321 | return (NULL); 322 | return (New_Prefix(AF_INET, &sin, bitlen)); 323 | } 324 | 325 | #ifdef HAVE_IPV6 326 | else if (family == AF_INET6) { 327 | // Get rid of this with next IPv6 upgrade 328 | #if defined(NT) && !defined(HAVE_INET_NTOP) 329 | inet6_addr(string, &sin6); 330 | return (New_Prefix(AF_INET6, &sin6, bitlen)); 331 | #else 332 | if ((result = inet_pton(AF_INET6, string, &sin6)) <= 0) 333 | return (NULL); 334 | #endif /* NT */ 335 | return (New_Prefix(AF_INET6, &sin6, bitlen)); 336 | } 337 | #endif /* HAVE_IPV6 */ 338 | else 339 | return (NULL); 340 | } 341 | 342 | prefix_t *Ref_Prefix(prefix_t *prefix) 343 | { 344 | if (prefix == NULL) 345 | return (NULL); 346 | if (prefix->ref_count == 0) { 347 | /* make a copy in case of a static prefix */ 348 | return (New_Prefix2(prefix->family, &prefix->add, prefix->bitlen, NULL)); 349 | } 350 | prefix->ref_count++; 351 | /* fprintf(stderr, "[A %s, %d]\n", prefix_toa (prefix), prefix->ref_count); 352 | */ 353 | return (prefix); 354 | } 355 | 356 | void Deref_Prefix(prefix_t *prefix) 357 | { 358 | if (prefix == NULL) 359 | return; 360 | /* for secure programming, raise an assert. no static prefix can call this 361 | */ 362 | assert(prefix->ref_count > 0); 363 | 364 | prefix->ref_count--; 365 | assert(prefix->ref_count >= 0); 366 | if (prefix->ref_count <= 0) { 367 | Delete(prefix); 368 | return; 369 | } 370 | } 371 | 372 | /* } */ 373 | 374 | /* #define PATRICIA_DEBUG 1 */ 375 | 376 | static int num_active_patricia = 0; 377 | 378 | /* these routines support continuous mask only */ 379 | 380 | patricia_tree_t *New_Patricia(int maxbits) 381 | { 382 | patricia_tree_t *patricia = calloc(1, sizeof *patricia); 383 | if (patricia == NULL) 384 | out_of_memory("patricia/new_patricia: unable to allocate memory"); 385 | 386 | patricia->maxbits = maxbits; 387 | patricia->head = NULL; 388 | patricia->num_active_node = 0; 389 | assert(maxbits <= PATRICIA_MAXBITS); /* XXX */ 390 | num_active_patricia++; 391 | return (patricia); 392 | } 393 | 394 | /* 395 | * if func is supplied, it will be called as func(node->data) 396 | * before deleting the node 397 | */ 398 | 399 | void Clear_Patricia(patricia_tree_t *patricia, data_fn_t func) 400 | { 401 | assert(patricia); 402 | if (patricia->head) { 403 | 404 | patricia_node_t *Xstack[PATRICIA_MAXBITS + 1]; 405 | patricia_node_t **Xsp = Xstack; 406 | patricia_node_t *Xrn = patricia->head; 407 | 408 | while (Xrn) { 409 | patricia_node_t *l = Xrn->l; 410 | patricia_node_t *r = Xrn->r; 411 | 412 | if (Xrn->prefix) { 413 | Deref_Prefix(Xrn->prefix); 414 | if (Xrn->data && func) 415 | func(Xrn->data); 416 | } else { 417 | assert(Xrn->data == NULL); 418 | } 419 | Delete(Xrn); 420 | patricia->num_active_node--; 421 | 422 | if (l) { 423 | if (r) { 424 | *Xsp++ = r; 425 | } 426 | Xrn = l; 427 | } else if (r) { 428 | Xrn = r; 429 | } else if (Xsp != Xstack) { 430 | Xrn = *(--Xsp); 431 | } else { 432 | Xrn = NULL; 433 | } 434 | } 435 | } 436 | assert(patricia->num_active_node == 0); 437 | /* Delete (patricia); */ 438 | } 439 | 440 | void Destroy_Patricia(patricia_tree_t *patricia, data_fn_t func) 441 | { 442 | Clear_Patricia(patricia, func); 443 | Delete(patricia); 444 | num_active_patricia--; 445 | } 446 | 447 | /* 448 | * if func is supplied, it will be called as func(node->prefix, node->data) 449 | */ 450 | 451 | void patricia_process(patricia_tree_t *patricia, prefix_data_fn_t func) 452 | { 453 | patricia_node_t *node; 454 | assert(func); 455 | 456 | PATRICIA_WALK(patricia->head, node) 457 | { 458 | func(node->prefix, node->data); 459 | } 460 | PATRICIA_WALK_END; 461 | } 462 | 463 | size_t patricia_walk_inorder(patricia_node_t *node, prefix_data_fn_t func) 464 | { 465 | size_t n = 0; 466 | assert(func); 467 | 468 | if (node->l) { 469 | n += patricia_walk_inorder(node->l, func); 470 | } 471 | 472 | if (node->prefix) { 473 | func(node->prefix, node->data); 474 | n++; 475 | } 476 | 477 | if (node->r) { 478 | n += patricia_walk_inorder(node->r, func); 479 | } 480 | 481 | return n; 482 | } 483 | 484 | patricia_node_t *patricia_search_exact(patricia_tree_t *patricia, 485 | prefix_t *prefix) 486 | { 487 | patricia_node_t *node; 488 | u_char *addr; 489 | u_int bitlen; 490 | 491 | assert(patricia); 492 | assert(prefix); 493 | assert(prefix->bitlen <= patricia->maxbits); 494 | 495 | if (patricia->head == NULL) 496 | return (NULL); 497 | 498 | node = patricia->head; 499 | addr = prefix_touchar(prefix); 500 | bitlen = prefix->bitlen; 501 | 502 | while (node->bit < bitlen) { 503 | 504 | if (BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { 505 | #ifdef PATRICIA_DEBUG 506 | if (node->prefix) 507 | fprintf(stderr, "patricia_search_exact: take right %s/%d\n", 508 | prefix_toa(node->prefix), node->prefix->bitlen); 509 | else 510 | fprintf(stderr, "patricia_search_exact: take right at %u\n", node->bit); 511 | #endif /* PATRICIA_DEBUG */ 512 | node = node->r; 513 | } else { 514 | #ifdef PATRICIA_DEBUG 515 | if (node->prefix) 516 | fprintf(stderr, "patricia_search_exact: take left %s/%d\n", 517 | prefix_toa(node->prefix), node->prefix->bitlen); 518 | else 519 | fprintf(stderr, "patricia_search_exact: take left at %u\n", node->bit); 520 | #endif /* PATRICIA_DEBUG */ 521 | node = node->l; 522 | } 523 | 524 | if (node == NULL) 525 | return (NULL); 526 | } 527 | 528 | #ifdef PATRICIA_DEBUG 529 | if (node->prefix) 530 | fprintf(stderr, "patricia_search_exact: stop at %s/%d\n", 531 | prefix_toa(node->prefix), node->prefix->bitlen); 532 | else 533 | fprintf(stderr, "patricia_search_exact: stop at %u\n", node->bit); 534 | #endif /* PATRICIA_DEBUG */ 535 | if (node->bit > bitlen || node->prefix == NULL) 536 | return (NULL); 537 | assert(node->bit == bitlen); 538 | assert(node->bit == node->prefix->bitlen); 539 | if (comp_with_mask(prefix_tochar(node->prefix), prefix_tochar(prefix), 540 | bitlen)) { 541 | #ifdef PATRICIA_DEBUG 542 | fprintf(stderr, "patricia_search_exact: found %s/%d\n", 543 | prefix_toa(node->prefix), node->prefix->bitlen); 544 | #endif /* PATRICIA_DEBUG */ 545 | return (node); 546 | } 547 | return (NULL); 548 | } 549 | 550 | int patricia_search_all(patricia_tree_t *patricia, prefix_t *prefix, 551 | patricia_node_t ***list, int *n) 552 | { 553 | patricia_node_t *node; 554 | patricia_node_t *stack[PATRICIA_MAXBITS + 1]; 555 | u_char *addr; 556 | u_int bitlen; 557 | int cnt = 0; 558 | 559 | assert(patricia); 560 | assert(prefix); 561 | assert(prefix->bitlen <= patricia->maxbits); 562 | assert(n); 563 | assert(list); 564 | assert(*list == NULL); 565 | 566 | *n = 0; 567 | 568 | if (patricia->head == NULL) 569 | return 0; 570 | 571 | node = patricia->head; 572 | addr = prefix_touchar(prefix); 573 | bitlen = prefix->bitlen; 574 | 575 | while (node->bit < bitlen) { 576 | 577 | if (node->prefix) { 578 | #ifdef PATRICIA_DEBUG 579 | fprintf(stderr, "patricia_search_all: push %s/%d\n", 580 | prefix_toa(node->prefix), node->prefix->bitlen); 581 | #endif /* PATRICIA_DEBUG */ 582 | stack[cnt++] = node; 583 | } 584 | 585 | if (BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { 586 | #ifdef PATRICIA_DEBUG 587 | if (node->prefix) 588 | fprintf(stderr, "patricia_search_all: take right %s/%d\n", 589 | prefix_toa(node->prefix), node->prefix->bitlen); 590 | else 591 | fprintf(stderr, "patricia_search_all: take right at %d\n", node->bit); 592 | #endif /* PATRICIA_DEBUG */ 593 | node = node->r; 594 | } else { 595 | #ifdef PATRICIA_DEBUG 596 | if (node->prefix) 597 | fprintf(stderr, "patricia_search_all: take left %s/%d\n", 598 | prefix_toa(node->prefix), node->prefix->bitlen); 599 | else 600 | fprintf(stderr, "patricia_search_all: take left at %d\n", node->bit); 601 | #endif /* PATRICIA_DEBUG */ 602 | node = node->l; 603 | } 604 | 605 | if (node == NULL) 606 | break; 607 | } 608 | 609 | if (node && node->prefix) 610 | stack[cnt++] = node; 611 | 612 | #ifdef PATRICIA_DEBUG 613 | if (node == NULL) 614 | fprintf(stderr, "patricia_search_all: stop at null\n"); 615 | else if (node->prefix) 616 | fprintf(stderr, "patricia_search_all: stop at %s/%d\n", 617 | prefix_toa(node->prefix), node->prefix->bitlen); 618 | else 619 | fprintf(stderr, "patricia_search_all: stop at %d\n", node->bit); 620 | #endif /* PATRICIA_DEBUG */ 621 | 622 | if (cnt <= 0) 623 | return 0; 624 | 625 | // ok, now we have an upper bound of how much we can return. Let's just alloc 626 | // that... 627 | patricia_node_t **outlist = calloc(cnt, sizeof(patricia_node_t *)); 628 | if (outlist == NULL) 629 | out_of_memory("patricia/patricia_search_all: unable to allocate memory"); 630 | 631 | while (--cnt >= 0) { 632 | node = stack[cnt]; 633 | #ifdef PATRICIA_DEBUG 634 | fprintf(stderr, "patricia_search_all: pop %s/%d\n", 635 | prefix_toa(node->prefix), node->prefix->bitlen); 636 | #endif /* PATRICIA_DEBUG */ 637 | if (comp_with_mask(prefix_tochar(node->prefix), prefix_tochar(prefix), 638 | node->prefix->bitlen)) { 639 | #ifdef PATRICIA_DEBUG 640 | fprintf(stderr, "patricia_search_all: found %s/%d\n", 641 | prefix_toa(node->prefix), node->prefix->bitlen); 642 | #endif /* PATRICIA_DEBUG */ 643 | outlist[*n] = node; 644 | (*n)++; 645 | } 646 | } 647 | *list = outlist; 648 | return *n != 0 ? 1 : 0; 649 | } 650 | 651 | /* if inclusive != 0, "best" may be the given prefix itself */ 652 | patricia_node_t *patricia_search_best2(patricia_tree_t *patricia, 653 | prefix_t *prefix, int inclusive) 654 | { 655 | patricia_node_t *node; 656 | patricia_node_t *stack[PATRICIA_MAXBITS + 1]; 657 | u_char *addr; 658 | u_int bitlen; 659 | int cnt = 0; 660 | 661 | assert(patricia); 662 | assert(prefix); 663 | assert(prefix->bitlen <= patricia->maxbits); 664 | 665 | if (patricia->head == NULL) 666 | return (NULL); 667 | 668 | node = patricia->head; 669 | addr = prefix_touchar(prefix); 670 | bitlen = prefix->bitlen; 671 | 672 | while (node->bit < bitlen) { 673 | 674 | if (node->prefix) { 675 | #ifdef PATRICIA_DEBUG 676 | fprintf(stderr, "patricia_search_best: push %s/%d\n", 677 | prefix_toa(node->prefix), node->prefix->bitlen); 678 | #endif /* PATRICIA_DEBUG */ 679 | stack[cnt++] = node; 680 | } 681 | 682 | if (BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { 683 | #ifdef PATRICIA_DEBUG 684 | if (node->prefix) 685 | fprintf(stderr, "patricia_search_best: take right %s/%d\n", 686 | prefix_toa(node->prefix), node->prefix->bitlen); 687 | else 688 | fprintf(stderr, "patricia_search_best: take right at %u\n", node->bit); 689 | #endif /* PATRICIA_DEBUG */ 690 | node = node->r; 691 | } else { 692 | #ifdef PATRICIA_DEBUG 693 | if (node->prefix) 694 | fprintf(stderr, "patricia_search_best: take left %s/%d\n", 695 | prefix_toa(node->prefix), node->prefix->bitlen); 696 | else 697 | fprintf(stderr, "patricia_search_best: take left at %u\n", node->bit); 698 | #endif /* PATRICIA_DEBUG */ 699 | node = node->l; 700 | } 701 | 702 | if (node == NULL) 703 | break; 704 | } 705 | 706 | if (inclusive && node && node->prefix) 707 | stack[cnt++] = node; 708 | 709 | #ifdef PATRICIA_DEBUG 710 | if (node == NULL) 711 | fprintf(stderr, "patricia_search_best: stop at null\n"); 712 | else if (node->prefix) 713 | fprintf(stderr, "patricia_search_best: stop at %s/%d\n", 714 | prefix_toa(node->prefix), node->prefix->bitlen); 715 | else 716 | fprintf(stderr, "patricia_search_best: stop at %u\n", node->bit); 717 | #endif /* PATRICIA_DEBUG */ 718 | 719 | if (cnt <= 0) 720 | return (NULL); 721 | 722 | while (--cnt >= 0) { 723 | node = stack[cnt]; 724 | #ifdef PATRICIA_DEBUG 725 | fprintf(stderr, "patricia_search_best: pop %s/%d\n", 726 | prefix_toa(node->prefix), node->prefix->bitlen); 727 | #endif /* PATRICIA_DEBUG */ 728 | if (comp_with_mask(prefix_tochar(node->prefix), prefix_tochar(prefix), 729 | node->prefix->bitlen) && 730 | node->prefix->bitlen <= bitlen) { 731 | #ifdef PATRICIA_DEBUG 732 | fprintf(stderr, "patricia_search_best: found %s/%d\n", 733 | prefix_toa(node->prefix), node->prefix->bitlen); 734 | #endif /* PATRICIA_DEBUG */ 735 | return (node); 736 | } 737 | } 738 | return (NULL); 739 | } 740 | 741 | patricia_node_t *patricia_search_best(patricia_tree_t *patricia, 742 | prefix_t *prefix) 743 | { 744 | return (patricia_search_best2(patricia, prefix, 1)); 745 | } 746 | 747 | patricia_node_t *patricia_lookup(patricia_tree_t *patricia, prefix_t *prefix) 748 | { 749 | patricia_node_t *node, *new_node, *parent, *glue; 750 | u_char *addr, *test_addr; 751 | u_int bitlen, check_bit, differ_bit; 752 | int i, j, r; 753 | 754 | assert(patricia); 755 | assert(prefix); 756 | assert(prefix->bitlen <= patricia->maxbits); 757 | 758 | if (patricia->head == NULL) { 759 | node = calloc(1, sizeof *node); 760 | if (node == NULL) 761 | out_of_memory("patricia/patricia_lookup: unable to allocate memory"); 762 | node->bit = prefix->bitlen; 763 | node->prefix = Ref_Prefix(prefix); 764 | node->parent = NULL; 765 | node->l = node->r = NULL; 766 | node->data = NULL; 767 | patricia->head = node; 768 | #ifdef PATRICIA_DEBUG 769 | fprintf(stderr, "patricia_lookup: new_node #0 %s/%d (head)\n", 770 | prefix_toa(prefix), prefix->bitlen); 771 | #endif /* PATRICIA_DEBUG */ 772 | patricia->num_active_node++; 773 | return (node); 774 | } 775 | 776 | addr = prefix_touchar(prefix); 777 | bitlen = prefix->bitlen; 778 | node = patricia->head; 779 | 780 | while (node->bit < bitlen || node->prefix == NULL) { 781 | 782 | if (node->bit < patricia->maxbits && 783 | BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { 784 | if (node->r == NULL) 785 | break; 786 | #ifdef PATRICIA_DEBUG 787 | if (node->prefix) 788 | fprintf(stderr, "patricia_lookup: take right %s/%d\n", 789 | prefix_toa(node->prefix), node->prefix->bitlen); 790 | else 791 | fprintf(stderr, "patricia_lookup: take right at %u\n", node->bit); 792 | #endif /* PATRICIA_DEBUG */ 793 | node = node->r; 794 | } else { 795 | if (node->l == NULL) 796 | break; 797 | #ifdef PATRICIA_DEBUG 798 | if (node->prefix) 799 | fprintf(stderr, "patricia_lookup: take left %s/%d\n", 800 | prefix_toa(node->prefix), node->prefix->bitlen); 801 | else 802 | fprintf(stderr, "patricia_lookup: take left at %u\n", node->bit); 803 | #endif /* PATRICIA_DEBUG */ 804 | node = node->l; 805 | } 806 | 807 | assert(node); 808 | } 809 | 810 | assert(node->prefix); 811 | #ifdef PATRICIA_DEBUG 812 | fprintf(stderr, "patricia_lookup: stop at %s/%d\n", prefix_toa(node->prefix), 813 | node->prefix->bitlen); 814 | #endif /* PATRICIA_DEBUG */ 815 | 816 | test_addr = prefix_touchar(node->prefix); 817 | /* find the first bit different */ 818 | check_bit = (node->bit < bitlen) ? node->bit : bitlen; 819 | differ_bit = 0; 820 | for (i = 0; i * 8 < check_bit; i++) { 821 | if ((r = (addr[i] ^ test_addr[i])) == 0) { 822 | differ_bit = (i + 1) * 8; 823 | continue; 824 | } 825 | /* I know the better way, but for now */ 826 | for (j = 0; j < 8; j++) { 827 | if (BIT_TEST(r, (0x80 >> j))) 828 | break; 829 | } 830 | /* must be found */ 831 | assert(j < 8); 832 | differ_bit = i * 8 + j; 833 | break; 834 | } 835 | if (differ_bit > check_bit) 836 | differ_bit = check_bit; 837 | #ifdef PATRICIA_DEBUG 838 | fprintf(stderr, "patricia_lookup: differ_bit %d\n", differ_bit); 839 | #endif /* PATRICIA_DEBUG */ 840 | 841 | parent = node->parent; 842 | while (parent && parent->bit >= differ_bit) { 843 | node = parent; 844 | parent = node->parent; 845 | #ifdef PATRICIA_DEBUG 846 | if (node->prefix) 847 | fprintf(stderr, "patricia_lookup: up to %s/%d\n", 848 | prefix_toa(node->prefix), node->prefix->bitlen); 849 | else 850 | fprintf(stderr, "patricia_lookup: up to %u\n", node->bit); 851 | #endif /* PATRICIA_DEBUG */ 852 | } 853 | 854 | if (differ_bit == bitlen && node->bit == bitlen) { 855 | if (node->prefix) { 856 | #ifdef PATRICIA_DEBUG 857 | fprintf(stderr, "patricia_lookup: found %s/%d\n", 858 | prefix_toa(node->prefix), node->prefix->bitlen); 859 | #endif /* PATRICIA_DEBUG */ 860 | return (node); 861 | } 862 | node->prefix = Ref_Prefix(prefix); 863 | #ifdef PATRICIA_DEBUG 864 | fprintf(stderr, "patricia_lookup: new node #1 %s/%d (glue mod)\n", 865 | prefix_toa(prefix), prefix->bitlen); 866 | #endif /* PATRICIA_DEBUG */ 867 | assert(node->data == NULL); 868 | return (node); 869 | } 870 | 871 | new_node = calloc(1, sizeof *new_node); 872 | if (new_node == NULL) 873 | out_of_memory("patricia/patricia_lookup: unable to allocate memory"); 874 | new_node->bit = prefix->bitlen; 875 | new_node->prefix = Ref_Prefix(prefix); 876 | new_node->parent = NULL; 877 | new_node->l = new_node->r = NULL; 878 | new_node->data = NULL; 879 | patricia->num_active_node++; 880 | 881 | if (node->bit == differ_bit) { 882 | new_node->parent = node; 883 | if (node->bit < patricia->maxbits && 884 | BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { 885 | assert(node->r == NULL); 886 | node->r = new_node; 887 | } else { 888 | assert(node->l == NULL); 889 | node->l = new_node; 890 | } 891 | #ifdef PATRICIA_DEBUG 892 | fprintf(stderr, "patricia_lookup: new_node #2 %s/%d (child)\n", 893 | prefix_toa(prefix), prefix->bitlen); 894 | #endif /* PATRICIA_DEBUG */ 895 | return (new_node); 896 | } 897 | 898 | if (bitlen == differ_bit) { 899 | if (bitlen < patricia->maxbits && 900 | BIT_TEST(test_addr[bitlen >> 3], 0x80 >> (bitlen & 0x07))) { 901 | new_node->r = node; 902 | } else { 903 | new_node->l = node; 904 | } 905 | new_node->parent = node->parent; 906 | if (node->parent == NULL) { 907 | assert(patricia->head == node); 908 | patricia->head = new_node; 909 | } else if (node->parent->r == node) { 910 | node->parent->r = new_node; 911 | } else { 912 | node->parent->l = new_node; 913 | } 914 | node->parent = new_node; 915 | #ifdef PATRICIA_DEBUG 916 | fprintf(stderr, "patricia_lookup: new_node #3 %s/%d (parent)\n", 917 | prefix_toa(prefix), prefix->bitlen); 918 | #endif /* PATRICIA_DEBUG */ 919 | } else { 920 | glue = calloc(1, sizeof *glue); 921 | if (glue == NULL) 922 | out_of_memory("patricia/patricia_lookup: unable to allocate memory"); 923 | glue->bit = differ_bit; 924 | glue->prefix = NULL; 925 | glue->parent = node->parent; 926 | glue->data = NULL; 927 | patricia->num_active_node++; 928 | if (differ_bit < patricia->maxbits && 929 | BIT_TEST(addr[differ_bit >> 3], 0x80 >> (differ_bit & 0x07))) { 930 | glue->r = new_node; 931 | glue->l = node; 932 | } else { 933 | glue->r = node; 934 | glue->l = new_node; 935 | } 936 | new_node->parent = glue; 937 | 938 | if (node->parent == NULL) { 939 | assert(patricia->head == node); 940 | patricia->head = glue; 941 | } else if (node->parent->r == node) { 942 | node->parent->r = glue; 943 | } else { 944 | node->parent->l = glue; 945 | } 946 | node->parent = glue; 947 | #ifdef PATRICIA_DEBUG 948 | fprintf(stderr, "patricia_lookup: new_node #4 %s/%d (glue+node)\n", 949 | prefix_toa(prefix), prefix->bitlen); 950 | #endif /* PATRICIA_DEBUG */ 951 | } 952 | return (new_node); 953 | } 954 | 955 | void patricia_remove(patricia_tree_t *patricia, patricia_node_t *node) 956 | { 957 | patricia_node_t *parent, *child; 958 | 959 | assert(patricia); 960 | assert(node); 961 | 962 | if (node->r && node->l) { 963 | #ifdef PATRICIA_DEBUG 964 | fprintf(stderr, "patricia_remove: #0 %s/%d (r & l)\n", 965 | prefix_toa(node->prefix), node->prefix->bitlen); 966 | #endif /* PATRICIA_DEBUG */ 967 | 968 | /* this might be a placeholder node -- have to check and make sure 969 | * there is a prefix aossciated with it ! */ 970 | if (node->prefix != NULL) 971 | Deref_Prefix(node->prefix); 972 | node->prefix = NULL; 973 | /* Also I needed to clear data pointer -- masaki */ 974 | node->data = NULL; 975 | return; 976 | } 977 | 978 | if (node->r == NULL && node->l == NULL) { 979 | #ifdef PATRICIA_DEBUG 980 | fprintf(stderr, "patricia_remove: #1 %s/%d (!r & !l)\n", 981 | prefix_toa(node->prefix), node->prefix->bitlen); 982 | #endif /* PATRICIA_DEBUG */ 983 | parent = node->parent; 984 | Deref_Prefix(node->prefix); 985 | Delete(node); 986 | patricia->num_active_node--; 987 | 988 | if (parent == NULL) { 989 | assert(patricia->head == node); 990 | patricia->head = NULL; 991 | return; 992 | } 993 | 994 | if (parent->r == node) { 995 | parent->r = NULL; 996 | child = parent->l; 997 | } else { 998 | assert(parent->l == node); 999 | parent->l = NULL; 1000 | child = parent->r; 1001 | } 1002 | 1003 | if (parent->prefix) 1004 | return; 1005 | 1006 | /* we need to remove parent too */ 1007 | 1008 | if (parent->parent == NULL) { 1009 | assert(patricia->head == parent); 1010 | patricia->head = child; 1011 | } else if (parent->parent->r == parent) { 1012 | parent->parent->r = child; 1013 | } else { 1014 | assert(parent->parent->l == parent); 1015 | parent->parent->l = child; 1016 | } 1017 | child->parent = parent->parent; 1018 | Delete(parent); 1019 | patricia->num_active_node--; 1020 | return; 1021 | } 1022 | 1023 | #ifdef PATRICIA_DEBUG 1024 | fprintf(stderr, "patricia_remove: #2 %s/%d (r ^ l)\n", 1025 | prefix_toa(node->prefix), node->prefix->bitlen); 1026 | #endif /* PATRICIA_DEBUG */ 1027 | if (node->r) { 1028 | child = node->r; 1029 | } else { 1030 | assert(node->l); 1031 | child = node->l; 1032 | } 1033 | parent = node->parent; 1034 | child->parent = parent; 1035 | 1036 | Deref_Prefix(node->prefix); 1037 | Delete(node); 1038 | patricia->num_active_node--; 1039 | 1040 | if (parent == NULL) { 1041 | assert(patricia->head == node); 1042 | patricia->head = child; 1043 | return; 1044 | } 1045 | 1046 | if (parent->r == node) { 1047 | parent->r = child; 1048 | } else { 1049 | assert(parent->l == node); 1050 | parent->l = child; 1051 | } 1052 | } 1053 | 1054 | /* { from demo.c */ 1055 | 1056 | patricia_node_t *make_and_lookup(patricia_tree_t *tree, char *string) 1057 | { 1058 | prefix_t *prefix; 1059 | patricia_node_t *node; 1060 | 1061 | prefix = ascii2prefix(AF_INET, string); 1062 | printf("make_and_lookup: %s/%d\n", prefix_toa(prefix), prefix->bitlen); 1063 | node = patricia_lookup(tree, prefix); 1064 | Deref_Prefix(prefix); 1065 | return (node); 1066 | } 1067 | 1068 | patricia_node_t *try_search_exact(patricia_tree_t *tree, char *string) 1069 | { 1070 | prefix_t *prefix; 1071 | patricia_node_t *node; 1072 | 1073 | prefix = ascii2prefix(AF_INET, string); 1074 | printf("try_search_exact: %s/%d\n", prefix_toa(prefix), prefix->bitlen); 1075 | if ((node = patricia_search_exact(tree, prefix)) == NULL) { 1076 | printf("try_search_exact: not found\n"); 1077 | } else { 1078 | printf("try_search_exact: %s/%d found\n", prefix_toa(node->prefix), 1079 | node->prefix->bitlen); 1080 | } 1081 | Deref_Prefix(prefix); 1082 | return (node); 1083 | } 1084 | 1085 | void lookup_then_remove(patricia_tree_t *tree, char *string) 1086 | { 1087 | patricia_node_t *node; 1088 | 1089 | if ((node = try_search_exact(tree, string))) 1090 | patricia_remove(tree, node); 1091 | } 1092 | 1093 | patricia_node_t *try_search_best(patricia_tree_t *tree, char *string) 1094 | { 1095 | prefix_t *prefix; 1096 | patricia_node_t *node; 1097 | 1098 | prefix = ascii2prefix(AF_INET, string); 1099 | printf("try_search_best: %s/%d\n", prefix_toa(prefix), prefix->bitlen); 1100 | if ((node = patricia_search_best(tree, prefix)) == NULL) 1101 | printf("try_search_best: not found\n"); 1102 | else 1103 | printf("try_search_best: %s/%d found\n", prefix_toa(node->prefix), 1104 | node->prefix->bitlen); 1105 | Deref_Prefix(prefix); 1106 | return (node); 1107 | } 1108 | 1109 | /* } */ 1110 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | 4 | [project] 5 | name = "pysubnettree" 6 | description = "Map subnets in CIDR notation to Python objects" 7 | readme = "README" 8 | dynamic = ["version"] 9 | 10 | license = { text = "3-clause BSD License" } 11 | 12 | requires-python = ">=3.9" 13 | 14 | classifiers = [ 15 | 'Development Status :: 5 - Production/Stable', 16 | 'Environment :: Console', 17 | 'License :: OSI Approved :: BSD License', 18 | 'Operating System :: POSIX :: Linux', 19 | 'Operating System :: POSIX :: BSD :: FreeBSD', 20 | 'Operating System :: MacOS :: MacOS X', 21 | 'Programming Language :: Python :: 3', 22 | 'Topic :: System :: Networking', 23 | 'Topic :: Utilities', 24 | ] 25 | 26 | [project.urls] 27 | Repository = "https://github.com/zeek/pysubnettree" 28 | 29 | [[project.maintainers]] 30 | name = "The Zeek Team" 31 | email = "info@zeek.org" 32 | 33 | [tool.ruff] 34 | exclude = ["SubnetTree.py"] 35 | 36 | [tool.ruff.lint] 37 | select = ["C4", "F", "I", "ISC", "UP"] 38 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | import os 4 | 5 | cflags = os.environ.get("CFLAGS", "") 6 | os.environ["CFLAGS"] = cflags + " -fno-strict-aliasing" 7 | 8 | from distutils.core import Extension 9 | 10 | from setuptools import setup 11 | 12 | setup( 13 | version="0.37.dev22", # Filled in automatically. 14 | py_modules=["SubnetTree"], 15 | url="https://github.com/zeek/pysubnettree", 16 | ext_modules=[ 17 | Extension( 18 | "_SubnetTree", 19 | sources=["SubnetTree.cc", "patricia.c", "SubnetTree_wrap.cc"], 20 | depends=["include/SubnetTree.h", "include/patricia.h"], 21 | include_dirs=["include/"], 22 | ) 23 | ], 24 | ) 25 | -------------------------------------------------------------------------------- /testing/.gitignore: -------------------------------------------------------------------------------- 1 | .tmp 2 | .btest.failed.dat 3 | diag.log 4 | *.pyc 5 | -------------------------------------------------------------------------------- /testing/Makefile: -------------------------------------------------------------------------------- 1 | 2 | DIAG=diag.log 3 | BTEST=../../../../btest/btest 4 | 5 | all: cleanup btest-verbose 6 | 7 | # Showing all tests. 8 | btest-verbose: 9 | @$(BTEST) -j -f $(DIAG) 10 | 11 | brief: cleanup btest-brief 12 | 13 | # Brief output showing only failed tests. 14 | btest-brief: 15 | @$(BTEST) -j -b -f $(DIAG) 16 | 17 | # Rerun only the failed tests. 18 | rerun: 19 | @$(BTEST) -r -j -f $(DIAG) 20 | 21 | cleanup: 22 | @rm -f $(DIAG) 23 | @rm -f .btest.failed.dat 24 | @rm -rf .tmp 25 | 26 | .PHONY: all btest-verbose btest-brief brief rerun cleanup 27 | -------------------------------------------------------------------------------- /testing/Scripts/testsetup.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | 4 | # Keep track of the test case number so that any error message is more useful. 5 | testnum = 0 6 | 7 | 8 | def testcase(success, errmsg): 9 | global testnum 10 | testnum += 1 11 | 12 | if not success: 13 | sys.stderr.write(f"Test #{testnum}: {errmsg}\n") 14 | sys.exit(1) 15 | 16 | 17 | def ipv4binary(ipstr): 18 | return socket.inet_pton(socket.AF_INET, ipstr) 19 | 20 | 21 | def ipv6binary(ipstr): 22 | return socket.inet_pton(socket.AF_INET6, ipstr) 23 | -------------------------------------------------------------------------------- /testing/btest.cfg: -------------------------------------------------------------------------------- 1 | [btest] 2 | TestDirs = pysubnettree 3 | TmpDir = %(testbase)s/.tmp 4 | BaselineDir = %(testbase)s/Baseline 5 | IgnoreDirs = .svn CVS .tmp 6 | IgnoreFiles = *.tmp *.swp #* *.trace .DS_Store 7 | 8 | [environment] 9 | PYTHONPATH=%(testbase)s/../build:%(testbase)s/../../../../../build/auxil/zeekctl/auxil/pysubnettree:%(testbase)s/Scripts 10 | PATH=%(testbase)s/../../../../btest:%(default_path)s 11 | TMPDIR=%(testbase)s/.tmp 12 | -------------------------------------------------------------------------------- /testing/pysubnettree/binary-mode-toggle.test: -------------------------------------------------------------------------------- 1 | # Test that lookups work when switching in and out of binary mode. 2 | # 3 | # @TEST-EXEC: python3 %INPUT 4 | 5 | from testsetup import testcase, ipv4binary, ipv6binary 6 | import SubnetTree 7 | 8 | t = SubnetTree.SubnetTree(True) 9 | 10 | t.insert("10.0.0.0/16") 11 | t["12.1.2.0/24"] = "IPv4 added in binary mode" 12 | t["2000:1234::/64"] = "IPv6 added in binary mode" 13 | 14 | ############################# 15 | # turn off binary lookup mode 16 | t.set_binary_lookup_mode(False) 17 | 18 | t.insert("11.11.0.0/16", "IPv4 added") 19 | t.insert("1:2::/32", "IPv6 added") 20 | 21 | # Verify that string lookups of entries added in binary lookup mode work 22 | testcase("10.0.1.255" in t, "lookup should work") 23 | testcase(t["2000:1234::1"] == "IPv6 added in binary mode", "lookup should work") 24 | 25 | ############################# 26 | # turn on binary lookup mode 27 | t.set_binary_lookup_mode(True) 28 | 29 | # Verify binary mode lookups work for entries added when binary mode was off 30 | testcase(t[ipv4binary("11.11.2.255")] == "IPv4 added", "lookup should work") 31 | testcase(ipv6binary("1:2:3:4::5") in t, "lookup should work") 32 | -------------------------------------------------------------------------------- /testing/pysubnettree/errors.test: -------------------------------------------------------------------------------- 1 | # Test that attempting to do something erroneous is handled correctly. 2 | # 3 | # @TEST-EXEC: python3 %INPUT 4 | 5 | from testsetup import testcase, ipv4binary 6 | import SubnetTree 7 | 8 | t = SubnetTree.SubnetTree() 9 | 10 | # Verify that trying to add invalid entries fails 11 | try: 12 | t["a.b.c.d/24"] = "invalid" 13 | testcase(False, "assignment should have failed") 14 | except ValueError: 15 | pass 16 | 17 | try: 18 | t["1.2.3.0/a"] = "invalid" 19 | testcase(False, "assignment should have failed") 20 | except ValueError: 21 | pass 22 | 23 | try: 24 | t[None] = "invalid" 25 | testcase(False, "assignment should have failed") 26 | except TypeError: 27 | pass 28 | 29 | # Verify that trying to do lookups with non-string data types fails 30 | try: 31 | if None in t: pass 32 | testcase(False, "None should not match anything") 33 | except TypeError: 34 | pass 35 | 36 | try: 37 | if t[None]: pass 38 | testcase(False, "lookup of None should fail") 39 | except TypeError: 40 | pass 41 | 42 | try: 43 | if t[42]: pass 44 | testcase(False, "lookup of integer should fail") 45 | except TypeError: 46 | pass 47 | 48 | # Verify that using non-ascii unicode strings throw TypeError 49 | try: 50 | if t["üpps"]: pass 51 | testcase(False, "lookup of non-ascii unicode should fail with TypeError") 52 | except TypeError: 53 | pass 54 | 55 | try: 56 | if "üpps" in t: pass 57 | testcase(False, "lookup of non-ascii unicode should fail with TypeError") 58 | except TypeError: 59 | pass 60 | 61 | # Verify that overly large strings do not crash. Due to a misplaced 62 | # Py_DECREF() allocated memory may have been freed and pages unmapped. 63 | # Only triggers with "large strings". 64 | if "a" * 1024 * 1024 in t: 65 | testcase(False, "should never pass, triggered crashes previously") 66 | 67 | # Verify that attempting to remove entry with wrong data type fails 68 | try: 69 | del t[None] 70 | testcase(False, "attempted removal should throw exception") 71 | except TypeError: 72 | pass 73 | 74 | 75 | # switch to binary lookup mode 76 | t.set_binary_lookup_mode() 77 | 78 | t["49.58.0.0/16"] = "Network 49.58.0.0/16" 79 | t["2000:1234::/64"] = "Network 2000:1234::/64" 80 | 81 | # Verify that string lookups fail in binary mode 82 | try: 83 | if "49.58.1.1" in t: pass 84 | testcase(False, "string lookup should throw exception in binary mode") 85 | except ValueError: 86 | pass 87 | 88 | try: 89 | if "2000:1234::1" in t: pass 90 | testcase(False, "string lookup should throw exception in binary mode") 91 | except ValueError: 92 | pass 93 | 94 | try: 95 | if t["49.58.1.1"]: pass 96 | testcase(False, "string lookup should throw exception in binary mode") 97 | except KeyError: 98 | pass 99 | 100 | # If lookup string length is 16 bytes then it is interpreted as IPv6 address 101 | # due to being in binary lookup mode 102 | testcase("2000:1234::99:99" not in t, "16-byte IPv6 text string") 103 | 104 | # If lookup string length is 4 bytes then it is interpreted as IPv4 address 105 | # due to being in binary lookup mode 106 | testcase(t["1::5"] == "Network 49.58.0.0/16", "4-byte IPv6 text string") 107 | 108 | 109 | # turn off binary lookup mode 110 | t.set_binary_lookup_mode(False) 111 | 112 | t.insert("1::/64", "Network 1::/64") 113 | 114 | # Verify that binary lookups fail when not in binary lookup mode 115 | try: 116 | if t[ipv4binary("1.2.3.4")]: pass 117 | testcase(False, "binary lookup should throw exception if not in binary mode") 118 | except KeyError: 119 | pass 120 | 121 | # Some IPv4 addrs in binary form have same value as a 4-byte IPv6 string 122 | testcase(t[ipv4binary("49.58.58.50")] == "Network 1::/64", "IPv4 binary lookup") 123 | -------------------------------------------------------------------------------- /testing/pysubnettree/get-binary-lookup.test: -------------------------------------------------------------------------------- 1 | # Test that get_binary_lookup_mode() returns expected result. 2 | # 3 | # @TEST-EXEC: python3 %INPUT 4 | 5 | from testsetup import testcase 6 | import SubnetTree 7 | 8 | t = SubnetTree.SubnetTree() 9 | testcase(not t.get_binary_lookup_mode(), "binary lookup mode should be False") 10 | 11 | t = SubnetTree.SubnetTree(False) 12 | testcase(not t.get_binary_lookup_mode(), "binary lookup mode should be False") 13 | 14 | t = SubnetTree.SubnetTree(True) 15 | testcase(t.get_binary_lookup_mode(), "binary lookup mode should be True") 16 | -------------------------------------------------------------------------------- /testing/pysubnettree/ipv4-mapped.test: -------------------------------------------------------------------------------- 1 | # Tests using IPv4-mapped IPv6 addresses. 2 | # 3 | # @TEST-EXEC: python3 %INPUT 4 | 5 | from testsetup import testcase 6 | import SubnetTree 7 | 8 | t = SubnetTree.SubnetTree() 9 | t.insert("::ffff:0:0/96", "IPv4-mapped addrs") 10 | t.insert("1:2:3:4::/64", "an IPv6 prefix") 11 | 12 | testcase(t["1.2.3.4"] == "IPv4-mapped addrs", "IPv4 uses mapped prefix") 13 | testcase(t["1:2:3:4:5::"] == "an IPv6 prefix", "v6 shouldn't match v4 prefix") 14 | testcase("6:7:8:9::" not in t, "v6 prefix shouldn't match") 15 | 16 | t = SubnetTree.SubnetTree() 17 | t.insert("0.0.0.0/8") 18 | 19 | testcase("127.0.0.1" not in t, "loopback shouldn't match") 20 | testcase("0.1.2.3" in t, "first IPv4 octet of IPv4 is zero") 21 | testcase("1:2:3:4::5" not in t, "first IPv4 octet of IPv6 not zero") 22 | testcase("::ffff:197.44.42.34" not in t, "first v4 octet of mapped-v4 not 0") 23 | testcase("::ffff:0.44.42.34" in t, "first v4 octet of mapped-v4 is 0") 24 | -------------------------------------------------------------------------------- /testing/pysubnettree/lookup.test: -------------------------------------------------------------------------------- 1 | # Test that lookups match the correct entry. 2 | # 3 | # @TEST-EXEC: python3 %INPUT 4 | 5 | from testsetup import testcase, ipv4binary, ipv6binary 6 | import SubnetTree 7 | 8 | def ipv4_func(ipstr): 9 | return ipstr 10 | 11 | def ipv6_func(ipstr): 12 | return ipstr 13 | 14 | def do_tests(binarymode): 15 | if binarymode: 16 | v4 = ipv4binary 17 | v6 = ipv6binary 18 | else: 19 | v4 = ipv4_func 20 | v6 = ipv6_func 21 | 22 | t = SubnetTree.SubnetTree(binarymode) 23 | 24 | t.insert("1.2.3.4/16", "Network 1.2.0.0/16") 25 | t["4.3.2.1/8"] = "Network 4.0.0.0/8" 26 | t.insert("1:2:3:4::/64") 27 | t.insert("1:2:3:7::/64", "Network 1:2:3:7::/64") 28 | t.insert("1:2::/32", "Network 1:2::/32") 29 | t["2:3::/32"] = "Network 2:3::/32" 30 | 31 | # Verify that lookups match the correct entry 32 | testcase(t[v4("4.1.255.255")] == "Network 4.0.0.0/8", "IPv4 in Network 4") 33 | testcase(t[v4("1.2.3.255")] == "Network 1.2.0.0/16", "IPv4 in Network 1") 34 | testcase(t[v6("1:2:3:4::1")] == "", "entry with no object") 35 | testcase(t[v6("2:3::1")] == "Network 2:3::/32", "IPv6 in Network 2") 36 | testcase(t[v6("1:2:99::")] == "Network 1:2::/32", "IPv6 in Network 1:2") 37 | testcase(t[v6("1:2:3:7:10::")] == "Network 1:2:3:7::/64", "IPv6 in Network 1:2:3:7") 38 | 39 | # Verify that lookups that don't match anything will fail 40 | try: 41 | if t[v4("1.3.3.255")]: pass 42 | testcase(False, "lookup should throw exception") 43 | except KeyError as e: 44 | # We expect a `KeyError` which reports the key. Since above address 45 | # encodes to invalid unicode we'd expect to see the raw bytes as error 46 | # message in binary mode. 47 | assert str(e) == r"b'\x01\x03\x03\xff'" if binarymode else r"b'1.3.3.255'" 48 | 49 | try: 50 | if t[v6("1:3:3::3")]: pass 51 | testcase(False, "lookup should throw exception") 52 | except KeyError: 53 | pass 54 | 55 | 56 | do_tests(False) 57 | do_tests(True) 58 | -------------------------------------------------------------------------------- /testing/pysubnettree/membership.test: -------------------------------------------------------------------------------- 1 | # Test that membership test operators ("in"/"not in") return expected result. 2 | # 3 | # @TEST-EXEC: python3 %INPUT 4 | 5 | from testsetup import testcase, ipv4binary, ipv6binary 6 | import SubnetTree 7 | 8 | def ipv4_func(ipstr): 9 | return ipstr 10 | 11 | def ipv6_func(ipstr): 12 | return ipstr 13 | 14 | def do_tests(binarymode): 15 | if binarymode: 16 | v4 = ipv4binary 17 | v6 = ipv6binary 18 | else: 19 | v4 = ipv4_func 20 | v6 = ipv6_func 21 | 22 | t = SubnetTree.SubnetTree(binarymode) 23 | 24 | # Test with no elements 25 | testcase(v4("10.0.2.3") not in t, "lookup should fail") 26 | 27 | # Test with inserting by assignment 28 | t["10.0.0.0/16"] = "Ten/16" 29 | testcase(v4("10.0.2.3") in t, "lookup should succeed") 30 | testcase(v4("10.1.2.3") not in t, "lookup should fail") 31 | 32 | # Test with inserting using insert() method with an object specified 33 | t.insert("10.0.0.0/8", "Ten/8") 34 | testcase(v4("10.1.2.3") in t, "lookup should succeed") 35 | testcase(v4("0.1.2.3") not in t, "lookup should fail") 36 | 37 | # Test with inserting using insert() method without an object 38 | t.insert("12.1.0.0/16") 39 | testcase(v4("12.1.2.3") in t, "lookup should succeed") 40 | testcase(v4("12.0.2.3") not in t, "lookup should fail") 41 | 42 | 43 | do_tests(False) 44 | do_tests(True) 45 | -------------------------------------------------------------------------------- /testing/pysubnettree/prefixes.test: -------------------------------------------------------------------------------- 1 | # Test the prefixes() function with all combinations of input parameters. 2 | # 3 | # @TEST-EXEC: python3 %INPUT 4 | 5 | from testsetup import testcase 6 | import SubnetTree 7 | 8 | t = SubnetTree.SubnetTree() 9 | 10 | p = t.prefixes() 11 | testcase(not p, "empty tree should have empty prefix set") 12 | 13 | t["::ffff:0:0/96"] = "IPv4-mapped addrs" 14 | t["1:2:3:4::/64"] = "IPv6 /64" 15 | t["10.1.0.0/16"] = "IPv4 /16" 16 | t["10.1.42.0/24"] = "IPv4 /24" 17 | t["2620:4d:4004:3::2/64"] = "IPv6 /64 2" 18 | t["189.247.142.246/24"] = "IPv4 /24 2" 19 | 20 | expected = set(['::ffff:10.1.0.0', '::ffff:189.247.142.246', '2620:4d:4004:3::2', '::ffff:0.0.0.0', '::ffff:10.1.42.0', '1:2:3:4::']) 21 | p = t.prefixes(with_len=False) - expected 22 | testcase(not p, "prefixes should be all IPv6 no len") 23 | 24 | expected = set(['2620:4d:4004:3::2/64', '::ffff:189.247.142.246/120', '::ffff:10.1.0.0/112', '1:2:3:4::/64', '::ffff:10.1.42.0/120', '::ffff:0.0.0.0/96']) 25 | p = t.prefixes() - expected 26 | testcase(not p, "prefixes should be all IPv6 with len") 27 | 28 | expected = set(['2620:4d:4004:3::2', '189.247.142.246', '10.1.42.0', '10.1.0.0', '0.0.0.0', '1:2:3:4::']) 29 | p = t.prefixes(ipv4_native=True, with_len=False) - expected 30 | testcase(not p, "prefixes should include IPv4 native no len") 31 | 32 | expected = set(['2620:4d:4004:3::2/64', '10.1.0.0/16', '10.1.42.0/24', '1:2:3:4::/64', '189.247.142.246/24', '0.0.0.0/0']) 33 | p = t.prefixes(ipv4_native=True) - expected 34 | testcase(not p, "prefixes should include IPv4 native with len") 35 | 36 | try: 37 | # Expect an exception when giving an invalid prefix 38 | t.insert('8.8.8.8/56') 39 | except ValueError: 40 | pass 41 | -------------------------------------------------------------------------------- /testing/pysubnettree/remove.test: -------------------------------------------------------------------------------- 1 | # Test that remove works with remove method or del statement, and removes 2 | # by exact match. 3 | # 4 | # @TEST-EXEC: python3 %INPUT 5 | 6 | from testsetup import testcase, ipv4binary, ipv6binary 7 | import SubnetTree 8 | 9 | def ipv4_func(ipstr): 10 | return ipstr 11 | 12 | def ipv6_func(ipstr): 13 | return ipstr 14 | 15 | def do_tests(binarymode): 16 | if binarymode: 17 | v4 = ipv4binary 18 | v6 = ipv6binary 19 | else: 20 | v4 = ipv4_func 21 | v6 = ipv6_func 22 | 23 | t = SubnetTree.SubnetTree(binarymode) 24 | t.insert("10.0.0.0/8") 25 | t.insert("10.1.99.99/16", "TenOne/16") 26 | t.insert("10.1.2.0/24") 27 | t.insert("11.1.0.0/16") 28 | 29 | # Verify that remove() works 30 | t.remove("10.0.0.0/8") 31 | testcase(v4("10.2.2.2") not in t, "lookup should fail") 32 | testcase(v4("10.1.3.3") in t, "lookup should succeed") 33 | testcase(v4("10.1.2.3") in t, "lookup should succeed") 34 | 35 | # Verify that "del" also works 36 | del t["10.1.0.0/16"] 37 | testcase(v4("10.1.3.3") not in t, "lookup should fail") 38 | testcase(v4("10.1.2.3") in t, "lookup should succeed") 39 | 40 | # Verify that lookups of removed elements fail as expected 41 | try: 42 | if t[v4("10.1.3.3")]: pass 43 | testcase(False, "lookup of deleted element should throw exception") 44 | except KeyError: 45 | pass 46 | 47 | # Verify that removals fail when they are not an exact match 48 | try: 49 | t.remove("11.1.0.0/24") 50 | testcase(False, "removal should throw exception") 51 | except RuntimeError: 52 | pass 53 | 54 | try: 55 | del t["11.1.0.0/24"] 56 | testcase(False, "removal should throw exception") 57 | except RuntimeError: 58 | pass 59 | 60 | 61 | # Test IPv6 62 | t.insert("1:2:3:4::/64") 63 | t.insert("1:2:3:7::/64", "Network 1:2:3:7::/64") 64 | t.insert("1:2::/32", "Network 1:2::/32") 65 | 66 | t.remove("1:2::/32") 67 | testcase(v6("1:2:99::") not in t, "lookup should fail") 68 | testcase(v6("1:2:3:4::5") in t, "lookup should succeed") 69 | 70 | del t["1:2:3:4::/64"] 71 | testcase(v6("1:2:3:4::5") not in t, "lookup should fail") 72 | 73 | 74 | do_tests(False) 75 | do_tests(True) 76 | -------------------------------------------------------------------------------- /testing/pysubnettree/search-all.test: -------------------------------------------------------------------------------- 1 | # Test that search all returns the correct entries. 2 | # 3 | # @TEST-EXEC: python3 %INPUT 4 | 5 | from testsetup import testcase, ipv4binary, ipv6binary 6 | import SubnetTree 7 | 8 | def ipv4_func(ipstr): 9 | return ipstr 10 | 11 | def ipv6_func(ipstr): 12 | return ipstr 13 | 14 | def do_tests(binarymode): 15 | if binarymode: 16 | v4 = ipv4binary 17 | v6 = ipv6binary 18 | else: 19 | v4 = ipv4_func 20 | v6 = ipv6_func 21 | 22 | t = SubnetTree.SubnetTree(binarymode) 23 | 24 | # Verify that searches when there are no items return empty 25 | testcase(t.search_all(v4("4.1.0.100")) == [], "IPv4 empty search") 26 | testcase(t.search_all(v6("1:2:3:7::0:0:1")) == [], "IPv6 empty search") 27 | 28 | t.insert("1.2.3.4/16", "Network 1.2.0.0/16") 29 | t["4.3.2.1/8"] = "Network 4.0.0.0/8" 30 | t.insert("1:2:3:4::/64") 31 | t.insert("1:2:3:7::/64", "Network 1:2:3:7::/64") 32 | t.insert("1:2::/32", "Network 1:2::/32") 33 | t["2:3::/32"] = "Network 2:3::/32" 34 | 35 | # Verify searches that return one or more items 36 | testcase(t.search_all(v4("4.1.0.100")) == ["Network 4.0.0.0/8"], "IPv4 search") 37 | testcase(t.search_all(v6("1:2:3:7::0:0:1")) == ["Network 1:2:3:7::/64", "Network 1:2::/32"], "IPv6 search") 38 | 39 | # Verify searches that should return no items 40 | testcase(t.search_all(v4("5.1.0.100")) == [], "IPv4 search with no results") 41 | testcase(t.search_all(v6("4:2:3:7::0:0:1")) == [], "IPv6 search with no results") 42 | 43 | 44 | do_tests(False) 45 | do_tests(True) 46 | -------------------------------------------------------------------------------- /testing/pysubnettree/set-binary-lookup.test: -------------------------------------------------------------------------------- 1 | # Test that set_binary_lookup_mode() sets the mode correctly. 2 | # 3 | # @TEST-EXEC: python3 %INPUT 4 | 5 | from testsetup import testcase 6 | import SubnetTree 7 | 8 | #################### 9 | # Initial mode is the default 10 | 11 | t = SubnetTree.SubnetTree() 12 | t.set_binary_lookup_mode(False) 13 | testcase(not t.get_binary_lookup_mode(), "binary lookup mode should be False") 14 | 15 | t = SubnetTree.SubnetTree() 16 | t.set_binary_lookup_mode(True) 17 | testcase(t.get_binary_lookup_mode(), "binary lookup mode should be True") 18 | 19 | t = SubnetTree.SubnetTree() 20 | t.set_binary_lookup_mode() 21 | testcase(t.get_binary_lookup_mode(), "binary lookup mode should be True") 22 | 23 | #################### 24 | # Initial value is False 25 | 26 | t = SubnetTree.SubnetTree(False) 27 | t.set_binary_lookup_mode(False) 28 | testcase(not t.get_binary_lookup_mode(), "binary lookup mode should be False") 29 | 30 | t = SubnetTree.SubnetTree(False) 31 | t.set_binary_lookup_mode(True) 32 | testcase(t.get_binary_lookup_mode(), "binary lookup mode should be True") 33 | 34 | t = SubnetTree.SubnetTree(False) 35 | t.set_binary_lookup_mode() 36 | testcase(t.get_binary_lookup_mode(), "binary lookup mode should be True") 37 | 38 | #################### 39 | # Initial value is True 40 | 41 | t = SubnetTree.SubnetTree(True) 42 | t.set_binary_lookup_mode(False) 43 | testcase(not t.get_binary_lookup_mode(), "binary lookup mode should be False") 44 | 45 | t = SubnetTree.SubnetTree(True) 46 | t.set_binary_lookup_mode(True) 47 | testcase(t.get_binary_lookup_mode(), "binary lookup mode should be True") 48 | 49 | t = SubnetTree.SubnetTree(True) 50 | t.set_binary_lookup_mode() 51 | testcase(t.get_binary_lookup_mode(), "binary lookup mode should be True") 52 | --------------------------------------------------------------------------------