Skip to content

Commit

Permalink
[NetKAT] Support a very rough representation of Ternary in the FE.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 732058235
  • Loading branch information
Ant2888 authored and copybara-github committed Feb 28, 2025
1 parent 29eb6a4 commit 85a7edd
Show file tree
Hide file tree
Showing 8 changed files with 611 additions and 12 deletions.
10 changes: 10 additions & 0 deletions gutil/status_matchers.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,14 @@ IsOkAndHoldsMatcher<InnerMatcher> IsOkAndHolds(InnerMatcher&& inner_matcher) {

} // namespace netkat

#ifndef EXPECT_OK
#define EXPECT_OK(x) \
EXPECT_THAT(x, ::netkat::StatusIs(absl::StatusCode::kOk, ::testing::_))
#endif

#ifndef ASSERT_OK
#define ASSERT_OK(x) \
ASSERT_THAT(x, ::netkat::StatusIs(absl::StatusCode::kOk, ::testing::_))
#endif

#endif // GOOGLE_NETKAT_GUTIL_STATUS_MATCHERS_H
29 changes: 29 additions & 0 deletions netkat/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ cc_library(
deps = [
":netkat_cc_proto",
":netkat_proto_constructors",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:string_view",
],
)
Expand All @@ -55,6 +56,34 @@ cc_test(
],
)

cc_library(
name = "table",
srcs = ["table.cc"],
hdrs = ["table.h"],
deps = [
"//netkat:frontend",
"@com_google_absl//absl/container:btree",
"@com_google_absl//absl/functional:any_invocable",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
],
)

cc_test(
name = "table_test",
srcs = ["table_test.cc"],
deps = [
":frontend",
":netkat_proto_constructors",
":table",
"//gutil:proto_matchers",
"//gutil:status_matchers",
"@com_google_absl//absl/status",
"@com_google_googletest//:gtest_main",
],
)

cc_test(
name = "netkat_test",
srcs = ["netkat_test.cc"],
Expand Down
119 changes: 109 additions & 10 deletions netkat/frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@
#ifndef GOOGLE_NETKAT_NETKAT_FRONTEND_H_
#define GOOGLE_NETKAT_NETKAT_FRONTEND_H_

#include <bitset>
#include <cstdint>
#include <optional>
#include <utility>
#include <vector>

#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "netkat/netkat.pb.h"

Expand Down Expand Up @@ -67,37 +71,51 @@ class Predicate {

// Returns the underlying IR proto.
//
// If `Predicate` is an R-value, the underlying proto will be moved leaving
// this class in a moved state.
//
// Users should generally not handle this proto directly, unless done with
// policy building.
PredicateProto ToProto() const& { return predicate_; }
PredicateProto ToProto() && { return std::move(predicate_); }

// Returns a reference to the underlying IR proto.
//
// This reference will only be valid for either the lifetime of this class OR
// until `ToProto()&&` is called (moving the underlying reference), whichever
// is sooner.
const PredicateProto& GetProto() const& { return predicate_; }

// TODO(anthonyroy): Add a FromProto.

// Logical operators. These perform exactly as expected, with the
// exception of short circuiting.
//
// These objects by themselves are not intrinsically truthy, so a lack of
// short circuiting will not generate semantically different sequences.
// short circuiting will not generate semantically different programs.
friend Predicate operator&&(Predicate lhs, Predicate rhs);
friend Predicate operator||(Predicate lhs, Predicate rhs);
friend Predicate operator!(Predicate predicate);

// Match operation for a Predicate. See below for the full definition. We
// utilize friend association to ensure program construction is well-formed.
friend Predicate Match(absl::string_view, int);

// Predicates that conceptually represent a packet being universally accepted
// or denied/droped.
//
// Concretely this is simply a constant `Predicate(true/false)`.
static Predicate True();
static Predicate False();

// Match operation for a Predicate. See below for the full definition. We
// utilize friend association to ensure program construction is well-formed.
friend Predicate Match(absl::string_view, int);

private:
// Hide default proto construction to hinder building of ill-formed programs.
explicit Predicate(PredicateProto pred) : predicate_(std::move(pred)) {}

// Calling GetProto on an R-value predicate is at best inefficient and, more
// likely, a bug. Use ToProto instead.
const PredicateProto& GetProto() && = delete;

PredicateProto predicate_;
};

Expand Down Expand Up @@ -142,10 +160,24 @@ Predicate Match(absl::string_view field, int value);
// built a unidirectional link policy here.
class Policy {
public:
// Returns the underlying IR proto.
//
// If `Policy` is an R-value, the underlying proto will be moved leaving
// this class in a moved state.
//
// Users should generally not handle this proto directly, unless done with
// policy building.
PolicyProto ToProto() const& { return policy_; }
PolicyProto ToProto() && { return std::move(policy_); }

// TODO: anthonyroy - Create a FromProto.
// Returns a reference to the underlying IR proto.
//
// This reference will only be valid for either the lifetime of this class OR
// until `ToProto()&&` is called (moving the underlying reference), whichever
// is sooner.
const PolicyProto& GetProto() const& { return policy_; }

// TODO(anthonyroy): Create a FromProto.

// The set of operations that define a NetKAT policy. See below for each
// operation's definition. We utilize friend association to ensure program
Expand All @@ -166,6 +198,10 @@ class Policy {
// Hide default proto construction to hinder building of ill-formed programs.
explicit Policy(PolicyProto policy) : policy_(std::move(policy)) {}

// Calling GetProto on an R-value policy is at best inefficient and, more
// likely, a bug. Use ToProto instead.
const PolicyProto& GetProto() && = delete;

// The underlying IR that has been built thus far.
PolicyProto policy_;
};
Expand Down Expand Up @@ -251,17 +287,17 @@ Policy Union(T&&... policies) {
// For a practical example, we may assume some topology built of link actions.
// E.g.
//
// Predicate at_src_link = Match("switch", 0) && Match("port", 0);
// Policy go_to_dst = Sequence(Modify("switch", 1), Modify("port", 1));
// Policy link_action0 = Sequence(Filter(at_src_link), go_to_dst);
// Predicate at_src0_link0 = Match("switch", 0) && Match("port", 0);
// Policy go_to_dst1 = Sequence(Modify("switch", 1), Modify("port", 1));
// Policy link_action0 = Sequence(Filter(at_src0_link0), go_to_dst1);
// ...
// Policy topology = Union(link_action0, link_action1, ...);
//
// We may then use `Iterate` to build a policy that "walks" all paths in the
// network, reachable by some arbitrary switch.
//
// Policy set_any_port = Union(Modify("port", 0), Modify("port", 1), ...);
// Policy walk_topology =
// Policy walk_topology_from_x =
// Sequence(Filter(Match("switch", X)), set_any_port, Iterate(topology));
Policy Iterate(Policy policy);

Expand All @@ -274,6 +310,69 @@ Policy Iterate(Policy policy);
// TODO: b/377697348 - Enhance this comment with a simple example.
Policy Record();

////////////////////////////////////////////////////////////////////////////////
// The following are a set of temporary helpers to utilize ternaries in NetKAT
// programs.
//
// Support ternaries this way is very inefficient in both representation and
// computation but exist to allow further prototyping. The final API will be
// more robust and efficient, involving a more catered representation in the IR
// and backend.
//
// Note that it is the users responsibility to ensure that each field has the
// correct bit-width. If two programs assume differing bit-widths, we presume
// their comparison to be indeterminate.
//
// TODO(anthonyroy): Replace this with an efficient representation.

template <uint8_t BitWidth>
struct TernaryField {
std::bitset<BitWidth> value;
std::bitset<BitWidth> mask;
};

// Matches a presumed ternary `field`. Only indicies with `new_value.mask` set
// to 1 will be matched. E.g. b0011/b0001 will only result in a `Predicate` that
// matches the LSB.
//
// An empty mask will result in Predicate::False.
template <uint8_t N>
inline Predicate Match(absl::string_view field, TernaryField<N> value) {
std::optional<Predicate> predicate;
for (int i = 0; i < N; ++i) {
if (!value.mask[i]) continue;
const int bit_val = value.value[i] ? 1 : 0;
if (!predicate.has_value()) {
predicate = Match(absl::StrCat(field, "_b", i), bit_val);
} else {
predicate =
*std::move(predicate) && Match(absl::StrCat(field, "_b", i), bit_val);
}
}
return predicate.value_or(Predicate::False());
}

// Modifies a presumed ternary `field`. Only indicies with `new_value.mask`set
// to 1 will be modified. E.g. b0011/b0001 will only result in the LSB being
// set, so a ternary like b1100 will only be set to b1101.
//
// An empty mask will result in Policy::Accept.
template <uint8_t N>
inline Policy Modify(absl::string_view field, TernaryField<N> new_value) {
std::optional<Policy> policy;
for (int i = 0; i < N; ++i) {
if (!new_value.mask[i]) continue;
const int value = new_value.value[i] ? 1 : 0;
if (!policy.has_value()) {
policy = Modify(absl::StrCat(field, "_b", i), value);
} else {
policy = Sequence(
{*std::move(policy), Modify(absl::StrCat(field, "_b", i), value)});
}
}
return policy.value_or(Policy::Accept());
}

} // namespace netkat

#endif // GOOGLE_NETKAT_NETKAT_FRONTEND_H_
38 changes: 38 additions & 0 deletions netkat/frontend_test.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#include "netkat/frontend.h"

#include <array>
#include <cstdint>
#include <limits>

#include "absl/strings/string_view.h"
#include "fuzztest/fuzztest.h"
#include "gmock/gmock.h"
Expand All @@ -15,6 +19,8 @@ namespace {
using ::fuzztest::ContainerOf;
using ::netkat::netkat_test::AtomicDupFreePolicyDomain;
using ::netkat::netkat_test::AtomicPredicateDomain;
using UlongTernaryField =
::netkat::TernaryField<std::numeric_limits<unsigned long>::digits>;

void MatchToProtoIsCorrect(absl::string_view field, int value) {
EXPECT_THAT(Match(field, value).ToProto(),
Expand Down Expand Up @@ -206,5 +212,37 @@ FUZZ_TEST(FrontEndTest, MixedPolicyOperationsHasCorrectOrder)
/*b=*/AtomicDupFreePolicyDomain(),
/*c=*/AtomicDupFreePolicyDomain());

TEST(FrontEndTest, ZeroWidthTernaryReturnsFalseMatch) {
TernaryField<0> value;
EXPECT_THAT(Match("f", value).ToProto(), EqualsProto(FalseProto()));
}

TEST(FrontEndTest, ZeroWidthTernaryReturnsAcceptPolicy) {
TernaryField<0> value;
EXPECT_THAT(Modify("f", value).ToProto(), EqualsProto(AcceptProto()));
}

void NoTernaryMaskGeneratesFalseMatch(unsigned long value) {
UlongTernaryField nomask_value = {.value = {value}};
EXPECT_THAT(Match("f", nomask_value).ToProto(), EqualsProto(FalseProto()));
}
FUZZ_TEST(FrontEndTest, NoTernaryMaskGeneratesFalseMatch);

void NoTernaryMaskGeneratesAcceptPolicy(unsigned long value) {
UlongTernaryField nomask_value = {.value = {value}};
EXPECT_THAT(Modify("f", nomask_value).ToProto(), EqualsProto(AcceptProto()));
}
FUZZ_TEST(FrontEndTest, NoTernaryMaskGeneratesAcceptPolicy);

TEST(FrontEndTest, ValueReflectedWhenMasked) {
UlongTernaryField value = {.value = {0b10}, .mask = {0b11}};
EXPECT_THAT(
Match("f", value).ToProto(),
EqualsProto(AndProto(MatchProto("f_b0", 0), MatchProto("f_b1", 1))));
EXPECT_THAT(Modify("f", value).ToProto(),
EqualsProto(SequenceProto(ModificationProto("f_b0", 0),
ModificationProto("f_b1", 1))));
}

} // namespace
} // namespace netkat
8 changes: 6 additions & 2 deletions netkat/gtest_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ using ::fuzztest::OneOf;

fuzztest::Domain<Predicate> AtomicPredicateDomain() {
return OneOf(Just(Predicate::True()), Just(Predicate::False()),
Map(Match, Arbitrary<absl::string_view>(), Arbitrary<int>()));
Map([](absl::string_view field,
int value) { return Match(field, value); },
Arbitrary<absl::string_view>(), Arbitrary<int>()));
}

fuzztest::Domain<Policy> AtomicDupFreePolicyDomain() {
return OneOf(Map(Filter, AtomicPredicateDomain()),
Map(Modify, Arbitrary<absl::string_view>(), Arbitrary<int>()));
Map([](absl::string_view field,
int value) { return Modify(field, value); },
Arbitrary<absl::string_view>(), Arbitrary<int>()));
}

} // namespace netkat::netkat_test
Loading

0 comments on commit 85a7edd

Please sign in to comment.