Skip to content

Commit 7ae1002

Browse files
jonathan-dilorenzocopybara-github
authored andcommitted
[NetKAT] Add a function to evaluate a policy on a packet.
PiperOrigin-RevId: 702512910
1 parent 4ef2697 commit 7ae1002

File tree

4 files changed

+226
-12
lines changed

4 files changed

+226
-12
lines changed

netkat/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ cc_library(
5454
deps = [
5555
":netkat_cc_proto",
5656
"@com_google_absl//absl/container:flat_hash_map",
57+
"@com_google_absl//absl/container:flat_hash_set",
5758
"@com_google_absl//absl/log",
5859
],
5960
)
@@ -87,6 +88,9 @@ cc_test(
8788
deps = [
8889
":evaluator",
8990
":netkat_cc_proto",
91+
":netkat_proto_constructors",
92+
"@com_google_absl//absl/container:flat_hash_set",
93+
"@com_google_fuzztest//fuzztest",
9094
"@com_google_googletest//:gtest_main",
9195
],
9296
)

netkat/evaluator.cc

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include "netkat/evaluator.h"
1717

18+
#include "absl/container/flat_hash_set.h"
1819
#include "absl/log/log.h"
1920
#include "netkat/netkat.pb.h"
2021

@@ -46,4 +47,56 @@ bool Evaluate(const PredicateProto& predicate, const Packet& packet) {
4647
<< static_cast<int>(predicate.predicate_case());
4748
}
4849

50+
absl::flat_hash_set<Packet> Evaluate(
51+
const PolicyProto& policy, const absl::flat_hash_set<Packet>& packets) {
52+
absl::flat_hash_set<Packet> result;
53+
for (const Packet& packet : packets) {
54+
result.merge(Evaluate(policy, packet));
55+
}
56+
return result;
57+
}
58+
59+
absl::flat_hash_set<Packet> Evaluate(const PolicyProto& policy,
60+
const Packet& packet) {
61+
switch (policy.policy_case()) {
62+
case PolicyProto::kFilter:
63+
return Evaluate(policy.filter(), packet)
64+
? absl::flat_hash_set<Packet>({packet})
65+
: absl::flat_hash_set<Packet>();
66+
case PolicyProto::kModification: {
67+
Packet modified_packet = packet;
68+
// Adds field if it doesn't exist, and modifies it otherwise.
69+
modified_packet[policy.modification().field()] =
70+
policy.modification().value();
71+
return {modified_packet};
72+
}
73+
case PolicyProto::kRecord:
74+
// Record is treated as a no-op.
75+
return {packet};
76+
case PolicyProto::kSequenceOp:
77+
return Evaluate(policy.sequence_op().right(),
78+
Evaluate(policy.sequence_op().left(), packet));
79+
case PolicyProto::kUnionOp: {
80+
absl::flat_hash_set<Packet> result =
81+
Evaluate(policy.union_op().left(), packet);
82+
result.merge(Evaluate(policy.union_op().right(), packet));
83+
return result;
84+
}
85+
case PolicyProto::kIterateOp: {
86+
// p* = 1 + p + p;p + p;p;p + ...
87+
absl::flat_hash_set<Packet> result = {packet}; // 1
88+
// Evaluate p on result until fixed point, marked by no change in size.
89+
int last_size;
90+
do {
91+
last_size = result.size();
92+
result.merge(Evaluate(policy.iterate_op().iterable(), result)); // p^n
93+
} while (last_size != result.size());
94+
return result;
95+
}
96+
case PolicyProto::POLICY_NOT_SET:
97+
// Unset policy is treated as DENY.
98+
return {};
99+
}
100+
}
101+
49102
} // namespace netkat

netkat/evaluator.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@
1818
//
1919
// Defines a library of functions for evaluating NetKAT predicates and policies
2020
// on concrete packets.
21+
//
22+
// See go/netkat-hld for more details.
2123

2224
#ifndef GOOGLE_NETKAT_NETKAT_EVALUATOR_H_
2325
#define GOOGLE_NETKAT_NETKAT_EVALUATOR_H_
2426

2527
#include <string>
2628

2729
#include "absl/container/flat_hash_map.h"
30+
#include "absl/container/flat_hash_set.h"
2831
#include "netkat/netkat.pb.h"
2932

3033
namespace netkat {
@@ -42,9 +45,21 @@ using Packet = absl::flat_hash_map<std::string, int>;
4245
// Returns true if the given `packet` satisfies the given `predicate`, false
4346
// otherwise.
4447
//
45-
// Note: Empty predicates are considered unsatisfiable.
48+
// Note: Uninitialized predicates are considered unsatisfiable.
4649
bool Evaluate(const PredicateProto& predicate, const Packet& packet);
4750

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 considered DENY, returning the empty set.
56+
absl::flat_hash_set<Packet> Evaluate(const PolicyProto& policy,
57+
const Packet& packet);
58+
59+
// Lifts policy evaluation to sets of packets.
60+
absl::flat_hash_set<Packet> Evaluate(
61+
const PolicyProto& policy, const absl::flat_hash_set<Packet>& packets);
62+
4863
} // namespace netkat
4964

5065
#endif // GOOGLE_NETKAT_NETKAT_EVALUATOR_H_

netkat/evaluator_test.cc

Lines changed: 153 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,24 @@
1515

1616
#include "netkat/evaluator.h"
1717

18+
#include "absl/container/flat_hash_set.h"
19+
#include "fuzztest/fuzztest.h"
20+
#include "gmock/gmock.h"
1821
#include "gtest/gtest.h"
1922
#include "netkat/netkat.pb.h"
23+
#include "netkat/netkat_proto_constructors.h"
2024

2125
namespace netkat {
2226
namespace {
2327

24-
TEST(EvaluatorTest, TrueIsTrueOnAnyPackets) {
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+
TEST(EvaluatePredicateProtoTest, TrueIsTrueOnAnyPackets) {
2536
PredicateProto true_predicate;
2637
true_predicate.mutable_bool_constant()->set_value(true);
2738

@@ -31,7 +42,7 @@ TEST(EvaluatorTest, TrueIsTrueOnAnyPackets) {
3142
Packet({{"field1", 1}, {"field2", 2}, {"field3", 3}})));
3243
}
3344

34-
TEST(EvaluatorTest, FalseIsFalseOnAnyPacket) {
45+
TEST(EvaluatePredicateProtoTest, FalseIsFalseOnAnyPacket) {
3546
PredicateProto false_predicate;
3647
false_predicate.mutable_bool_constant()->set_value(false);
3748

@@ -41,7 +52,7 @@ TEST(EvaluatorTest, FalseIsFalseOnAnyPacket) {
4152
Packet({{"field1", 1}, {"field2", 2}, {"field3", 3}})));
4253
}
4354

44-
TEST(EvaluatorTest, EmptyPredicateIsFalseOnAnyPacket) {
55+
TEST(EvaluatePredicateProtoTest, EmptyPredicateIsFalseOnAnyPacket) {
4556
PredicateProto empty_predicate;
4657

4758
EXPECT_FALSE(Evaluate(empty_predicate, Packet()));
@@ -50,7 +61,7 @@ TEST(EvaluatorTest, EmptyPredicateIsFalseOnAnyPacket) {
5061
Packet({{"field1", 1}, {"field2", 2}, {"field3", 3}})));
5162
}
5263

53-
TEST(EvaluatorTest, NotTrueIsFalseOnAnyPackets) {
64+
TEST(EvaluatePredicateProtoTest, NotTrueIsFalseOnAnyPackets) {
5465
PredicateProto not_true_predicate;
5566
not_true_predicate.mutable_not_op()
5667
->mutable_negand()
@@ -63,7 +74,7 @@ TEST(EvaluatorTest, NotTrueIsFalseOnAnyPackets) {
6374
Packet({{"field1", 1}, {"field2", 2}, {"field3", 3}})));
6475
}
6576

66-
TEST(EvaluatorTest, NotFalseIsTrueOnAnyPacket) {
77+
TEST(EvaluatePredicateProtoTest, NotFalseIsTrueOnAnyPacket) {
6778
PredicateProto not_false_predicate;
6879
not_false_predicate.mutable_not_op()
6980
->mutable_negand()
@@ -76,7 +87,7 @@ TEST(EvaluatorTest, NotFalseIsTrueOnAnyPacket) {
7687
Packet({{"field1", 1}, {"field2", 2}, {"field3", 3}})));
7788
}
7889

79-
TEST(EvaluatorTest, NotNotTrueIsTrueOnAnyPackets) {
90+
TEST(EvaluatePredicateProtoTest, NotNotTrueIsTrueOnAnyPackets) {
8091
PredicateProto not_not_true_predicate;
8192
not_not_true_predicate.mutable_not_op()
8293
->mutable_negand()
@@ -91,7 +102,7 @@ TEST(EvaluatorTest, NotNotTrueIsTrueOnAnyPackets) {
91102
Packet({{"field1", 1}, {"field2", 2}, {"field3", 3}})));
92103
}
93104

94-
TEST(EvaluatorTest, MatchesFieldWithCorrectValue) {
105+
TEST(EvaluatePredicateProtoTest, MatchesFieldWithCorrectValue) {
95106
PredicateProto match_predicate;
96107
match_predicate.mutable_match()->set_field("field1");
97108
match_predicate.mutable_match()->set_value(1);
@@ -102,7 +113,7 @@ TEST(EvaluatorTest, MatchesFieldWithCorrectValue) {
102113
Packet({{"field1", 1}, {"field2", 2}, {"field3", 3}})));
103114
}
104115

105-
TEST(EvaluatorTest, DoesNotMatchFieldWithWrongValue) {
116+
TEST(EvaluatePredicateProtoTest, DoesNotMatchFieldWithWrongValue) {
106117
PredicateProto match_predicate;
107118
match_predicate.mutable_match()->set_field("field1");
108119
match_predicate.mutable_match()->set_value(2);
@@ -113,7 +124,7 @@ TEST(EvaluatorTest, DoesNotMatchFieldWithWrongValue) {
113124
Packet({{"field1", 1}, {"field2", 2}, {"field3", 3}})));
114125
}
115126

116-
TEST(EvaluatorTest, AndIsLogicallyCorrect) {
127+
TEST(EvaluatePredicateProtoTest, AndIsLogicallyCorrect) {
117128
PredicateProto true_and_true_predicate;
118129
true_and_true_predicate.mutable_and_op()
119130
->mutable_left()
@@ -160,7 +171,7 @@ TEST(EvaluatorTest, AndIsLogicallyCorrect) {
160171
Packet({{"field1", 1}, {"field2", 2}, {"field3", 3}})));
161172
}
162173

163-
TEST(EvaluatorTest, OrIsLogicallyCorrect) {
174+
TEST(EvaluatePredicateProtoTest, OrIsLogicallyCorrect) {
164175
PredicateProto true_or_true_predicate;
165176
true_or_true_predicate.mutable_or_op()
166177
->mutable_left()
@@ -207,7 +218,7 @@ TEST(EvaluatorTest, OrIsLogicallyCorrect) {
207218
Packet({{"field1", 1}, {"field2", 2}, {"field3", 3}})));
208219
}
209220

210-
TEST(EvaluatorTest, DeMorganHolds) {
221+
TEST(EvaluatePredicateProtoTest, DeMorganHolds) {
211222
const Packet kEmptyPacket = Packet();
212223
const Packet kOneFieldPacket = Packet({{"field1", 1}});
213224
const Packet kThreeFieldsPacket =
@@ -294,5 +305,136 @@ TEST(EvaluatorTest, DeMorganHolds) {
294305
}
295306
}
296307

308+
/*--- Basic policy properties ------------------------------------------------*/
309+
310+
void LiftedEvaluationIsCorrect(absl::flat_hash_set<Packet> packets,
311+
PolicyProto policy) {
312+
absl::flat_hash_set<Packet> expected_packets;
313+
for (const Packet& packet : packets) {
314+
expected_packets.merge(Evaluate(policy, packet));
315+
}
316+
EXPECT_THAT(Evaluate(policy, packets), ContainerEq(expected_packets));
317+
}
318+
FUZZ_TEST(EvaluatePolicyProtoTest, LiftedEvaluationIsCorrect);
319+
320+
void RecordIsAccept(Packet packet) {
321+
EXPECT_THAT(Evaluate(RecordProto(), packet), UnorderedElementsAre(packet));
322+
}
323+
FUZZ_TEST(EvaluatePolicyProtoTest, RecordIsAccept);
324+
325+
void UninitializedPolicyIsDeny(Packet packet) {
326+
EXPECT_THAT(Evaluate(PolicyProto(), packet), IsEmpty());
327+
}
328+
FUZZ_TEST(EvaluatePolicyProtoTest, UninitializedPolicyIsDeny);
329+
330+
void FilterIsCorrect(Packet packet, PredicateProto predicate) {
331+
if (Evaluate(predicate, packet)) {
332+
EXPECT_THAT(Evaluate(FilterProto(predicate), packet),
333+
UnorderedElementsAre(packet));
334+
} else {
335+
EXPECT_THAT(Evaluate(FilterProto(predicate), packet), IsEmpty());
336+
}
337+
}
338+
FUZZ_TEST(EvaluatePolicyProtoTest, FilterIsCorrect);
339+
340+
void ModifyModifies(Packet packet, std::string field, int value) {
341+
Packet expected_packet = packet;
342+
expected_packet[field] = value;
343+
EXPECT_THAT(Evaluate(ModificationProto(field, value), packet),
344+
UnorderedElementsAre(expected_packet));
345+
}
346+
FUZZ_TEST(EvaluatePolicyProtoTest, ModifyModifies);
347+
348+
void UnionCombines(Packet packet, PolicyProto left, PolicyProto right) {
349+
absl::flat_hash_set<Packet> expected_packets = Evaluate(left, packet);
350+
expected_packets.merge(Evaluate(right, packet));
351+
352+
EXPECT_THAT(Evaluate(UnionProto(left, right), packet),
353+
ContainerEq(expected_packets));
354+
}
355+
FUZZ_TEST(EvaluatePolicyProtoTest, UnionCombines);
356+
357+
void SequenceSequences(Packet packet, PolicyProto left, PolicyProto right) {
358+
absl::flat_hash_set<Packet> expected_packets =
359+
Evaluate(right, Evaluate(left, packet));
360+
361+
EXPECT_THAT(Evaluate(SequenceProto(left, right), packet),
362+
ContainerEq(expected_packets));
363+
}
364+
FUZZ_TEST(EvaluatePolicyProtoTest, SequenceSequences);
365+
366+
PolicyProto UnionUpToNthPower(PolicyProto iterable, int n) {
367+
PolicyProto union_policy = AcceptProto();
368+
PolicyProto next_sequence = iterable;
369+
for (int i = 1; i <= n; ++i) {
370+
union_policy = UnionProto(union_policy, next_sequence);
371+
next_sequence = SequenceProto(iterable, next_sequence);
372+
}
373+
return union_policy;
374+
}
375+
376+
void IterateIsSupersetOfUnionOfNSequences(Packet packet, PolicyProto iterable,
377+
int n) {
378+
EXPECT_THAT(Evaluate(IterateProto(iterable), packet),
379+
IsSupersetOf(Evaluate(UnionUpToNthPower(iterable, n), packet)));
380+
}
381+
FUZZ_TEST(EvaluatePolicyProtoTest, IterateIsSupersetOfUnionOfNSequences)
382+
.WithDomains(/*packet=*/Arbitrary<Packet>(),
383+
/*iterable=*/Arbitrary<PolicyProto>(),
384+
/*n=*/InRange(0, 100));
385+
386+
void IterateIsUnionOfNSequencesForSomeN(Packet packet, PolicyProto iterable) {
387+
absl::flat_hash_set<Packet> iterate_output_packets =
388+
Evaluate(IterateProto(iterable), packet);
389+
390+
// Evaluate successively larger unions until we find one that matches all
391+
// packets in `iterate_packets`.
392+
absl::flat_hash_set<Packet> union_output_packets;
393+
int last_size;
394+
int n = 0;
395+
do {
396+
last_size = union_output_packets.size();
397+
union_output_packets = Evaluate(UnionUpToNthPower(iterable, n++), packet);
398+
} while (iterate_output_packets != union_output_packets &&
399+
union_output_packets.size() > last_size);
400+
401+
EXPECT_THAT(iterate_output_packets, ContainerEq(union_output_packets));
402+
}
403+
FUZZ_TEST(EvaluatePolicyProtoTest, IterateIsUnionOfNSequencesForSomeN);
404+
405+
TEST(EvaluatePolicyProtoTest, SimpleIterateThroughFiltersAndModifies) {
406+
// f == 0; f:=1 + f == 1; f := 2 + f == 2; f := 3
407+
PolicyProto iterable = UnionProto(
408+
SequenceProto(FilterProto(MatchProto("f", 0)), ModificationProto("f", 1)),
409+
UnionProto(SequenceProto(FilterProto(MatchProto("f", 1)),
410+
ModificationProto("f", 2)),
411+
SequenceProto(FilterProto(MatchProto("f", 2)),
412+
ModificationProto("f", 3))));
413+
414+
// If the packet contains the field, then the output is the union of the
415+
// input and the modified packets.
416+
EXPECT_THAT(Evaluate(IterateProto(iterable), Packet({{"f", 0}})),
417+
UnorderedElementsAre(Packet({{"f", 0}}), Packet({{"f", 1}}),
418+
Packet({{"f", 2}}), Packet({{"f", 3}})));
419+
420+
// If the packet doesn't contain the field, then the only output is the
421+
// input.
422+
EXPECT_THAT(Evaluate(IterateProto(iterable), Packet()),
423+
UnorderedElementsAre(Packet()));
424+
}
425+
426+
/*--- Advanced policy properties ---------------------------------------------*/
427+
void ModifyThenMatchIsEquivalentToModify(Packet packet, std::string field,
428+
int value) {
429+
// f := n;f == n is equivalent to f := n.
430+
EXPECT_THAT(Evaluate(SequenceProto(ModificationProto(field, value),
431+
FilterProto(MatchProto(field, value))),
432+
packet),
433+
ContainerEq(Evaluate(ModificationProto(field, value), packet)));
434+
}
435+
FUZZ_TEST(EvaluatePolicyProtoTest, ModifyThenMatchIsEquivalentToModify);
436+
437+
// TODO(dilo): Add tests for each of the NetKAT axioms.
438+
297439
} // namespace
298440
} // namespace netkat

0 commit comments

Comments
 (0)