Skip to content

Commit

Permalink
Add isIpPrefix (#45)
Browse files Browse the repository at this point in the history
Related to: bufbuild/protovalidate#99

Based on @higebu's idea on the PR, I've implemented `isIpPrefix()` for
C++.

---------

Co-authored-by: Chris Roche <[email protected]>
  • Loading branch information
paina and rodaine authored Nov 7, 2023
1 parent 7572f0c commit 64da14e
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 9 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ COPYRIGHT_YEARS := 2023
LICENSE_IGNORE := -e internal/testdata/
LICENSE_HEADER_VERSION := 0294fdbe1ce8649ebaf5e87e8cdd588e33730bbb
# NOTE: Keep this version in sync with the version in `/bazel/deps.bzl`.
PROTOVALIDATE_VERSION ?= v0.4.2
PROTOVALIDATE_VERSION ?= v0.5.1

# Set to use a different compiler. For example, `GO=go1.18rc1 make test`.
GO ?= go
Expand Down
6 changes: 3 additions & 3 deletions bazel/deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ _dependencies = {
},
# NOTE: Keep Version in sync with `/Makefile`.
"com_github_bufbuild_protovalidate": {
"sha256": "0dfa0a054f8739938172e28cdaa666c3e175e337d1fbf411ef76e729458be232",
"strip_prefix": "protovalidate-0.4.2",
"sha256": "67c360e29651a3bf81f7a2394f5d7f5353a67c0f79cb304ba420d27900e30203",
"strip_prefix": "protovalidate-0.5.1",
"urls": [
"https://github.com/bufbuild/protovalidate/archive/v0.4.2.tar.gz",
"https://github.com/bufbuild/protovalidate/archive/v0.5.1.tar.gz",
],
},
}
Expand Down
9 changes: 9 additions & 0 deletions buf/validate/internal/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,12 @@ cc_test(
"@com_google_googletest//:gtest_main",
],
)

cc_test(
name = "extra_func_test",
srcs = ["extra_func_test.cc"],
deps = [
":extra_func",
"@com_google_googletest//:gtest_main",
],
)
143 changes: 139 additions & 4 deletions buf/validate/internal/extra_func.cc
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,13 @@ cel::CelValue isEmail(google::protobuf::Arena* arena, cel::CelValue::StringHolde
}

bool IsIpv4(const std::string_view to_validate) {
struct sockaddr_in sa;
return !(inet_pton(AF_INET, to_validate.data(), &sa.sin_addr) < 1);
struct in_addr result;
return inet_pton(AF_INET, to_validate.data(), &result) == 1;
}

bool IsIpv6(const std::string_view to_validate) {
struct sockaddr_in6 sa_six;
return !(inet_pton(AF_INET6, to_validate.data(), &sa_six.sin6_addr) < 1);
struct in6_addr result;
return inet_pton(AF_INET6, to_validate.data(), &result) == 1;
}

bool IsIp(const std::string_view to_validate) {
Expand All @@ -220,6 +220,123 @@ cel::CelValue isIP(google::protobuf::Arena* arena, cel::CelValue::StringHolder l
return isIPvX(arena, lhs, cel::CelValue::CreateInt64(0));
}

/**
* IP Prefix Validation
*/
bool IsIpv4Prefix(const std::string_view to_validate, bool strict) {
std::vector<std::string> split = absl::StrSplit(to_validate.data(), '/');
if (split.size() != 2) {
return false;
}
std::string ip = split[0];
std::string prefixlen = split[1];

// validate ip
struct in_addr addr;
if (inet_pton(AF_INET, ip.c_str(), &addr) != 1) {
return false;
}

// validate prefixlen
if (prefixlen.empty()) {
return false;
}
int prefixlen_int;
if (!absl::SimpleAtoi(prefixlen, &prefixlen_int)) {
return false;
}
if (prefixlen_int < 0 || prefixlen_int > 32) {
return false;
}

// check host part is all-zero if strict
if (strict) {
struct in_addr mask;
mask.s_addr = htonl((1ull << (32 - prefixlen_int)) - 1);
return ((addr.s_addr & mask.s_addr) == 0);
}

return true;
}

bool IsIpv6Prefix(const std::string_view to_validate, bool strict) {
std::vector<std::string> split = absl::StrSplit(to_validate.data(), '/');
if (split.size() != 2) {
return false;
}
std::string ip = split[0];
std::string prefixlen = split[1];

// validate ip
struct in6_addr addr;
if (inet_pton(AF_INET6, ip.c_str(), &addr) != 1) {
return false;
}

// validate prefixlen
if (prefixlen.empty()) {
return false;
}
int prefixlen_int;
if (!absl::SimpleAtoi(prefixlen, &prefixlen_int)) {
return false;
}
if (prefixlen_int < 0 || prefixlen_int > 128) {
return false;
}

// check host part is all-zero if strict
if (strict) {
int i;
int l;
for (i = 15, l = (128 - prefixlen_int); l > 0; i--, l -= 8) {
uint8_t mask = (l < 8) ? (1 << l) - 1 : 0xff;
if ((addr.s6_addr[i] & mask) != 0) {
return false;
}
}
}

return true;
}

bool IsIpPrefix(const std::string_view to_validate, bool strict) {
return IsIpv4Prefix(to_validate, strict) || IsIpv6Prefix(to_validate, strict);
}

cel::CelValue isIpPrefixXY(
google::protobuf::Arena* arena,
cel::CelValue::StringHolder lhs,
cel::CelValue rhs1,
cel::CelValue rhs2) {
std::string_view str = lhs.value();
int ver = rhs1.Int64OrDie();
bool strict = rhs2.BoolOrDie();

switch (ver) {
case 0:
return cel::CelValue::CreateBool(IsIpPrefix(str, strict));
case 4:
return cel::CelValue::CreateBool(IsIpv4Prefix(str, strict));
case 6:
return cel::CelValue::CreateBool(IsIpv6Prefix(str, strict));
default:
return cel::CelValue::CreateBool(false);
}
}

cel::CelValue isIpPrefixX(
google::protobuf::Arena* arena, cel::CelValue::StringHolder lhs, cel::CelValue rhs) {
if (rhs.IsBool()) {
return isIpPrefixXY(arena, lhs, cel::CelValue::CreateInt64(0), rhs);
}
return isIpPrefixXY(arena, lhs, rhs, cel::CelValue::CreateBool(false));
}

cel::CelValue isIpPrefix(google::protobuf::Arena* arena, cel::CelValue::StringHolder lhs) {
return isIpPrefixXY(arena, lhs, cel::CelValue::CreateInt64(0), cel::CelValue::CreateBool(false));
}

/**
* Naive URI validation.
*/
Expand Down Expand Up @@ -322,6 +439,24 @@ absl::Status RegisterExtraFuncs(
if (!isIpStatus.ok()) {
return isIpStatus;
}
auto isIpPrefixStatusXY = cel::
FunctionAdapter<cel::CelValue, cel::CelValue::StringHolder, cel::CelValue, cel::CelValue>::
CreateAndRegister("isIpPrefix", true, &isIpPrefixXY, &registry);
if (!isIpPrefixStatusXY.ok()) {
return isIpPrefixStatusXY;
}
auto isIpPrefixStatusX =
cel::FunctionAdapter<cel::CelValue, cel::CelValue::StringHolder, cel::CelValue>::
CreateAndRegister("isIpPrefix", true, &isIpPrefixX, &registry);
if (!isIpPrefixStatusX.ok()) {
return isIpPrefixStatusX;
}
auto isIpPrefixStatus =
cel::FunctionAdapter<cel::CelValue, cel::CelValue::StringHolder>::CreateAndRegister(
"isIpPrefix", true, &isIpPrefix, &registry);
if (!isIpPrefixStatus.ok()) {
return isIpPrefixStatus;
}
auto startsWithStatus =
cel::FunctionAdapter<cel::CelValue, cel::CelValue::BytesHolder, cel::CelValue>::
CreateAndRegister("startsWith", true, &startsWith, &registry);
Expand Down
7 changes: 6 additions & 1 deletion buf/validate/internal/extra_func.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ namespace buf::validate::internal {
absl::Status RegisterExtraFuncs(
google::api::expr::runtime::CelFunctionRegistry& registry, google::protobuf::Arena* regArena);

} // namespace buf::validate::internal
// define for testing
bool IsIpv4Prefix(const std::string_view to_validate, bool strict);
bool IsIpv6Prefix(const std::string_view to_validate, bool strict);
bool IsIpPrefix(const std::string_view to_validate, bool strict);

} // namespace buf::validate::internal
56 changes: 56 additions & 0 deletions buf/validate/internal/extra_func_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2023 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "buf/validate/internal/extra_func.h"

#include "gtest/gtest.h"

TEST(ExtraFuncTest, TestIpPrefix) {
EXPECT_TRUE(buf::validate::internal::IsIpPrefix("1.2.3.0/24", false));
EXPECT_TRUE(buf::validate::internal::IsIpPrefix("1.2.3.4/24", false));
EXPECT_TRUE(buf::validate::internal::IsIpPrefix("1.2.3.0/24", true));
EXPECT_FALSE(buf::validate::internal::IsIpPrefix("1.2.3.4/24", true));
EXPECT_TRUE(
buf::validate::internal::IsIpPrefix("fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118", false));
EXPECT_TRUE(
buf::validate::internal::IsIpPrefix("fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118", false));
EXPECT_FALSE(
buf::validate::internal::IsIpPrefix("fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118", true));
EXPECT_FALSE(buf::validate::internal::IsIpPrefix("1.2.3.4", false));
EXPECT_FALSE(
buf::validate::internal::IsIpPrefix("fd7a:115c:a1e0:ab12:4843:cd96:626b:430b", false));
EXPECT_TRUE(buf::validate::internal::IsIpv4Prefix("1.2.3.0/24", false));
EXPECT_TRUE(buf::validate::internal::IsIpv4Prefix("1.2.3.4/24", false));
EXPECT_TRUE(buf::validate::internal::IsIpv4Prefix("1.2.3.0/24", true));
EXPECT_FALSE(buf::validate::internal::IsIpv4Prefix("1.2.3.4/24", true));
EXPECT_FALSE(
buf::validate::internal::IsIpv4Prefix("fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118", false));
EXPECT_TRUE(
buf::validate::internal::IsIpv6Prefix("fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118", false));
EXPECT_TRUE(
buf::validate::internal::IsIpv6Prefix("fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118", false));
EXPECT_TRUE(
buf::validate::internal::IsIpv6Prefix("fd7a:115c:a1e0:ab12:4843:cd96:626b:4000/118", true));
EXPECT_FALSE(
buf::validate::internal::IsIpv6Prefix("fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118", true));
EXPECT_FALSE(buf::validate::internal::IsIpv6Prefix("1.2.3.0/24", false));
// Additional Tests for IsIpv6Prefix()
EXPECT_TRUE(buf::validate::internal::IsIpv6Prefix("2001:db8::/29", true));
EXPECT_FALSE(buf::validate::internal::IsIpv6Prefix("2001:db9::/29", true));
EXPECT_TRUE(buf::validate::internal::IsIpv6Prefix("2001:db8:ff00::/40", true));
EXPECT_FALSE(buf::validate::internal::IsIpv6Prefix("2001:db8:ff80::/40", true));
EXPECT_TRUE(buf::validate::internal::IsIpv6Prefix("2001:db8:ff00:ff00::/57", true));
EXPECT_TRUE(buf::validate::internal::IsIpv6Prefix("2001:db8:ff00:ff80::/57", true));
EXPECT_FALSE(buf::validate::internal::IsIpv6Prefix("2001:db8:ff00:ffc0::/57", true));
}

0 comments on commit 64da14e

Please sign in to comment.