├── .bazelrc ├── .github └── workflows │ └── build.yml ├── .gitignore ├── BUILD.bazel ├── LICENSE ├── MODULE.bazel ├── README.md ├── docs └── CONTRIBUTING.md ├── fuzztest.bazelrc └── netkat ├── BUILD.bazel ├── analysis_engine.cc ├── analysis_engine.h ├── analysis_engine_test.cc ├── evaluator.cc ├── evaluator.h ├── evaluator_test.cc ├── frontend.cc ├── frontend.h ├── frontend_test.cc ├── gtest_utils.cc ├── gtest_utils.h ├── interned_field.cc ├── interned_field.h ├── interned_field_test.cc ├── netkat.proto ├── netkat_proto_constructors.cc ├── netkat_proto_constructors.h ├── netkat_proto_constructors_test.cc ├── netkat_test.cc ├── paged_stable_vector.h ├── paged_stable_vector_test.cc ├── symbolic_packet.cc ├── symbolic_packet.h ├── symbolic_packet_test.cc ├── symbolic_packet_test.expected ├── symbolic_packet_test_runner.cc ├── symbolic_packet_transformer.cc ├── symbolic_packet_transformer.h ├── symbolic_packet_transformer_test.cc ├── symbolic_packet_transformer_test.expected ├── symbolic_packet_transformer_test_runner.cc ├── table.cc ├── table.h └── table_test.cc /.bazelrc: -------------------------------------------------------------------------------- 1 | # Use C++ 20. 2 | build --cxxopt=-std=c++20 3 | build --host_cxxopt=-std=c++20 4 | 5 | # Force the use of Clang for all builds. FuzzTest relies on Clang for sanitizer 6 | # coverage (https://clang.llvm.org/docs/SanitizerCoverage.html). 7 | build --action_env=CC=clang 8 | build --action_env=CXX=clang++ 9 | 10 | # Show everything when running tests. 11 | test --test_output=streamed 12 | 13 | # To create this file, please run: 14 | # 15 | # bazel run @com_google_fuzztest//bazel:setup_configs > fuzztest.bazelrc 16 | # 17 | try-import %workspace%/fuzztest.bazelrc 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | # Controls when the workflow will run 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the "main" branch 6 | push: 7 | branches: [ "main" ] 8 | pull_request: 9 | branches: [ "main" ] 10 | schedule: 11 | # Run daily at midnight to ensure we catch regressions. 12 | - cron: "0 0 * * *" 13 | # Allow manual triggering of the workflow. 14 | # https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow 15 | workflow_dispatch: 16 | 17 | jobs: 18 | build: 19 | # TODO(anthonyroy): Bump to 24.04 once llvm+absl issues are resolved. https://github.com/llvm/llvm-project/issues/102443 20 | runs-on: ubuntu-22.04 21 | 22 | steps: 23 | # TODO(anthonyroy): Remove once the following is fixed: 24 | # https://github.com/actions/runner-images/issues/9491 25 | - name: Reduce ASLR entropy as a temporary workaround 26 | run: | 27 | sudo sysctl -w vm.mmap_rnd_bits=28 28 | 29 | - uses: actions/checkout@v4 30 | 31 | - name: Install dependencies 32 | run: | 33 | sudo apt-get update && sudo apt-get install -yq \ 34 | clang 35 | 36 | - name: Mount bazel cache 37 | uses: actions/cache/restore@v4 38 | with: 39 | path: "~/.cache/bazel" 40 | key: bazel-${{ hashFiles('*.bazel', '*.bazelrc') }}-${{ github.ref_name }} 41 | restore-keys: | 42 | bazel-${{hashFiles('*.bazel', '*.bazelrc') }} 43 | bazel- 44 | 45 | - name: Save start time 46 | uses: josStorer/get-current-time@v2 47 | id: start-time 48 | with: 49 | # Unix timestamp -- seconds since 1970. 50 | format: X 51 | 52 | - name: Build 53 | run: bazel build --test_output=errors //... 54 | - name: Test 55 | run: bazel test --test_output=errors //... 56 | 57 | - name: Save end time 58 | # Always save the end time so we can calculate the build duration. 59 | if: always() 60 | uses: josStorer/get-current-time@v2 61 | id: end-time 62 | with: 63 | # Unix timestamp -- seconds since 1970. 64 | format: X 65 | 66 | - name: Calculate build duration 67 | # Always calculate the build duration so we can update the cache if needed. 68 | if: always() 69 | run: | 70 | START=${{ steps.start-time.outputs.formattedTime }} 71 | END=${{ steps.end-time.outputs.formattedTime }} 72 | DURATION=$(( $END - $START )) 73 | echo "duration=$DURATION" | tee "$GITHUB_ENV" 74 | 75 | 76 | - name: Compress cache 77 | # Always compress the cache so we can update the cache if needed. 78 | if: always() 79 | run: rm -rf $(bazel info repository_cache) 80 | 81 | - name: Save bazel cache 82 | uses: actions/cache/save@v4 83 | # Only create a new cache entry if we're on the main branch or the build takes >5mins. 84 | # 85 | # NOTE: Even though `always()` evaluates to true, and `true && x == x`, the `always() &&` 86 | # prefix is not redundant! The call to `always()` has a side effect, which is to override 87 | # the default behavior of automagically canceling this step if a previous step failed. 88 | # (Don't blame me, blame GitHub Actions!) 89 | if: always() && (github.ref_name == 'main' || env.duration > 300) 90 | with: 91 | path: "~/.cache/bazel" 92 | key: bazel-${{ hashFiles('*.bazel', '*.bazelrc') }}-${{ github.ref_name }}-${{ github.run_id }} 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't track lock files. 2 | *.lock 3 | 4 | # Don't track Bazel output folders. 5 | bazel-* 6 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | # NetKAT, a framework for describing and reasoning about packet-switched networks. 2 | 3 | load("@rules_license//rules:license.bzl", "license") 4 | 5 | package( 6 | default_applicable_licenses = [":license"], 7 | default_visibility = [":__subpackages__"], 8 | ) 9 | 10 | # Define the license for this package, which is used as the default license 11 | # for all targets in this package based on default_applicable_licenses above. 12 | license(name = "license") 13 | 14 | licenses(["notice"]) 15 | 16 | exports_files(["LICENSE"]) 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The NetKAT authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # https://bazel.build/external/overview#bzlmod 16 | 17 | module( 18 | name = "google-netkat", 19 | version = "0.0.1", 20 | bazel_compatibility = [">=7.3.2"], 21 | compatibility_level = 1, 22 | ) 23 | 24 | bazel_dep(name = "protobuf", version = "29.0", repo_name = "com_google_protobuf") 25 | bazel_dep(name = "abseil-cpp", version = "20240722.0", repo_name = "com_google_absl") 26 | bazel_dep(name = "gutil", version = "20250502.0", repo_name = "com_google_gutil") 27 | bazel_dep(name = "re2", version = "2024-07-02.bcr.1", repo_name = "com_googlesource_code_re2") 28 | 29 | # Dev Depdencies. 30 | bazel_dep(name = "rules_license", version = "1.0.0", dev_dependency = True) 31 | bazel_dep(name = "googletest", version = "1.15.2", dev_dependency = True, repo_name = "com_google_googletest") 32 | bazel_dep(name = "fuzztest", version = "20241028.0", dev_dependency = True, repo_name = "com_google_fuzztest") 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NetKAT 2 | 3 | This is a C++ implementation of NetKAT. 4 | 5 | NetKAT is a domain specific language (DSL) and system for specifying, 6 | programming, and reasoning about packet-switched networks. Key features include: 7 | 8 | * Automated reasoning and verification. 9 | * Modular composition, supporting clean isolation and abstraction. 10 | * Simple, yet very powerful syntax & semantics. 11 | * Strong mathematical foundation. 12 | 13 | If you want to learn more, you may check out the list of 14 | [publications on NetKAT](#academic-publications-on-netkat) below. In the future, 15 | we also hope to provide a gentler introduction to NetKAT in the form of a 16 | tutorial. 17 | 18 | Note: We expect that this NetKAT implementation may diverge from NetKAT as 19 | described [in the literature](#academic-publications-on-netkat) over time, as we 20 | take liberty to optimize and adjust the language for industrial use. Build 21 | rules/targets will also be kept restricted for the time being to discourage any 22 | active external dependents. 23 | 24 | ## Disclaimer 25 | 26 | This is not an officially supported Google product. This project is not eligible 27 | for the 28 | [Google Open Source Software Vulnerability Rewards Program](https://bughunters.google.com/open-source-security). 29 | 30 | ## Academic Publications on NetKAT 31 | 32 | NetKAT was first conceived and studied in academia. Here, we list a small 33 | selection of key publications related to NetKAT. 34 | 35 | * **NetKAT: Semantic Foundations for Networks.** POPL 2014. 36 | [[PDF]](https://www.cs.cornell.edu/~jnfoster/papers/frenetic-netkat.pdf) 37 | * **A Fast Compiler for NetKAT.** ICFP 2015. 38 | [[PDF]](https://www.cs.cornell.edu/~jnfoster/papers/netkat-compiler.pdf) 39 | * **KATch: A Fast Symbolic Verifier for NetKAT.** PLDI 2024. 40 | [[PDF]](https://research.google/pubs/katch-a-fast-symbolic-verifier-for-netkat/) 41 | 42 | ## Contributing 43 | 44 | We would love to accept your patches and contributions to this project. Please 45 | familiarize yourself with [our contribution process](docs/CONTRIBUTING.md). 46 | 47 | ### Source Code Headers 48 | 49 | Every file containing source code must include copyright and license 50 | information. 51 | 52 | Apache header: 53 | 54 | ``` 55 | Copyright 2024 The NetKAT authors 56 | 57 | Licensed under the Apache License, Version 2.0 (the "License"); 58 | you may not use this file except in compliance with the License. 59 | You may obtain a copy of the License at 60 | 61 | https://www.apache.org/licenses/LICENSE-2.0 62 | 63 | Unless required by applicable law or agreed to in writing, software 64 | distributed under the License is distributed on an "AS IS" BASIS, 65 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 66 | See the License for the specific language governing permissions and 67 | limitations under the License. 68 | ``` 69 | 70 | This can be done automatically using 71 | [addlicense](https://github.com/google/addlicense) as follows: `sh addlicense -c 72 | "The NetKAT authors" -l apache .` 73 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We would love to accept your patches and contributions to this project. 4 | 5 | ## Before you begin 6 | 7 | ### Sign our Contributor License Agreement 8 | 9 | Contributions to this project must be accompanied by a 10 | [Contributor License Agreement](https://cla.developers.google.com/about) (CLA). 11 | You (or your employer) retain the copyright to your contribution; this simply 12 | gives us permission to use and redistribute your contributions as part of the 13 | project. 14 | 15 | If you or your current employer have already signed the Google CLA (even if it 16 | was for a different project), you probably don't need to do it again. 17 | 18 | Visit to see your current agreements or to 19 | sign a new one. 20 | 21 | ### Review our Community Guidelines 22 | 23 | This project follows 24 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). 25 | 26 | ## Contribution process 27 | 28 | ### Code Reviews 29 | 30 | All submissions, including submissions by project members, require review. We 31 | use [GitHub pull requests](https://docs.github.com/articles/about-pull-requests) 32 | for this purpose. 33 | -------------------------------------------------------------------------------- /fuzztest.bazelrc: -------------------------------------------------------------------------------- 1 | ### DO NOT EDIT. Generated file. 2 | # 3 | # To regenerate, run the following from your project's workspace: 4 | # 5 | # bazel run @com_google_fuzztest//bazel:setup_configs > fuzztest.bazelrc 6 | # 7 | # And don't forget to add the following to your project's .bazelrc: 8 | # 9 | # try-import %workspace%/fuzztest.bazelrc 10 | ### Common options. 11 | # 12 | # Do not use directly. 13 | 14 | # Standard define for \"ifdef-ing\" any fuzz test specific code. 15 | build:fuzztest-common --copt=-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION 16 | 17 | # In fuzz tests, we want to catch assertion violations even in optimized builds. 18 | build:fuzztest-common --copt=-UNDEBUG 19 | 20 | # Enable libc++ assertions. 21 | # See https://libcxx.llvm.org/UsingLibcxx.html#enabling-the-safe-libc-mode 22 | build:fuzztest-common --copt=-D_LIBCPP_ENABLE_ASSERTIONS=1 23 | ### ASan (Address Sanitizer) build configuration. 24 | # 25 | # Use with: --config=asan 26 | 27 | build:asan --linkopt=-fsanitize=address 28 | build:asan --copt=-fsanitize=address 29 | ### FuzzTest build configuration. 30 | # 31 | # Use with: --config=fuzztest 32 | # 33 | # Note that this configuration includes the ASan configuration. 34 | 35 | build:fuzztest --config=asan 36 | build:fuzztest --config=fuzztest-common 37 | 38 | # Link statically. 39 | build:fuzztest --dynamic_mode=off 40 | 41 | # We rely on the following flag instead of the compiler provided 42 | # __has_feature(address_sanitizer) to know that we have an ASAN build even in 43 | # the uninstrumented runtime. 44 | build:fuzztest --copt=-DADDRESS_SANITIZER 45 | -------------------------------------------------------------------------------- /netkat/BUILD.bazel: -------------------------------------------------------------------------------- 1 | 2 | 3 | load("@com_google_gutil//gutil:diff_test.bzl", "cmd_diff_test") 4 | 5 | 6 | 7 | package( 8 | default_applicable_licenses = ["//:license"], 9 | default_visibility = [":__subpackages__"], 10 | ) 11 | 12 | proto_library( 13 | name = "netkat_proto", 14 | srcs = ["netkat.proto"], 15 | ) 16 | 17 | cc_proto_library( 18 | name = "netkat_cc_proto", 19 | deps = [":netkat_proto"], 20 | ) 21 | 22 | cc_library( 23 | name = "gtest_utils", 24 | testonly = 1, 25 | srcs = ["gtest_utils.cc"], 26 | hdrs = ["gtest_utils.h"], 27 | deps = [ 28 | ":frontend", 29 | ":netkat_cc_proto", 30 | "@com_google_fuzztest//fuzztest", 31 | "@com_google_protobuf//:protobuf", 32 | ], 33 | ) 34 | 35 | cc_library( 36 | name = "frontend", 37 | srcs = ["frontend.cc"], 38 | hdrs = ["frontend.h"], 39 | deps = [ 40 | ":netkat_cc_proto", 41 | ":netkat_proto_constructors", 42 | "@com_google_absl//absl/status", 43 | "@com_google_absl//absl/status:statusor", 44 | "@com_google_absl//absl/strings:string_view", 45 | "@com_google_gutil//gutil:status", 46 | ], 47 | ) 48 | 49 | cc_test( 50 | name = "frontend_test", 51 | srcs = ["frontend_test.cc"], 52 | shard_count = 8, 53 | deps = [ 54 | ":frontend", 55 | ":gtest_utils", 56 | ":netkat_cc_proto", 57 | ":netkat_proto_constructors", 58 | "@com_google_absl//absl/status", 59 | "@com_google_absl//absl/status:status_matchers", 60 | "@com_google_absl//absl/strings:string_view", 61 | "@com_google_fuzztest//fuzztest", 62 | "@com_google_googletest//:gtest_main", 63 | "@com_google_gutil//gutil:proto_matchers", 64 | "@com_google_gutil//gutil:status_matchers", 65 | ], 66 | ) 67 | 68 | cc_library( 69 | name = "table", 70 | srcs = ["table.cc"], 71 | hdrs = ["table.h"], 72 | deps = [ 73 | ":frontend", 74 | ":netkat_cc_proto", 75 | ":symbolic_packet", 76 | ":symbolic_packet_transformer", 77 | "@com_google_absl//absl/container:btree", 78 | "@com_google_absl//absl/log:check", 79 | "@com_google_absl//absl/status", 80 | "@com_google_absl//absl/strings", 81 | ], 82 | ) 83 | 84 | cc_test( 85 | name = "table_test", 86 | srcs = ["table_test.cc"], 87 | deps = [ 88 | ":frontend", 89 | ":gtest_utils", 90 | ":netkat_cc_proto", 91 | ":netkat_proto_constructors", 92 | ":table", 93 | "@com_google_absl//absl/status", 94 | "@com_google_fuzztest//fuzztest", 95 | "@com_google_googletest//:gtest_main", 96 | "@com_google_gutil//gutil:proto_matchers", 97 | "@com_google_gutil//gutil:status_matchers", 98 | ], 99 | ) 100 | 101 | cc_test( 102 | name = "netkat_test", 103 | srcs = ["netkat_test.cc"], 104 | shard_count = 8, 105 | deps = [ 106 | ":netkat_cc_proto", 107 | "@com_google_absl//absl/log", 108 | "@com_google_fuzztest//fuzztest", 109 | "@com_google_googletest//:gtest_main", 110 | ], 111 | ) 112 | 113 | cc_library( 114 | name = "symbolic_packet", 115 | srcs = ["symbolic_packet.cc"], 116 | hdrs = ["symbolic_packet.h"], 117 | deps = [ 118 | ":evaluator", 119 | ":interned_field", 120 | ":netkat_cc_proto", 121 | ":paged_stable_vector", 122 | "@com_google_absl//absl/container:fixed_array", 123 | "@com_google_absl//absl/container:flat_hash_map", 124 | "@com_google_absl//absl/container:flat_hash_set", 125 | "@com_google_absl//absl/log", 126 | "@com_google_absl//absl/log:check", 127 | "@com_google_absl//absl/status", 128 | "@com_google_absl//absl/strings", 129 | "@com_google_absl//absl/strings:str_format", 130 | "@com_google_gutil//gutil:status", 131 | ], 132 | ) 133 | 134 | cc_test( 135 | name = "symbolic_packet_test", 136 | srcs = ["symbolic_packet_test.cc"], 137 | shard_count = 8, 138 | deps = [ 139 | ":evaluator", 140 | ":netkat_proto_constructors", 141 | ":symbolic_packet", 142 | "@com_google_absl//absl/base:no_destructor", 143 | "@com_google_absl//absl/container:flat_hash_map", 144 | "@com_google_absl//absl/container:flat_hash_set", 145 | "@com_google_absl//absl/strings", 146 | "@com_google_fuzztest//fuzztest", 147 | "@com_google_googletest//:gtest_main", 148 | "@com_google_gutil//gutil:status_matchers", 149 | "@com_googlesource_code_re2//:re2", 150 | ], 151 | ) 152 | 153 | # go/golden-test-with-coverage 154 | cc_test( 155 | name = "symbolic_packet_test_runner", 156 | srcs = ["symbolic_packet_test_runner.cc"], 157 | linkstatic = True, 158 | deps = [ 159 | ":netkat_proto_constructors", 160 | ":symbolic_packet", 161 | ], 162 | ) 163 | 164 | cmd_diff_test( 165 | name = "symbolic_packet_diff_test", 166 | actual_cmd = "$(execpath :symbolic_packet_test_runner)", 167 | expected = ":symbolic_packet_test.expected", 168 | tools = [":symbolic_packet_test_runner"], 169 | ) 170 | 171 | cc_library( 172 | name = "evaluator", 173 | srcs = ["evaluator.cc"], 174 | hdrs = ["evaluator.h"], 175 | deps = [ 176 | ":netkat_cc_proto", 177 | "@com_google_absl//absl/container:flat_hash_map", 178 | "@com_google_absl//absl/container:flat_hash_set", 179 | "@com_google_absl//absl/log", 180 | ], 181 | ) 182 | 183 | cc_test( 184 | name = "evaluator_test", 185 | srcs = ["evaluator_test.cc"], 186 | shard_count = 8, 187 | deps = [ 188 | ":evaluator", 189 | ":netkat_cc_proto", 190 | ":netkat_proto_constructors", 191 | "@com_google_absl//absl/container:flat_hash_set", 192 | "@com_google_fuzztest//fuzztest", 193 | "@com_google_googletest//:gtest_main", 194 | ], 195 | ) 196 | 197 | cc_library( 198 | name = "netkat_proto_constructors", 199 | srcs = ["netkat_proto_constructors.cc"], 200 | hdrs = ["netkat_proto_constructors.h"], 201 | deps = [ 202 | ":netkat_cc_proto", 203 | "@com_google_absl//absl/strings", 204 | "@com_google_absl//absl/strings:str_format", 205 | "@com_google_absl//absl/strings:string_view", 206 | ], 207 | ) 208 | 209 | cc_test( 210 | name = "netkat_proto_constructors_test", 211 | srcs = ["netkat_proto_constructors_test.cc"], 212 | shard_count = 8, 213 | deps = [ 214 | ":netkat_cc_proto", 215 | ":netkat_proto_constructors", 216 | "@com_google_absl//absl/strings:string_view", 217 | "@com_google_fuzztest//fuzztest", 218 | "@com_google_googletest//:gtest_main", 219 | "@com_google_gutil//gutil:proto_matchers", 220 | ], 221 | ) 222 | 223 | cc_library( 224 | name = "interned_field", 225 | srcs = ["interned_field.cc"], 226 | hdrs = ["interned_field.h"], 227 | deps = [ 228 | "@com_google_absl//absl/container:flat_hash_map", 229 | "@com_google_absl//absl/log", 230 | "@com_google_absl//absl/status", 231 | "@com_google_absl//absl/strings:str_format", 232 | "@com_google_absl//absl/strings:string_view", 233 | "@com_google_gutil//gutil:status", 234 | ], 235 | ) 236 | 237 | cc_test( 238 | name = "interned_field_test", 239 | srcs = ["interned_field_test.cc"], 240 | shard_count = 4, 241 | deps = [ 242 | ":interned_field", 243 | "@com_google_absl//absl/base:no_destructor", 244 | "@com_google_absl//absl/container:flat_hash_set", 245 | "@com_google_absl//absl/strings", 246 | "@com_google_googletest//:gtest_main", 247 | "@com_google_gutil//gutil:status_matchers", 248 | ], 249 | ) 250 | 251 | cc_library( 252 | name = "paged_stable_vector", 253 | hdrs = ["paged_stable_vector.h"], 254 | ) 255 | 256 | cc_library( 257 | name = "analysis_engine", 258 | srcs = ["analysis_engine.cc"], 259 | hdrs = ["analysis_engine.h"], 260 | deps = [ 261 | ":frontend", 262 | ":symbolic_packet_transformer", 263 | ], 264 | ) 265 | 266 | cc_test( 267 | name = "analysis_engine_test", 268 | srcs = ["analysis_engine_test.cc"], 269 | shard_count = 2, 270 | deps = [ 271 | ":analysis_engine", 272 | ":frontend", 273 | "@com_google_googletest//:gtest_main", 274 | ], 275 | ) 276 | 277 | cc_library( 278 | name = "symbolic_packet_transformer", 279 | srcs = ["symbolic_packet_transformer.cc"], 280 | hdrs = ["symbolic_packet_transformer.h"], 281 | deps = [ 282 | ":evaluator", 283 | ":interned_field", 284 | ":netkat_cc_proto", 285 | ":paged_stable_vector", 286 | ":symbolic_packet", 287 | "@com_google_absl//absl/algorithm:container", 288 | "@com_google_absl//absl/container:btree", 289 | "@com_google_absl//absl/container:flat_hash_map", 290 | "@com_google_absl//absl/container:flat_hash_set", 291 | "@com_google_absl//absl/functional:any_invocable", 292 | "@com_google_absl//absl/log", 293 | "@com_google_absl//absl/log:check", 294 | "@com_google_absl//absl/status", 295 | "@com_google_absl//absl/strings", 296 | "@com_google_absl//absl/strings:str_format", 297 | "@com_google_absl//absl/strings:string_view", 298 | "@com_google_gutil//gutil:status", 299 | ], 300 | ) 301 | 302 | cc_test( 303 | name = "paged_stable_vector_test", 304 | srcs = ["paged_stable_vector_test.cc"], 305 | shard_count = 4, 306 | deps = [ 307 | ":paged_stable_vector", 308 | "@com_google_fuzztest//fuzztest", 309 | "@com_google_googletest//:gtest_main", 310 | ], 311 | ) 312 | 313 | cc_test( 314 | name = "symbolic_packet_transformer_test", 315 | srcs = ["symbolic_packet_transformer_test.cc"], 316 | shard_count = 5, 317 | deps = [ 318 | ":evaluator", 319 | ":netkat_cc_proto", 320 | ":netkat_proto_constructors", 321 | ":symbolic_packet", 322 | ":symbolic_packet_transformer", 323 | "@com_google_absl//absl/base:no_destructor", 324 | "@com_google_absl//absl/container:flat_hash_map", 325 | "@com_google_absl//absl/container:flat_hash_set", 326 | "@com_google_absl//absl/log", 327 | "@com_google_absl//absl/strings", 328 | "@com_google_fuzztest//fuzztest", 329 | "@com_google_googletest//:gtest_main", 330 | "@com_google_gutil//gutil:status_matchers", 331 | "@com_googlesource_code_re2//:re2", 332 | ], 333 | ) 334 | 335 | # go/golden-test-with-coverage 336 | cc_test( 337 | name = "symbolic_packet_transformer_test_runner", 338 | srcs = ["symbolic_packet_transformer_test_runner.cc"], 339 | linkstatic = True, 340 | deps = [ 341 | ":netkat_proto_constructors", 342 | ":symbolic_packet_transformer", 343 | ], 344 | ) 345 | 346 | cmd_diff_test( 347 | name = "symbolic_packet_transformer_diff_test", 348 | actual_cmd = "$(execpath :symbolic_packet_transformer_test_runner)", 349 | expected = ":symbolic_packet_transformer_test.expected", 350 | tools = [":symbolic_packet_transformer_test_runner"], 351 | ) 352 | -------------------------------------------------------------------------------- /netkat/analysis_engine.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "netkat/analysis_engine.h" 16 | 17 | #include "netkat/frontend.h" 18 | 19 | namespace netkat { 20 | 21 | bool AnalysisEngine::CheckEquivalent(const Predicate& left, 22 | const Predicate& right) { 23 | return packet_transformer_manager_.Compile(Filter(left).ToProto()) == 24 | packet_transformer_manager_.Compile(Filter(right).ToProto()); 25 | } 26 | 27 | bool AnalysisEngine::CheckEquivalent(const Policy& left, const Policy& right) { 28 | return packet_transformer_manager_.Compile(left.ToProto()) == 29 | packet_transformer_manager_.Compile(right.ToProto()); 30 | } 31 | 32 | } // namespace netkat 33 | -------------------------------------------------------------------------------- /netkat/analysis_engine.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // ----------------------------------------------------------------------------- 16 | // File: analysis_engine.h 17 | // ----------------------------------------------------------------------------- 18 | // 19 | // The user-facing API for reasoning about NetKAT policies and predicates. 20 | 21 | #ifndef GOOGLE_NETKAT_NETKAT_ANALYSIS_ENGINE_H_ 22 | #define GOOGLE_NETKAT_NETKAT_ANALYSIS_ENGINE_H_ 23 | 24 | #include "netkat/frontend.h" 25 | #include "netkat/symbolic_packet_transformer.h" 26 | 27 | namespace netkat { 28 | 29 | // Class for reasoning about NetKAT policies and predicates. 30 | // 31 | // This is a class rather than a namespace of free functions as the class 32 | // maintains state to optimize the speed of repeated calls. The state is not 33 | // intended to have any effect on functional behavior. 34 | // 35 | // TODO(b/398303840): Persistent use of an `AnalysisEngine` object can incur 36 | // unbounded memory growth. Consider adding some garbage collection mechanism. 37 | class AnalysisEngine { 38 | public: 39 | // Checks whether two predicates are "equivalent", meaning they match the same 40 | // set of packets, meaning `Evaluate(left, packet) == Evaluate(right, packet)` 41 | // for all packets. 42 | bool CheckEquivalent(const Predicate& left, const Predicate& right); 43 | 44 | // Checks whether two policies are "equivalent", meaning they have the same 45 | // packets transformations, meaning Evaluate(left, packet) == Evaluate(right, 46 | // packet) for all packets. 47 | bool CheckEquivalent(const Policy& left, const Policy& right); 48 | 49 | private: 50 | SymbolicPacketTransformerManager packet_transformer_manager_; 51 | }; 52 | 53 | } // namespace netkat 54 | 55 | #endif // GOOGLE_NETKAT_NETKAT_ANALYSIS_ENGINE_H_ 56 | -------------------------------------------------------------------------------- /netkat/analysis_engine_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "netkat/analysis_engine.h" 16 | 17 | #include "gtest/gtest.h" 18 | #include "netkat/frontend.h" 19 | 20 | namespace netkat { 21 | namespace { 22 | 23 | // We include only a single `CheckEquivalent` test as a smoke test since the 24 | // function is implemented in terms of `SymbolicPacketManager`, which is tested 25 | // thoroughly in its own unit tests. 26 | TEST(AnalysisEngineTest, CheckEquivalentSmokeTests) { 27 | AnalysisEngine analyzer; 28 | 29 | // Check that true and false are equivalent to themselves but not each other. 30 | EXPECT_TRUE(analyzer.CheckEquivalent(Predicate::True(), Predicate::True())); 31 | EXPECT_TRUE(analyzer.CheckEquivalent(Predicate::False(), Predicate::False())); 32 | EXPECT_FALSE(analyzer.CheckEquivalent(Predicate::True(), Predicate::False())); 33 | EXPECT_FALSE(analyzer.CheckEquivalent(Predicate::False(), Predicate::True())); 34 | 35 | // Check that some simple predicates are equivalent to themselves but not each 36 | // other. 37 | const Predicate p1 = Match("port", 1) && Match("vlan", 10); 38 | const Predicate p2 = Match("port", 2); 39 | EXPECT_TRUE(analyzer.CheckEquivalent(p1, p1)); 40 | EXPECT_TRUE(analyzer.CheckEquivalent(p2, p2)); 41 | EXPECT_FALSE(analyzer.CheckEquivalent(p1, p2)); 42 | EXPECT_FALSE(analyzer.CheckEquivalent(p2, p1)); 43 | 44 | // Check some properties of negations. 45 | EXPECT_TRUE(analyzer.CheckEquivalent(!Predicate::True(), Predicate::False())); 46 | EXPECT_TRUE(analyzer.CheckEquivalent(!Predicate::False(), Predicate::True())); 47 | EXPECT_TRUE(analyzer.CheckEquivalent(!!p1, p1)); 48 | 49 | // Check De Morgan's laws. 50 | EXPECT_TRUE(analyzer.CheckEquivalent(!(p1 && p2), !p1 || !p2)); 51 | EXPECT_TRUE(analyzer.CheckEquivalent(!(p1 || p2), !p1 && !p2)); 52 | } 53 | 54 | TEST(AnalysisEngineTest, CheckPolicyEquivalentSmokeTests) { 55 | AnalysisEngine analyzer; 56 | // Checks Deny and Accept are equivalent to themselves but not each other. 57 | EXPECT_TRUE(analyzer.CheckEquivalent(Policy::Deny(), Policy::Deny())); 58 | EXPECT_TRUE(analyzer.CheckEquivalent(Policy::Accept(), Policy::Accept())); 59 | EXPECT_FALSE(analyzer.CheckEquivalent(Policy::Accept(), Policy::Deny())); 60 | EXPECT_FALSE(analyzer.CheckEquivalent(Policy::Deny(), Policy::Accept())); 61 | 62 | // Checks different policies are equivalent to themselves but are not 63 | // equivalent to each other. 64 | const Policy p1 = Sequence(Filter(Match("port", 10)), Modify("port", 20)); 65 | const Policy p2 = Sequence(Filter(Match("switch", 42)), Modify("port", 21)); 66 | EXPECT_TRUE(analyzer.CheckEquivalent(p1, p1)); 67 | EXPECT_TRUE(analyzer.CheckEquivalent(p2, p2)); 68 | EXPECT_FALSE(analyzer.CheckEquivalent(p1, p2)); 69 | EXPECT_FALSE(analyzer.CheckEquivalent(p2, p1)); 70 | 71 | // Checks Union of Policies are commutative. 72 | EXPECT_TRUE(analyzer.CheckEquivalent(Union(p1, p2), Union(p2, p1))); 73 | 74 | // Check Union of Policies are associative. 75 | const Policy p3 = Filter(Match("dst_mac", 30)); 76 | EXPECT_TRUE(analyzer.CheckEquivalent(Union(p1, Union(p2, p3)), 77 | Union(Union(p1, p2), p3))); 78 | 79 | // Some Sequence of Policies are not commutative: modifying the same field 80 | // with different values in different order would result in different 81 | // policies. 82 | const Policy modify_port_1 = Modify("port", 10); 83 | const Policy modify_port_2 = Modify("port", 20); 84 | EXPECT_FALSE( 85 | analyzer.CheckEquivalent(Sequence(modify_port_1, modify_port_2), 86 | Sequence(modify_port_2, modify_port_1))); 87 | 88 | // Sequence(p1, p2) can be equivalent to Sequence(p2, p1) depending 89 | // on what's p1 and p2: e.g., modifying different fields in different order 90 | // would result in equivalent policies. 91 | // This is the PA-MOD-MOD-COMM axiom in the NetKAT paper. 92 | const Policy modify_dst_mac = Modify("dst_mac", 1); 93 | EXPECT_TRUE( 94 | analyzer.CheckEquivalent(Sequence(modify_dst_mac, modify_port_1), 95 | Sequence(modify_port_1, modify_dst_mac))); 96 | } 97 | 98 | // A netkat::Policy representing the given topology 99 | // [Switch 1] -----> [Switch 2] -----> [Switch 3] 100 | // - Packets start from Switch 1 are forwarded to Switch 2 and then to Switch 3. 101 | // - Packets start from Switch 2 are also forwarded to Switch 3. 102 | // - Packets start from non-existing switch are denied. 103 | TEST(AnalysisEngineTest, TopologyTraversalIsAccepted) { 104 | Policy s1_to_s2 = Sequence(Filter(Match("switch", 1)), Modify("switch", 2)); 105 | Policy s2_to_s3 = Sequence(Filter(Match("switch", 2)), Modify("switch", 3)); 106 | Policy topology = Union(s1_to_s2, s2_to_s3); 107 | AnalysisEngine analyzer; 108 | 109 | Policy traverse_topo_from_s1 = Sequence( 110 | Modify("switch", 1), Iterate(topology), Filter(Match("switch", 3))); 111 | EXPECT_TRUE( 112 | analyzer.CheckEquivalent(traverse_topo_from_s1, Modify("switch", 3))); 113 | 114 | Policy traverse_topo_from_s2 = Sequence( 115 | Modify("switch", 2), Iterate(topology), Filter(Match("switch", 3))); 116 | EXPECT_TRUE( 117 | analyzer.CheckEquivalent(traverse_topo_from_s2, Modify("switch", 3))); 118 | 119 | Policy traverse_from_non_existing_switch = Sequence( 120 | Modify("switch", 42), Iterate(topology), Filter(Match("switch", 3))); 121 | EXPECT_TRUE(analyzer.CheckEquivalent(traverse_from_non_existing_switch, 122 | Policy::Deny())); 123 | } 124 | 125 | } // namespace 126 | } // namespace netkat 127 | -------------------------------------------------------------------------------- /netkat/evaluator.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // ----------------------------------------------------------------------------- 15 | 16 | #include "netkat/evaluator.h" 17 | 18 | #include "absl/container/flat_hash_set.h" 19 | #include "absl/log/log.h" 20 | #include "netkat/netkat.pb.h" 21 | 22 | namespace netkat { 23 | 24 | bool Evaluate(const PredicateProto& predicate, const Packet& packet) { 25 | switch (predicate.predicate_case()) { 26 | case PredicateProto::kBoolConstant: 27 | return predicate.bool_constant().value(); 28 | case PredicateProto::kNotOp: 29 | return !Evaluate(predicate.not_op().negand(), packet); 30 | case PredicateProto::kAndOp: 31 | return Evaluate(predicate.and_op().left(), packet) && 32 | Evaluate(predicate.and_op().right(), packet); 33 | case PredicateProto::kOrOp: 34 | return Evaluate(predicate.or_op().left(), packet) || 35 | Evaluate(predicate.or_op().right(), packet); 36 | case PredicateProto::kXorOp: 37 | return (!Evaluate(predicate.xor_op().left(), packet) && 38 | Evaluate(predicate.xor_op().right(), packet)) || 39 | (Evaluate(predicate.xor_op().left(), packet) && 40 | !Evaluate(predicate.xor_op().right(), packet)); 41 | case PredicateProto::kMatch: 42 | if (auto iter = packet.find(predicate.match().field()); 43 | iter != packet.end()) { 44 | return iter->second == predicate.match().value(); 45 | } else { 46 | return false; 47 | } 48 | case PredicateProto::PREDICATE_NOT_SET: 49 | return false; 50 | } 51 | LOG(FATAL) << "Unexpected value for PredicateProto predicate_case: " 52 | << static_cast(predicate.predicate_case()); 53 | } 54 | 55 | absl::flat_hash_set Evaluate( 56 | const PolicyProto& policy, const absl::flat_hash_set& packets) { 57 | absl::flat_hash_set result; 58 | for (const Packet& packet : packets) { 59 | result.merge(Evaluate(policy, packet)); 60 | } 61 | return result; 62 | } 63 | 64 | absl::flat_hash_set Evaluate(const PolicyProto& policy, 65 | const Packet& packet) { 66 | switch (policy.policy_case()) { 67 | case PolicyProto::kFilter: 68 | return Evaluate(policy.filter(), packet) 69 | ? absl::flat_hash_set({packet}) 70 | : absl::flat_hash_set(); 71 | case PolicyProto::kModification: { 72 | Packet modified_packet = packet; 73 | // Adds field if it doesn't exist, and modifies it otherwise. 74 | modified_packet[policy.modification().field()] = 75 | policy.modification().value(); 76 | return {modified_packet}; 77 | } 78 | case PolicyProto::kRecord: 79 | // Record is treated as a no-op. 80 | return {packet}; 81 | case PolicyProto::kSequenceOp: 82 | return Evaluate(policy.sequence_op().right(), 83 | Evaluate(policy.sequence_op().left(), packet)); 84 | case PolicyProto::kUnionOp: { 85 | absl::flat_hash_set result = 86 | Evaluate(policy.union_op().left(), packet); 87 | result.merge(Evaluate(policy.union_op().right(), packet)); 88 | return result; 89 | } 90 | case PolicyProto::kIterateOp: { 91 | // p* = 1 + p + p;p + p;p;p + ... 92 | absl::flat_hash_set result = {packet}; // 1 93 | // Evaluate p on result until fixed point, marked by no change in size. 94 | int last_size; 95 | do { 96 | last_size = result.size(); 97 | result.merge(Evaluate(policy.iterate_op().iterable(), result)); // p^n 98 | } while (last_size != result.size()); 99 | return result; 100 | } 101 | case PolicyProto::POLICY_NOT_SET: 102 | // Unset policy is treated as Deny. 103 | return {}; 104 | } 105 | } 106 | 107 | } // namespace netkat 108 | -------------------------------------------------------------------------------- /netkat/evaluator.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // ----------------------------------------------------------------------------- 16 | // File: evaluator.h 17 | // ----------------------------------------------------------------------------- 18 | // 19 | // Defines a library of functions for evaluating NetKAT predicates and policies 20 | // on concrete packets. 21 | // 22 | // See go/netkat-hld for more details. 23 | 24 | #ifndef GOOGLE_NETKAT_NETKAT_EVALUATOR_H_ 25 | #define GOOGLE_NETKAT_NETKAT_EVALUATOR_H_ 26 | 27 | #include 28 | 29 | #include "absl/container/flat_hash_map.h" 30 | #include "absl/container/flat_hash_set.h" 31 | #include "netkat/netkat.pb.h" 32 | 33 | namespace netkat { 34 | 35 | // A NetKAT packet is a map from field names to their values. 36 | // 37 | // NOTE: This is a simplistic, initial definition of `Packet` that we expect to 38 | // replace in the future. In particular, the value type will change to support 39 | // things like 128-bit IPv6 addresses. 40 | // 41 | // Fields that are not present in the map are assumed to carry an implicit "not 42 | // present" value that is distinct from all explicitly-assignable values. 43 | using Packet = absl::flat_hash_map; 44 | 45 | // Returns true if the given `packet` satisfies the given `predicate`, false 46 | // otherwise. 47 | // 48 | // Note: Uninitialized predicates are considered unsatisfiable. 49 | bool Evaluate(const PredicateProto& predicate, const Packet& packet); 50 | 51 | // Returns the output packets produced by running the given policy on the given 52 | // input packet. Treats `Record` (aka `dup`) as no-op and does not keep track of 53 | // packet histories. 54 | // 55 | // Note: Uninitialized policies are treated as Deny, returning the empty set. 56 | absl::flat_hash_set Evaluate(const PolicyProto& policy, 57 | const Packet& packet); 58 | 59 | // Lifts policy evaluation to sets of packets. 60 | absl::flat_hash_set Evaluate( 61 | const PolicyProto& policy, const absl::flat_hash_set& packets); 62 | 63 | } // namespace netkat 64 | 65 | #endif // GOOGLE_NETKAT_NETKAT_EVALUATOR_H_ 66 | -------------------------------------------------------------------------------- /netkat/evaluator_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // ----------------------------------------------------------------------------- 15 | 16 | #include "netkat/evaluator.h" 17 | 18 | #include "absl/container/flat_hash_set.h" 19 | #include "fuzztest/fuzztest.h" 20 | #include "gmock/gmock.h" 21 | #include "gtest/gtest.h" 22 | #include "netkat/netkat.pb.h" 23 | #include "netkat/netkat_proto_constructors.h" 24 | 25 | namespace netkat { 26 | namespace { 27 | 28 | using ::fuzztest::Arbitrary; 29 | using ::fuzztest::InRange; 30 | using ::testing::ContainerEq; 31 | using ::testing::IsEmpty; 32 | using ::testing::IsSupersetOf; 33 | using ::testing::UnorderedElementsAre; 34 | 35 | /*--- Basic predicate properties ---------------------------------------------*/ 36 | 37 | void TrueIsTrueOnAnyPackets(Packet packet) { 38 | EXPECT_TRUE(Evaluate(TrueProto(), packet)); 39 | } 40 | FUZZ_TEST(EvaluatePredicateProtoTest, TrueIsTrueOnAnyPackets); 41 | 42 | void FalseIsFalseOnAnyPackets(Packet packet) { 43 | EXPECT_FALSE(Evaluate(FalseProto(), packet)); 44 | } 45 | FUZZ_TEST(EvaluatePredicateProtoTest, FalseIsFalseOnAnyPackets); 46 | 47 | void EmptyPredicateIsFalseOnAnyPackets(Packet packet) { 48 | EXPECT_FALSE(Evaluate(PredicateProto(), packet)); 49 | } 50 | FUZZ_TEST(EvaluatePredicateProtoTest, EmptyPredicateIsFalseOnAnyPackets); 51 | 52 | void NotIsLogicalNot(Packet packet, PredicateProto negand) { 53 | EXPECT_EQ(Evaluate(NotProto(negand), packet), !Evaluate(negand, packet)); 54 | } 55 | FUZZ_TEST(EvaluatePredicateProtoTest, NotIsLogicalNot); 56 | 57 | void MatchOnlyMatchesPacketsWithCorrectValueAndField(Packet packet, 58 | std::string field, 59 | int value) { 60 | packet[field] = value; 61 | EXPECT_TRUE(Evaluate(MatchProto(field, value), packet)); 62 | 63 | packet[field] = ~value; 64 | EXPECT_FALSE(Evaluate(MatchProto(field, value), packet)); 65 | 66 | packet.erase(field); 67 | EXPECT_FALSE(Evaluate(MatchProto(field, value), packet)); 68 | } 69 | FUZZ_TEST(EvaluatePredicateProtoTest, 70 | MatchOnlyMatchesPacketsWithCorrectValueAndField); 71 | 72 | void AndIsLogicalAnd(Packet packet, PredicateProto left, PredicateProto right) { 73 | EXPECT_EQ(Evaluate(AndProto(left, right), packet), 74 | Evaluate(left, packet) && Evaluate(right, packet)); 75 | } 76 | FUZZ_TEST(EvaluatePredicateProtoTest, AndIsLogicalAnd); 77 | 78 | void OrIsLogicalOr(Packet packet, PredicateProto left, PredicateProto right) { 79 | EXPECT_EQ(Evaluate(OrProto(left, right), packet), 80 | Evaluate(left, packet) || Evaluate(right, packet)); 81 | } 82 | FUZZ_TEST(EvaluatePredicateProtoTest, OrIsLogicalOr); 83 | 84 | /*--- Boolean algebra axioms and equivalences --------------------------------*/ 85 | 86 | void PredOrItsNegationIsTrue(const Packet& packet, 87 | const PredicateProto& predicate) { 88 | EXPECT_TRUE(Evaluate(OrProto(predicate, NotProto(predicate)), packet)); 89 | } 90 | FUZZ_TEST(EvaluatePredicateProtoTest, PredOrItsNegationIsTrue); 91 | 92 | void PredAndItsNegationIsFalse(const Packet& packet, 93 | const PredicateProto& predicate) { 94 | EXPECT_FALSE(Evaluate(AndProto(predicate, NotProto(predicate)), packet)); 95 | } 96 | FUZZ_TEST(EvaluatePredicateProtoTest, PredAndItsNegationIsFalse); 97 | 98 | void AndIsIdempotent(const Packet& packet, const PredicateProto& predicate) { 99 | EXPECT_EQ(Evaluate(AndProto(predicate, predicate), packet), 100 | Evaluate(predicate, packet)); 101 | } 102 | FUZZ_TEST(EvaluatePredicateProtoTest, AndIsIdempotent); 103 | 104 | void AndTrueIsIdentity(const Packet& packet, const PredicateProto& predicate) { 105 | EXPECT_EQ(Evaluate(AndProto(predicate, TrueProto()), packet), 106 | Evaluate(predicate, packet)); 107 | } 108 | FUZZ_TEST(EvaluatePredicateProtoTest, AndTrueIsIdentity); 109 | 110 | void AndFalseIsFalse(const Packet& packet, const PredicateProto& predicate) { 111 | EXPECT_FALSE(Evaluate(AndProto(predicate, FalseProto()), packet)); 112 | } 113 | FUZZ_TEST(EvaluatePredicateProtoTest, AndFalseIsFalse); 114 | 115 | void AndIsCommutative(const Packet& packet, const PredicateProto& left, 116 | const PredicateProto& right) { 117 | EXPECT_EQ(Evaluate(AndProto(left, right), packet), 118 | Evaluate(AndProto(right, left), packet)); 119 | } 120 | FUZZ_TEST(EvaluatePredicateProtoTest, AndIsCommutative); 121 | 122 | void AndIsAssociative(const Packet& packet, const PredicateProto& left, 123 | const PredicateProto& middle, 124 | const PredicateProto& right) { 125 | EXPECT_EQ(Evaluate(AndProto(AndProto(left, middle), right), packet), 126 | Evaluate(AndProto(left, AndProto(middle, right)), packet)); 127 | } 128 | FUZZ_TEST(EvaluatePredicateProtoTest, AndIsAssociative); 129 | 130 | void OrIsIdempotent(const Packet& packet, const PredicateProto& predicate) { 131 | EXPECT_EQ(Evaluate(OrProto(predicate, predicate), packet), 132 | Evaluate(predicate, packet)); 133 | } 134 | FUZZ_TEST(EvaluatePredicateProtoTest, OrIsIdempotent); 135 | 136 | void OrFalseIsIdentity(const Packet& packet, const PredicateProto& predicate) { 137 | EXPECT_EQ(Evaluate(OrProto(predicate, FalseProto()), packet), 138 | Evaluate(predicate, packet)); 139 | } 140 | FUZZ_TEST(EvaluatePredicateProtoTest, OrFalseIsIdentity); 141 | 142 | void OrTrueIsTrue(const Packet& packet, const PredicateProto& predicate) { 143 | EXPECT_TRUE(Evaluate(OrProto(predicate, TrueProto()), packet)); 144 | } 145 | FUZZ_TEST(EvaluatePredicateProtoTest, OrTrueIsTrue); 146 | 147 | void OrIsCommutative(const Packet& packet, const PredicateProto& left, 148 | const PredicateProto& right) { 149 | EXPECT_EQ(Evaluate(OrProto(left, right), packet), 150 | Evaluate(OrProto(right, left), packet)); 151 | } 152 | FUZZ_TEST(EvaluatePredicateProtoTest, OrIsCommutative); 153 | 154 | void OrIsAssociative(const Packet& packet, const PredicateProto& left, 155 | const PredicateProto& middle, 156 | const PredicateProto& right) { 157 | EXPECT_EQ(Evaluate(OrProto(OrProto(left, middle), right), packet), 158 | Evaluate(OrProto(left, OrProto(middle, right)), packet)); 159 | } 160 | FUZZ_TEST(EvaluatePredicateProtoTest, OrIsAssociative); 161 | 162 | void XorFalseIsIdentity(const Packet& packet, const PredicateProto& predicate) { 163 | EXPECT_EQ(Evaluate(XorProto(predicate, FalseProto()), packet), 164 | Evaluate(predicate, packet)); 165 | } 166 | FUZZ_TEST(EvaluatePredicateProtoTest, XorFalseIsIdentity); 167 | 168 | void XorSelfIsFalse(const Packet& packet, const PredicateProto& pred) { 169 | EXPECT_FALSE(Evaluate(XorProto(pred, pred), packet)); 170 | } 171 | FUZZ_TEST(SymbolicPacketManagerTest, XorSelfIsFalse); 172 | 173 | void XorIsCommutative(const Packet& packet, const PredicateProto& left, 174 | PredicateProto right) { 175 | EXPECT_EQ(Evaluate(XorProto(left, right), packet), 176 | Evaluate(XorProto(right, left), packet)); 177 | } 178 | FUZZ_TEST(EvaluatePredicateProtoTest, XorIsCommutative); 179 | 180 | void XorIsAssociative(const Packet& packet, const PredicateProto& left, 181 | const PredicateProto& middle, PredicateProto right) { 182 | EXPECT_EQ(Evaluate(XorProto(XorProto(left, middle), right), packet), 183 | Evaluate(XorProto(left, XorProto(middle, right)), packet)); 184 | } 185 | FUZZ_TEST(EvaluatePredicateProtoTest, XorIsAssociative); 186 | 187 | void DistributiveLawHolds(const Packet& packet, const PredicateProto& first, 188 | const PredicateProto& second, 189 | const PredicateProto& third) { 190 | // (a || b) && c == (a && c) || (b && c) 191 | EXPECT_EQ(Evaluate(AndProto(OrProto(first, second), third), packet), 192 | Evaluate(OrProto(AndProto(first, third), AndProto(second, third)), 193 | packet)); 194 | 195 | // (a && b) || c == (a || c) && (b || c) 196 | EXPECT_EQ(Evaluate(OrProto(AndProto(first, second), third), packet), 197 | Evaluate(AndProto(OrProto(first, third), OrProto(second, third)), 198 | packet)); 199 | } 200 | FUZZ_TEST(EvaluatePredicateProtoTest, DistributiveLawHolds); 201 | 202 | void DeMorganHolds(const Packet& packet, const PredicateProto& left, 203 | const PredicateProto& right) { 204 | // Not(a && b) == Not(a) || Not(b) 205 | EXPECT_EQ(Evaluate(NotProto(AndProto(left, right)), packet), 206 | Evaluate(OrProto(NotProto(left), NotProto(right)), packet)); 207 | 208 | // Not(a || b) == Not(a) && Not(b) 209 | EXPECT_EQ(Evaluate(NotProto(OrProto(left, right)), packet), 210 | Evaluate(AndProto(NotProto(left), NotProto(right)), packet)); 211 | } 212 | FUZZ_TEST(EvaluatePredicateProtoTest, DeMorganHolds); 213 | 214 | /*--- Basic policy properties ------------------------------------------------*/ 215 | 216 | void LiftedEvaluationIsCorrect(absl::flat_hash_set packets, 217 | PolicyProto policy) { 218 | absl::flat_hash_set expected_packets; 219 | for (const Packet& packet : packets) { 220 | expected_packets.merge(Evaluate(policy, packet)); 221 | } 222 | EXPECT_THAT(Evaluate(policy, packets), ContainerEq(expected_packets)); 223 | } 224 | FUZZ_TEST(EvaluatePolicyProtoTest, LiftedEvaluationIsCorrect); 225 | 226 | void RecordIsAccept(Packet packet) { 227 | EXPECT_THAT(Evaluate(RecordProto(), packet), UnorderedElementsAre(packet)); 228 | } 229 | FUZZ_TEST(EvaluatePolicyProtoTest, RecordIsAccept); 230 | 231 | void UninitializedPolicyIsDeny(Packet packet) { 232 | EXPECT_THAT(Evaluate(PolicyProto(), packet), IsEmpty()); 233 | } 234 | FUZZ_TEST(EvaluatePolicyProtoTest, UninitializedPolicyIsDeny); 235 | 236 | void FilterIsCorrect(Packet packet, PredicateProto predicate) { 237 | if (Evaluate(predicate, packet)) { 238 | EXPECT_THAT(Evaluate(FilterProto(predicate), packet), 239 | UnorderedElementsAre(packet)); 240 | } else { 241 | EXPECT_THAT(Evaluate(FilterProto(predicate), packet), IsEmpty()); 242 | } 243 | } 244 | FUZZ_TEST(EvaluatePolicyProtoTest, FilterIsCorrect); 245 | 246 | void ModifyModifies(Packet packet, std::string field, int value) { 247 | Packet expected_packet = packet; 248 | expected_packet[field] = value; 249 | EXPECT_THAT(Evaluate(ModificationProto(field, value), packet), 250 | UnorderedElementsAre(expected_packet)); 251 | } 252 | FUZZ_TEST(EvaluatePolicyProtoTest, ModifyModifies); 253 | 254 | void UnionCombines(Packet packet, PolicyProto left, PolicyProto right) { 255 | absl::flat_hash_set expected_packets = Evaluate(left, packet); 256 | expected_packets.merge(Evaluate(right, packet)); 257 | 258 | EXPECT_THAT(Evaluate(UnionProto(left, right), packet), 259 | ContainerEq(expected_packets)); 260 | } 261 | FUZZ_TEST(EvaluatePolicyProtoTest, UnionCombines); 262 | 263 | void SequenceSequences(Packet packet, PolicyProto left, PolicyProto right) { 264 | absl::flat_hash_set expected_packets = 265 | Evaluate(right, Evaluate(left, packet)); 266 | 267 | EXPECT_THAT(Evaluate(SequenceProto(left, right), packet), 268 | ContainerEq(expected_packets)); 269 | } 270 | FUZZ_TEST(EvaluatePolicyProtoTest, SequenceSequences); 271 | 272 | PolicyProto UnionUpToNthPower(PolicyProto iterable, int n) { 273 | PolicyProto union_policy = AcceptProto(); 274 | PolicyProto next_sequence = iterable; 275 | for (int i = 1; i <= n; ++i) { 276 | union_policy = UnionProto(union_policy, next_sequence); 277 | next_sequence = SequenceProto(iterable, next_sequence); 278 | } 279 | return union_policy; 280 | } 281 | 282 | void IterateIsSupersetOfUnionOfNSequences(Packet packet, PolicyProto iterable, 283 | int n) { 284 | EXPECT_THAT(Evaluate(IterateProto(iterable), packet), 285 | IsSupersetOf(Evaluate(UnionUpToNthPower(iterable, n), packet))); 286 | } 287 | FUZZ_TEST(EvaluatePolicyProtoTest, IterateIsSupersetOfUnionOfNSequences) 288 | .WithDomains(/*packet=*/Arbitrary(), 289 | /*iterable=*/Arbitrary(), 290 | /*n=*/InRange(0, 100)); 291 | 292 | void IterateIsUnionOfNSequencesForSomeN(Packet packet, PolicyProto iterable) { 293 | absl::flat_hash_set iterate_output_packets = 294 | Evaluate(IterateProto(iterable), packet); 295 | 296 | // Evaluate successively larger unions until we find one that matches all 297 | // packets in `iterate_packets`. 298 | absl::flat_hash_set union_output_packets; 299 | int last_size; 300 | int n = 0; 301 | do { 302 | last_size = union_output_packets.size(); 303 | union_output_packets = Evaluate(UnionUpToNthPower(iterable, n++), packet); 304 | } while (iterate_output_packets != union_output_packets && 305 | union_output_packets.size() > last_size); 306 | 307 | EXPECT_THAT(iterate_output_packets, ContainerEq(union_output_packets)); 308 | } 309 | FUZZ_TEST(EvaluatePolicyProtoTest, IterateIsUnionOfNSequencesForSomeN); 310 | 311 | TEST(EvaluatePolicyProtoTest, SimpleIterateThroughFiltersAndModifies) { 312 | // f == 0; f:=1 + f == 1; f := 2 + f == 2; f := 3 313 | PolicyProto iterable = UnionProto( 314 | SequenceProto(FilterProto(MatchProto("f", 0)), ModificationProto("f", 1)), 315 | UnionProto(SequenceProto(FilterProto(MatchProto("f", 1)), 316 | ModificationProto("f", 2)), 317 | SequenceProto(FilterProto(MatchProto("f", 2)), 318 | ModificationProto("f", 3)))); 319 | 320 | // If the packet contains the field, then the output is the union of the 321 | // input and the modified packets. 322 | EXPECT_THAT(Evaluate(IterateProto(iterable), Packet({{"f", 0}})), 323 | UnorderedElementsAre(Packet({{"f", 0}}), Packet({{"f", 1}}), 324 | Packet({{"f", 2}}), Packet({{"f", 3}}))); 325 | 326 | // If the packet doesn't contain the field, then the only output is the 327 | // input. 328 | EXPECT_THAT(Evaluate(IterateProto(iterable), Packet()), 329 | UnorderedElementsAre(Packet())); 330 | } 331 | 332 | /*--- Advanced policy properties ---------------------------------------------*/ 333 | void ModifyThenMatchIsEquivalentToModify(Packet packet, std::string field, 334 | int value) { 335 | // f := n;f == n is equivalent to f := n. 336 | EXPECT_THAT(Evaluate(SequenceProto(ModificationProto(field, value), 337 | FilterProto(MatchProto(field, value))), 338 | packet), 339 | ContainerEq(Evaluate(ModificationProto(field, value), packet))); 340 | } 341 | FUZZ_TEST(EvaluatePolicyProtoTest, ModifyThenMatchIsEquivalentToModify); 342 | 343 | // TODO(dilo): Add tests for each of the NetKAT axioms. 344 | 345 | } // namespace 346 | } // namespace netkat 347 | -------------------------------------------------------------------------------- /netkat/frontend.cc: -------------------------------------------------------------------------------- 1 | #include "netkat/frontend.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "absl/status/status.h" 7 | #include "absl/status/statusor.h" 8 | #include "absl/strings/string_view.h" 9 | #include "gutil/status.h" 10 | #include "netkat/netkat.pb.h" 11 | #include "netkat/netkat_proto_constructors.h" 12 | namespace netkat { 13 | 14 | // Recursively checks whether `predicate_proto` is valid. 15 | absl::Status RecursivelyCheckIsValid(const PredicateProto& predicate_proto) { 16 | switch (predicate_proto.predicate_case()) { 17 | case PredicateProto::PREDICATE_NOT_SET: 18 | return absl::InvalidArgumentError("Unset Predicate case is invalid"); 19 | case PredicateProto::kMatch: 20 | if (predicate_proto.match().field().empty()) { 21 | return absl::InvalidArgumentError( 22 | "PredicateProto::Match::field is invalid because it is empty."); 23 | } 24 | return absl::OkStatus(); 25 | case PredicateProto::kBoolConstant: 26 | return absl::OkStatus(); 27 | case PredicateProto::kAndOp: { 28 | RETURN_IF_ERROR(RecursivelyCheckIsValid(predicate_proto.and_op().left())) 29 | .SetPrepend() 30 | << "PredicateProto::And's lhs is invalid: "; 31 | RETURN_IF_ERROR(RecursivelyCheckIsValid(predicate_proto.and_op().right())) 32 | .SetPrepend() 33 | << "PredicateProto::And's rhs is invalid: "; 34 | return absl::OkStatus(); 35 | } 36 | case PredicateProto::kOrOp: { 37 | RETURN_IF_ERROR(RecursivelyCheckIsValid(predicate_proto.or_op().left())) 38 | .SetPrepend() 39 | << "PredicateProto::Or's lhs is invalid: "; 40 | RETURN_IF_ERROR(RecursivelyCheckIsValid(predicate_proto.or_op().right())) 41 | .SetPrepend() 42 | << "PredicateProto::Or's rhs is invalid: "; 43 | return absl::OkStatus(); 44 | } 45 | case PredicateProto::kXorOp: { 46 | RETURN_IF_ERROR(RecursivelyCheckIsValid(predicate_proto.xor_op().left())) 47 | .SetPrepend() 48 | << "PredicateProto::Xor's lhs is invalid: "; 49 | RETURN_IF_ERROR(RecursivelyCheckIsValid(predicate_proto.xor_op().right())) 50 | .SetPrepend() 51 | << "PredicateProto::Xor's rhs is invalid: "; 52 | return absl::OkStatus(); 53 | } 54 | case PredicateProto::kNotOp: 55 | RETURN_IF_ERROR( 56 | RecursivelyCheckIsValid(predicate_proto.not_op().negand())) 57 | .SetPrepend() 58 | << "PredicateProto::Not's negand is invalid: "; 59 | return absl::OkStatus(); 60 | } 61 | } 62 | 63 | absl::StatusOr Predicate::FromProto(PredicateProto predicate_proto) { 64 | RETURN_IF_ERROR(RecursivelyCheckIsValid(predicate_proto)); 65 | return Predicate(std::move(predicate_proto)); 66 | } 67 | 68 | Predicate operator!(Predicate predicate) { 69 | return Predicate(NotProto(std::move(predicate).ToProto())); 70 | } 71 | 72 | Predicate operator&&(Predicate lhs, Predicate rhs) { 73 | return Predicate( 74 | AndProto(std::move(lhs).ToProto(), std::move(rhs).ToProto())); 75 | } 76 | 77 | Predicate operator||(Predicate lhs, Predicate rhs) { 78 | return Predicate(OrProto(std::move(lhs).ToProto(), std::move(rhs).ToProto())); 79 | } 80 | 81 | Predicate Xor(Predicate lhs, Predicate rhs) { 82 | return Predicate( 83 | XorProto(std::move(lhs).ToProto(), std::move(rhs).ToProto())); 84 | } 85 | 86 | Predicate Predicate::True() { return Predicate(TrueProto()); } 87 | 88 | Predicate Predicate::False() { return Predicate(FalseProto()); } 89 | 90 | Predicate Match(absl::string_view field, int value) { 91 | return Predicate(MatchProto(field, value)); 92 | } 93 | 94 | absl::Status RecursivelyCheckIsValid(const PolicyProto& policy_proto) { 95 | switch (policy_proto.policy_case()) { 96 | case PolicyProto::kFilter: 97 | return RecursivelyCheckIsValid(policy_proto.filter()); 98 | case PolicyProto::kModification: 99 | if (policy_proto.modification().field().empty()) { 100 | return absl::InvalidArgumentError( 101 | "PolicyProto::Modification::field is invalid because it is empty."); 102 | } 103 | return absl::OkStatus(); 104 | case PolicyProto::kRecord: 105 | return absl::OkStatus(); 106 | case PolicyProto::kSequenceOp: 107 | RETURN_IF_ERROR( 108 | RecursivelyCheckIsValid(policy_proto.sequence_op().left())) 109 | .SetPrepend() 110 | << "PolicyProto::SequenceOp::left is invalid: "; 111 | RETURN_IF_ERROR( 112 | RecursivelyCheckIsValid(policy_proto.sequence_op().right())) 113 | .SetPrepend() 114 | << "PolicyProto::SequenceOp::right is invalid: "; 115 | return absl::OkStatus(); 116 | case PolicyProto::kUnionOp: 117 | RETURN_IF_ERROR(RecursivelyCheckIsValid(policy_proto.union_op().left())) 118 | .SetPrepend() 119 | << "PolicyProto::UnionOp::left is invalid: "; 120 | RETURN_IF_ERROR(RecursivelyCheckIsValid(policy_proto.union_op().right())) 121 | .SetPrepend() 122 | << "PolicyProto::UnionOp::right is invalid: "; 123 | return absl::OkStatus(); 124 | case PolicyProto::kIterateOp: 125 | RETURN_IF_ERROR( 126 | RecursivelyCheckIsValid(policy_proto.iterate_op().iterable())) 127 | << "PolicyProto::Iterate::policy is invalid: "; 128 | return absl::OkStatus(); 129 | case PolicyProto::POLICY_NOT_SET: 130 | return absl::InvalidArgumentError("Unset Policy case is invalid"); 131 | } 132 | } 133 | absl::StatusOr Policy::FromProto(PolicyProto policy_proto) { 134 | RETURN_IF_ERROR(RecursivelyCheckIsValid(policy_proto)); 135 | return Policy(std::move(policy_proto)); 136 | } 137 | 138 | Policy Modify(absl::string_view field, int new_value) { 139 | return Policy(ModificationProto(field, new_value)); 140 | } 141 | 142 | Policy Sequence(std::vector policies) { 143 | if (policies.empty()) return Policy::Accept(); 144 | if (policies.size() == 1) return std::move(policies[0]); 145 | 146 | PolicyProto proto; 147 | PolicyProto::Sequence* root = proto.mutable_sequence_op(); 148 | for (int i = policies.size() - 1; i > 1; --i) { 149 | *root->mutable_right() = std::move(policies[i]).ToProto(); 150 | root = root->mutable_left()->mutable_sequence_op(); 151 | } 152 | *root->mutable_left() = std::move(policies[0]).ToProto(); 153 | *root->mutable_right() = std::move(policies[1]).ToProto(); 154 | return Policy(std::move(proto)); 155 | } 156 | 157 | Policy Union(std::vector policies) { 158 | if (policies.empty()) return Policy::Deny(); 159 | if (policies.size() == 1) return std::move(policies[0]); 160 | 161 | PolicyProto proto; 162 | PolicyProto::Union* root = proto.mutable_union_op(); 163 | for (int i = policies.size() - 1; i > 1; --i) { 164 | *root->mutable_right() = std::move(policies[i]).ToProto(); 165 | root = root->mutable_left()->mutable_union_op(); 166 | } 167 | *root->mutable_left() = std::move(policies[0]).ToProto(); 168 | *root->mutable_right() = std::move(policies[1]).ToProto(); 169 | return Policy(std::move(proto)); 170 | } 171 | 172 | Policy Iterate(Policy policy) { 173 | return Policy(IterateProto(std::move(policy).ToProto())); 174 | } 175 | 176 | Policy Record() { return Policy(RecordProto()); } 177 | 178 | Policy Filter(Predicate predicate) { 179 | return Policy(FilterProto(std::move(predicate).ToProto())); 180 | } 181 | 182 | Policy Policy::Accept() { return Filter(Predicate::True()); } 183 | 184 | Policy Policy::Deny() { return Filter(Predicate::False()); } 185 | 186 | } // namespace netkat 187 | -------------------------------------------------------------------------------- /netkat/frontend.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // ----------------------------------------------------------------------------- 16 | // File: frontend.h 17 | // ----------------------------------------------------------------------------- 18 | // 19 | // This file contains the definitions for the frontend facing NetKAT API. 20 | // This API is how a majority of users are expected to build and manipulate 21 | // NetKAT related policy. 22 | // 23 | // Under the hood, very minimal logic is performed at this stage. This API acts 24 | // as a set of convenient helpers to generate a valid intermediate proto 25 | // representation (IR). See `netkat.proto`. 26 | #ifndef GOOGLE_NETKAT_NETKAT_FRONTEND_H_ 27 | #define GOOGLE_NETKAT_NETKAT_FRONTEND_H_ 28 | 29 | #include 30 | #include 31 | 32 | #include "absl/status/statusor.h" 33 | #include "absl/strings/string_view.h" 34 | #include "netkat/netkat.pb.h" 35 | 36 | namespace netkat { 37 | 38 | // Represents a NetKAT predicate. 39 | // 40 | // In general terms, a NetKAT predicate is some Boolean combination of matches 41 | // on packets. Practically speaking, it is useful to think of predicates as a 42 | // "filter" on the sets of packets at some given point in a NetKAT program. 43 | // 44 | // TODO: b/377697348 - create and point to resources/tutorials for NetKAT. 45 | // 46 | // This class provides overloads, and therefore support, for the given boolean 47 | // operations: `&&`, `||` and `!`. These overloads follow conventional operation 48 | // precedence order and, as per the literature, logically behave as expected. 49 | // These overloads allow for convenient building of Predicates, for example: 50 | // 51 | // Predicate allowed_packets = 52 | // Match("port", 1) && Match("vlan", 10) || Match("dst_mac", X); 53 | // 54 | // NOTE: SHORT CIRCUITING DOES NOT OCCUR! The following equivalent statements 55 | // will generate differing protos: 56 | // 57 | // Predicate p1 = Predicate::True() || Predicate::False(); 58 | // Predicate p2 = Predicate::True(); 59 | // assert(!MessageDifferencer::Equivalent(p1.ToProto(), p2.ToProto()); 60 | // 61 | // Internally this class simply builds a `PredicateProto` and does not 62 | // *currently* perform any specific optimizations of the proto as it is built. 63 | class Predicate { 64 | public: 65 | // We currently only allow predicate construction through helpers, e.g. 66 | // `Match`, `True`, `False` or `Predicate::FromProto(...)`. 67 | Predicate() = delete; 68 | 69 | // Creates a Predicate from `predicate_proto`. 70 | // If `predicate_proto` is ill-formed, returns InvalidArgument error. 71 | // A `predicate_proto` is considered valid if: 72 | // - For scalar OneOf fields 73 | // - `bool_constant` is valid 74 | // - `match` is valid if `match::field` is not empty. 75 | // - For Recursive OneOf fields made up of PredicateProto, it is valid if 76 | // the member fields are present and valid. For example, `and_op` is 77 | // valid if `and_op::left` and `and_op::right` are valid. 78 | static absl::StatusOr FromProto(PredicateProto predicate_proto); 79 | 80 | // Returns the underlying IR proto. 81 | // 82 | // Users should generally not handle this proto directly. 83 | PredicateProto ToProto() const& { return predicate_; } 84 | PredicateProto ToProto() && { return std::move(predicate_); } 85 | 86 | // Returns a reference to the underlying IR proto. 87 | // 88 | // This reference will only be valid for either the lifetime of this class OR 89 | // until the object is moved. 90 | const PredicateProto& GetProto() const& { return predicate_; } 91 | 92 | // Logical operators. These perform exactly as expected, with the 93 | // exception of short circuiting. 94 | // 95 | // These objects by themselves are not intrinsically truthy, so a lack of 96 | // short circuiting will not generate semantically different programs. 97 | friend Predicate operator&&(Predicate lhs, Predicate rhs); 98 | friend Predicate operator||(Predicate lhs, Predicate rhs); 99 | friend Predicate operator!(Predicate predicate); 100 | friend Predicate Xor(Predicate lhs, Predicate rhs); 101 | 102 | // Predicates that conceptually represent a packet being universally accepted 103 | // or denied/droped. 104 | // 105 | // Concretely this is simply a constant `Predicate(true/false)`. 106 | static Predicate True(); 107 | static Predicate False(); 108 | 109 | // Match operation for a Predicate. See below for the full definition. We 110 | // utilize friend association to ensure program construction is well-formed. 111 | friend Predicate Match(absl::string_view, int); 112 | 113 | private: 114 | // Hide default proto construction to hinder building of ill-formed programs. 115 | explicit Predicate(PredicateProto pred) : predicate_(std::move(pred)) {} 116 | 117 | // Calling GetProto on an R-value predicate is at best inefficient and, more 118 | // likely, a bug. Use ToProto instead. 119 | const PredicateProto& GetProto() && = delete; 120 | 121 | PredicateProto predicate_; 122 | }; 123 | 124 | // Represents a match on some field in the NetKAT packet. This is typically 125 | // referred to as a "test" in the literature. 126 | // 127 | // Matches may be on any concrete packet field, switch local meta-fields, NetKAT 128 | // specific fields (e.g. location), or even arbitrary labels introduced only for 129 | // the specific programs. 130 | // 131 | // netkat::Match("ethertype", 0x0800) // L2 Header field. 132 | // netkat::Match("dst_ip", X) // L3 Header field. 133 | // netkat::Match("pkt_mark", Y) // OVS metadata field. 134 | // netkat::Match("switch", Z) // Location for the NetKAT Automata. 135 | // netkat::Match("has_foo", 0) // Custom program label. 136 | // 137 | // TODO: b/377704955 - Add type safety. 138 | // 139 | // Field verification is currently limited when using raw strings, both in type 140 | // safety and naming. Prefer to use either enum<>string mappings OR constants 141 | // rather than re-typing strings for field names. 142 | Predicate Match(absl::string_view field, int value); 143 | 144 | // Represents a NetKAT policy. 145 | // 146 | // A NetKAT policy, sometimes referred to as a NetKAT program, is the 147 | // over-arching type used to define network behavior. More formally, a NetKAT 148 | // policy is a logical combination of `Predicate`s, `Modify`s, `Record`s, etc. 149 | // 150 | // A `Predicate` on its own is sufficient to be a policy, see `Filter`, but 151 | // we combine these with actions, such as `Modify`, to fully realize a 152 | // network automata. 153 | // 154 | // Predicate at_src_link = Match("switch", 0) && Match("port", 0); 155 | // Policy go_to_dst = Sequence(Modify("switch", 1), Modify("port", 1)); 156 | // Policy link_action = Sequence(Filter(at_src_link), go_to_dst); 157 | // 158 | // In the above example we define some source link and an action, or policy, 159 | // that would send a packet to a given destination switch. The composition of 160 | // those two then builds an automata that is roughly "If the packet is at 161 | // switch0:port0, it is sent to switch1:port1." Though take note that we've only 162 | // built a unidirectional link policy here. 163 | class Policy { 164 | public: 165 | // Returns the underlying IR proto. 166 | // 167 | // Users should generally not handle this proto directly. 168 | PolicyProto ToProto() const& { return policy_; } 169 | PolicyProto ToProto() && { return std::move(policy_); } 170 | 171 | // Creates a Policy from `policy_proto`. 172 | // If `policy_proto` is ill-formed, returns InvalidArgument error. 173 | // A `policy_proto` is considered valid if: 174 | // - An empty PolicyProto is invalid. 175 | // - For scalar `policy` OneOf field: 176 | // - `record` is valid. 177 | // - `modification` is valid if `Modification::field` is not empty. 178 | // - `filter` is valid if `filter` is a valid PredicateProto (see 179 | // definition above). 180 | // - For recursive OneOf fields made up of PolicyProto(s), it is valid if 181 | // the member fields are present and valid. For example, `sequence_op` is 182 | // valid if `sequence_op::left` and `sequence_op::right` are valid. 183 | static absl::StatusOr FromProto(PolicyProto policy_proto); 184 | 185 | // Returns a reference to the underlying IR proto. 186 | // 187 | // This reference will only be valid for either the lifetime of this class OR 188 | // until `ToProto()&&` is called (moving the underlying reference), whichever 189 | // is sooner. 190 | const PolicyProto& GetProto() const& { return policy_; } 191 | 192 | // The set of operations that define a NetKAT policy. See below for each 193 | // operation's definition. We utilize friend association to ensure program 194 | // construction is well-formed. 195 | friend Policy Filter(Predicate); 196 | friend Policy Modify(absl::string_view, int); 197 | friend Policy Sequence(std::vector); 198 | friend Policy Union(std::vector); 199 | friend Policy Iterate(Policy); 200 | friend Policy Record(); 201 | 202 | // Policies that conceptually represent a program that should accept or 203 | // deny/drop all packets. 204 | static Policy Accept(); 205 | static Policy Deny(); 206 | 207 | private: 208 | // Hide default proto construction to hinder building of ill-formed programs. 209 | explicit Policy(PolicyProto policy) : policy_(std::move(policy)) {} 210 | 211 | // Calling GetProto on an R-value policy is at best inefficient and, more 212 | // likely, a bug. Use ToProto instead. 213 | const PolicyProto& GetProto() && = delete; 214 | 215 | // The underlying IR that has been built thus far. 216 | PolicyProto policy_; 217 | }; 218 | 219 | // Returns a policy that filters packets by `predicate`. 220 | Policy Filter(Predicate predicate); 221 | 222 | // Performs a modification on some `field` in the NetKAT packet. This is not 223 | // required to be a label that yet exists and may even be an arbitrary label. 224 | // 225 | // Similar to `Match` except this sets `field` to `new_value` while match 226 | // instead filters on the value. 227 | Policy Modify(absl::string_view field, int new_value); 228 | 229 | // Performs a left-to-right sequential composition of each policy in `policies`. 230 | // 231 | // For example, 232 | // 233 | // Sequence({p0, p1, p2, p3}) 234 | // 235 | // Is equivalent to 236 | // 237 | // Sequence({Sequence({Sequence({p0, p1}), p2}), p3}) 238 | // 239 | // Semantically this behaves like a function composition in which, for some 240 | // list p0...pn, we feed the preceeding program inputs into p0 and forward each 241 | // of p0's outputs into p1, we then forward each of p1's outputs into p2, etc. 242 | // 243 | // Note that this means Sequence(p0, p1) and Sequence(p1, p0) may be 244 | // semantically different as sequential composition is non-commutative. It is 245 | // however associative so Sequence(p0, Sequence(p1, p2)) is the same as 246 | // Sequence(Sequence(p0, p1), p2). 247 | // 248 | // Also note, an empty list will return the Accept policy, while a singular 249 | // entry will simply be the policy itself. 250 | Policy Sequence(std::vector policies); 251 | 252 | // Allows callers to Sequence policies without wrapping them in a list. Prefer 253 | // this overload when reasonble. For example, instead of 254 | // 255 | // Sequence({p0, p1, p2, p3}) 256 | // 257 | // Prefer 258 | // 259 | // Sequence(p0, p1, p2, p3) 260 | template 261 | Policy Sequence(T&&... policies) { 262 | return Sequence({std::forward(policies)...}); 263 | } 264 | 265 | // Performs a left-to-right set union of each policy in `policies`. For example, 266 | // 267 | // Union({p0, p1, p2, p3}); 268 | // 269 | // Is equivalent to 270 | // 271 | // Union({Union({Union({p0, p1}), p2}), p3}) 272 | // 273 | // Union is both associative and commutative. 274 | // 275 | // Note that an empty list will return the Deny policy, while a singular 276 | // entry will simply be the policy itself. 277 | Policy Union(std::vector policies); 278 | 279 | // Allows callers to Union policies without wrapping them in a list. Prefer 280 | // this overload when reasonble. For example, instead of 281 | // 282 | // Union({p0, p1, p2, p3}) 283 | // 284 | // Prefer 285 | // 286 | // Union(p0, p1, p2, p3) 287 | template 288 | Policy Union(T&&... policies) { 289 | return Union({std::forward(policies)...}); 290 | } 291 | 292 | // Iterates over the given policy 0 to many times. Also known as the Kleene 293 | // Star operation. Iterate may be otherwise defined as, 294 | // 295 | // Iterate(p) == Union(Policy::Accept(), p, Sequence(p,p), ...); 296 | // 297 | // For a practical example, we may assume some topology built of link actions. 298 | // E.g. 299 | // 300 | // Predicate at_src0_link0 = Match("switch", 0) && Match("port", 0); 301 | // Policy go_to_dst1 = Sequence(Modify("switch", 1), Modify("port", 1)); 302 | // Policy link_action0 = Sequence(Filter(at_src0_link0), go_to_dst1); 303 | // ... 304 | // Policy topology = Union(link_action0, link_action1, ...); 305 | // 306 | // We may then use `Iterate` to build a policy that "walks" all paths in the 307 | // network, reachable by some arbitrary switch. 308 | // 309 | // Policy set_any_port = Union(Modify("port", 0), Modify("port", 1), ...); 310 | // Policy walk_topology_from_x = 311 | // Sequence(Filter(Match("switch", X)), set_any_port, Iterate(topology)); 312 | Policy Iterate(Policy policy); 313 | 314 | // Records the packet into the packet history. Referred to as 'dup' in the 315 | // literature. 316 | // 317 | // It is necessary to emplace Record statements in a program wherever decisions 318 | // wish to be disambiguated/verified. 319 | // 320 | // TODO: b/377697348 - Enhance this comment with a simple example. 321 | Policy Record(); 322 | 323 | } // namespace netkat 324 | 325 | #endif // GOOGLE_NETKAT_NETKAT_FRONTEND_H_ 326 | -------------------------------------------------------------------------------- /netkat/frontend_test.cc: -------------------------------------------------------------------------------- 1 | #include "netkat/frontend.h" 2 | 3 | #include "absl/status/status.h" 4 | #include "absl/status/status_matchers.h" 5 | #include "absl/strings/string_view.h" 6 | #include "fuzztest/fuzztest.h" 7 | #include "gmock/gmock.h" 8 | #include "gtest/gtest.h" 9 | #include "gutil/proto_matchers.h" 10 | #include "gutil/status_matchers.h" // IWYU pragma: keep 11 | #include "netkat/gtest_utils.h" 12 | #include "netkat/netkat.pb.h" 13 | #include "netkat/netkat_proto_constructors.h" 14 | 15 | namespace netkat { 16 | namespace { 17 | 18 | using ::absl_testing::StatusIs; 19 | using ::fuzztest::ContainerOf; 20 | using ::gutil::EqualsProto; 21 | 22 | using ::netkat::netkat_test::ArbitraryValidPolicyProto; 23 | using ::netkat::netkat_test::ArbitraryValidPredicateProto; 24 | using ::netkat::netkat_test::AtomicDupFreePolicyDomain; 25 | using ::netkat::netkat_test::AtomicPredicateDomain; 26 | 27 | void MatchToProtoIsCorrect(absl::string_view field, int value) { 28 | EXPECT_THAT(Match(field, value).ToProto(), 29 | EqualsProto(MatchProto(field, value))); 30 | } 31 | FUZZ_TEST(FrontEndTest, MatchToProtoIsCorrect); 32 | 33 | void ExpectFromProtoCanParseValidProto(PredicateProto predicate_proto) { 34 | EXPECT_OK(Predicate::FromProto(predicate_proto)); 35 | } 36 | FUZZ_TEST(FrontEndTest, ExpectFromProtoCanParseValidProto) 37 | .WithDomains(ArbitraryValidPredicateProto()); 38 | 39 | // Returns an invalid PredicateProto based on `predicate_proto`, where 40 | // the set `predicate` will be mutated into an invalid state. 41 | PredicateProto InvalidPredicateProto(PredicateProto predicate_proto) { 42 | // For `predicate_proto` with PredicateProto as operand(s), an empty operand 43 | // makes `predicate_proto` invalid. 44 | switch (predicate_proto.predicate_case()) { 45 | case PredicateProto::kAndOp: 46 | predicate_proto.mutable_and_op()->clear_left(); 47 | break; 48 | case PredicateProto::kOrOp: 49 | predicate_proto.mutable_or_op()->clear_right(); 50 | break; 51 | case PredicateProto::kXorOp: 52 | predicate_proto.mutable_xor_op()->clear_left(); 53 | break; 54 | case PredicateProto::kNotOp: 55 | predicate_proto.mutable_not_op()->clear_negand(); 56 | break; 57 | // Match is invalid if `Match::field` is empty. 58 | case PredicateProto::kMatch: 59 | predicate_proto.mutable_match()->clear_field(); 60 | break; 61 | // Unset predicate is invalid. 62 | case PredicateProto::PREDICATE_NOT_SET: 63 | break; 64 | // Unset predicate is invalid. 65 | case PredicateProto::kBoolConstant: 66 | predicate_proto.Clear(); 67 | break; 68 | } 69 | return predicate_proto; 70 | } 71 | 72 | void ExpectFromProtoToFailWithInvalidPredicateProto( 73 | const PredicateProto& predicate_proto) { 74 | PredicateProto invalid_proto = InvalidPredicateProto(predicate_proto); 75 | EXPECT_THAT(Predicate::FromProto(invalid_proto), 76 | StatusIs(absl::StatusCode::kInvalidArgument)) 77 | << invalid_proto.DebugString(); 78 | } 79 | FUZZ_TEST(FrontEndTest, ExpectFromProtoToFailWithInvalidPredicateProto) 80 | .WithDomains(ArbitraryValidPredicateProto()); 81 | 82 | TEST(FrontEndTest, TrueToProtoIsCorrect) { 83 | EXPECT_THAT(Predicate::True().ToProto(), EqualsProto(TrueProto())); 84 | } 85 | 86 | TEST(FrontEndTest, FalseToProtoIsCorrect) { 87 | EXPECT_THAT(Predicate::False().ToProto(), EqualsProto(FalseProto())); 88 | } 89 | 90 | void NegateToProtoIsCorrect(Predicate predicate) { 91 | Predicate negand = !predicate; 92 | EXPECT_THAT(negand.ToProto(), EqualsProto(NotProto(predicate.ToProto()))); 93 | } 94 | FUZZ_TEST(FrontEndTest, NegateToProtoIsCorrect) 95 | .WithDomains(AtomicPredicateDomain()); 96 | 97 | void AndToProtoIsCorrect(Predicate lhs, Predicate rhs) { 98 | Predicate and_pred = lhs && rhs; 99 | EXPECT_THAT(and_pred.ToProto(), 100 | EqualsProto(AndProto(lhs.ToProto(), rhs.ToProto()))); 101 | } 102 | FUZZ_TEST(FrontEndTest, AndToProtoIsCorrect) 103 | .WithDomains(/*lhs=*/AtomicPredicateDomain(), 104 | /*rhs=*/AtomicPredicateDomain()); 105 | 106 | void OrToProtoIsCorrect(Predicate lhs, Predicate rhs) { 107 | Predicate or_pred = lhs || rhs; 108 | EXPECT_THAT(or_pred.ToProto(), 109 | EqualsProto(OrProto(lhs.ToProto(), rhs.ToProto()))); 110 | } 111 | FUZZ_TEST(FrontEndTest, OrToProtoIsCorrect) 112 | .WithDomains(/*lhs=*/AtomicPredicateDomain(), 113 | /*rhs=*/AtomicPredicateDomain()); 114 | 115 | void XorToProtoIsCorrect(Predicate lhs, Predicate rhs) { 116 | Predicate xor_pred = Xor(lhs, rhs); 117 | EXPECT_THAT(xor_pred.ToProto(), 118 | EqualsProto(XorProto(lhs.ToProto(), rhs.ToProto()))); 119 | } 120 | FUZZ_TEST(FrontEndTest, XorToProtoIsCorrect) 121 | .WithDomains(/*lhs=*/AtomicPredicateDomain(), 122 | /*rhs=*/AtomicPredicateDomain()); 123 | 124 | void OperationOrderIsPreserved(Predicate a, Predicate b, Predicate c) { 125 | Predicate abc = !(a || b) && c || a; 126 | EXPECT_THAT( 127 | abc.ToProto(), 128 | EqualsProto(OrProto( 129 | AndProto(NotProto(OrProto(a.ToProto(), b.ToProto())), c.ToProto()), 130 | a.ToProto()))); 131 | } 132 | FUZZ_TEST(FrontEndTest, OperationOrderIsPreserved) 133 | .WithDomains(/*a=*/AtomicPredicateDomain(), 134 | /*b=*/AtomicPredicateDomain(), 135 | /*c=*/AtomicPredicateDomain()); 136 | 137 | void ExpectFromProtoCanParseValidPolicyProto(const PolicyProto& policy_proto) { 138 | EXPECT_OK(Policy::FromProto(policy_proto)); 139 | } 140 | FUZZ_TEST(FrontEndTest, ExpectFromProtoCanParseValidPolicyProto) 141 | .WithDomains(ArbitraryValidPolicyProto()); 142 | 143 | void ExpectFromProtoToFailWithInvalidPolicyProto(PolicyProto policy_proto) { 144 | // For `policy_proto` with PolicyProto as operand(s), an empty operand 145 | // makes `policy_proto` invalid. 146 | switch (policy_proto.policy_case()) { 147 | case PolicyProto::kFilter: { 148 | PredicateProto invalid_predicate = 149 | InvalidPredicateProto(policy_proto.filter()); 150 | *policy_proto.mutable_filter() = invalid_predicate; 151 | break; 152 | } 153 | case PolicyProto::kModification: 154 | policy_proto.mutable_modification()->clear_field(); 155 | break; 156 | case PolicyProto::kRecord: 157 | GTEST_SKIP() << "Record PolicyProto is always valid."; 158 | break; 159 | case PolicyProto::kSequenceOp: 160 | policy_proto.mutable_sequence_op()->clear_left(); 161 | break; 162 | case PolicyProto::kUnionOp: 163 | policy_proto.mutable_union_op()->clear_right(); 164 | break; 165 | case PolicyProto::kIterateOp: 166 | policy_proto.mutable_iterate_op()->clear_iterable(); 167 | break; 168 | // Unset policy is invalid. 169 | case PolicyProto::POLICY_NOT_SET: 170 | break; 171 | } 172 | EXPECT_THAT(Policy::FromProto(policy_proto), 173 | StatusIs(absl::StatusCode::kInvalidArgument)) 174 | << policy_proto.DebugString(); 175 | } 176 | FUZZ_TEST(FrontEndTest, ExpectFromProtoToFailWithInvalidPolicyProto) 177 | .WithDomains(ArbitraryValidPolicyProto()); 178 | 179 | TEST(FrontEndTest, DenyToProtoIsCorrect) { 180 | EXPECT_THAT(Policy::Deny().ToProto(), EqualsProto(DenyProto())); 181 | } 182 | 183 | TEST(FrontEndTest, RecordToProtoIsCorrect) { 184 | EXPECT_THAT(Record().ToProto(), EqualsProto(RecordProto())); 185 | } 186 | 187 | void FilteredPredicateToProtoIsCorrect(Predicate predicate) { 188 | EXPECT_THAT(Filter(predicate).ToProto(), 189 | EqualsProto(FilterProto(predicate.ToProto()))); 190 | } 191 | FUZZ_TEST(FrontEndTest, FilteredPredicateToProtoIsCorrect) 192 | .WithDomains(/*predicate=*/AtomicPredicateDomain()); 193 | 194 | void ModifyToProtoIsCorrect(absl::string_view field, int value) { 195 | EXPECT_THAT(Modify(field, value).ToProto(), 196 | EqualsProto(ModificationProto(field, value))); 197 | } 198 | FUZZ_TEST(FrontEndTest, ModifyToProtoIsCorrect); 199 | 200 | void IterateToProtoIsCorrect(Policy policy) { 201 | EXPECT_THAT(Iterate(policy).ToProto(), 202 | EqualsProto(IterateProto(policy.ToProto()))); 203 | } 204 | FUZZ_TEST(FrontEndTest, IterateToProtoIsCorrect) 205 | .WithDomains(/*policy=*/AtomicDupFreePolicyDomain()); 206 | 207 | TEST(FrontEndTest, SequenceWithNoElementsIsAccept) { 208 | EXPECT_THAT(Sequence().ToProto(), EqualsProto(AcceptProto())); 209 | } 210 | 211 | void SequenceWithOneElementIsSelf(Policy policy) { 212 | EXPECT_THAT(Sequence(policy).ToProto(), EqualsProto(policy.ToProto())); 213 | } 214 | FUZZ_TEST(FrontEndTest, SequenceWithOneElementIsSelf) 215 | .WithDomains(/*policy=*/AtomicDupFreePolicyDomain()); 216 | 217 | void SequencePreservesOrder(std::vector policies) { 218 | if (policies.size() < 2) GTEST_SKIP(); 219 | 220 | Policy policy = Sequence(policies[0], policies[1]); 221 | PolicyProto expected_proto = 222 | SequenceProto(policies[0].ToProto(), policies[1].ToProto()); 223 | for (int i = 2; i < policies.size(); ++i) { 224 | policy = Sequence(policy, policies[i]); 225 | expected_proto = SequenceProto(expected_proto, policies[i].ToProto()); 226 | } 227 | 228 | EXPECT_THAT(Sequence(policies).ToProto(), EqualsProto(policy.ToProto())); 229 | EXPECT_THAT(policy.ToProto(), EqualsProto(expected_proto)); 230 | } 231 | FUZZ_TEST(FrontEndTest, SequencePreservesOrder) 232 | .WithDomains( 233 | /*policies=*/ContainerOf>( 234 | AtomicDupFreePolicyDomain()) 235 | .WithMinSize(2) 236 | .WithMaxSize(64)); // Limit the max size to avoid stack crash. 237 | 238 | void SequenceNArgsIsSameAsList(Policy a, Policy b, Policy c) { 239 | EXPECT_THAT(Sequence(a, b, c).ToProto(), 240 | EqualsProto(Sequence({a, b, c}).ToProto())); 241 | } 242 | FUZZ_TEST(FrontEndTest, SequenceNArgsIsSameAsList) 243 | .WithDomains( 244 | /*a=*/AtomicDupFreePolicyDomain(), 245 | /*b=*/AtomicDupFreePolicyDomain(), 246 | /*c=*/AtomicDupFreePolicyDomain()); 247 | 248 | TEST(FrontEndTest, UnionWithNoElementsIsDeny) { 249 | EXPECT_THAT(Union().ToProto(), EqualsProto(DenyProto())); 250 | } 251 | 252 | void UnionWithOneElementIsSelf(Policy policy) { 253 | EXPECT_THAT(Union(policy).ToProto(), EqualsProto(policy.ToProto())); 254 | } 255 | FUZZ_TEST(FrontEndTest, UnionWithOneElementIsSelf) 256 | .WithDomains(/*policy=*/AtomicDupFreePolicyDomain()); 257 | 258 | void UnionPreservesOrder(std::vector policies) { 259 | if (policies.size() < 2) GTEST_SKIP(); 260 | 261 | Policy policy = Union(policies[0], policies[1]); 262 | PolicyProto expected_proto = 263 | UnionProto(policies[0].ToProto(), policies[1].ToProto()); 264 | for (int i = 2; i < policies.size(); ++i) { 265 | policy = Union(policy, policies[i]); 266 | expected_proto = UnionProto(expected_proto, policies[i].ToProto()); 267 | } 268 | 269 | EXPECT_THAT(Union(policies).ToProto(), EqualsProto(policy.ToProto())); 270 | EXPECT_THAT(policy.ToProto(), EqualsProto(expected_proto)); 271 | } 272 | FUZZ_TEST(FrontEndTest, UnionPreservesOrder) 273 | .WithDomains( 274 | /*policies=*/ContainerOf>( 275 | AtomicDupFreePolicyDomain()) 276 | .WithMinSize(2) 277 | .WithMaxSize(64)); // Limit the max size to avoid stack crash. 278 | 279 | void UnionNArgsIsSameAsList(Policy a, Policy b, Policy c) { 280 | EXPECT_THAT(Union(a, b, c).ToProto(), 281 | EqualsProto(Union({a, b, c}).ToProto())); 282 | } 283 | FUZZ_TEST(FrontEndTest, UnionNArgsIsSameAsList) 284 | .WithDomains( 285 | /*a=*/AtomicDupFreePolicyDomain(), 286 | /*b=*/AtomicDupFreePolicyDomain(), 287 | /*c=*/AtomicDupFreePolicyDomain()); 288 | 289 | void MixedPolicyOperationsHasCorrectOrder(Policy a, Policy b, Policy c) { 290 | // Create an arbitrary policy, 291 | // p = ((a + b + c); c; record) + a; b; record 292 | Policy mixed_policy = 293 | Union(Sequence(Union(a, b, c), c, Record()), Sequence(a, b, Record())); 294 | 295 | // Should be equivalent to: ((((a+b) + c); c); record) + ((a;b); record) 296 | EXPECT_THAT( 297 | mixed_policy.ToProto(), 298 | EqualsProto(UnionProto( 299 | SequenceProto( 300 | SequenceProto( 301 | UnionProto(UnionProto(a.ToProto(), b.ToProto()), c.ToProto()), 302 | c.ToProto()), 303 | RecordProto()), 304 | SequenceProto(SequenceProto(a.ToProto(), b.ToProto()), 305 | RecordProto())))); 306 | } 307 | FUZZ_TEST(FrontEndTest, MixedPolicyOperationsHasCorrectOrder) 308 | .WithDomains( 309 | /*a=*/AtomicDupFreePolicyDomain(), 310 | /*b=*/AtomicDupFreePolicyDomain(), 311 | /*c=*/AtomicDupFreePolicyDomain()); 312 | 313 | } // namespace 314 | } // namespace netkat 315 | -------------------------------------------------------------------------------- /netkat/gtest_utils.cc: -------------------------------------------------------------------------------- 1 | #include "netkat/gtest_utils.h" 2 | 3 | #include "fuzztest/fuzztest.h" 4 | #include "google/protobuf/descriptor.h" 5 | #include "netkat/frontend.h" 6 | #include "netkat/netkat.pb.h" 7 | 8 | namespace netkat::netkat_test { 9 | 10 | using ::fuzztest::Arbitrary; 11 | using ::fuzztest::Just; 12 | using ::fuzztest::Map; 13 | using ::fuzztest::OneOf; 14 | 15 | namespace { 16 | 17 | template 18 | bool FieldTypeIs(const google::protobuf::FieldDescriptor* field) { 19 | return field->message_type() == T::descriptor(); 20 | }; 21 | 22 | } // namespace 23 | 24 | fuzztest::Domain ArbitraryValidPredicateProto() { 25 | return fuzztest::Arbitrary() 26 | // The domain will recursively set all fields. This ensures 27 | // PredicateProto will have its members PredicateProto set. 28 | .WithFieldsAlwaysSet() 29 | // The domain will ensure all PredicateProto::Match::field will be 30 | // non-empty. 31 | .WithProtobufFields( 32 | FieldTypeIs, 33 | fuzztest::Arbitrary().WithStringFieldAlwaysSet( 34 | "field", fuzztest::String().WithMinSize(1))); 35 | } 36 | 37 | fuzztest::Domain ArbitraryValidPolicyProto() { 38 | return fuzztest::Arbitrary() 39 | // The domain will recursively set all fields. This ensures 40 | // PolicyProto will have its members PolicyProto set. 41 | .WithFieldsAlwaysSet() 42 | // The domain will ensure all PolicyProto::Modification::field will be 43 | // non-empty. 44 | .WithProtobufFields(FieldTypeIs, 45 | fuzztest::Arbitrary() 46 | .WithStringFieldAlwaysSet( 47 | "field", fuzztest::String().WithMinSize(1))) 48 | .WithProtobufFields(FieldTypeIs, 49 | ArbitraryValidPredicateProto()); 50 | } 51 | 52 | fuzztest::Domain AtomicPredicateDomain() { 53 | return OneOf(Just(Predicate::True()), Just(Predicate::False()), 54 | Map(Match, Arbitrary(), Arbitrary())); 55 | } 56 | 57 | fuzztest::Domain AtomicDupFreePolicyDomain() { 58 | return OneOf(Map(Filter, AtomicPredicateDomain()), 59 | Map(Modify, Arbitrary(), Arbitrary())); 60 | } 61 | 62 | } // namespace netkat::netkat_test 63 | -------------------------------------------------------------------------------- /netkat/gtest_utils.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // ----------------------------------------------------------------------------- 16 | // File: gtest_utils.h 17 | // ----------------------------------------------------------------------------- 18 | // 19 | // This file contains useful functions/matchers to be used for NetKAT testing. 20 | // As such all definitions in this file are expected to be used exclusively in 21 | // tests. 22 | #ifndef GOOGLE_NETKAT_NETKAT_GTEST_UTILS_H_ 23 | #define GOOGLE_NETKAT_NETKAT_GTEST_UTILS_H_ 24 | 25 | #include "fuzztest/fuzztest.h" 26 | #include "netkat/frontend.h" 27 | 28 | namespace netkat::netkat_test { 29 | 30 | // Returns a FUZZ_TEST domain for an arbitrary valid PredicateProto. 31 | // See netkat::Predicate::FromProto for the definition of a valid 32 | // PredicateProto. 33 | // Nonetheless, invalid protos are accepted in the backend where empty is 34 | // defined to mean false. 35 | fuzztest::Domain ArbitraryValidPredicateProto(); 36 | 37 | // Returns a FUZZ_TEST domain for an arbitrary valid PolicyProto. 38 | // See netkat::Policy::FromProto for the definition of a valid PolicyProto. 39 | // Nonetheless, invalid protos are accepted in the backend where empty is 40 | // defined to mean DENY policy. 41 | fuzztest::Domain ArbitraryValidPolicyProto(); 42 | 43 | // Returns a FUZZ_TEST domain for an arbitrary, atomic Predicate. I.e., the 44 | // predicate may be any of: an arbitrary Match, or the True/False predicates. 45 | fuzztest::Domain AtomicPredicateDomain(); 46 | 47 | // Returns a FUZZ_TEST domain for an arbitrary, dup-free, atomic Policy. I.e., 48 | // the policy may be any of an arbitrary Modify or filtered, atomic predicate. 49 | fuzztest::Domain AtomicDupFreePolicyDomain(); 50 | 51 | } // namespace netkat::netkat_test 52 | 53 | #endif // GOOGLE_NETKAT_NETKAT_GTEST_UTILS_H_ 54 | -------------------------------------------------------------------------------- /netkat/interned_field.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "netkat/interned_field.h" 16 | 17 | #include 18 | 19 | #include "absl/log/log.h" 20 | #include "absl/status/status.h" 21 | #include "absl/strings/string_view.h" 22 | #include "gutil/status.h" 23 | 24 | namespace netkat { 25 | 26 | InternedField InternedFieldManager::GetOrCreateInternedField( 27 | absl::string_view field_name) { 28 | auto [it, inserted] = interned_field_by_name_.try_emplace( 29 | field_name, InternedField(field_names_.size())); 30 | if (inserted) field_names_.push_back(std::string(field_name)); 31 | return it->second; 32 | } 33 | 34 | std::string InternedFieldManager::GetFieldName(InternedField field) const { 35 | if (field.index_ >= field_names_.size()) { 36 | LOG(DFATAL) << "InternedFieldManager::GetFieldName: field index " 37 | << field.index_ 38 | << " out of bounds. Returning arbitrary string."; 39 | return "INTERNAL ERROR: InternedFieldManager::GetFieldName out of bounds"; 40 | } 41 | return field_names_[field.index_]; 42 | } 43 | 44 | absl::Status InternedFieldManager::CheckInternalInvariants() const { 45 | for (int i = 0; i < field_names_.size(); ++i) { 46 | auto it = interned_field_by_name_.find(field_names_[i]); 47 | RET_CHECK(it != interned_field_by_name_.end()); 48 | RET_CHECK(it->second.index_ == i); 49 | } 50 | 51 | for (const auto& [name, field] : interned_field_by_name_) { 52 | RET_CHECK(field.index_ < field_names_.size()); 53 | RET_CHECK(field_names_[field.index_] == name); 54 | } 55 | 56 | return absl::OkStatus(); 57 | } 58 | 59 | } // namespace netkat 60 | -------------------------------------------------------------------------------- /netkat/interned_field.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // ----------------------------------------------------------------------------- 16 | // File: interned_field.h 17 | // ----------------------------------------------------------------------------- 18 | // 19 | // A module for "interning" (aka hash-consing) NetKAT packet fields, see 20 | // https://en.wikipedia.org/wiki/String_interning. This makes it cheap to 21 | // compare, hash, copy and store packet fields (small constant time/space). 22 | 23 | #ifndef GOOGLE_NETKAT_NETKAT_INTERNED_FIELD_H_ 24 | #define GOOGLE_NETKAT_NETKAT_INTERNED_FIELD_H_ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "absl/container/flat_hash_map.h" 33 | #include "absl/status/status.h" 34 | #include "absl/strings/str_format.h" 35 | #include "absl/strings/string_view.h" 36 | 37 | namespace netkat { 38 | 39 | // An "interned" (aka hash-consed) NetKAT packet field, e.g. "dst_ip". 40 | // 41 | // Technically, a lightweight handle (16 bits) that is very cheap (O(1)) to 42 | // copy, store, hash, and compare. Handles can only be created by an 43 | // `InternedFieldManager` object, which owns the field name (e.g. "dst_ip") 44 | // associated with the handle. 45 | // 46 | // CAUTION: Each `InternedField` is implicitly associated with the manager 47 | // object that created it; using it with a different manager object has 48 | // undefined behavior. 49 | class [[nodiscard]] InternedField { 50 | public: 51 | // `InternedField`s can only be created by `InternedFieldManager`. 52 | InternedField() = delete; 53 | friend class InternedFieldManager; 54 | 55 | // O(1) comparison, thanks to interning/hash-consing. 56 | friend auto operator<=>(InternedField a, InternedField b) = default; 57 | 58 | // Hashing, see https://abseil.io/docs/cpp/guides/hash. 59 | template 60 | friend H AbslHashValue(H h, InternedField field) { 61 | return H::combine(std::move(h), field.index_); 62 | } 63 | 64 | // Formatting, see https://abseil.io/docs/cpp/guides/abslstringify. 65 | template 66 | friend void AbslStringify(Sink& sink, InternedField field) { 67 | absl::Format(&sink, "InternedField<%d>", field.index_); 68 | } 69 | 70 | private: 71 | // An index into the `field_names_` vector of the `InternedFieldManager` 72 | // object associated with this `InternedField`: `field_names_[index_]` is the 73 | // name of the field. The index is otherwise arbitrary and meaningless. 74 | // 75 | // We use a 16-bit index as a tradeoff between minimizing memory usage while 76 | // supporting sufficiently many fields. We expect 100s, but not more than 77 | // 2^16 ~= 65k fields. 78 | uint16_t index_; 79 | explicit InternedField(uint16_t index) : index_(index) {} 80 | }; 81 | 82 | // Protect against regressions in the memory layout, as it affects performance. 83 | static_assert(sizeof(InternedField) <= 2); 84 | 85 | // An "arena" for interning NetKAT packet fields, owning the memory associated 86 | // with the interned fields. 87 | class InternedFieldManager { 88 | public: 89 | InternedFieldManager() = default; 90 | 91 | // Returns an interned representation of field with the given name. 92 | InternedField GetOrCreateInternedField(absl::string_view field_name); 93 | 94 | // Returns the name of the given interned field, assuming it was created by 95 | // this manager object. Otherwise, the behavior is undefined. 96 | std::string GetFieldName(InternedField field) const; 97 | 98 | // Dynamically checks all class invariants. Exposed for testing only. 99 | absl::Status CheckInternalInvariants() const; 100 | 101 | private: 102 | // All field names interned by this manager object. The name of an interned 103 | // field `f` created by this object is `field_names_[f.index_]`. 104 | std::vector field_names_; 105 | 106 | // A so called "unique table" to ensure each field name is added to 107 | // `field_names_` at most once, and thus is represented by a unique index into 108 | // that vector. 109 | // 110 | // Invariant: 111 | // `interned_field_by_name_[n] == f` iff `field_names_[f.index_] == n`. 112 | absl::flat_hash_map interned_field_by_name_; 113 | }; 114 | 115 | } // namespace netkat 116 | 117 | #endif // GOOGLE_NETKAT_NETKAT_INTERNED_FIELD_H_ 118 | -------------------------------------------------------------------------------- /netkat/interned_field_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "netkat/interned_field.h" 16 | 17 | #include "absl/base/no_destructor.h" 18 | #include "absl/container/flat_hash_set.h" 19 | #include "absl/strings/str_cat.h" 20 | #include "gmock/gmock.h" 21 | #include "gtest/gtest.h" 22 | #include "gutil/status_matchers.h" // IWYU pragma: keep 23 | 24 | namespace netkat { 25 | 26 | namespace { 27 | 28 | using ::testing::StartsWith; 29 | 30 | // We use a global manager object across all tests to exercise statefulness. 31 | InternedFieldManager& Manager() { 32 | static absl::NoDestructor manager; 33 | return *manager; 34 | } 35 | 36 | // After executing all tests, we check once that no invariants are violated. 37 | class CheckInternedFieldManagerInvariantsOnTearDown 38 | : public testing::Environment { 39 | public: 40 | ~CheckInternedFieldManagerInvariantsOnTearDown() override {} 41 | void SetUp() override {} 42 | void TearDown() override { ASSERT_OK(Manager().CheckInternalInvariants()); } 43 | }; 44 | testing::Environment* const foo_env = testing::AddGlobalTestEnvironment( 45 | new CheckInternedFieldManagerInvariantsOnTearDown); 46 | 47 | TEST(InternedFieldManagerTest, AbslStringifyWorks) { 48 | EXPECT_THAT(absl::StrCat(Manager().GetOrCreateInternedField("foo")), 49 | StartsWith("InternedField")); 50 | } 51 | 52 | TEST(InternedFieldManagerTest, AbslHashValueWorks) { 53 | absl::flat_hash_set set = { 54 | Manager().GetOrCreateInternedField("foo"), 55 | Manager().GetOrCreateInternedField("bar"), 56 | }; 57 | EXPECT_EQ(set.size(), 2); 58 | } 59 | 60 | TEST(InternedFieldManagerTest, 61 | GetOrCreateInternedFieldReturnsSameFieldForSameName) { 62 | EXPECT_EQ(Manager().GetOrCreateInternedField("foo"), 63 | Manager().GetOrCreateInternedField("foo")); 64 | } 65 | 66 | TEST(InternedFieldManagerTest, 67 | GetOrCreateInternedFieldReturnsDifferentFieldForDifferentNames) { 68 | EXPECT_NE(Manager().GetOrCreateInternedField("foo"), 69 | Manager().GetOrCreateInternedField("bar")); 70 | } 71 | 72 | TEST(InternedFieldManagerTest, GetFieldNameReturnsNameOfInternedField) { 73 | InternedField foo = Manager().GetOrCreateInternedField("foo"); 74 | InternedField bar = Manager().GetOrCreateInternedField("bar"); 75 | EXPECT_EQ(Manager().GetFieldName(foo), "foo"); 76 | EXPECT_EQ(Manager().GetFieldName(bar), "bar"); 77 | } 78 | } // namespace 79 | } // namespace netkat 80 | -------------------------------------------------------------------------------- /netkat/netkat.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // ----------------------------------------------------------------------------- 16 | // File: netkat.proto 17 | // ----------------------------------------------------------------------------- 18 | // 19 | // Proto representation of NetKAT programs (predicates and policies). 20 | // 21 | // This representation is NOT intended as a user-facing API. Instead, it serves 22 | // as an intermdiate representation (IR) that is produced by user-facing 23 | // NetKAT APIs (NetKAT frontend(s)) and consumed by NetKAT backends. 24 | // 25 | // This reprensentation is expected to lack the convenience and type safety of 26 | // the user-facing API. 27 | // 28 | // Why have an IR? 29 | // * Designing an ergonomic user-facing API is hard, and requires resolving many 30 | // design questions without an obvious "best" answer. 31 | // * By having an IR, we can immediately work on the backend without waiting for 32 | // the user-facing API to be finalized. More generally, it decouples the 33 | // frontend design from the backend design. 34 | // * Given that there is likely not a single best user-facing API, we may want 35 | // to explore multiple ones (but share the IR and backend). 36 | // * It may make sense to have a specialized frontend API tailroed to Google's 37 | // internal needs, but it will likely not be open-sourceable. 38 | // 39 | // Why use protobufs for the IR? 40 | // * It provides many useful features out of the box: 41 | // serialization/deserialization, pretty-printing, a text format, fuzzing 42 | // (e.g. using https://github.com/google/fuzztest), diffing. 43 | // * It makes it easy to implement frontends and backends in different 44 | // programming languages. 45 | // * It makes it easy to run backends as (gRPC) services. 46 | 47 | syntax = "proto3"; 48 | 49 | package netkat; 50 | 51 | // The intermediate representation of a NetKAT predicate. 52 | // By convention, uninitialized predicates must be treated as `false`. 53 | message PredicateProto { 54 | // A predicate is defined as some Boolean combination of matches and/or 55 | // constants. 56 | oneof predicate { 57 | Bool bool_constant = 1; 58 | Match match = 2; 59 | 60 | // Combinators. 61 | And and_op = 3; 62 | Or or_op = 4; 63 | Not not_op = 5; 64 | Xor xor_op = 6; 65 | } 66 | 67 | // A boolean constant, i.e. true or false. Equivalent to Accept/Deny. 68 | message Bool { 69 | bool value = 1; 70 | } 71 | 72 | // Checks if a field has a specific value. 73 | // 74 | // NOTE: This message is expected to change in the future! E.g., the type of 75 | // `value` will change to support things like 128-bit IPv6 addresses. 76 | message Match { 77 | string field = 1; 78 | int32 value = 2; 79 | } 80 | 81 | // Boolean conjunction of two predicates, i.e. a && b. 82 | message And { 83 | PredicateProto left = 1; 84 | PredicateProto right = 2; 85 | } 86 | 87 | // Boolean disjunction of two predicates, i.e. a || b. 88 | message Or { 89 | PredicateProto left = 1; 90 | PredicateProto right = 2; 91 | } 92 | 93 | // Boolean negation of a predicate, i.e. !a. 94 | message Not { 95 | PredicateProto negand = 1; 96 | } 97 | 98 | // Boolean exclusive Or of two predicates, i.e. a (+) b. 99 | message Xor { 100 | PredicateProto left = 1; 101 | PredicateProto right = 2; 102 | } 103 | } 104 | 105 | // The intermediate representation of a NetKAT policy. 106 | // By convention, uninitialized policies must be treated as Deny (i.e. the 107 | // false filter). We call the true filter Accept. 108 | message PolicyProto { 109 | // A policy is defined as some combination of actions and predicates. 110 | oneof policy { 111 | PredicateProto filter = 1; 112 | Modification modification = 2; 113 | Record record = 3; 114 | 115 | // Combinators. 116 | Sequence sequence_op = 4; 117 | Union union_op = 5; 118 | Iterate iterate_op = 6; 119 | } 120 | 121 | // Sets the field to the given value. 122 | // 123 | // NOTE: This message is expected to change in the future! E.g., the type of 124 | // `value` will change to support things like 128-bit IPv6 addresses. 125 | message Modification { 126 | string field = 1; 127 | int32 value = 2; 128 | } 129 | 130 | // Represents the sequential composition of a policy, i.e. a; b. 131 | // 132 | // Semantically, this behaves like a function composition in which we feed the 133 | // program inputs into a and forward each output of a into b. 134 | message Sequence { 135 | PolicyProto left = 1; 136 | PolicyProto right = 2; 137 | } 138 | 139 | // Represents the union of two policies, i.e. a + b. 140 | message Union { 141 | PolicyProto left = 1; 142 | PolicyProto right = 2; 143 | } 144 | 145 | // Represents iteration of a given policy, a.k.a the Kleene Star. E.g. p*. 146 | // I.e. Iterate(p) == Union(Accept, p, Sequence(p,p), Sequence(p,p,p), ...) 147 | message Iterate { 148 | PolicyProto iterable = 1; 149 | } 150 | 151 | // Records the packet, at the given point, into the history. Referred to as 152 | // "dup" in the literature. 153 | message Record {} 154 | } 155 | -------------------------------------------------------------------------------- /netkat/netkat_proto_constructors.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "netkat/netkat_proto_constructors.h" 16 | 17 | #include 18 | #include 19 | 20 | #include "absl/strings/str_cat.h" 21 | #include "absl/strings/str_format.h" 22 | #include "absl/strings/string_view.h" 23 | #include "netkat/netkat.pb.h" 24 | 25 | namespace netkat { 26 | 27 | PredicateProto TrueProto() { 28 | PredicateProto proto; 29 | proto.mutable_bool_constant()->set_value(true); 30 | return proto; 31 | } 32 | 33 | PredicateProto FalseProto() { 34 | PredicateProto proto; 35 | proto.mutable_bool_constant()->set_value(false); 36 | return proto; 37 | } 38 | 39 | PredicateProto MatchProto(absl::string_view field, int value) { 40 | PredicateProto proto; 41 | PredicateProto::Match& match = *proto.mutable_match(); 42 | match.set_field(std::string(field)); 43 | match.set_value(value); 44 | return proto; 45 | } 46 | PredicateProto AndProto(PredicateProto left, PredicateProto right) { 47 | PredicateProto proto; 48 | PredicateProto::And& and_op = *proto.mutable_and_op(); 49 | *and_op.mutable_left() = std::move(left); 50 | *and_op.mutable_right() = std::move(right); 51 | return proto; 52 | } 53 | 54 | PredicateProto OrProto(PredicateProto left, PredicateProto right) { 55 | PredicateProto proto; 56 | PredicateProto::Or& or_op = *proto.mutable_or_op(); 57 | *or_op.mutable_left() = std::move(left); 58 | *or_op.mutable_right() = std::move(right); 59 | return proto; 60 | } 61 | PredicateProto NotProto(PredicateProto negand) { 62 | PredicateProto proto; 63 | PredicateProto::Not& not_op = *proto.mutable_not_op(); 64 | *not_op.mutable_negand() = std::move(negand); 65 | return proto; 66 | } 67 | PredicateProto XorProto(PredicateProto left, PredicateProto right) { 68 | PredicateProto proto; 69 | PredicateProto::Xor& xor_op = *proto.mutable_xor_op(); 70 | *xor_op.mutable_left() = std::move(left); 71 | *xor_op.mutable_right() = std::move(right); 72 | return proto; 73 | } 74 | 75 | // -- Basic Policy constructors ------------------------------------------------ 76 | 77 | PolicyProto FilterProto(PredicateProto filter) { 78 | PolicyProto policy; 79 | *policy.mutable_filter() = std::move(filter); 80 | return policy; 81 | } 82 | 83 | PolicyProto ModificationProto(absl::string_view field, int value) { 84 | PolicyProto policy; 85 | policy.mutable_modification()->set_field(field); 86 | policy.mutable_modification()->set_value(value); 87 | return policy; 88 | } 89 | 90 | PolicyProto RecordProto() { 91 | PolicyProto policy; 92 | policy.mutable_record(); 93 | return policy; 94 | } 95 | 96 | PolicyProto SequenceProto(PolicyProto left, PolicyProto right) { 97 | PolicyProto policy; 98 | *policy.mutable_sequence_op()->mutable_left() = std::move(left); 99 | *policy.mutable_sequence_op()->mutable_right() = std::move(right); 100 | return policy; 101 | } 102 | 103 | PolicyProto UnionProto(PolicyProto left, PolicyProto right) { 104 | PolicyProto policy; 105 | *policy.mutable_union_op()->mutable_left() = std::move(left); 106 | *policy.mutable_union_op()->mutable_right() = std::move(right); 107 | return policy; 108 | } 109 | 110 | PolicyProto IterateProto(PolicyProto iterable) { 111 | PolicyProto policy; 112 | *policy.mutable_iterate_op()->mutable_iterable() = std::move(iterable); 113 | return policy; 114 | } 115 | 116 | // -- Derived Policy constructors ---------------------------------------------- 117 | 118 | PolicyProto DenyProto() { return FilterProto(FalseProto()); } 119 | 120 | PolicyProto AcceptProto() { return FilterProto(TrueProto()); } 121 | 122 | std::string AsShorthandString(PredicateProto predicate) { 123 | switch (predicate.predicate_case()) { 124 | case PredicateProto::kBoolConstant: 125 | return predicate.bool_constant().value() ? "true" : "false"; 126 | case PredicateProto::kMatch: 127 | return absl::StrFormat("@%s==%d", predicate.match().field(), 128 | predicate.match().value()); 129 | case PredicateProto::kAndOp: 130 | return absl::StrFormat("(%s && %s)", 131 | AsShorthandString(predicate.and_op().left()), 132 | AsShorthandString(predicate.and_op().right())); 133 | case PredicateProto::kOrOp: 134 | return absl::StrFormat("(%s || %s)", 135 | AsShorthandString(predicate.or_op().left()), 136 | AsShorthandString(predicate.or_op().right())); 137 | case PredicateProto::kNotOp: 138 | return absl::StrCat("!", AsShorthandString(predicate.not_op().negand())); 139 | case PredicateProto::kXorOp: 140 | return absl::StrFormat("(%s (+) %s)", 141 | AsShorthandString(predicate.xor_op().left()), 142 | AsShorthandString(predicate.xor_op().right())); 143 | case PredicateProto::PREDICATE_NOT_SET: 144 | return "false"; 145 | } 146 | } 147 | 148 | std::string AsShorthandString(PolicyProto policy) { 149 | switch (policy.policy_case()) { 150 | case PolicyProto::kFilter: 151 | return AsShorthandString(policy.filter()); 152 | case PolicyProto::kModification: 153 | return absl::StrFormat("@%s:=%d", policy.modification().field(), 154 | policy.modification().value()); 155 | case PolicyProto::kRecord: 156 | return "record"; 157 | case PolicyProto::kSequenceOp: 158 | return absl::StrFormat("(%s; %s)", 159 | AsShorthandString(policy.sequence_op().left()), 160 | AsShorthandString(policy.sequence_op().right())); 161 | case PolicyProto::kUnionOp: 162 | return absl::StrFormat("(%s + %s)", 163 | AsShorthandString(policy.union_op().left()), 164 | AsShorthandString(policy.union_op().right())); 165 | case PolicyProto::kIterateOp: 166 | return absl::StrFormat("(%s)*", 167 | AsShorthandString(policy.iterate_op().iterable())); 168 | case PolicyProto::POLICY_NOT_SET: 169 | return "deny"; 170 | } 171 | } 172 | 173 | } // namespace netkat 174 | -------------------------------------------------------------------------------- /netkat/netkat_proto_constructors.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // ----------------------------------------------------------------------------- 16 | // File: netkat_proto_helpers.h 17 | // ----------------------------------------------------------------------------- 18 | // 19 | // Helper functions to make constructing netkat.proto messages more readable, 20 | // specifically in unit test where readability is key. 21 | 22 | #ifndef GOOGLE_NETKAT_NETKAT_NETKAT_PROTO_CONSTRUCTORS_H_ 23 | #define GOOGLE_NETKAT_NETKAT_NETKAT_PROTO_CONSTRUCTORS_H_ 24 | 25 | #include 26 | 27 | #include "absl/strings/string_view.h" 28 | #include "netkat/netkat.pb.h" 29 | 30 | namespace netkat { 31 | 32 | // -- Predicate constructors --------------------------------------------------- 33 | 34 | PredicateProto TrueProto(); 35 | PredicateProto FalseProto(); 36 | PredicateProto MatchProto(absl::string_view field, int value); 37 | PredicateProto AndProto(PredicateProto left, PredicateProto right); 38 | PredicateProto OrProto(PredicateProto left, PredicateProto right); 39 | PredicateProto NotProto(PredicateProto negand); 40 | PredicateProto XorProto(PredicateProto left, PredicateProto right); 41 | 42 | // -- Basic Policy constructors ------------------------------------------------ 43 | 44 | PolicyProto FilterProto(PredicateProto filter); 45 | PolicyProto ModificationProto(absl::string_view field, int value); 46 | PolicyProto RecordProto(); 47 | PolicyProto SequenceProto(PolicyProto left, PolicyProto right); 48 | PolicyProto UnionProto(PolicyProto left, PolicyProto right); 49 | PolicyProto IterateProto(PolicyProto iterable); 50 | 51 | // -- Derived Policy constructors ---------------------------------------------- 52 | 53 | PolicyProto DenyProto(); 54 | PolicyProto AcceptProto(); 55 | 56 | // Returns a shorthand string from a given NetKAT policy/predicate. This follows 57 | // roughly the shorthand typically used in literature. Specifically: 58 | // 59 | // Predicate And -> '&&' 60 | // Predicate Or -> '||' 61 | // Predicate Not -> '!' 62 | // Predicate Xor -> '(+)' 63 | // Policy Sequence -> ';' 64 | // Policy Or -> '+' 65 | // Iterate -> '*' 66 | // Record -> 'record' 67 | // Match -> '@field==value' 68 | // Modify -> '@field:=value' 69 | // True -> 'true' 70 | // False -> 'false' 71 | // 72 | // Note that parenthesis elimination is not performed. 73 | // 74 | // TODO(anthonyroy): Refactor this out to a different helper filer and/or widen 75 | // the scope of this one. 76 | std::string AsShorthandString(PolicyProto policy); 77 | std::string AsShorthandString(PredicateProto predicate); 78 | 79 | } // namespace netkat 80 | 81 | #endif // GOOGLE_NETKAT_NETKAT_NETKAT_PROTO_CONSTRUCTORS_H_ 82 | -------------------------------------------------------------------------------- /netkat/netkat_proto_constructors_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "netkat/netkat_proto_constructors.h" 16 | 17 | #include "absl/strings/string_view.h" 18 | #include "fuzztest/fuzztest.h" 19 | #include "gmock/gmock.h" 20 | #include "gtest/gtest.h" 21 | #include "gutil/proto_matchers.h" 22 | #include "netkat/netkat.pb.h" 23 | 24 | namespace netkat { 25 | namespace { 26 | 27 | using ::gutil::EqualsProto; 28 | 29 | TEST(TrueProtoTest, ReturnsTrueProto) { 30 | EXPECT_THAT(TrueProto(), EqualsProto(R"pb(bool_constant { value: true })pb")); 31 | } 32 | 33 | TEST(FalseProtoTest, ReturnsFalseProto) { 34 | EXPECT_THAT(FalseProto(), 35 | EqualsProto(R"pb(bool_constant { value: false })pb")); 36 | } 37 | 38 | void MatchProtoReturnsMatch(absl::string_view field, int value) { 39 | PredicateProto match_proto; 40 | PredicateProto::Match& match = *match_proto.mutable_match(); 41 | match.set_field(field); 42 | match.set_value(value); 43 | EXPECT_THAT(MatchProto(field, value), EqualsProto(match_proto)); 44 | } 45 | FUZZ_TEST(MatchProtoTest, MatchProtoReturnsMatch); 46 | 47 | void AndProtoReturnsAnd(PredicateProto left, PredicateProto right) { 48 | PredicateProto and_proto; 49 | PredicateProto::And& and_op = *and_proto.mutable_and_op(); 50 | *and_op.mutable_left() = left; 51 | *and_op.mutable_right() = right; 52 | EXPECT_THAT(AndProto(left, right), EqualsProto(and_proto)); 53 | } 54 | FUZZ_TEST(AndProtoTest, AndProtoReturnsAnd); 55 | 56 | void OrProtoReturnsOr(PredicateProto left, PredicateProto right) { 57 | PredicateProto or_proto; 58 | PredicateProto::Or& or_op = *or_proto.mutable_or_op(); 59 | *or_op.mutable_left() = left; 60 | *or_op.mutable_right() = right; 61 | EXPECT_THAT(OrProto(left, right), EqualsProto(or_proto)); 62 | } 63 | FUZZ_TEST(OrProtoTest, OrProtoReturnsOr); 64 | 65 | void NotProtoReturnsNot(PredicateProto negand) { 66 | PredicateProto not_proto; 67 | PredicateProto::Not& not_op = *not_proto.mutable_not_op(); 68 | *not_op.mutable_negand() = negand; 69 | EXPECT_THAT(NotProto(negand), EqualsProto(not_proto)); 70 | } 71 | FUZZ_TEST(NotProtoTest, NotProtoReturnsNot); 72 | 73 | void XorProtoReturnsXor(PredicateProto left, PredicateProto right) { 74 | PredicateProto xor_proto; 75 | PredicateProto::Xor& xor_op = *xor_proto.mutable_xor_op(); 76 | *xor_op.mutable_left() = left; 77 | *xor_op.mutable_right() = right; 78 | EXPECT_THAT(XorProto(left, right), EqualsProto(xor_proto)); 79 | } 80 | FUZZ_TEST(XorProtoTest, XorProtoReturnsXor); 81 | 82 | // -- Basic Policy constructors ------------------------------------------------ 83 | 84 | void FilterProtoReturnsFilter(PredicateProto filter) { 85 | PolicyProto expected_policy; 86 | *expected_policy.mutable_filter() = filter; 87 | EXPECT_THAT(FilterProto(filter), EqualsProto(expected_policy)); 88 | } 89 | FUZZ_TEST(PolicyProtoTest, FilterProtoReturnsFilter); 90 | 91 | void ModificationProtoReturnsModification(std::string field, int value) { 92 | PolicyProto expected_policy; 93 | expected_policy.mutable_modification()->set_field(field); 94 | expected_policy.mutable_modification()->set_value(value); 95 | 96 | EXPECT_THAT(ModificationProto(field, value), EqualsProto(expected_policy)); 97 | } 98 | FUZZ_TEST(PolicyProtoTest, ModificationProtoReturnsModification); 99 | 100 | TEST(PolicyProtoTest, RecordProtoReturnsRecordPolicy) { 101 | EXPECT_THAT(RecordProto(), EqualsProto(R"pb(record {})pb")); 102 | } 103 | 104 | void SequenceProtoReturnsSequence(PolicyProto left, PolicyProto right) { 105 | PolicyProto expected_policy; 106 | *expected_policy.mutable_sequence_op()->mutable_left() = left; 107 | *expected_policy.mutable_sequence_op()->mutable_right() = right; 108 | 109 | EXPECT_THAT(SequenceProto(left, right), EqualsProto(expected_policy)); 110 | } 111 | FUZZ_TEST(PolicyProtoTest, SequenceProtoReturnsSequence); 112 | 113 | void UnionProtoReturnsUnion(PolicyProto left, PolicyProto right) { 114 | PolicyProto expected_policy; 115 | *expected_policy.mutable_union_op()->mutable_left() = left; 116 | *expected_policy.mutable_union_op()->mutable_right() = right; 117 | 118 | EXPECT_THAT(UnionProto(left, right), EqualsProto(expected_policy)); 119 | } 120 | FUZZ_TEST(PolicyProtoTest, UnionProtoReturnsUnion); 121 | 122 | void IterateProtoReturnsIterate(PolicyProto iterable) { 123 | PolicyProto expected_policy; 124 | *expected_policy.mutable_iterate_op()->mutable_iterable() = iterable; 125 | 126 | EXPECT_THAT(IterateProto(iterable), EqualsProto(expected_policy)); 127 | } 128 | FUZZ_TEST(PolicyProtoTest, IterateProtoReturnsIterate); 129 | 130 | // -- Derived Policy tests ----------------------------------------------------- 131 | 132 | TEST(PolicyProtoTest, DenyProtoFiltersOnFalse) { 133 | EXPECT_THAT(DenyProto(), 134 | EqualsProto(R"pb(filter { bool_constant { value: false } })pb")); 135 | } 136 | 137 | TEST(PolicyProtoTest, AcceptProtoFiltersOnTrue) { 138 | EXPECT_THAT(AcceptProto(), 139 | EqualsProto(R"pb(filter { bool_constant { value: true } })pb")); 140 | } 141 | 142 | // -- Short hand tests --------------------------------------------------------- 143 | 144 | TEST(AsShorthandStringTest, RecordStringIsCorrect) { 145 | EXPECT_EQ(AsShorthandString(RecordProto()), "record"); 146 | } 147 | 148 | TEST(AsShorthandStringTest, UnsetPredicateIsFalse) { 149 | EXPECT_EQ(AsShorthandString(PredicateProto()), "false"); 150 | } 151 | 152 | TEST(AsShorthandStringTest, UnsetPolicyIsDeny) { 153 | EXPECT_EQ(AsShorthandString(PolicyProto()), "deny"); 154 | } 155 | 156 | void FilterIsJustPredicate(PredicateProto predicate) { 157 | EXPECT_EQ(AsShorthandString(FilterProto(predicate)), 158 | AsShorthandString(predicate)); 159 | } 160 | FUZZ_TEST(AsShorthandStringTest, FilterIsJustPredicate); 161 | 162 | TEST(AsShorthandStringTest, BoolConstantIsCorrect) { 163 | EXPECT_EQ(AsShorthandString(FalseProto()), "false"); 164 | EXPECT_EQ(AsShorthandString(TrueProto()), "true"); 165 | } 166 | 167 | TEST(AsShorthandStringTest, AndIsOkay) { 168 | EXPECT_EQ(AsShorthandString(AndProto(TrueProto(), FalseProto())), 169 | "(true && false)"); 170 | } 171 | 172 | TEST(AsShorthandStringTest, SequenceIsOkay) { 173 | EXPECT_EQ(AsShorthandString(SequenceProto(AcceptProto(), DenyProto())), 174 | "(true; false)"); 175 | } 176 | 177 | TEST(AsShorthandStringTest, OrIsOkay) { 178 | EXPECT_EQ(AsShorthandString(OrProto(TrueProto(), FalseProto())), 179 | "(true || false)"); 180 | } 181 | 182 | TEST(AsShorthandStringTest, UnionIsOkay) { 183 | EXPECT_EQ(AsShorthandString(UnionProto(AcceptProto(), DenyProto())), 184 | "(true + false)"); 185 | } 186 | 187 | TEST(AsShorthandStringTest, NegationIsOkay) { 188 | EXPECT_EQ(AsShorthandString(NotProto(OrProto(TrueProto(), FalseProto()))), 189 | "!(true || false)"); 190 | } 191 | 192 | TEST(AsShorthandStringTest, ModifyIsCorrect) { 193 | EXPECT_EQ(AsShorthandString(ModificationProto("field", 2)), "@field:=2"); 194 | } 195 | 196 | TEST(AsShorthandStringTest, IterateIsCorrect) { 197 | EXPECT_EQ(AsShorthandString(IterateProto(AcceptProto())), "(true)*"); 198 | } 199 | 200 | TEST(AsShorthandStringTest, MixedPolicyOrderIsPreserved) { 201 | const PredicateProto a = MatchProto("a", 1); 202 | const PredicateProto b = MatchProto("b", 2); 203 | const PredicateProto c = MatchProto("c", 3); 204 | EXPECT_EQ( 205 | AsShorthandString(IterateProto(UnionProto( 206 | SequenceProto(SequenceProto(FilterProto(OrProto(OrProto(a, b), c)), 207 | AcceptProto()), 208 | RecordProto()), 209 | SequenceProto(SequenceProto(FilterProto(a), FilterProto(b)), 210 | RecordProto())))), 211 | "((((((@a==1 || @b==2) || @c==3); true); record) + ((@a==1; @b==2); " 212 | "record)))*"); 213 | } 214 | 215 | } // namespace 216 | } // namespace netkat 217 | -------------------------------------------------------------------------------- /netkat/netkat_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // ----------------------------------------------------------------------------- 15 | 16 | #include "absl/log/log.h" 17 | #include "fuzztest/fuzztest.h" 18 | #include "gtest/gtest.h" 19 | #include "netkat/netkat.pb.h" 20 | 21 | namespace netkat { 22 | namespace { 23 | 24 | // Sanity fuzz test to show that the FuzzTest library works. 25 | void DummmyFuzzTest(PredicateProto pred, PolicyProto pol) { 26 | LOG_EVERY_N_SEC(INFO, 1) << "pred = " << pred; 27 | LOG_EVERY_N_SEC(INFO, 1) << "pol = " << pol; 28 | } 29 | FUZZ_TEST(NetkatProtoTest, DummmyFuzzTest); 30 | 31 | // Ensures that the protobuf C++ compiler does not add underscores to the 32 | // generated code for sub messages and oneof fields of `PredicateProto`. 33 | // 34 | // This test is needed because we uses key words such as "and", "or", "not", 35 | // "bool", which are all reserved protobuf/C++ keywords. 36 | TEST(NetkatProtoTest, PredicateOneOfFieldNamesDontRequireUnderscores) { 37 | PredicateProto predicate; 38 | switch (predicate.predicate_case()) { 39 | case PredicateProto::kAndOp: { 40 | const PredicateProto::And& and_op = predicate.and_op(); 41 | LOG(INFO) << "and_op: " << and_op; 42 | break; 43 | } 44 | case PredicateProto::kOrOp: { 45 | const PredicateProto::Or& or_op = predicate.or_op(); 46 | LOG(INFO) << "or_op: " << or_op; 47 | break; 48 | } 49 | case PredicateProto::kNotOp: { 50 | const PredicateProto::Not& not_op = predicate.not_op(); 51 | LOG(INFO) << "not_op: " << not_op; 52 | break; 53 | } 54 | case PredicateProto::kXorOp: { 55 | const PredicateProto::Xor& xor_op = predicate.xor_op(); 56 | LOG(INFO) << "xor_op: " << xor_op; 57 | break; 58 | } 59 | case PredicateProto::kMatch: { 60 | const PredicateProto::Match& match = predicate.match(); 61 | LOG(INFO) << "match: " << match; 62 | break; 63 | } 64 | case PredicateProto::kBoolConstant: { 65 | const PredicateProto::Bool& bool_constant = predicate.bool_constant(); 66 | LOG(INFO) << "bool_constant: " << bool_constant; 67 | break; 68 | } 69 | case PredicateProto::PREDICATE_NOT_SET: 70 | break; 71 | } 72 | } 73 | 74 | TEST(NetkatProtoTest, PolicyOneOfFieldNamesDontRequireUnderscores) { 75 | PolicyProto policy; 76 | switch (policy.policy_case()) { 77 | case PolicyProto::kFilter: { 78 | const PredicateProto& filter = policy.filter(); 79 | LOG(INFO) << "filter: " << filter; 80 | break; 81 | } 82 | case PolicyProto::kModification: { 83 | const PolicyProto::Modification& modification = policy.modification(); 84 | LOG(INFO) << "modification: " << modification; 85 | break; 86 | } 87 | case PolicyProto::kRecord: { 88 | const PolicyProto::Record& record = policy.record(); 89 | LOG(INFO) << "record: " << record; 90 | break; 91 | } 92 | case PolicyProto::kSequenceOp: { 93 | const PolicyProto::Sequence& sequence_op = policy.sequence_op(); 94 | LOG(INFO) << "sequence: " << sequence_op; 95 | break; 96 | } 97 | case PolicyProto::kUnionOp: { 98 | const PolicyProto::Union& union_op = policy.union_op(); 99 | LOG(INFO) << "union: " << union_op; 100 | break; 101 | } 102 | case PolicyProto::kIterateOp: { 103 | const PolicyProto::Iterate& iter = policy.iterate_op(); 104 | LOG(INFO) << "iterate: " << iter; 105 | break; 106 | } 107 | case PolicyProto::POLICY_NOT_SET: 108 | break; 109 | } 110 | } 111 | 112 | } // namespace 113 | } // namespace netkat 114 | -------------------------------------------------------------------------------- /netkat/paged_stable_vector.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // ----------------------------------------------------------------------------- 16 | // File: paged_stable_vector.h 17 | // ----------------------------------------------------------------------------- 18 | 19 | #ifndef GOOGLE_NETKAT_NETKAT_PAGED_STABLE_VECTOR_H_ 20 | #define GOOGLE_NETKAT_NETKAT_PAGED_STABLE_VECTOR_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | namespace netkat { 27 | 28 | // A variant of `std::vector` that allocates memory in pages (or "chunks") of 29 | // fixed `PageSize`. This introduces an extra level of indirection and 30 | // introduces some level of discontiguity (depending on `PageSize`), but allows 31 | // the class to guarantee pointer stability: calls to `push_back`/`emplace_back` 32 | // never invalidate pointers/iterators/references to elements previously added 33 | // to the vector. 34 | // 35 | // Allocating memory in pages also avoids the cost of relocation, which may be 36 | // significant for very large vectors in performance-sensitive applications. 37 | // 38 | // The API of this class is kept just large enough to cover our use cases. 39 | template 40 | class PagedStableVector { 41 | public: 42 | PagedStableVector() = default; 43 | 44 | size_t size() const { 45 | return data_.empty() ? 0 46 | : (data_.size() - 1) * PageSize + data_.back().size(); 47 | } 48 | 49 | template 50 | void push_back(Value&& value) { 51 | if (size() % PageSize == 0) data_.emplace_back().reserve(PageSize); 52 | data_.back().push_back(std::forward(value)); 53 | } 54 | 55 | template 56 | void emplace_back(Args&&... value) { 57 | if (size() % PageSize == 0) data_.emplace_back().reserve(PageSize); 58 | data_.back().emplace_back(std::forward(value)...); 59 | } 60 | 61 | T& operator[](size_t index) { 62 | return data_[index / PageSize][index % PageSize]; 63 | } 64 | const T& operator[](size_t index) const { 65 | return data_[index / PageSize][index % PageSize]; 66 | } 67 | 68 | private: 69 | std::vector> data_; 70 | }; 71 | 72 | } // namespace netkat 73 | 74 | #endif // GOOGLE_NETKAT_NETKAT_PAGED_STABLE_VECTOR_H_ 75 | -------------------------------------------------------------------------------- /netkat/paged_stable_vector_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "netkat/paged_stable_vector.h" 16 | 17 | #include 18 | #include 19 | 20 | #include "fuzztest/fuzztest.h" 21 | #include "gtest/gtest.h" 22 | 23 | namespace netkat { 24 | namespace { 25 | 26 | // A small, but otherwise random page size used throughout the tests. 27 | // Using a small page size is useful for exercising the page replacement logic. 28 | static constexpr int kSmallPageSize = 3; 29 | 30 | void PushBackInreasesSize(std::vector elements) { 31 | PagedStableVector vector; 32 | for (const auto& element : elements) { 33 | vector.push_back(element); 34 | } 35 | EXPECT_EQ(vector.size(), elements.size()); 36 | } 37 | FUZZ_TEST(PagedStableVectorTest, PushBackInreasesSize); 38 | 39 | void EmplaceBackInreasesSize(std::vector elements) { 40 | PagedStableVector vector; 41 | for (const auto& element : elements) { 42 | vector.emplace_back(element); 43 | } 44 | EXPECT_EQ(vector.size(), elements.size()); 45 | } 46 | FUZZ_TEST(PagedStableVectorTest, EmplaceBackInreasesSize); 47 | 48 | void PushBackAddsElementToBack(std::vector elements) { 49 | PagedStableVector vector; 50 | for (int i = 0; i < elements.size(); ++i) { 51 | vector.push_back(elements[i]); 52 | for (int j = 0; j < i; ++j) { 53 | EXPECT_EQ(vector[j], elements[j]); 54 | } 55 | } 56 | } 57 | FUZZ_TEST(PagedStableVectorTest, PushBackAddsElementToBack); 58 | 59 | void EmplaceBackAddsElementToBack(std::vector elements) { 60 | PagedStableVector vector; 61 | for (int i = 0; i < elements.size(); ++i) { 62 | vector.emplace_back(elements[i]); 63 | for (int j = 0; j < i; ++j) { 64 | EXPECT_EQ(vector[j], elements[j]); 65 | } 66 | } 67 | } 68 | FUZZ_TEST(PagedStableVectorTest, EmplaceBackAddsElementToBack); 69 | 70 | void BracketAssigmentWorks(std::vector elements) { 71 | PagedStableVector vector; 72 | for (int i = 0; i < elements.size(); ++i) { 73 | vector.push_back("initial value"); 74 | } 75 | for (int i = 0; i < elements.size(); ++i) { 76 | vector[i] = elements[i]; 77 | } 78 | for (int i = 0; i < elements.size(); ++i) { 79 | EXPECT_EQ(vector[i], elements[i]); 80 | } 81 | } 82 | FUZZ_TEST(PagedStableVectorTest, BracketAssigmentWorks); 83 | 84 | TEST(PagedStableVectorTest, ReferencesDontGetInvalidated) { 85 | PagedStableVector vector; 86 | 87 | // Store a few references. 88 | vector.push_back("first element"); 89 | std::string* first_element_ptr = &vector[0]; 90 | vector.push_back("second element"); 91 | std::string* second_element_ptr = &vector[1]; 92 | 93 | // Push a ton of elements to trigger page allocation. 94 | // If this were a regular std::vector, the references would be invalidated. 95 | for (int i = 0; i < 100 * kSmallPageSize; ++i) { 96 | vector.push_back("dummy"); 97 | } 98 | 99 | // Check that the references are still valid. 100 | EXPECT_EQ(&vector[0], first_element_ptr); 101 | EXPECT_EQ(&vector[1], second_element_ptr); 102 | }; 103 | 104 | } // namespace 105 | } // namespace netkat 106 | -------------------------------------------------------------------------------- /netkat/symbolic_packet.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // ----------------------------------------------------------------------------- 16 | // File: symbolic_packet.h 17 | // ----------------------------------------------------------------------------- 18 | // 19 | // Defines `SymbolicPacket` and its companion class `SymbolicPacketManager`. 20 | // Together, they provide a compact and efficient representation of large packet 21 | // sets, exploiting structural properties that packet sets seen in practice 22 | // typically exhibit. 23 | // 24 | // This is a low level library designed for maximum efficiency, rather than a 25 | // high level library designed for safety and convenience. 26 | // 27 | // The implementation is based on the paper "KATch: A Fast Symbolic Verifier for 28 | // NetKAT" and is closely related to Binary Decision Diagrams (BDDs), see 29 | // https://en.wikipedia.org/wiki/Binary_decision_diagram. 30 | // 31 | // ----------------------------------------------------------------------------- 32 | // Why have a manager class? 33 | // ----------------------------------------------------------------------------- 34 | // 35 | // The APIs for creating, manipulating, and inspecting `SymbolicPacket`s are all 36 | // defined as methods of the `SymbolicPacketManager` class. But why? 37 | // 38 | // TL;DR, all data associated with `SymbolicPacket`s is stored by the manager 39 | // class; `SymbolicPacket` itself is just a lightweight (32-bit) handle. This 40 | // design pattern is motivated by computational and memory efficiency, and is 41 | // standard for BDD-based libraries. 42 | // 43 | // The manager object acts as an "arena" that owns and manages all memory 44 | // associated with `SymbolicPacket`s, enhancing data locality and sharing. This 45 | // technique is known as interning or hash-consing and is similar to the 46 | // flyweight pattern. It has a long list of benefits, most importantly: 47 | // 48 | // * Canonicity: Can guarantee that semantically identical `SymbolicPacket` are 49 | // represented by the same handle, making semantic `SymbolicPacket` comparison 50 | // O(1) (just comparing two integers)! 51 | // 52 | // * Memory efficiency: The graph structures used to encode symbolic packets are 53 | // maximally shared across all packets, avoiding redundant copies of isomorph 54 | // subgraphs. 55 | // 56 | // * Cache friendliness: Storing all data in contiguous arrays within the 57 | // manager improves data locality and thus cache utilization. 58 | // 59 | // * Light representation: Since `SymbolicPacket`s are simply integers in 60 | // memory, they are cheap to store, copy, compare, and hash. 61 | // 62 | // * Memoization: Thanks to canonicity and lightness of representation, 63 | // computations on `SymbolicPacket`s can be memoized very efficiently in the 64 | // manager object. For example, a binary function of type 65 | // 66 | // SymbolicPacket, SymbolicPacket -> SymbolicPacket 67 | // 68 | // can be memoized as a lookup table of type (int, int) -> int. 69 | // 70 | // ----------------------------------------------------------------------------- 71 | // 72 | // CAUTION: This implementation has NOT yet been optimized for performance. 73 | // See the TODOs in the cc file for low hanging fruit. Beyond known 74 | // inefficiencies, performance can likely be improved significantly further 75 | // through profiling and benchmarking. Also see "Efficient Implementation of a 76 | // BDD Package" for standard techniques to improve performance. 77 | 78 | #ifndef GOOGLE_NETKAT_NETKAT_SYMBOLIC_PACKET_H_ 79 | #define GOOGLE_NETKAT_NETKAT_SYMBOLIC_PACKET_H_ 80 | 81 | #include 82 | #include 83 | #include 84 | #include 85 | #include 86 | 87 | #include "absl/container/fixed_array.h" 88 | #include "absl/container/flat_hash_map.h" 89 | #include "absl/status/status.h" 90 | #include "absl/strings/str_format.h" 91 | #include "absl/strings/string_view.h" 92 | #include "netkat/evaluator.h" 93 | #include "netkat/interned_field.h" 94 | #include "netkat/netkat.pb.h" 95 | #include "netkat/paged_stable_vector.h" 96 | 97 | namespace netkat { 98 | 99 | // A "symbolic packet" is a lightweight handle (32 bits) that represents a set 100 | // of packets. Handles can only be created by a `SymbolicPacketManager` object, 101 | // which owns the graph-based representation of the set. The representation can 102 | // efficiently encode typical large and even infinite sets seen in practice. 103 | // 104 | // The APIs of this object are almost entirely defined as methods of the 105 | // companion class `SymbolicPacketManager`. See the section "Why have a manager 106 | // class?" at the top of the file to learn why. 107 | // 108 | // CAUTION: Each `SymbolicPacket` is implicitly associated with the manager 109 | // object that created it; using it with a different manager has undefined 110 | // behavior. 111 | // 112 | // This data structure enjoys the following powerful *canonicity property*: two 113 | // symbolic packets represent the same set if and only if they have the same 114 | // memory representation. Since the memory representation is just 32 bits, 115 | // semantic set equality is cheap: O(1)! 116 | // 117 | // Compared to NetKAT predicates, which semantically also represent sets of 118 | // packets, symbolic packets have a few advantages: 119 | // * Cheap to store, copy, hash, and compare: O(1) 120 | // * Cheap to check set equality: O(1) 121 | // * Cheap to check set membership and set containment: O(# packet fields) 122 | class [[nodiscard]] SymbolicPacket { 123 | public: 124 | // Default constructor: the empty set of packets. 125 | SymbolicPacket(); 126 | 127 | // Two symbolic packets compare equal iff they represent the same set of 128 | // concrete packets. Comparison is O(1), thanks to interning/hash-consing. 129 | friend auto operator<=>(SymbolicPacket a, SymbolicPacket b) = default; 130 | 131 | // Hashing, see https://abseil.io/docs/cpp/guides/hash. 132 | template 133 | friend H AbslHashValue(H h, SymbolicPacket packet) { 134 | return H::combine(std::move(h), packet.node_index_); 135 | } 136 | 137 | // Formatting, see https://abseil.io/docs/cpp/guides/abslstringify. 138 | // NOTE: These functions do not produce particularly useful output. Instead, 139 | // use `SymbolicPacketManager::ToString(packet)` whenever possible. 140 | template 141 | friend void AbslStringify(Sink& sink, SymbolicPacket packet) { 142 | absl::Format(&sink, "%s", packet.ToString()); 143 | } 144 | std::string ToString() const; 145 | 146 | private: 147 | // An index into the `nodes_` vector of the `SymbolicPacketManager` object 148 | // associated with this `SymbolicPacket`. The semantics of this symbolic 149 | // packet is entirely determined by the node `nodes_[node_index_]`. The index 150 | // is otherwise arbitrary and meaningless. 151 | // 152 | // We use a 32-bit index as a tradeoff between minimizing memory usage and 153 | // maximizing the number of `SymbolicPacket`s that can be created, both 154 | // aspects that impact how well we scale to large NetKAT models. We expect 155 | // millions, but not billions, of symbolic packets in practice, and 2^32 ~= 4 156 | // billion. 157 | uint32_t node_index_; 158 | explicit SymbolicPacket(uint32_t node_index) : node_index_(node_index) {} 159 | friend class SymbolicPacketManager; 160 | }; 161 | 162 | // Protect against regressions in the memory layout, as it affects performance. 163 | static_assert(sizeof(SymbolicPacket) <= 4); 164 | 165 | // An "arena" in which `SymbolicPacket`s can be created and manipulated. 166 | // 167 | // This class defines the majority of operations on `SymbolicPacket`s and owns 168 | // all the memory associated with the `SymbolicPacket`s returned by the class's 169 | // methods. 170 | // 171 | // CAUTION: Using a `SymbolicPacket` returned by one `SymbolicPacketManager` 172 | // object with a different manager is undefined behavior. 173 | // 174 | // TODO(b/398303840): Persistent use of an `SymbolicPacketManager` object can 175 | // incur unbounded memory growth. Consider adding some garbage collection 176 | // mechanism. 177 | class SymbolicPacketManager { 178 | public: 179 | SymbolicPacketManager() = default; 180 | 181 | // The class is move-only: not copyable, but movable. 182 | SymbolicPacketManager(const SymbolicPacketManager&) = delete; 183 | SymbolicPacketManager& operator=(const SymbolicPacketManager&) = delete; 184 | SymbolicPacketManager(SymbolicPacketManager&&) = default; 185 | SymbolicPacketManager& operator=(SymbolicPacketManager&&) = default; 186 | 187 | // Returns true iff this symbolic packet represents the empty set of packets. 188 | bool IsEmptySet(SymbolicPacket packet) const; 189 | 190 | // Returns true iff this symbolic packet represents the set of all packets. 191 | bool IsFullSet(SymbolicPacket packet) const; 192 | 193 | // Returns true if the set represented by `symbolic_packet` contains the given 194 | // `concrete_packet`, or false otherwise. 195 | bool Contains(SymbolicPacket symbolic_packet, 196 | const Packet& concrete_packet) const; 197 | 198 | // Returns a dot string representation of the given `symbolic_packet`. 199 | std::string ToDot(const SymbolicPacket& symbolic_packet) const; 200 | 201 | // Compiles the given `PredicateProto` into a `SymbolicPacket` that 202 | // represents the set of packets satisfying the predicate. 203 | SymbolicPacket Compile(const PredicateProto& pred); 204 | 205 | // The symbolic packet representing the empty set of packets. 206 | SymbolicPacket EmptySet() const; 207 | 208 | // The symbolic packet representing the set of all packets. 209 | SymbolicPacket FullSet() const; 210 | 211 | // Returns the set of packets whose `field` is equal to `value`. 212 | SymbolicPacket Match(absl::string_view field, int value); 213 | 214 | // Returns the set of packets that are in the `left` *AND* in the `right` set. 215 | // Also known as set intersection. 216 | SymbolicPacket And(SymbolicPacket left, SymbolicPacket right); 217 | 218 | // Returns the set of packets that are in the `left` *OR* in the `right` set. 219 | // Also known as set union. 220 | SymbolicPacket Or(SymbolicPacket left, SymbolicPacket right); 221 | 222 | // Returns the set of packets that are *NOT* in the given set. 223 | // Also known as set complement. 224 | SymbolicPacket Not(SymbolicPacket negand); 225 | 226 | // Returns the set of packets that are in either in the `left` or the `right` 227 | // set, but not in both. Also known as symmetric set difference. 228 | SymbolicPacket Xor(SymbolicPacket left, SymbolicPacket right); 229 | 230 | // Returns a human-readable string representation of the given `packet`, 231 | // intended for debugging. 232 | [[nodiscard]] std::string ToString(SymbolicPacket packet) const; 233 | 234 | // -- For Testing Only ------------------------------------------------------- 235 | 236 | // Dynamically checks all class invariants. Exposed for testing only. 237 | absl::Status CheckInternalInvariants() const; 238 | 239 | // Returns an arbitrary list of concrete packets that are contained in the 240 | // given symbolic_packet. 241 | // 242 | // This list is not guaranteed to be exhaustive. The only guarantees are: 243 | // * If the set is non-empty, we return at least one packet. 244 | // * Every packet we return is contained in the set. 245 | std::vector GetConcretePackets(SymbolicPacket symbolic_packet) const; 246 | 247 | // TODO(smolkaj): There are many additional operations supported by this data 248 | // structure, but not currently implemented. Add them as needed. Examples: 249 | // * subset - is one set a subset of another? 250 | // * witness - given a (non-empty) set, return one (or n) elements from the 251 | // set. 252 | // * sample - return a member from the set uniformly at random. 253 | 254 | private: 255 | // Internally, this class represents symbolic packets (and thus packet sets) 256 | // as nodes in a directed acyclic graph (DAG). Each node branches based on the 257 | // value of a single packet field, and each branch points to another 258 | // symbolic packet, which in turn is either the full/empty set, or represented 259 | // by another node in the graph. 260 | // 261 | // The graph is "ordered", "reduced", and contains no "isomorphic subgraphs": 262 | // 263 | // * Ordered: Along each path through the graph, fields increase strictly 264 | // monotonically (with respect to `<` defined on `InternedField`s). 265 | // * Reduced: Intutively, there exist no redundant branches or nodes. 266 | // Invariants 1 and 2 on `branch_by_field_value` formalize this intuition. 267 | // * No isomorphic subgraphs: Nodes are interned by the class, ensuring that 268 | // structurally identical nodes are guaranteed to be stored by the class 269 | // only once. Together with the other two properties, this implies that each 270 | // node stored by the class represents a unique set of packets. 271 | // 272 | // This representation is closely related to Binary Decision Diagrams (BDDs), 273 | // see https://en.wikipedia.org/wiki/Binary_decision_diagram. This variant of 274 | // BDDs is described in the paper "KATch: A Fast Symbolic Verifier for 275 | // NetKAT". 276 | 277 | // A decision node in the symbolic packet DAG. The node branches on the value 278 | // of a single `field`, and (the consequent of) each branch is a 279 | // `SymbolicPacket` corresponding to either another decision node or the 280 | // full/empty set. Semantically, represents a cascading conditional of the 281 | // form: 282 | // 283 | // if (field == value_1) then branch_1 284 | // else if (field == value_2) then branch_2 285 | // ... 286 | // else default_branch 287 | struct DecisionNode { 288 | // The packet field whose value this decision node branches on. 289 | // 290 | // INVARIANTS: 291 | // * Strictly smaller (`<`) than the fields of other decision nodes 292 | // reachable from this node. 293 | // * Interned by `field_manager_`. 294 | InternedField field; 295 | 296 | // The consequent of the "else" branch of this decision node. 297 | SymbolicPacket default_branch; 298 | 299 | // The "if" branches of the decision node, "keyed" by the value they branch 300 | // on. Each element of the array is a (value, branch)-pair encoding 301 | // "if (field == value) then branch". 302 | // 303 | // CHOICE OF DATA STRUCTURE: 304 | // Logically this is a value -> branch map, but we store it as a fixed-size 305 | // array to optimize memory layout (contiguous, compact, flat), exploiting 306 | // the following observations: 307 | // * Nodes are not mutated after creation, so we can use a fixed-size 308 | // container and save some bytes relative to dynamically-sized containers. 309 | // * None of the set combinator implementations (`And`, `Or`, `Not`) require 310 | // fast lookups, so we can avoid the overhead of lookup-optimized data 311 | // structures like hash maps. 312 | // 313 | // INVARIANTS: 314 | // 1. Maintained by `NodeToPacket`: `branch_by_field_value` is non-empty. 315 | // (If it is empty, the decision node gets replaced by `default_branch`.) 316 | // 2. Each branch is != `default_branch`. 317 | // (If the branch is == `default_branch`, it must be omitted.) 318 | // 3. The pairs are ordered by strictly increasing value. No two 319 | // branches have the same value. 320 | absl::FixedArray, 321 | /*use_heap_allocation_above_size=*/0> 322 | branch_by_field_value; 323 | 324 | // Protect against regressions in memory layout, as it affects performance. 325 | static_assert(sizeof(branch_by_field_value) == 16); 326 | 327 | friend auto operator<=>(const DecisionNode& a, 328 | const DecisionNode& b) = default; 329 | 330 | // Hashing, see https://abseil.io/docs/cpp/guides/hash. 331 | template 332 | friend H AbslHashValue(H h, const DecisionNode& node) { 333 | return H::combine(std::move(h), node.field, node.default_branch, 334 | node.branch_by_field_value); 335 | } 336 | }; 337 | 338 | // Protect against regressions in memory layout, as it affects performance. 339 | static_assert(sizeof(DecisionNode) == 24); 340 | static_assert(alignof(DecisionNode) == 8); 341 | 342 | SymbolicPacket NodeToPacket(DecisionNode&& node); 343 | 344 | // Helper function for GetConcretePackets that recursively generates a list of 345 | // concrete packets that are contained in the given symbolic packet. This 346 | // function is only used for testing. 347 | void GetConcretePacketsDfs(const SymbolicPacket& symbolic_packet, 348 | Packet& current_packet, 349 | std::vector& result) const; 350 | 351 | // Returns the `DecisionNode` corresponding to the given `SymbolicPacket`, or 352 | // crashes if the `packet` is `EmptySet()` or `FullSet()`. 353 | // 354 | // Unless there is a bug in the implementation of this class, this function 355 | // is NOT expected to be called with these special packets that crash. 356 | const DecisionNode& GetNodeOrDie(SymbolicPacket packet) const; 357 | 358 | [[nodiscard]] std::string ToString(const DecisionNode& node) const; 359 | 360 | // The page size of the `nodes_` vector: 64 MiB or ~ 67 MB. 361 | // Chosen large enough to reduce the cost of dynamic allocation, and small 362 | // enough to avoid excessive memory overhead. 363 | static constexpr size_t kPageSize = (1 << 26) / sizeof(DecisionNode); 364 | 365 | // The decision nodes forming the BDD-style DAG representation of symbolic 366 | // packets. `SymbolicPacket::node_index_` indexes into this vector. 367 | // 368 | // We use a custom vector class that provides pointer stability, allowing us 369 | // to create new nodes while traversing the graph (e.g. during operations like 370 | // `And`, `Or`, `Not`). The class also avoids expensive relocations. 371 | PagedStableVector nodes_; 372 | 373 | // A so called "unique table" to ensure each node is only added to `nodes_` 374 | // once, and thus has a unique `SymbolicPacket::node_index`. 375 | // 376 | // INVARIANT: `packet_by_node_[n] = s` iff `nodes_[s.node_index_] == n`. 377 | absl::flat_hash_map packet_by_node_; 378 | 379 | // INVARIANT: All `DecisionNode` fields are interned by this manager. 380 | InternedFieldManager field_manager_; 381 | 382 | // Allow `SymbolicPacketTransformerManager` to access private methods. 383 | friend class SymbolicPacketTransformerManager; 384 | }; 385 | 386 | } // namespace netkat 387 | 388 | #endif // GOOGLE_NETKAT_NETKAT_SYMBOLIC_PACKET_H_ 389 | -------------------------------------------------------------------------------- /netkat/symbolic_packet_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // ----------------------------------------------------------------------------- 15 | 16 | #include "netkat/symbolic_packet.h" 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "absl/base/no_destructor.h" 23 | #include "absl/container/flat_hash_map.h" 24 | #include "absl/container/flat_hash_set.h" 25 | #include "absl/strings/str_cat.h" 26 | #include "absl/strings/str_split.h" 27 | #include "absl/strings/string_view.h" 28 | #include "fuzztest/fuzztest.h" 29 | #include "gmock/gmock.h" 30 | #include "gtest/gtest.h" 31 | #include "gutil/status_matchers.h" // IWYU pragma: keep 32 | #include "netkat/evaluator.h" 33 | #include "netkat/netkat_proto_constructors.h" 34 | #include "re2/re2.h" 35 | 36 | namespace netkat { 37 | 38 | // We use a global manager object to exercise statefulness more deeply across 39 | // test cases. This also enables better pretty printing for debugging, see 40 | // `PrintTo`. 41 | SymbolicPacketManager& Manager() { 42 | static absl::NoDestructor manager; 43 | return *manager; 44 | } 45 | 46 | // The default `SymbolicPacket` pretty printer sucks! It does not have access to 47 | // the graph structure representing the packet, since that is stored in the 48 | // manager object. Thus, it returns opaque strings like "SymbolicPacket<123>". 49 | // 50 | // We define this much better override, which GoogleTest gives precedence to. 51 | void PrintTo(const SymbolicPacket& packet, std::ostream* os) { 52 | *os << Manager().ToString(packet); 53 | } 54 | 55 | namespace { 56 | 57 | using ::testing::Ge; 58 | using ::testing::Pair; 59 | using ::testing::SizeIs; 60 | using ::testing::StartsWith; 61 | using ::testing::UnorderedElementsAre; 62 | 63 | // After executing all tests, we check once that no invariants are violated, for 64 | // defense in depth. Checking invariants after each test (e.g. using a fixture) 65 | // would likely not scale and seems overkill. 66 | class CheckSymbolicPacketManagerInvariantsOnTearDown 67 | : public testing::Environment { 68 | public: 69 | ~CheckSymbolicPacketManagerInvariantsOnTearDown() override = default; 70 | void SetUp() override {} 71 | void TearDown() override { ASSERT_OK(Manager().CheckInternalInvariants()); } 72 | }; 73 | testing::Environment* const foo_env = testing::AddGlobalTestEnvironment( 74 | new CheckSymbolicPacketManagerInvariantsOnTearDown); 75 | 76 | TEST(SymbolicPacketManagerTest, EmptySetIsEmptySet) { 77 | EXPECT_TRUE(Manager().IsEmptySet(Manager().EmptySet())); 78 | EXPECT_FALSE(Manager().IsFullSet(Manager().EmptySet())); 79 | } 80 | 81 | TEST(SymbolicPacketManagerTest, FullSetIsFullSet) { 82 | EXPECT_TRUE(Manager().IsFullSet(Manager().FullSet())); 83 | EXPECT_FALSE(Manager().IsEmptySet(Manager().FullSet())); 84 | } 85 | 86 | TEST(SymbolicPacketManagerTest, EmptySetDoesNotEqualFullSet) { 87 | EXPECT_NE(Manager().EmptySet(), Manager().FullSet()); 88 | } 89 | 90 | TEST(SymbolicPacketManagerTest, AbslStringifyWorksForEmptySet) { 91 | EXPECT_THAT(absl::StrCat(Manager().EmptySet()), StartsWith("SymbolicPacket")); 92 | } 93 | 94 | TEST(SymbolicPacketManagerTest, AbslStringifyWorksForFullSet) { 95 | EXPECT_THAT(absl::StrCat(Manager().FullSet()), StartsWith("SymbolicPacket")); 96 | } 97 | 98 | TEST(SymbolicPacketManagerTest, AbslHashValueWorks) { 99 | absl::flat_hash_set set = { 100 | Manager().EmptySet(), 101 | Manager().FullSet(), 102 | }; 103 | EXPECT_EQ(set.size(), 2); 104 | } 105 | 106 | TEST(SymbolicPacketManagerTest, TrueCompilesToFullSet) { 107 | EXPECT_EQ(Manager().Compile(TrueProto()), Manager().FullSet()); 108 | } 109 | 110 | TEST(SymbolicPacketManagerTest, FalseCompilesToEmptySet) { 111 | EXPECT_EQ(Manager().Compile(FalseProto()), Manager().EmptySet()); 112 | } 113 | 114 | void MatchCompilesToMatch(std::string field, int value) { 115 | EXPECT_EQ(Manager().Compile(MatchProto(field, value)), 116 | Manager().Match(field, value)); 117 | } 118 | FUZZ_TEST(SymbolicPacketManagerTest, MatchCompilesToMatch); 119 | 120 | void AndCompilesToAnd(const PredicateProto& left, const PredicateProto& right) { 121 | EXPECT_EQ(Manager().Compile(AndProto(left, right)), 122 | Manager().And(Manager().Compile(left), Manager().Compile(right))); 123 | } 124 | FUZZ_TEST(SymbolicPacketManagerTest, AndCompilesToAnd); 125 | 126 | void OrCompilesToOr(const PredicateProto& left, const PredicateProto& right) { 127 | EXPECT_EQ(Manager().Compile(OrProto(left, right)), 128 | Manager().Or(Manager().Compile(left), Manager().Compile(right))); 129 | } 130 | FUZZ_TEST(SymbolicPacketManagerTest, OrCompilesToOr); 131 | 132 | void NotCompilesToNot(const PredicateProto& pred) { 133 | EXPECT_EQ(Manager().Compile(NotProto(pred)), 134 | Manager().Not(Manager().Compile(pred))); 135 | } 136 | FUZZ_TEST(SymbolicPacketManagerTest, NotCompilesToNot); 137 | 138 | void CompilationPreservesSemantics(const PredicateProto& pred, 139 | const Packet& packet) { 140 | EXPECT_EQ(Manager().Contains(Manager().Compile(pred), packet), 141 | Evaluate(pred, packet)); 142 | } 143 | FUZZ_TEST(SymbolicPacketManagerTest, CompilationPreservesSemantics); 144 | 145 | void GetConcretePacketsReturnsNonEmptyListForNonEmptySet( 146 | const PredicateProto& pred) { 147 | SymbolicPacket symbolic_packet = Manager().Compile(pred); 148 | if (!Manager().IsEmptySet(symbolic_packet)) { 149 | EXPECT_THAT(Manager().GetConcretePackets(symbolic_packet), SizeIs(Ge(1))); 150 | } 151 | } 152 | FUZZ_TEST(SymbolicPacketManagerTest, 153 | GetConcretePacketsReturnsNonEmptyListForNonEmptySet); 154 | 155 | void GetConcretePacketsReturnsPacketsInSet(const PredicateProto& pred) { 156 | SymbolicPacket symbolic_packet = Manager().Compile(pred); 157 | for (const Packet& concrete_packet : 158 | Manager().GetConcretePackets(symbolic_packet)) { 159 | EXPECT_TRUE(Manager().Contains(symbolic_packet, concrete_packet)); 160 | } 161 | } 162 | FUZZ_TEST(SymbolicPacketManagerTest, GetConcretePacketsReturnsPacketsInSet); 163 | 164 | TEST(SymbolicPacketManagerTest, 165 | PacketsFromPacketSetWithMultipleFieldsAreContainedInPacketSet) { 166 | // p = (a=3 && b=4) || (b!=5 && c=5) 167 | SymbolicPacket symbolic_packet = Manager().Compile( 168 | OrProto(AndProto(MatchProto("a", 3), MatchProto("b", 4)), 169 | AndProto(NotProto(MatchProto("b", 5)), MatchProto("c", 5)))); 170 | std::vector concrete_packets = 171 | Manager().GetConcretePackets(symbolic_packet); 172 | for (const Packet& concrete_packet : concrete_packets) { 173 | EXPECT_TRUE(Manager().Contains(symbolic_packet, concrete_packet)); 174 | } 175 | } 176 | 177 | void EqualPredicatesCompileToEqualSymbolicPackets(const PredicateProto& pred) { 178 | EXPECT_EQ(Manager().Compile(pred), Manager().Compile(pred)); 179 | } 180 | FUZZ_TEST(SymbolicPacketManagerTest, 181 | EqualPredicatesCompileToEqualSymbolicPackets); 182 | 183 | void NegationCompilesToDifferentSymbolicPacket(const PredicateProto& pred) { 184 | EXPECT_NE(Manager().Compile(pred), Manager().Compile(NotProto(pred))); 185 | } 186 | FUZZ_TEST(SymbolicPacketManagerTest, NegationCompilesToDifferentSymbolicPacket); 187 | 188 | void DoubleNegationCompilesToSameSymbolicPacket(const PredicateProto& pred) { 189 | EXPECT_EQ(Manager().Compile(pred), 190 | Manager().Compile(NotProto(NotProto(pred)))); 191 | } 192 | FUZZ_TEST(SymbolicPacketManagerTest, 193 | DoubleNegationCompilesToSameSymbolicPacket); 194 | 195 | TEST(SymbolicPacketManagerTest, TrueNotEqualsMatch) { 196 | EXPECT_NE(Manager().Compile(TrueProto()), 197 | Manager().Compile(MatchProto("hi", 42))); 198 | } 199 | TEST(SymbolicPacketManagerTest, FalseNotEqualsMatch) { 200 | EXPECT_NE(Manager().Compile(FalseProto()), 201 | Manager().Compile(MatchProto("hi", 42))); 202 | } 203 | TEST(SymbolicPacketManagerTest, MatchNotEqualsDifferentMatch) { 204 | EXPECT_NE(Manager().Compile(MatchProto("hi", 42)), 205 | Manager().Compile(MatchProto("bye", 42))); 206 | EXPECT_NE(Manager().Compile(MatchProto("hi", 42)), 207 | Manager().Compile(MatchProto("hi", 24))); 208 | } 209 | TEST(SymbolicPacketManagerTest, NotTrueEqualsFalse) { 210 | EXPECT_EQ(Manager().Compile(NotProto(TrueProto())), 211 | Manager().Compile(FalseProto())); 212 | } 213 | 214 | void AndIsIdempotent(const PredicateProto& pred) { 215 | EXPECT_EQ(Manager().Compile(AndProto(pred, pred)), Manager().Compile(pred)); 216 | } 217 | FUZZ_TEST(SymbolicPacketManagerTest, AndIsIdempotent); 218 | 219 | void OrIsIdempotent(const PredicateProto& pred) { 220 | EXPECT_EQ(Manager().Compile(OrProto(pred, pred)), Manager().Compile(pred)); 221 | } 222 | FUZZ_TEST(SymbolicPacketManagerTest, OrIsIdempotent); 223 | 224 | void PredOrItsNegationIsTrue(const PredicateProto& pred) { 225 | EXPECT_EQ(Manager().Compile(OrProto(pred, NotProto(pred))), 226 | Manager().Compile(TrueProto())); 227 | } 228 | FUZZ_TEST(SymbolicPacketManagerTest, PredOrItsNegationIsTrue); 229 | 230 | void PredAndItsNegationIsFalse(const PredicateProto& pred) { 231 | EXPECT_EQ(Manager().Compile(AndProto(pred, NotProto(pred))), 232 | Manager().Compile(FalseProto())); 233 | } 234 | FUZZ_TEST(SymbolicPacketManagerTest, PredAndItsNegationIsFalse); 235 | 236 | void AndTrueIsIdentity(const PredicateProto& pred) { 237 | EXPECT_EQ(Manager().Compile(AndProto(pred, TrueProto())), 238 | Manager().Compile(pred)); 239 | } 240 | FUZZ_TEST(SymbolicPacketManagerTest, AndTrueIsIdentity); 241 | 242 | void OrFalseIsIdentity(const PredicateProto& pred) { 243 | EXPECT_EQ(Manager().Compile(OrProto(pred, FalseProto())), 244 | Manager().Compile(pred)); 245 | } 246 | FUZZ_TEST(SymbolicPacketManagerTest, OrFalseIsIdentity); 247 | 248 | void XorFalseIsIdentity(const PredicateProto& pred) { 249 | EXPECT_EQ(Manager().Compile(XorProto(pred, FalseProto())), 250 | Manager().Compile(pred)); 251 | } 252 | FUZZ_TEST(SymbolicPacketManagerTest, XorFalseIsIdentity); 253 | 254 | void AndFalseIsFalse(const PredicateProto& pred) { 255 | EXPECT_EQ(Manager().Compile(AndProto(pred, FalseProto())), 256 | Manager().Compile(FalseProto())); 257 | } 258 | FUZZ_TEST(SymbolicPacketManagerTest, AndFalseIsFalse); 259 | 260 | void OrTrueIsTrue(const PredicateProto& pred) { 261 | EXPECT_EQ(Manager().Compile(OrProto(pred, TrueProto())), 262 | Manager().Compile(TrueProto())); 263 | } 264 | FUZZ_TEST(SymbolicPacketManagerTest, OrTrueIsTrue); 265 | 266 | void XorSelfIsFalse(const PredicateProto& pred) { 267 | EXPECT_EQ(Manager().Compile(XorProto(pred, pred)), 268 | Manager().Compile(FalseProto())); 269 | } 270 | FUZZ_TEST(SymbolicPacketManagerTest, XorSelfIsFalse); 271 | 272 | void AndIsCommutative(const PredicateProto& a, const PredicateProto& b) { 273 | EXPECT_EQ(Manager().Compile(AndProto(a, b)), 274 | Manager().Compile(AndProto(b, a))); 275 | } 276 | FUZZ_TEST(SymbolicPacketManagerTest, AndIsCommutative); 277 | 278 | void OrIsCommutative(const PredicateProto& a, const PredicateProto& b) { 279 | EXPECT_EQ(Manager().Compile(OrProto(a, b)), Manager().Compile(OrProto(b, a))); 280 | } 281 | FUZZ_TEST(SymbolicPacketManagerTest, OrIsCommutative); 282 | 283 | void XorIsCommutative(const PredicateProto& a, const PredicateProto& b) { 284 | EXPECT_EQ(Manager().Compile(XorProto(a, b)), 285 | Manager().Compile(XorProto(b, a))); 286 | } 287 | FUZZ_TEST(SymbolicPacketManagerTest, XorIsCommutative); 288 | 289 | void DistributiveLawsHolds(const PredicateProto& a, const PredicateProto& b, 290 | const PredicateProto& c) { 291 | EXPECT_EQ(Manager().Compile(AndProto(a, OrProto(b, c))), 292 | Manager().Compile(OrProto(AndProto(a, b), AndProto(a, c)))); 293 | EXPECT_EQ(Manager().Compile(OrProto(a, AndProto(b, c))), 294 | Manager().Compile(AndProto(OrProto(a, b), OrProto(a, c)))); 295 | } 296 | FUZZ_TEST(SymbolicPacketManagerTest, DistributiveLawsHolds); 297 | 298 | void DeMorgansLawsHolds(const PredicateProto& a, const PredicateProto& b) { 299 | EXPECT_EQ(Manager().Compile(NotProto(AndProto(a, b))), 300 | Manager().Compile(OrProto(NotProto(a), NotProto(b)))); 301 | EXPECT_EQ(Manager().Compile(NotProto(OrProto(a, b))), 302 | Manager().Compile(AndProto(NotProto(a), NotProto(b)))); 303 | } 304 | FUZZ_TEST(SymbolicPacketManagerTest, DeMorgansLawsHolds); 305 | 306 | void XorDefinition(const PredicateProto& a, const PredicateProto& b) { 307 | EXPECT_EQ(Manager().Compile(XorProto(a, b)), 308 | Manager().Compile( 309 | OrProto(AndProto(NotProto(a), b), AndProto(a, NotProto(b))))); 310 | } 311 | FUZZ_TEST(SymbolicPacketManagerTest, XorDefinition); 312 | 313 | void AndIsAssociative(const PredicateProto& a, const PredicateProto& b, 314 | const PredicateProto& c) { 315 | EXPECT_EQ(Manager().Compile(AndProto(a, AndProto(b, c))), 316 | Manager().Compile(AndProto(AndProto(a, b), c))); 317 | } 318 | FUZZ_TEST(SymbolicPacketManagerTest, AndIsAssociative); 319 | 320 | void OrIsAssociative(const PredicateProto& a, const PredicateProto& b, 321 | const PredicateProto& c) { 322 | EXPECT_EQ(Manager().Compile(OrProto(a, OrProto(b, c))), 323 | Manager().Compile(OrProto(OrProto(a, b), c))); 324 | } 325 | FUZZ_TEST(SymbolicPacketManagerTest, OrIsAssociative); 326 | 327 | void XorIsAssociative(const PredicateProto& a, const PredicateProto& b, 328 | const PredicateProto& c) { 329 | EXPECT_EQ(Manager().Compile(XorProto(a, XorProto(b, c))), 330 | Manager().Compile(XorProto(XorProto(a, b), c))); 331 | } 332 | FUZZ_TEST(SymbolicPacketManagerTest, XorIsAssociative); 333 | 334 | } // namespace 335 | } // namespace netkat 336 | -------------------------------------------------------------------------------- /netkat/symbolic_packet_test.expected: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | Test case: p := (a=3 && b=4) || (b!=5 && c=5). Example from Katch paper Fig 3. 3 | ================================================================================ 4 | -- STRING ---------------------------------------------------------------------- 5 | SymbolicPacket<14>: 6 | InternedField<0>:'a' == 3 -> SymbolicPacket<13> 7 | InternedField<0>:'a' == * -> SymbolicPacket<6> 8 | SymbolicPacket<13>: 9 | InternedField<1>:'b' == 4 -> SymbolicPacket 10 | InternedField<1>:'b' == 5 -> SymbolicPacket 11 | InternedField<1>:'b' == * -> SymbolicPacket<5> 12 | SymbolicPacket<6>: 13 | InternedField<1>:'b' == 5 -> SymbolicPacket 14 | InternedField<1>:'b' == * -> SymbolicPacket<5> 15 | SymbolicPacket<5>: 16 | InternedField<2>:'c' == 5 -> SymbolicPacket 17 | InternedField<2>:'c' == * -> SymbolicPacket 18 | -- DOT ------------------------------------------------------------------------- 19 | digraph { 20 | 4294967294 [label="T" shape=box] 21 | 4294967295 [label="F" shape=box] 22 | 14 [label="a"] 23 | 14 -> 13 [label="3"] 24 | 14 -> 6 [style=dashed] 25 | 13 [label="b"] 26 | 13 -> 4294967294 [label="4"] 27 | 13 -> 4294967295 [label="5"] 28 | 13 -> 5 [style=dashed] 29 | 6 [label="b"] 30 | 6 -> 4294967295 [label="5"] 31 | 6 -> 5 [style=dashed] 32 | 5 [label="c"] 33 | 5 -> 4294967294 [label="5"] 34 | 5 -> 4294967295 [style=dashed] 35 | } 36 | ================================================================================ 37 | Test case: q := (b=3 && c=4) || (a=5 && c!=5). Example from Katch paper Fig 3. 38 | ================================================================================ 39 | -- STRING ---------------------------------------------------------------------- 40 | SymbolicPacket<24>: 41 | InternedField<0>:'a' == 5 -> SymbolicPacket<9> 42 | InternedField<0>:'a' == * -> SymbolicPacket<17> 43 | SymbolicPacket<9>: 44 | InternedField<2>:'c' == 5 -> SymbolicPacket 45 | InternedField<2>:'c' == * -> SymbolicPacket 46 | SymbolicPacket<17>: 47 | InternedField<1>:'b' == 3 -> SymbolicPacket<16> 48 | InternedField<1>:'b' == * -> SymbolicPacket 49 | SymbolicPacket<16>: 50 | InternedField<2>:'c' == 4 -> SymbolicPacket 51 | InternedField<2>:'c' == * -> SymbolicPacket 52 | -- DOT ------------------------------------------------------------------------- 53 | digraph { 54 | 4294967294 [label="T" shape=box] 55 | 4294967295 [label="F" shape=box] 56 | 24 [label="a"] 57 | 24 -> 9 [label="5"] 58 | 24 -> 17 [style=dashed] 59 | 9 [label="c"] 60 | 9 -> 4294967295 [label="5"] 61 | 9 -> 4294967294 [style=dashed] 62 | 17 [label="b"] 63 | 17 -> 16 [label="3"] 64 | 17 -> 4294967295 [style=dashed] 65 | 16 [label="c"] 66 | 16 -> 4294967294 [label="4"] 67 | 16 -> 4294967295 [style=dashed] 68 | } 69 | ================================================================================ 70 | Test case: p + q. Example from Katch paper Fig 3. 71 | ================================================================================ 72 | -- STRING ---------------------------------------------------------------------- 73 | SymbolicPacket<34>: 74 | InternedField<0>:'a' == 3 -> SymbolicPacket<32> 75 | InternedField<0>:'a' == 5 -> SymbolicPacket<33> 76 | InternedField<0>:'a' == * -> SymbolicPacket<31> 77 | SymbolicPacket<32>: 78 | InternedField<1>:'b' == 3 -> SymbolicPacket<30> 79 | InternedField<1>:'b' == 4 -> SymbolicPacket 80 | InternedField<1>:'b' == 5 -> SymbolicPacket 81 | InternedField<1>:'b' == * -> SymbolicPacket<5> 82 | SymbolicPacket<33>: 83 | InternedField<1>:'b' == 5 -> SymbolicPacket<9> 84 | InternedField<1>:'b' == * -> SymbolicPacket 85 | SymbolicPacket<31>: 86 | InternedField<1>:'b' == 3 -> SymbolicPacket<30> 87 | InternedField<1>:'b' == 5 -> SymbolicPacket 88 | InternedField<1>:'b' == * -> SymbolicPacket<5> 89 | SymbolicPacket<30>: 90 | InternedField<2>:'c' == 4 -> SymbolicPacket 91 | InternedField<2>:'c' == 5 -> SymbolicPacket 92 | InternedField<2>:'c' == * -> SymbolicPacket 93 | SymbolicPacket<5>: 94 | InternedField<2>:'c' == 5 -> SymbolicPacket 95 | InternedField<2>:'c' == * -> SymbolicPacket 96 | SymbolicPacket<9>: 97 | InternedField<2>:'c' == 5 -> SymbolicPacket 98 | InternedField<2>:'c' == * -> SymbolicPacket 99 | -- DOT ------------------------------------------------------------------------- 100 | digraph { 101 | 4294967294 [label="T" shape=box] 102 | 4294967295 [label="F" shape=box] 103 | 34 [label="a"] 104 | 34 -> 32 [label="3"] 105 | 34 -> 33 [label="5"] 106 | 34 -> 31 [style=dashed] 107 | 32 [label="b"] 108 | 32 -> 30 [label="3"] 109 | 32 -> 4294967294 [label="4"] 110 | 32 -> 4294967295 [label="5"] 111 | 32 -> 5 [style=dashed] 112 | 33 [label="b"] 113 | 33 -> 9 [label="5"] 114 | 33 -> 4294967294 [style=dashed] 115 | 31 [label="b"] 116 | 31 -> 30 [label="3"] 117 | 31 -> 4294967295 [label="5"] 118 | 31 -> 5 [style=dashed] 119 | 30 [label="c"] 120 | 30 -> 4294967294 [label="4"] 121 | 30 -> 4294967294 [label="5"] 122 | 30 -> 4294967295 [style=dashed] 123 | 5 [label="c"] 124 | 5 -> 4294967294 [label="5"] 125 | 5 -> 4294967295 [style=dashed] 126 | 9 [label="c"] 127 | 9 -> 4294967295 [label="5"] 128 | 9 -> 4294967294 [style=dashed] 129 | } 130 | -------------------------------------------------------------------------------- /netkat/symbolic_packet_test_runner.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // ----------------------------------------------------------------------------- 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "netkat/netkat_proto_constructors.h" 22 | #include "netkat/symbolic_packet.h" 23 | 24 | namespace netkat { 25 | namespace { 26 | 27 | constexpr char kBanner[] = 28 | "==========================================================================" 29 | "======\n"; 30 | constexpr char kDotHeader[] = 31 | "-- DOT -------------------------------------------------------------------" 32 | "------\n"; 33 | constexpr char kStringHeader[] = 34 | "-- STRING ----------------------------------------------------------------" 35 | "------\n"; 36 | 37 | // A test case for the `ValidateTestRun` function. 38 | struct TestCase { 39 | // Human-readable description of this test case, for documentation. 40 | std::string description; 41 | PredicateProto predicate; 42 | }; 43 | 44 | std::vector TestCases() { 45 | std::vector test_cases; 46 | 47 | PredicateProto p = 48 | OrProto(AndProto(MatchProto("a", 3), MatchProto("b", 4)), 49 | AndProto(NotProto(MatchProto("b", 5)), MatchProto("c", 5))); 50 | test_cases.push_back({ 51 | .description = 52 | "p := (a=3 && b=4) || (b!=5 && c=5). Example from Katch paper Fig 3.", 53 | .predicate = p, 54 | }); 55 | 56 | PredicateProto q = 57 | OrProto(AndProto(MatchProto("b", 3), MatchProto("c", 4)), 58 | AndProto(MatchProto("a", 5), NotProto(MatchProto("c", 5)))); 59 | test_cases.push_back({ 60 | .description = 61 | "q := (b=3 && c=4) || (a=5 && c!=5). Example from Katch paper Fig 3.", 62 | .predicate = q, 63 | }); 64 | 65 | test_cases.push_back({ 66 | .description = "p + q. Example from Katch paper Fig 3.", 67 | .predicate = OrProto(p, q), 68 | }); 69 | 70 | return test_cases; 71 | } 72 | 73 | void main() { 74 | // This test needs a deterministic field interning order, and thus must start 75 | // from a fresh manager. 76 | SymbolicPacketManager manager; 77 | for (const TestCase& test_case : TestCases()) { 78 | netkat::SymbolicPacket symbolic_packet = 79 | manager.Compile(test_case.predicate); 80 | std::cout << kBanner << "Test case: " << test_case.description << std::endl 81 | << kBanner; 82 | std::cout << kStringHeader << manager.ToString(symbolic_packet); 83 | std::cout << kDotHeader << manager.ToDot(symbolic_packet); 84 | } 85 | } 86 | 87 | } // namespace 88 | } // namespace netkat 89 | 90 | int main() { netkat::main(); } 91 | -------------------------------------------------------------------------------- /netkat/symbolic_packet_transformer_test.expected: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | Test case: p := (a=5 + b=2);(b:=1 + c=5). Example from Katch paper Fig 5. 3 | ================================================================================ 4 | -- STRING ---------------------------------------------------------------------- 5 | SymbolicPacketTransformer<7>: 6 | InternedField<0>:'a' == 5: 7 | InternedField<0>:'a' := 5 -> SymbolicPacketTransformer<5> 8 | InternedField<0>:'a' == *: 9 | InternedField<0>:'a' == * -> SymbolicPacketTransformer<6> 10 | SymbolicPacketTransformer<5>: 11 | InternedField<1>:'b' == *: 12 | InternedField<1>:'b' := 1 -> SymbolicPacketTransformer 13 | InternedField<1>:'b' == * -> SymbolicPacketTransformer<4> 14 | SymbolicPacketTransformer<6>: 15 | InternedField<1>:'b' == 2: 16 | InternedField<1>:'b' := 1 -> SymbolicPacketTransformer 17 | InternedField<1>:'b' := 2 -> SymbolicPacketTransformer<4> 18 | InternedField<1>:'b' == *: 19 | InternedField<1>:'b' == * -> SymbolicPacketTransformer 20 | SymbolicPacketTransformer<4>: 21 | InternedField<2>:'c' == 5: 22 | InternedField<2>:'c' := 5 -> SymbolicPacketTransformer 23 | InternedField<2>:'c' == *: 24 | InternedField<2>:'c' == * -> SymbolicPacketTransformer 25 | -- DOT ------------------------------------------------------------------------- 26 | digraph { 27 | 4294967294 [label="T" shape=box] 28 | 4294967295 [label="F" shape=box] 29 | 7 [label="a"] 30 | 7 -> 5 [label="a==5; a:=5"] 31 | 7 -> 6 [style=dashed] 32 | 5 [label="b"] 33 | 5 -> 4294967294 [label="b==*; b:=1"] 34 | 5 -> 4 [style=dashed] 35 | 6 [label="b"] 36 | 6 -> 4294967294 [label="b==2; b:=1"] 37 | 6 -> 4 [label="b==2; b:=2"] 38 | 6 -> 4294967295 [style=dashed] 39 | 4 [label="c"] 40 | 4 -> 4294967294 [label="c==5; c:=5"] 41 | 4 -> 4294967295 [style=dashed] 42 | } 43 | ================================================================================ 44 | Test case: q := (b=1 + c:=4 + a:=5;b:=1). Example from Katch paper Fig 5. 45 | ================================================================================ 46 | -- STRING ---------------------------------------------------------------------- 47 | SymbolicPacketTransformer<16>: 48 | InternedField<0>:'a' == 1: 49 | InternedField<0>:'a' := 1 -> SymbolicPacketTransformer<13> 50 | InternedField<0>:'a' == *: 51 | InternedField<0>:'a' := 1 -> SymbolicPacketTransformer<3> 52 | InternedField<0>:'a' == * -> SymbolicPacketTransformer<15> 53 | SymbolicPacketTransformer<13>: 54 | InternedField<1>:'b' == 1: 55 | InternedField<1>:'b' := 1 -> SymbolicPacketTransformer<12> 56 | InternedField<1>:'b' == *: 57 | InternedField<1>:'b' := 1 -> SymbolicPacketTransformer 58 | InternedField<1>:'b' == * -> SymbolicPacketTransformer<9> 59 | SymbolicPacketTransformer<3>: 60 | InternedField<1>:'b' == *: 61 | InternedField<1>:'b' := 1 -> SymbolicPacketTransformer 62 | InternedField<1>:'b' == * -> SymbolicPacketTransformer 63 | SymbolicPacketTransformer<15>: 64 | InternedField<1>:'b' == 1: 65 | InternedField<1>:'b' := 1 -> SymbolicPacketTransformer<12> 66 | InternedField<1>:'b' == *: 67 | InternedField<1>:'b' == * -> SymbolicPacketTransformer<9> 68 | SymbolicPacketTransformer<12>: 69 | InternedField<2>:'c' == *: 70 | InternedField<2>:'c' := 4 -> SymbolicPacketTransformer 71 | InternedField<2>:'c' == * -> SymbolicPacketTransformer 72 | SymbolicPacketTransformer<9>: 73 | InternedField<2>:'c' == *: 74 | InternedField<2>:'c' := 4 -> SymbolicPacketTransformer 75 | InternedField<2>:'c' == * -> SymbolicPacketTransformer 76 | -- DOT ------------------------------------------------------------------------- 77 | digraph { 78 | 4294967294 [label="T" shape=box] 79 | 4294967295 [label="F" shape=box] 80 | 16 [label="a"] 81 | 16 -> 13 [label="a==1; a:=1"] 82 | 16 -> 3 [label="a==*; a:=1"] 83 | 16 -> 15 [style=dashed] 84 | 13 [label="b"] 85 | 13 -> 12 [label="b==1; b:=1"] 86 | 13 -> 4294967294 [label="b==*; b:=1"] 87 | 13 -> 9 [style=dashed] 88 | 3 [label="b"] 89 | 3 -> 4294967294 [label="b==*; b:=1"] 90 | 3 -> 4294967295 [style=dashed] 91 | 15 [label="b"] 92 | 15 -> 12 [label="b==1; b:=1"] 93 | 15 -> 9 [style=dashed] 94 | 12 [label="c"] 95 | 12 -> 4294967294 [label="c==*; c:=4"] 96 | 12 -> 4294967294 [style=dashed] 97 | 9 [label="c"] 98 | 9 -> 4294967294 [label="c==*; c:=4"] 99 | 9 -> 4294967295 [style=dashed] 100 | } 101 | ================================================================================ 102 | Test case: p;q. Example from Katch paper Fig 5. 103 | ================================================================================ 104 | -- STRING ---------------------------------------------------------------------- 105 | SymbolicPacketTransformer<21>: 106 | InternedField<0>:'a' == 1: 107 | InternedField<0>:'a' := 1 -> SymbolicPacketTransformer<18> 108 | InternedField<0>:'a' == 5: 109 | InternedField<0>:'a' := 1 -> SymbolicPacketTransformer<3> 110 | InternedField<0>:'a' := 5 -> SymbolicPacketTransformer<20> 111 | InternedField<0>:'a' == *: 112 | InternedField<0>:'a' := 1 -> SymbolicPacketTransformer<19> 113 | InternedField<0>:'a' == * -> SymbolicPacketTransformer<18> 114 | SymbolicPacketTransformer<18>: 115 | InternedField<1>:'b' == 2: 116 | InternedField<1>:'b' := 1 -> SymbolicPacketTransformer<12> 117 | InternedField<1>:'b' := 2 -> SymbolicPacketTransformer<17> 118 | InternedField<1>:'b' == *: 119 | InternedField<1>:'b' == * -> SymbolicPacketTransformer 120 | SymbolicPacketTransformer<3>: 121 | InternedField<1>:'b' == *: 122 | InternedField<1>:'b' := 1 -> SymbolicPacketTransformer 123 | InternedField<1>:'b' == * -> SymbolicPacketTransformer 124 | SymbolicPacketTransformer<20>: 125 | InternedField<1>:'b' == *: 126 | InternedField<1>:'b' := 1 -> SymbolicPacketTransformer<12> 127 | InternedField<1>:'b' == * -> SymbolicPacketTransformer<17> 128 | SymbolicPacketTransformer<19>: 129 | InternedField<1>:'b' == 2: 130 | InternedField<1>:'b' := 1 -> SymbolicPacketTransformer 131 | InternedField<1>:'b' == *: 132 | InternedField<1>:'b' == * -> SymbolicPacketTransformer 133 | SymbolicPacketTransformer<12>: 134 | InternedField<2>:'c' == *: 135 | InternedField<2>:'c' := 4 -> SymbolicPacketTransformer 136 | InternedField<2>:'c' == * -> SymbolicPacketTransformer 137 | SymbolicPacketTransformer<17>: 138 | InternedField<2>:'c' == 5: 139 | InternedField<2>:'c' := 4 -> SymbolicPacketTransformer 140 | InternedField<2>:'c' == *: 141 | InternedField<2>:'c' == * -> SymbolicPacketTransformer 142 | -- DOT ------------------------------------------------------------------------- 143 | digraph { 144 | 4294967294 [label="T" shape=box] 145 | 4294967295 [label="F" shape=box] 146 | 21 [label="a"] 147 | 21 -> 18 [label="a==1; a:=1"] 148 | 21 -> 3 [label="a==5; a:=1"] 149 | 21 -> 20 [label="a==5; a:=5"] 150 | 21 -> 19 [label="a==*; a:=1"] 151 | 21 -> 18 [style=dashed] 152 | 18 [label="b"] 153 | 18 -> 12 [label="b==2; b:=1"] 154 | 18 -> 17 [label="b==2; b:=2"] 155 | 18 -> 4294967295 [style=dashed] 156 | 3 [label="b"] 157 | 3 -> 4294967294 [label="b==*; b:=1"] 158 | 3 -> 4294967295 [style=dashed] 159 | 20 [label="b"] 160 | 20 -> 12 [label="b==*; b:=1"] 161 | 20 -> 17 [style=dashed] 162 | 19 [label="b"] 163 | 19 -> 4294967294 [label="b==2; b:=1"] 164 | 19 -> 4294967295 [style=dashed] 165 | 12 [label="c"] 166 | 12 -> 4294967294 [label="c==*; c:=4"] 167 | 12 -> 4294967294 [style=dashed] 168 | 17 [label="c"] 169 | 17 -> 4294967294 [label="c==5; c:=4"] 170 | 17 -> 4294967295 [style=dashed] 171 | } 172 | -------------------------------------------------------------------------------- /netkat/symbolic_packet_transformer_test_runner.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // ----------------------------------------------------------------------------- 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "netkat/netkat_proto_constructors.h" 22 | #include "netkat/symbolic_packet_transformer.h" 23 | 24 | namespace netkat { 25 | namespace { 26 | 27 | constexpr char kBanner[] = 28 | "==========================================================================" 29 | "======\n"; 30 | constexpr char kDotHeader[] = 31 | "-- DOT -------------------------------------------------------------------" 32 | "------\n"; 33 | constexpr char kStringHeader[] = 34 | "-- STRING ----------------------------------------------------------------" 35 | "------\n"; 36 | 37 | // A test case for the `ValidateTestRun` function. 38 | struct TestCase { 39 | // Human-readable description of this test case, for documentation. 40 | std::string description; 41 | PolicyProto policy; 42 | }; 43 | 44 | std::vector TestCases() { 45 | std::vector test_cases; 46 | PolicyProto p = SequenceProto( 47 | UnionProto(FilterProto(MatchProto("a", 5)), 48 | FilterProto(MatchProto("b", 2))), 49 | UnionProto(ModificationProto("b", 1), FilterProto(MatchProto("c", 5)))); 50 | 51 | test_cases.push_back({ 52 | .description = 53 | "p := (a=5 + b=2);(b:=1 + c=5). Example from Katch paper Fig 5.", 54 | .policy = p, 55 | }); 56 | 57 | PolicyProto q = UnionProto( 58 | FilterProto(MatchProto("b", 1)), 59 | UnionProto( 60 | ModificationProto("c", 4), 61 | SequenceProto(ModificationProto("a", 1), ModificationProto("b", 1)))); 62 | 63 | test_cases.push_back({ 64 | .description = 65 | "q := (b=1 + c:=4 + a:=5;b:=1). Example from Katch paper Fig 5.", 66 | .policy = q, 67 | }); 68 | 69 | test_cases.push_back({ 70 | .description = "p;q. Example from Katch paper Fig 5.", 71 | .policy = SequenceProto(p, q), 72 | }); 73 | 74 | return test_cases; 75 | } 76 | 77 | void main() { 78 | // This test needs a deterministic field interning order, and thus must start 79 | // from a fresh manager. 80 | SymbolicPacketTransformerManager manager; 81 | for (const TestCase& test_case : TestCases()) { 82 | netkat::SymbolicPacketTransformer symbolic_packet_transformer = 83 | manager.Compile(test_case.policy); 84 | std::cout << kBanner << "Test case: " << test_case.description << std::endl 85 | << kBanner; 86 | std::cout << kStringHeader << manager.ToString(symbolic_packet_transformer); 87 | std::cout << kDotHeader << manager.ToDot(symbolic_packet_transformer); 88 | } 89 | } 90 | 91 | } // namespace 92 | } // namespace netkat 93 | 94 | int main() { netkat::main(); } 95 | -------------------------------------------------------------------------------- /netkat/table.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // ----------------------------------------------------------------------------- 16 | // File: table.cc 17 | // ----------------------------------------------------------------------------- 18 | #include "netkat/table.h" 19 | 20 | #include 21 | #include 22 | 23 | #include "absl/container/btree_map.h" 24 | #include "absl/log/check.h" 25 | #include "absl/status/status.h" 26 | #include "absl/strings/str_cat.h" 27 | #include "netkat/frontend.h" 28 | #include "netkat/netkat.pb.h" 29 | #include "netkat/symbolic_packet.h" 30 | #include "netkat/symbolic_packet_transformer.h" 31 | 32 | namespace netkat { 33 | namespace { 34 | 35 | // Walks the underlying policy in `action` and returns an error if a `Filter` 36 | // policy is present. 37 | // 38 | // TODO(anthonyroy): Can we make this IR-unaware? Maybe add it to the backend? 39 | absl::Status VerifyActionHasNoPredicate(const Policy& action) { 40 | std::vector stack = {&action.GetProto()}; 41 | while (!stack.empty()) { 42 | const PolicyProto* policy = stack.back(); 43 | stack.pop_back(); 44 | 45 | switch (policy->policy_case()) { 46 | case PolicyProto::PolicyCase::kIterateOp: 47 | stack.push_back(&policy->iterate_op().iterable()); 48 | break; 49 | case PolicyProto::PolicyCase::kUnionOp: 50 | stack.push_back(&policy->union_op().left()); 51 | stack.push_back(&policy->union_op().right()); 52 | break; 53 | case PolicyProto::PolicyCase::kSequenceOp: 54 | stack.push_back(&policy->sequence_op().left()); 55 | stack.push_back(&policy->sequence_op().right()); 56 | break; 57 | case PolicyProto::PolicyCase::kFilter: 58 | // Allow the Deny policy. 59 | if (policy->filter().has_bool_constant() && 60 | policy->filter().bool_constant().value() == false) { 61 | continue; 62 | } 63 | return absl::InvalidArgumentError( 64 | absl::StrCat("Action contains predicate: ", *policy)); 65 | case PolicyProto::PolicyCase::kModification: 66 | case PolicyProto::PolicyCase::kRecord: 67 | break; 68 | case PolicyProto::PolicyCase::POLICY_NOT_SET: 69 | return absl::InvalidArgumentError( 70 | absl::StrCat("Policy case missing: ", *policy)); 71 | } 72 | } 73 | return absl::OkStatus(); 74 | } 75 | 76 | // Returns whether or not the new vs old rules maintain determinism. I.e. this 77 | // enforces the p1 && p2 ≡ False OR p1; p2; p1_action ≡ p1; p2; p2_action 78 | // requirement. 79 | // 80 | // This is only run against rules of the same priority. 81 | absl::Status VerifyRuleDeterminism( 82 | const NetkatTable::PendingRuleInfo& info, 83 | SymbolicPacketTransformerManager& policy_manager) { 84 | if (info.existing_match == nullptr || info.existing_policy == nullptr) { 85 | return absl::OkStatus(); 86 | } 87 | 88 | SymbolicPacketManager& packet_manager = 89 | policy_manager.GetSymbolicPacketManager(); 90 | SymbolicPacket new_packet = packet_manager.Compile(info.new_match.GetProto()); 91 | SymbolicPacket old_packet = 92 | packet_manager.Compile(info.existing_match->GetProto()); 93 | SymbolicPacket new_and_old = packet_manager.And(new_packet, old_packet); 94 | if (new_and_old == packet_manager.EmptySet()) { 95 | return absl::OkStatus(); 96 | } 97 | 98 | SymbolicPacketTransformer new_and_old_policy = 99 | policy_manager.FromSymbolicPacket(new_and_old); 100 | SymbolicPacketTransformer with_new_action = policy_manager.Sequence( 101 | new_and_old_policy, policy_manager.Compile(info.new_action.GetProto())); 102 | SymbolicPacketTransformer with_old_action = policy_manager.Sequence( 103 | new_and_old_policy, 104 | policy_manager.Compile(info.existing_policy->GetProto())); 105 | if (with_new_action != with_old_action) { 106 | return absl::InvalidArgumentError("New rule collides with existing rule."); 107 | } 108 | return absl::OkStatus(); 109 | } 110 | 111 | // Returns the priority-unioned policy based on `GetPolicy`. 112 | Policy GetPolicyInternal( 113 | absl::btree_map> rules, 114 | Policy default_action) { 115 | if (rules.empty()) return default_action; 116 | 117 | // We want to translate the rules in each priority into a statement that is 118 | // equivalent to: 119 | // 120 | // p1; p1_action + !p1; p2; p2_action + !p1; !p2; p3; p3_action + ... 121 | // 122 | // However, that will cause N^2 matches to be generated. Instead, we simplify 123 | // the expression to: 124 | // 125 | // p1; p1_action + !p1; (p2; p2_action + !p2; (...)) 126 | // 127 | // Note that rules is in non-descending order, which we want to be the most 128 | // guarded match. 129 | // 130 | // TODO(anthonyroy): Will proto unmarshalling limits get in the way here? 131 | Policy final_policy = std::move(default_action); 132 | for (auto it = rules.begin(); it != rules.end(); ++it) { 133 | auto& [match, policy] = it->second; 134 | final_policy = Union(std::move(policy), Sequence(Filter(!std::move(match)), 135 | std::move(final_policy))); 136 | } 137 | return final_policy; 138 | } 139 | 140 | } // namespace 141 | 142 | NetkatTable::NetkatTable(std::vector constraints, 143 | bool accept_default) 144 | : accept_default_(accept_default), constraints_(std::move(constraints)) { 145 | // TODO(anthonyroy): Consider an unsafe variant that only performs these 146 | // checks in DEBUG builds. 147 | constraints_.push_back([](const PendingRuleInfo& info) { 148 | return VerifyActionHasNoPredicate(info.new_action); 149 | }); 150 | constraints_.push_back([this](const PendingRuleInfo& info) { 151 | return VerifyRuleDeterminism(info, policy_manager_); 152 | }); 153 | } 154 | 155 | absl::Status NetkatTable::AddRule(int priority, Predicate match, 156 | Policy action) { 157 | auto [it, inserted] = 158 | rules_.try_emplace(priority, std::move(match), std::move(action)); 159 | 160 | auto& [current_match, current_action] = it->second; 161 | PendingRuleInfo info = { 162 | .priority = priority, 163 | .new_match = inserted ? current_match : match, 164 | .new_action = inserted ? current_action : action, 165 | .existing_match = inserted ? nullptr : ¤t_match, 166 | .existing_policy = inserted ? nullptr : ¤t_action}; 167 | for (TableConstraint& constraint : constraints_) { 168 | absl::Status status = constraint(info); 169 | if (!status.ok()) { 170 | if (inserted) rules_.erase(it); 171 | return status; 172 | } 173 | } 174 | 175 | // If this is the first rule in the priority band, the action does not yet 176 | // include the match. 177 | if (inserted) { 178 | current_action = Sequence(Filter(current_match), std::move(current_action)); 179 | return absl::OkStatus(); 180 | } 181 | 182 | current_match = std::move(current_match) || match; 183 | current_action = Union(std::move(current_action), 184 | Sequence(Filter(std::move(match)), std::move(action))); 185 | return absl::OkStatus(); 186 | } 187 | 188 | Policy NetkatTable::GetPolicy() const& { 189 | return GetPolicyInternal(rules_, 190 | accept_default_ ? Policy::Accept() : Policy::Deny()); 191 | } 192 | Policy NetkatTable::GetPolicy() && { 193 | return GetPolicyInternal(std::move(rules_), 194 | accept_default_ ? Policy::Accept() : Policy::Deny()); 195 | } 196 | 197 | } // namespace netkat 198 | -------------------------------------------------------------------------------- /netkat/table.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // ----------------------------------------------------------------------------- 16 | // File: table.h 17 | // ----------------------------------------------------------------------------- 18 | // 19 | // This file contains the definitions for modeling match-action tables in 20 | // NetKAT. Modeling tables in NetKAT enables reasoning about them using the 21 | // NetKAT framework, e.g. via the `AnalysisEngine`. 22 | 23 | #ifndef GOOGLE_NETKAT_NETKAT_TABLE_H_ 24 | #define GOOGLE_NETKAT_NETKAT_TABLE_H_ 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "absl/container/btree_map.h" 31 | #include "absl/status/status.h" 32 | #include "netkat/frontend.h" 33 | #include "netkat/symbolic_packet_transformer.h" 34 | 35 | namespace netkat { 36 | 37 | // Represents a prioritized match-action table of some networking switch using 38 | // NetKAT. Rules will be prioritized in descending order, i.e. higher is better. 39 | // Policy should generally limit matches and mutations of packets based on the 40 | // capabilities of the table/switch this intends to model. 41 | // 42 | // For any given packet, this class requires that it be modified by at most one 43 | // unique action in the table. See `AddRule` for more details. 44 | // 45 | // `TableContraint`s may be added to enforce additional expectations on each 46 | // rule. See the constructor for more information. 47 | class NetkatTable { 48 | public: 49 | // Information provided to `TableConstraint` for rule evaluation. 50 | struct PendingRuleInfo { 51 | // The priority the rule is being added for. 52 | int priority; 53 | 54 | // The predicate and policy of the rule to be added. Note that the lifetimes 55 | // are bound to the given `AddRule` call and should not be held longer than 56 | // the duration of the callback. 57 | const Predicate& new_match; 58 | const Policy& new_action; 59 | 60 | // Represents the union of all matches currently in the table at the given 61 | // priority. This will be nullptr if this is the first rule to be added in 62 | // this priority. 63 | const Predicate* existing_match; 64 | 65 | // Represents the total policy of the table, at this priority. 66 | // 67 | // E.g. for some previously added (p1_match, p1_action), (p2_match, 68 | // p2_action), this will be: p1_match; p1_action + p2_match; p2_action 69 | const Policy* existing_policy; 70 | }; 71 | 72 | // A functor that represents some constraint to be applied or evaluated 73 | // against a pending rule of the table. The constraint must return either an 74 | // error detailing the cause of the violation or OK if there is none. 75 | using TableConstraint = std::function; 76 | 77 | // Returns an empty NetKAT Table as described above. 78 | // 79 | // A list of TableConstraints may be provided. Each rule will be evaluated 80 | // against `constraints` prior to being added. Rules will only be added if 81 | // they conform to all constraints. 82 | // 83 | // `accept_default` will determine what the default action of the table is, 84 | // i.e. either Accept or Deny. See `GetPolicy` for more information. 85 | explicit NetkatTable(std::vector constraints = {}, 86 | bool accept_default = false); 87 | 88 | // Adds a match-action rule into the table, at the given priority. For 89 | // example, if this were to represent a VRF table then a possible rule would 90 | // be: 91 | // 92 | // AddRule(Match("ip_tos", kCs4Tos), Modify("vrf", 114), /*priority=*/1136); 93 | // 94 | // For any given (match1,action1) and (match2, action2) which share a given 95 | // priority, it is required that either: no packet matches both match1 and 96 | // match2 OR action1 and action2 mutate such a packet equivalently. More 97 | // formally, 98 | // 99 | // match1 && match2 ≡ False OR 100 | // match1; match2; action1 ≡ match1; match2; action2 101 | // 102 | // This ensures that a packet will always produce exactly one possible output 103 | // packet. Note that it is OK for any given rule to have a disjunction in its 104 | // action, e.g. setting multiple possible output ports for one match. 105 | // 106 | // For any two predicates that *do not* share a given priority, the rules will 107 | // be eventually merged such that higher priority rules are ordered first. See 108 | // `GetPolicy` for more information. 109 | // 110 | // With the exception of the `Deny` policy, `action` must also not restrict 111 | // packets further than `match` and should be used exclusively for 112 | // modifications. I.e., `action` must have no `Predicate`s, except `False`, 113 | // present in its policy. 114 | // 115 | // Finally, each rule must meet any and all configured `TableConstraint`s. If 116 | // a rule fails to meet any of the requirements above it will not be added and 117 | // an appropriate error will be returned. Otherwise, the rule will be added 118 | // and an OkStatus returned. 119 | // 120 | // TODO(anthonyroy): Consider a more API-friendly way to disallow `action` 121 | // from being more restrictive than `match`. 122 | absl::Status AddRule(int priority, Predicate match, Policy action); 123 | 124 | // Returns a unified policy representing all rules in the NetkatTable. The 125 | // returned policy will emulate a priority based match-action table of a 126 | // switch. E.g. for rules r1, r2, r3, if priority(r1) > priority(r2) > 127 | // priority(r3), the resulting policy will be equivalent to: 128 | // 129 | // match(r1); action(r1) + 130 | // !match(r1); match(r2); action(r2) + 131 | // !match(r1); !match(r2); match(r3); action(r3) 132 | // 133 | // If the table is empty, this will return `Policy::Accept()` if 134 | // `accept_default` is true, else `Policy::Deny()`. Similarly, any packet 135 | // that does not match any policy in the table will have the same logic 136 | // applied. 137 | // 138 | // TODO(anthonyroy): Consider adding configurability for misses. This assumes 139 | // the table is explicitly deny/allow-list based. The caveat is that any "on 140 | // miss" action must be subject to the same constraints as the table is 141 | // globally. 142 | Policy GetPolicy() const&; 143 | Policy GetPolicy() &&; 144 | 145 | private: 146 | // Whether the default action should be Accept or Deny. 147 | const bool accept_default_; 148 | 149 | // The list of constraints/policies each to-be-added rule must conform to. 150 | std::vector constraints_; 151 | 152 | // The rules in order of priority. 153 | // 154 | // For each pair, predicate represents the union of each 155 | // predicate seen at the same priority thus far. E.g. 156 | // 157 | // predicate = p1_match || p2_match || ... 158 | // 159 | // The policy represents the true policy of this priority. E.g. for some 160 | // (p1_match, p1_action), (p2_match, ...), ..., then 161 | // 162 | // policy = p1_match; p1_action + p2_match; p2_action + ... 163 | // 164 | // Note that we utilize the predicate to later build the priority-ordered 165 | // table policy. 166 | absl::btree_map> rules_; 167 | 168 | // Manager for evaluating policy against contraints. E.g. rule determinism. 169 | SymbolicPacketTransformerManager policy_manager_; 170 | }; 171 | 172 | } // namespace netkat 173 | 174 | #endif // GOOGLE_NETKAT_NETKAT_TABLE_H_ 175 | -------------------------------------------------------------------------------- /netkat/table_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The NetKAT authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // ----------------------------------------------------------------------------- 16 | // File: table_test.cc 17 | // ----------------------------------------------------------------------------- 18 | 19 | #include "netkat/table.h" 20 | 21 | #include "absl/status/status.h" 22 | #include "fuzztest/fuzztest.h" 23 | #include "gmock/gmock.h" 24 | #include "gtest/gtest.h" 25 | #include "gutil/proto_matchers.h" 26 | #include "gutil/status_matchers.h" // IWYU pragma: keep 27 | #include "netkat/frontend.h" 28 | #include "netkat/gtest_utils.h" 29 | #include "netkat/netkat.pb.h" 30 | #include "netkat/netkat_proto_constructors.h" 31 | 32 | namespace netkat { 33 | namespace { 34 | 35 | using ::gutil::EqualsProto; 36 | using ::gutil::IsOk; 37 | using ::gutil::StatusIs; 38 | 39 | // TODO: b/416297041 - It would be quite nice to not look at the protos these 40 | // all generate. Instead, we could use the AnalysisEngine to verify correctness. 41 | // At least for tests that don't care what the proto looks like. 42 | // 43 | // Using proto comparison is peeking a bit into the implementation details and 44 | // also requires rule order stability, which may be true but is not required. 45 | // It's also a bit hard to read through... 46 | 47 | TEST(NetkatTableTest, EmptyTableIsDefaultAction) { 48 | EXPECT_THAT(NetkatTable().GetPolicy().ToProto(), EqualsProto(DenyProto())); 49 | EXPECT_THAT(NetkatTable({}, /*accept_default=*/false).GetPolicy().ToProto(), 50 | EqualsProto(DenyProto())); 51 | EXPECT_THAT(NetkatTable({}, /*accept_default=*/true).GetPolicy().ToProto(), 52 | EqualsProto(AcceptProto())); 53 | } 54 | 55 | class NetkatTableTest 56 | : public ::testing::TestWithParam { 57 | protected: 58 | static Policy DefaultPolicy() { 59 | return GetParam() ? Policy::Accept() : Policy::Deny(); 60 | } 61 | 62 | static bool accept_default() { return GetParam(); } 63 | }; 64 | 65 | TEST_P(NetkatTableTest, SingleRuleIsAddedCorrectly) { 66 | NetkatTable table({}, accept_default()); 67 | ASSERT_OK(table.AddRule(/*priority=*/0, Match("port", 0), Modify("vrf", 1))); 68 | 69 | EXPECT_THAT( 70 | table.GetPolicy().ToProto(), 71 | EqualsProto(Union(Sequence(Filter(Match("port", 0)), Modify("vrf", 1)), 72 | Sequence(Filter(!Match("port", 0)), DefaultPolicy())) 73 | .ToProto())); 74 | } 75 | 76 | TEST_P(NetkatTableTest, MultipleRulesInSamePriorityAreAddedCorrectly) { 77 | NetkatTable table({}, accept_default()); 78 | ASSERT_OK(table.AddRule(/*priority=*/0, Match("port", 0), Modify("vrf", 1))); 79 | ASSERT_OK(table.AddRule(/*priority=*/0, Match("port", 1), Modify("vrf", 2))); 80 | 81 | PolicyProto expected = 82 | Union(Union(Sequence(Filter(Match("port", 0)), Modify("vrf", 1)), 83 | Sequence(Filter(Match("port", 1)), Modify("vrf", 2))), 84 | Sequence(Filter(!(Match("port", 0) || Match("port", 1))), 85 | DefaultPolicy())) 86 | .ToProto(); 87 | EXPECT_THAT(table.GetPolicy().ToProto(), EqualsProto(expected)) 88 | << AsShorthandString(table.GetPolicy().ToProto()) 89 | << "\n expected: " << AsShorthandString(expected); 90 | } 91 | 92 | TEST_P(NetkatTableTest, MultiplePrioritiesAreMergedCorrectly) { 93 | NetkatTable table({}, accept_default()); 94 | ASSERT_OK(table.AddRule(/*priority=*/0, Match("port", 0), Modify("vrf", 1))); 95 | ASSERT_OK(table.AddRule(/*priority=*/1, Match("port", 1), Modify("vrf", 2))); 96 | 97 | PolicyProto expected = 98 | Union( 99 | Sequence(Filter(Match("port", 1)), Modify("vrf", 2)), 100 | Sequence(Filter(!Match("port", 1)), 101 | Union(Sequence(Filter(Match("port", 0)), Modify("vrf", 1)), 102 | Sequence(Filter(!Match("port", 0)), DefaultPolicy())))) 103 | .ToProto(); 104 | EXPECT_THAT(table.GetPolicy().ToProto(), EqualsProto(expected)) 105 | << AsShorthandString(table.GetPolicy().ToProto()) 106 | << "\n expected: " << AsShorthandString(expected); 107 | } 108 | 109 | TEST_P(NetkatTableTest, CustomConstraintViolationIsPropagated) { 110 | // Create a rule that enforces a maximal priority. 111 | NetkatTable::TableConstraint priority_limit = 112 | [](const NetkatTable::PendingRuleInfo& info) { 113 | if (info.priority > 10) 114 | return absl::InvalidArgumentError("Bad priority."); 115 | return absl::OkStatus(); 116 | }; 117 | 118 | NetkatTable table({priority_limit}, accept_default()); 119 | ASSERT_OK(table.AddRule(/*priority=*/10, Match("port", 0), Modify("vrf", 1))); 120 | ASSERT_OK(table.AddRule(/*priority=*/10, Match("port", 1), Modify("vrf", 2))); 121 | EXPECT_THAT( 122 | table.AddRule(/*priority=*/11, Match("port", 2), Modify("vrf", 3)), 123 | StatusIs(absl::StatusCode::kInvalidArgument)); 124 | 125 | // Ensure the rule was not actually added. 126 | PolicyProto expected = 127 | Union(Union(Sequence(Filter(Match("port", 0)), Modify("vrf", 1)), 128 | Sequence(Filter(Match("port", 1)), Modify("vrf", 2))), 129 | Sequence(Filter(!(Match("port", 0) || Match("port", 1))), 130 | DefaultPolicy())) 131 | .ToProto(); 132 | EXPECT_THAT(table.GetPolicy().ToProto(), EqualsProto(expected)) 133 | << AsShorthandString(table.GetPolicy().ToProto()) 134 | << "\n expected: " << AsShorthandString(expected); 135 | } 136 | 137 | INSTANTIATE_TEST_SUITE_P(DefaultTablePolicy, NetkatTableTest, 138 | ::testing::Bool()); 139 | 140 | void RuleWithFilterIsInvalid(PredicateProto predicate) { 141 | if (predicate.has_bool_constant() && 142 | predicate.bool_constant().value() == false) { 143 | // A False predicate is equivalent to drop, which is the only allowed 144 | // filter in a rules policy. 145 | GTEST_SKIP(); 146 | } 147 | ASSERT_OK_AND_ASSIGN(Predicate pred, Predicate::FromProto(predicate)); 148 | 149 | NetkatTable table; 150 | EXPECT_THAT(table.AddRule(/*priority=*/10, pred, Filter(pred)), 151 | StatusIs(absl::StatusCode::kInvalidArgument)); 152 | } 153 | FUZZ_TEST(NetkatTableTest, RuleWithFilterIsInvalid) 154 | .WithDomains(netkat_test::ArbitraryValidPredicateProto()); 155 | 156 | void RuleWithDropActionIsValid(PredicateProto match) { 157 | ASSERT_OK_AND_ASSIGN(Predicate pred, Predicate::FromProto(match)); 158 | 159 | NetkatTable table; 160 | EXPECT_THAT(table.AddRule(/*priority=*/10, pred, Policy::Deny()), IsOk()); 161 | } 162 | FUZZ_TEST(NetkatTableTest, RuleWithDropActionIsValid) 163 | .WithDomains(netkat_test::ArbitraryValidPredicateProto()); 164 | 165 | TEST(NetkatTable, NonDeterministicRuleRejected) { 166 | NetkatTable table; 167 | ASSERT_OK(table.AddRule(/*priority=*/10, Match("port", 0), Modify("vrf", 1))); 168 | 169 | EXPECT_THAT( 170 | table.AddRule(/*priority=*/10, Match("vlan", 0), Modify("vrf", 2)), 171 | StatusIs(absl::StatusCode::kInvalidArgument)); 172 | } 173 | 174 | TEST(NetkatTable, NonDeterministicRuleWithSameActionIsOk) { 175 | NetkatTable table; 176 | ASSERT_OK(table.AddRule(/*priority=*/10, Match("port", 0), Modify("vrf", 1))); 177 | EXPECT_OK(table.AddRule(/*priority=*/10, Match("vlan", 0), Modify("vrf", 1))); 178 | } 179 | 180 | } // namespace 181 | } // namespace netkat 182 | --------------------------------------------------------------------------------