blob: bca065707753f94e68810599d232c2fd4402da5b [file] [edit]
// Copyright 2025 Google LLC
//
// 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.
package ext
import (
"fmt"
"math"
"net/netip"
"reflect"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker"
"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter"
)
const (
// Version1 is the initial version of the Network library, providing
// parity with Kubernetes v1.30+ CEL network functions.
Version1 uint32 = 1
)
// Network returns a cel.EnvOption to configure extended functions for network
// address parsing, inspection, and CIDR range manipulation.
//
// Note: This library defines global functions `ip`, `cidr`, `isIP`, `isCIDR`
// and `ip.isCanonical`. If you are currently using variables named `ip` or
// `cidr`, these functions will likely work as intended, however there is a
// chance for collision.
//
// The library closely mirrors the behavior of the Kubernetes CEL network
// libraries, treating IP addresses and CIDR ranges as opaque types. It parses
// IPs strictly: IPv4-mapped IPv6 addresses and IP zones are not allowed.
//
// This library includes a TypeAdapter that allows `netip.Addr` and
// `netip.Prefix` Go types to be passed directly into the CEL environment.
//
// # IP Addresses
//
// The `ip` function converts a string to an IP address (IPv4 or IPv6). If the
// string is not a valid IP, an error is returned. The `isIP` function checks
// if a string is a valid IP address without throwing an error.
//
// ip(string) -> ip
// isIP(string) -> bool
//
// Examples:
//
// ip('127.0.0.1')
// ip('::1')
// isIP('1.2.3.4') // true
// isIP('invalid') // false
//
// # CIDR Ranges
//
// The `cidr` function converts a string to a Classless Inter-Domain Routing
// (CIDR) range. If the string is not valid, an error is returned.
//
// The `isCIDR` function checks if a string is a valid CIDR notation. Note that
// `isCIDR` allows CIDR values with or without host bits (e.g., '10.0.0.1/8'
// or '10.0.0.0/8').
//
// cidr(string) -> cidr
// isCIDR(string) -> bool
//
// Examples:
//
// cidr('192.168.0.0/24')
// cidr('::1/128')
// isCIDR('10.0.0.0/8') // true
// isCIDR('10.0.0.1/8') // true
//
// # IP Inspection and Canonicalization
//
// IP objects support various inspection methods.
//
// <ip>.family() -> int
// <ip>.isLoopback() -> bool
// <ip>.isGlobalUnicast() -> bool
// <ip>.isLinkLocalMulticast() -> bool
// <ip>.isLinkLocalUnicast() -> bool
// <ip>.isUnspecified() -> bool
//
// The `ip.isCanonical` function takes a string and returns true if it matches
// the RFC 5952 canonical string representation of that address.
//
// ip.isCanonical(string) -> bool
//
// Examples:
//
// ip('127.0.0.1').family() == 4
// ip('::1').family() == 6
// ip('127.0.0.1').isLoopback() == true
// ip.isCanonical('2001:db8::1') == true // RFC 5952 format
// ip.isCanonical('2001:DB8::1') == false // Uppercase is not canonical
// ip.isCanonical('2001:db8:0:0:0:0:0:1') == false // Expanded is not canonical
//
// # CIDR Member Functions
//
// CIDR objects support containment checks and property extraction.
//
// <cidr>.containsIP(ip|string) -> bool
// <cidr>.containsCIDR(cidr|string) -> bool
// <cidr>.ip() -> ip
// <cidr>.isMask() -> bool
// <cidr>.masked() -> cidr
// <cidr>.prefixLength() -> int
//
// Examples:
//
// cidr('10.0.0.0/8').containsIP(ip('10.0.0.1')) == true
// cidr('10.0.0.0/8').containsIP('10.0.0.1') == true
// cidr('10.0.0.0/8').containsCIDR('10.1.0.0/16') == true
// cidr('192.168.1.5/24').ip() == ip('192.168.1.5')
// cidr('192.168.1.0/24').isMask() == true
// cidr('192.168.1.5/24').isMask() == false
// cidr('192.168.1.5/24').masked() == cidr('192.168.1.0/24')
// cidr('192.168.1.0/24').prefixLength() == 24
func Network(opts ...NetworkOption) cel.EnvOption {
lib := &networkLib{version: Version1}
for _, o := range opts {
lib = o(lib)
}
return func(e *cel.Env) (*cel.Env, error) {
// Install the library (Types and Functions)
e, err := cel.Lib(lib)(e)
if err != nil {
return nil, err
}
// Install the Adapter (Wrapping the existing one)
adapter := &networkAdapter{Adapter: e.CELTypeAdapter()}
return cel.CustomTypeAdapter(adapter)(e)
}
}
// NetworkOption declares a functional operator for configuring the Network library behavior.
type NetworkOption func(*networkLib) *networkLib
// NetworkVersion sets the version of the network library to an explicit version.
func NetworkVersion(version uint32) NetworkOption {
return func(lib *networkLib) *networkLib {
lib.version = version
return lib
}
}
const (
// Function names matching the original Kubernetes implementation of this networking library.
// isStrictCIDR and isInterfaceAddress are added to enable strict isCIDR parsing without breaking
// functionality for existing users. Ctx: https://github.com/kubernetes/kubernetes/issues/134224
cidrFunc = "cidr"
cidrToString = "string"
containsCIDRFunc = "containsCIDR"
containsIPFunc = "containsIP"
familyFunc = "family"
ipFunc = "ip"
ipToString = "string"
isCanonicalFunc = "ip.isCanonical"
isCIDRFunc = "isCIDR"
isGlobalUnicastFunc = "isGlobalUnicast"
isIPFunc = "isIP"
isLinkLocalMcastFunc = "isLinkLocalMulticast"
isLinkLocalUcastFunc = "isLinkLocalUnicast"
isLoopbackFunc = "isLoopback"
isMaskFunc = "isMask"
isUnspecifiedFunc = "isUnspecified"
maskedFunc = "masked"
prefixLengthFunc = "prefixLength"
)
var (
// Definitions for the Opaque Types
// IPType represents a network IP address.
IPType = types.NewOpaqueType("net.IP")
// CIDRType represents a CIDR-format network range.
CIDRType = types.NewOpaqueType("net.CIDR")
)
type networkLib struct {
version uint32
}
func (*networkLib) LibraryName() string {
return "cel.lib.ext.network"
}
func (*networkLib) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{
cel.Types(
IPType,
CIDRType,
),
cel.Function(cidrFunc,
// K8s Parity: Following the pattern, this is "string_to_cidr"
cel.Overload("string_to_cidr", []*cel.Type{cel.StringType}, CIDRType,
cel.UnaryBinding(netCIDRString)),
),
cel.Function(cidrToString,
cel.Overload("cidr_to_string", []*cel.Type{CIDRType}, cel.StringType,
cel.UnaryBinding(netCIDRToString)),
),
cel.Function(containsCIDRFunc,
cel.MemberOverload("cidr_contains_cidr", []*cel.Type{CIDRType, CIDRType}, cel.BoolType,
cel.BinaryBinding(netCIDRContainsCIDR)),
cel.MemberOverload("cidr_contains_cidr_string", []*cel.Type{CIDRType, cel.StringType}, cel.BoolType,
cel.BinaryBinding(netCIDRContainsCIDRString)),
),
cel.Function(containsIPFunc,
cel.MemberOverload("cidr_contains_ip_ip", []*cel.Type{CIDRType, IPType}, cel.BoolType,
cel.BinaryBinding(netCIDRContainsIP)),
cel.MemberOverload("cidr_contains_ip_string", []*cel.Type{CIDRType, cel.StringType}, cel.BoolType,
cel.BinaryBinding(netCIDRContainsIPString)),
),
cel.Function(familyFunc,
cel.MemberOverload("ip_family", []*cel.Type{IPType}, cel.IntType,
cel.UnaryBinding(netIPFamily)),
),
cel.Function(ipFunc,
// K8s Parity: The global overload is named "string_to_ip"
cel.Overload("string_to_ip", []*cel.Type{cel.StringType}, IPType,
cel.UnaryBinding(netIPString)),
// K8s Parity: The member overload is named "cidr_ip"
cel.MemberOverload("cidr_ip", []*cel.Type{CIDRType}, IPType,
cel.UnaryBinding(netCIDRIP)),
),
cel.Function(ipToString,
cel.Overload("ip_to_string", []*cel.Type{IPType}, cel.StringType,
cel.UnaryBinding(netIPToString)),
),
cel.Function(isCanonicalFunc,
cel.Overload("ip_is_canonical", []*cel.Type{cel.StringType}, cel.BoolType,
cel.UnaryBinding(netIPIsCanonical)),
),
cel.Function(isCIDRFunc,
cel.Overload("is_cidr", []*cel.Type{cel.StringType}, cel.BoolType,
cel.UnaryBinding(netIsCIDR)),
),
cel.Function(isGlobalUnicastFunc,
cel.MemberOverload("ip_is_global_unicast", []*cel.Type{IPType}, cel.BoolType,
cel.UnaryBinding(netIPIsGlobalUnicast)),
),
cel.Function(isIPFunc,
cel.Overload("is_ip", []*cel.Type{cel.StringType}, cel.BoolType,
cel.UnaryBinding(netIsIP)),
),
cel.Function(isLinkLocalMcastFunc,
cel.MemberOverload("ip_is_link_local_multicast", []*cel.Type{IPType}, cel.BoolType,
cel.UnaryBinding(netIPIsLinkLocalMulticast)),
),
cel.Function(isLinkLocalUcastFunc,
cel.MemberOverload("ip_is_link_local_unicast", []*cel.Type{IPType}, cel.BoolType,
cel.UnaryBinding(netIPIsLinkLocalUnicast)),
),
cel.Function(isLoopbackFunc,
cel.MemberOverload("ip_is_loopback", []*cel.Type{IPType}, cel.BoolType,
cel.UnaryBinding(netIPIsLoopback)),
),
cel.Function(isMaskFunc,
cel.MemberOverload("cidr_is_mask", []*cel.Type{CIDRType}, cel.BoolType,
cel.UnaryBinding(netCIDRIsMask)),
),
cel.Function(isUnspecifiedFunc,
cel.MemberOverload("ip_is_unspecified", []*cel.Type{IPType}, cel.BoolType,
cel.UnaryBinding(netIPIsUnspecified)),
),
cel.Function(maskedFunc,
cel.MemberOverload("cidr_masked", []*cel.Type{CIDRType}, CIDRType,
cel.UnaryBinding(netCIDRMasked)),
),
cel.Function(prefixLengthFunc,
cel.MemberOverload("cidr_prefix_length", []*cel.Type{CIDRType}, cel.IntType,
cel.UnaryBinding(netCIDRPrefixLength)),
),
cel.ASTValidators(
networkFormatValidator{funcName: ipFunc, argNum: 0, check: checkIP},
networkFormatValidator{funcName: cidrFunc, argNum: 0, check: checkCIDR},
),
cel.CostEstimatorOptions(
checker.OverloadCostEstimate("string_to_cidr", estimateNetworkParseCost),
checker.OverloadCostEstimate("cidr_to_string", estimateNetworkNominalStringCost),
checker.OverloadCostEstimate("cidr_contains_cidr", estimateNetworkContainsCIDRCIDRCost),
checker.OverloadCostEstimate("cidr_contains_cidr_string", estimateNetworkContainsCIDRStringCost),
checker.OverloadCostEstimate("cidr_contains_ip_ip", estimateNetworkContainsIPIPCost),
checker.OverloadCostEstimate("cidr_contains_ip_string", estimateNetworkContainsIPStringCost),
checker.OverloadCostEstimate("ip_family", estimateNetworkNominalCost),
checker.OverloadCostEstimate("string_to_ip", estimateNetworkParseCost),
checker.OverloadCostEstimate("cidr_ip", estimateNetworkNominalOpaqueCost),
checker.OverloadCostEstimate("ip_to_string", estimateNetworkNominalStringCost),
checker.OverloadCostEstimate("ip_is_canonical", estimateIPIsCanonicalCost),
checker.OverloadCostEstimate("is_cidr", estimateNetworkParseBoolCost),
checker.OverloadCostEstimate("ip_is_global_unicast", estimateNetworkNominalCost),
checker.OverloadCostEstimate("is_ip", estimateNetworkParseBoolCost),
checker.OverloadCostEstimate("ip_is_link_local_multicast", estimateNetworkNominalCost),
checker.OverloadCostEstimate("ip_is_link_local_unicast", estimateNetworkNominalCost),
checker.OverloadCostEstimate("ip_is_loopback", estimateNetworkNominalCost),
checker.OverloadCostEstimate("cidr_is_mask", estimateNetworkNominalCost),
checker.OverloadCostEstimate("ip_is_unspecified", estimateNetworkNominalCost),
checker.OverloadCostEstimate("cidr_masked", estimateNetworkNominalOpaqueCost),
checker.OverloadCostEstimate("cidr_prefix_length", estimateNetworkNominalCost),
),
}
}
func (*networkLib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{
cel.CostTrackerOptions(
interpreter.OverloadCostTracker("string_to_cidr", trackNetworkParseCost),
interpreter.OverloadCostTracker("cidr_to_string", trackNetworkNominalCost),
interpreter.OverloadCostTracker("cidr_contains_cidr", trackNetworkContainsCIDRCIDRCost),
interpreter.OverloadCostTracker("cidr_contains_cidr_string", trackNetworkContainsCIDRStringCost),
interpreter.OverloadCostTracker("cidr_contains_ip_ip", trackNetworkContainsIPIPCost),
interpreter.OverloadCostTracker("cidr_contains_ip_string", trackNetworkContainsIPStringCost),
interpreter.OverloadCostTracker("ip_family", trackNetworkNominalCost),
interpreter.OverloadCostTracker("string_to_ip", trackNetworkParseCost),
interpreter.OverloadCostTracker("cidr_ip", trackNetworkNominalCost),
interpreter.OverloadCostTracker("ip_to_string", trackNetworkNominalCost),
interpreter.OverloadCostTracker("ip_is_canonical", trackIPIsCanonicalCost),
interpreter.OverloadCostTracker("is_cidr", trackNetworkParseCost),
interpreter.OverloadCostTracker("ip_is_global_unicast", trackNetworkNominalCost),
interpreter.OverloadCostTracker("is_ip", trackNetworkParseCost),
interpreter.OverloadCostTracker("ip_is_link_local_multicast", trackNetworkNominalCost),
interpreter.OverloadCostTracker("ip_is_link_local_unicast", trackNetworkNominalCost),
interpreter.OverloadCostTracker("ip_is_loopback", trackNetworkNominalCost),
interpreter.OverloadCostTracker("cidr_is_mask", trackNetworkNominalCost),
interpreter.OverloadCostTracker("ip_is_unspecified", trackNetworkNominalCost),
interpreter.OverloadCostTracker("cidr_masked", trackNetworkNominalCost),
interpreter.OverloadCostTracker("cidr_prefix_length", trackNetworkNominalCost),
),
}
}
// networkAdapter adapts netip types while preserving existing adapters.
type networkAdapter struct {
types.Adapter
}
func (a *networkAdapter) NativeToValue(value any) ref.Val {
switch v := value.(type) {
case netip.Addr:
return IP{Addr: v}
case netip.Prefix:
return CIDR{Prefix: v}
}
// Delegate to the wrapped adapter (e.g., Protobuf adapter)
return a.Adapter.NativeToValue(value)
}
// --- Implementation Logic ---
func netCIDRContainsCIDR(lhs, rhs ref.Val) ref.Val {
parent := lhs.(CIDR)
child := rhs.(CIDR)
return types.Bool(parent.Prefix.Overlaps(child.Prefix) && parent.Prefix.Bits() <= child.Prefix.Bits())
}
func netCIDRContainsCIDRString(lhs, rhs ref.Val) ref.Val {
parent := lhs.(CIDR)
s := rhs.(types.String)
childPrefix, err := parseCIDR(string(s))
if err != nil {
return types.WrapErr(err)
}
return types.Bool(parent.Prefix.Overlaps(childPrefix) && parent.Prefix.Bits() <= childPrefix.Bits())
}
func netCIDRContainsIP(lhs, rhs ref.Val) ref.Val {
cidr := lhs.(CIDR)
ip := rhs.(IP)
return types.Bool(cidr.Prefix.Contains(ip.Addr))
}
func netCIDRContainsIPString(lhs, rhs ref.Val) ref.Val {
cidr := lhs.(CIDR)
s := rhs.(types.String)
addr, err := parseIPAddr(string(s))
if err != nil {
return types.WrapErr(err)
}
return types.Bool(cidr.Prefix.Contains(addr))
}
func netCIDRIP(val ref.Val) ref.Val {
cidr := val.(CIDR)
return IP{Addr: cidr.Prefix.Addr()}
}
func netCIDRMasked(val ref.Val) ref.Val {
cidr := val.(CIDR)
return CIDR{Prefix: cidr.Prefix.Masked()}
}
func netCIDRPrefixLength(val ref.Val) ref.Val {
cidr := val.(CIDR)
return types.Int(cidr.Prefix.Bits())
}
func netCIDRString(val ref.Val) ref.Val {
s := val.(types.String)
str := string(s)
prefix, err := parseCIDR(str)
if err != nil {
return types.WrapErr(err)
}
return CIDR{Prefix: prefix}
}
func netCIDRToString(val ref.Val) ref.Val {
cidr := val.(CIDR)
return types.String(cidr.Prefix.String())
}
func netIPFamily(val ref.Val) ref.Val {
ip := val.(IP)
if ip.Addr.Is4() {
return types.Int(4)
}
return types.Int(6)
}
func netIPIsCanonical(val ref.Val) ref.Val {
s := val.(types.String)
str := string(s)
addr, err := parseIPAddr(str)
if err != nil {
return types.WrapErr(err)
}
return types.Bool(addr.String() == str)
}
func netIPIsGlobalUnicast(val ref.Val) ref.Val {
ip := val.(IP)
return types.Bool(ip.Addr.IsGlobalUnicast())
}
func netIPIsLinkLocalMulticast(val ref.Val) ref.Val {
ip := val.(IP)
return types.Bool(ip.Addr.IsLinkLocalMulticast())
}
func netIPIsLinkLocalUnicast(val ref.Val) ref.Val {
ip := val.(IP)
return types.Bool(ip.Addr.IsLinkLocalUnicast())
}
func netIPIsLoopback(val ref.Val) ref.Val {
ip := val.(IP)
return types.Bool(ip.Addr.IsLoopback())
}
func netIPIsUnspecified(val ref.Val) ref.Val {
ip := val.(IP)
return types.Bool(ip.Addr.IsUnspecified())
}
func netIPString(val ref.Val) ref.Val {
s := val.(types.String)
str := string(s)
addr, err := parseIPAddr(str)
if err != nil {
return types.WrapErr(err)
}
return IP{Addr: addr}
}
func netIPToString(val ref.Val) ref.Val {
ip := val.(IP)
return types.String(ip.Addr.String())
}
func netIsCIDR(val ref.Val) ref.Val {
s := val.(types.String)
_, err := parseCIDR(string(s))
return types.Bool(err == nil)
}
func netIsIP(val ref.Val) ref.Val {
s := val.(types.String)
_, err := parseIPAddr(string(s))
return types.Bool(err == nil)
}
func netCIDRIsMask(val ref.Val) ref.Val {
cidr := val.(CIDR)
return types.Bool(cidr.Prefix.Addr() == cidr.Prefix.Masked().Addr())
}
func parseCIDR(raw string) (netip.Prefix, error) {
prefix, err := netip.ParsePrefix(raw)
if err != nil {
return netip.Prefix{}, fmt.Errorf("CIDR %q parse error during conversion from string: %v", raw, err)
}
if prefix.Addr().Zone() != "" {
return netip.Prefix{}, fmt.Errorf("CIDR %q with zone value is not allowed", raw)
}
if prefix.Addr().Is4In6() {
return netip.Prefix{}, fmt.Errorf("IPv4-mapped IPv6 address %q is not allowed", raw)
}
return prefix, nil
}
func parseIPAddr(raw string) (netip.Addr, error) {
addr, err := netip.ParseAddr(raw)
if err != nil {
return netip.Addr{}, fmt.Errorf("IP Address %q parse error during conversion from string: %v", raw, err)
}
if addr.Zone() != "" {
return netip.Addr{}, fmt.Errorf("IP address %q with zone value is not allowed", raw)
}
if addr.Is4In6() {
return netip.Addr{}, fmt.Errorf("IPv4-mapped IPv6 address %q is not allowed", raw)
}
return addr, nil
}
// IP represents an IP address type.
type IP struct {
netip.Addr
}
// ConvertToNative converts the IP value to a native Go type.
func (i IP) ConvertToNative(typeDesc reflect.Type) (any, error) {
if typeDesc == reflect.TypeFor[netip.Addr]() {
return i.Addr, nil
}
if typeDesc.Kind() == reflect.String {
return i.Addr.String(), nil
}
return nil, fmt.Errorf("unsupported type conversion to '%v'", typeDesc)
}
// ConvertToType converts the IP value to a CEL type.
func (i IP) ConvertToType(typeValue ref.Type) ref.Val {
switch typeValue {
case types.StringType:
return types.String(i.Addr.String())
case IPType:
return i
case types.TypeType:
return IPType
}
return types.NewErr("type conversion error from '%s' to '%s'", IPType, typeValue)
}
// Equal returns true if this IP is equal to the other ref.Val.
func (i IP) Equal(other ref.Val) ref.Val {
o, ok := other.(IP)
if !ok {
return types.False
}
return types.Bool(i.Addr == o.Addr)
}
// Type returns the CEL type of the IP.
func (i IP) Type() ref.Type {
return IPType
}
// Value returns the raw Go value (netip.Addr) of the IP.
func (i IP) Value() any {
return i.Addr
}
// Size returns the size of the IP address in bytes.
// /Used in the size estimation of the runtime cost.
func (i IP) Size() ref.Val {
return types.Int(int64(math.Ceil(float64(i.Addr.BitLen()) / 8)))
}
// CIDR represents the CIDR network mask format.
type CIDR struct {
netip.Prefix
}
// ConvertToNative converts the CIDR value to a native Go type.
func (c CIDR) ConvertToNative(typeDesc reflect.Type) (any, error) {
if typeDesc == reflect.TypeFor[netip.Prefix]() {
return c.Prefix, nil
}
if typeDesc.Kind() == reflect.String {
return c.Prefix.String(), nil
}
return nil, fmt.Errorf("unsupported type conversion to '%v'", typeDesc)
}
// ConvertToType converts the CIDR value to a CEL type.
func (c CIDR) ConvertToType(typeValue ref.Type) ref.Val {
switch typeValue {
case types.StringType:
return types.String(c.Prefix.String())
case CIDRType:
return c
case types.TypeType:
return CIDRType
}
return types.NewErr("type conversion error from '%s' to '%s'", CIDRType, typeValue)
}
// Equal returns true if this CIDR is equal to the other ref.Val.
func (c CIDR) Equal(other ref.Val) ref.Val {
o, ok := other.(CIDR)
if !ok {
return types.False
}
return types.Bool(c.Prefix == o.Prefix)
}
// Type returns the CEL type of the CIDR.
func (c CIDR) Type() ref.Type {
return CIDRType
}
// Value returns the raw Go value (netip.Prefix) of the CIDR.
func (c CIDR) Value() any {
return c.Prefix
}
// Size returns the size of the CIDR prefix address in bytes.
// Used in the size estimation of the runtime cost.
func (c CIDR) Size() ref.Val {
return types.Int(int64(math.Ceil(float64(c.Prefix.Bits()) / 8)))
}
// --- Static Validators ---
type argChecker func(e *cel.Env, call, arg ast.Expr) error
type networkFormatValidator struct {
funcName string
argNum int
check argChecker
}
func (v networkFormatValidator) Name() string {
return fmt.Sprintf("cel.validator.network.%s", v.funcName)
}
func (v networkFormatValidator) Validate(e *cel.Env, _ cel.ValidatorConfig, a *ast.AST, iss *cel.Issues) {
root := ast.NavigateAST(a)
funcCalls := ast.MatchDescendants(root, ast.FunctionMatcher(v.funcName))
for _, call := range funcCalls {
callArgs := call.AsCall().Args()
if len(callArgs) <= v.argNum {
continue
}
litArg := callArgs[v.argNum]
if litArg.Kind() != ast.LiteralKind {
continue
}
if err := v.check(e, call, litArg); err != nil {
iss.ReportErrorAtID(litArg.ID(), "invalid %s argument: %v", v.funcName, err)
}
}
}
func checkIP(e *cel.Env, call, arg ast.Expr) error {
pattern := arg.AsLiteral().Value().(string)
_, err := parseIPAddr(pattern)
return err
}
func checkCIDR(e *cel.Env, call, arg ast.Expr) error {
pattern := arg.AsLiteral().Value().(string)
_, err := parseCIDR(pattern)
return err
}
// Cost estimation functions for network extensions.
func estimateNetworkParseCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
if len(args) < 1 {
return nil
}
sz := estimateSize(estimator, args[0])
resultSize := rangedSizeEstimate(4, 16)
return callEstimate(sz.MultiplyByCostFactor(stringCostFactor), &resultSize)
}
func estimateNetworkParseBoolCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
if len(args) < 1 {
return nil
}
sz := estimateSize(estimator, args[0])
return callEstimate(sz.MultiplyByCostFactor(stringCostFactor), nil)
}
func estimateIPIsCanonicalCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
if len(args) < 1 {
return nil
}
sz := estimateSize(estimator, args[0])
return callEstimate(sz.MultiplyByCostFactor(2*stringCostFactor), nil)
}
func estimateNetworkNominalCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
return callEstimate(callCostEstimate, nil)
}
func estimateNetworkNominalOpaqueCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
resultSize := rangedSizeEstimate(4, 16)
return callEstimate(callCostEstimate, &resultSize)
}
func estimateNetworkNominalStringCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
resultSize := rangedSizeEstimate(3, 45)
return callEstimate(callCostEstimate, &resultSize)
}
func estimateNetworkContainsIPIPCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
sz := rangedSizeEstimate(4, 16)
ipCompCost := sz.Add(sz).MultiplyByCostFactor(stringCostFactor)
return callEstimate(ipCompCost, nil)
}
func estimateNetworkContainsIPStringCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
if len(args) < 1 {
return nil
}
sz := rangedSizeEstimate(4, 16)
ipCompCost := sz.Add(sz).MultiplyByCostFactor(stringCostFactor)
argSz := estimateSize(estimator, args[0])
ipCompCost = ipCompCost.Add(argSz.MultiplyByCostFactor(stringCostFactor))
return callEstimate(ipCompCost, nil)
}
func estimateNetworkContainsCIDRCIDRCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
sz := rangedSizeEstimate(4, 16)
ipCompCost := sz.Add(sz).MultiplyByCostFactor(stringCostFactor)
ipCompCost = ipCompCost.Add(sz.MultiplyByCostFactor(stringCostFactor))
// K8s adds one for the extra IP traversal
ipCompCost = ipCompCost.Add(callCostEstimate)
return callEstimate(ipCompCost, nil)
}
func estimateNetworkContainsCIDRStringCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
if len(args) < 1 {
return nil
}
sz := rangedSizeEstimate(4, 16)
ipCompCost := sz.Add(sz).MultiplyByCostFactor(stringCostFactor)
ipCompCost = ipCompCost.Add(sz.MultiplyByCostFactor(stringCostFactor))
argSz := estimateSize(estimator, args[0])
ipCompCost = ipCompCost.Add(argSz.MultiplyByCostFactor(stringCostFactor))
// K8s adds one for the extra IP traversal
ipCompCost = ipCompCost.Add(callCostEstimate)
return callEstimate(ipCompCost, nil)
}
// Runtime cost tracking functions for network extensions.
func trackNetworkParseCost(args []ref.Val, result ref.Val) *uint64 {
cost := uint64(math.Ceil(float64(actualSize(args[0])) * stringCostFactor))
return &cost
}
func trackIPIsCanonicalCost(args []ref.Val, result ref.Val) *uint64 {
cost := uint64(math.Ceil(float64(actualSize(args[0])) * 2 * stringCostFactor))
return &cost
}
func trackNetworkNominalCost(args []ref.Val, result ref.Val) *uint64 {
return &callCost
}
func trackNetworkContainsIPIPCost(args []ref.Val, result ref.Val) *uint64 {
cidrSize := actualSize(args[0])
cost := uint64(math.Ceil(float64(cidrSize+cidrSize) * stringCostFactor))
return &cost
}
func trackNetworkContainsIPStringCost(args []ref.Val, result ref.Val) *uint64 {
cidrSize := actualSize(args[0])
otherSize := actualSize(args[1])
cost := uint64(math.Ceil(float64(cidrSize+cidrSize) * stringCostFactor))
cost = safeAdd(cost, uint64(math.Ceil(float64(otherSize)*stringCostFactor)))
return &cost
}
func trackNetworkContainsCIDRCIDRCost(args []ref.Val, result ref.Val) *uint64 {
cidrSize := actualSize(args[0])
cost := uint64(math.Ceil(float64(cidrSize+cidrSize) * stringCostFactor))
cost = safeAdd(cost, uint64(math.Ceil(float64(cidrSize)*stringCostFactor)), 1)
return &cost
}
func trackNetworkContainsCIDRStringCost(args []ref.Val, result ref.Val) *uint64 {
cidrSize := actualSize(args[0])
otherSize := actualSize(args[1])
cost := uint64(math.Ceil(float64(cidrSize+cidrSize) * stringCostFactor))
cost = safeAdd(cost, uint64(math.Ceil(float64(cidrSize)*stringCostFactor)), 1)
cost = safeAdd(cost, uint64(math.Ceil(float64(otherSize)*stringCostFactor)))
return &cost
}