├── .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*accept_default*/ bool> {
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 |
--------------------------------------------------------------------------------