common/helpers: correctly validate netip.Addr/netip.Prefix

validate is only able to validate non-struct types (or recurse inside
struct). So, if we want to use "required" on some of them, we need a
custom type.

Fix #263
This commit is contained in:
Vincent Bernat
2022-11-15 18:37:46 +01:00
parent 6644d99001
commit bf99e2211e
3 changed files with 79 additions and 0 deletions

View File

@@ -5,6 +5,7 @@ package helpers
import ( import (
"net" "net"
"net/netip"
"reflect" "reflect"
"strconv" "strconv"
@@ -31,6 +32,23 @@ func RegisterSubnetMapValidation[V any]() {
Validate.RegisterCustomTypeFunc(validatorFunc, zero) Validate.RegisterCustomTypeFunc(validatorFunc, zero)
} }
// netipValidation validates netip.Addr and netip.Prefix by turning them into a string.
func netipValidation(fl reflect.Value) interface{} {
switch netipSomething := fl.Interface().(type) {
case netip.Addr:
if (netipSomething == netip.Addr{}) {
return ""
}
return netipSomething.String()
case netip.Prefix:
if (netipSomething == netip.Prefix{}) {
return ""
}
return netipSomething.String()
}
return nil
}
// isListen validates a <dns>:<port> combination for fields typically used for listening address // isListen validates a <dns>:<port> combination for fields typically used for listening address
func isListen(fl validator.FieldLevel) bool { func isListen(fl validator.FieldLevel) bool {
val := fl.Field().String() val := fl.Field().String()
@@ -53,5 +71,6 @@ func isListen(fl validator.FieldLevel) bool {
func init() { func init() {
Validate = validator.New() Validate = validator.New()
Validate.RegisterValidation("listen", isListen) Validate.RegisterValidation("listen", isListen)
Validate.RegisterCustomTypeFunc(netipValidation, netip.Addr{}, netip.Prefix{})
RegisterSubnetMapValidation[string]() RegisterSubnetMapValidation[string]()
} }

View File

@@ -4,6 +4,7 @@
package helpers_test package helpers_test
import ( import (
"net/netip"
"testing" "testing"
"akvorado/common/helpers" "akvorado/common/helpers"
@@ -86,3 +87,59 @@ func TestSubnetMapValidator(t *testing.T) {
}) })
} }
} }
func TestNetIPValidation(t *testing.T) {
type SomeStruct struct {
Src netip.Addr `validate:"required"`
DstNet netip.Prefix `validate:"required"`
Nothing netip.Addr `validate:"isdefault"`
}
cases := []struct {
Description string
Value interface{}
Error bool
}{
{
Description: "Valid SomeStruct",
Value: SomeStruct{
Src: netip.MustParseAddr("203.0.113.14"),
DstNet: netip.MustParsePrefix("203.0.113.0/24"),
Nothing: netip.Addr{},
},
}, {
Description: "Missing netip.Addr",
Value: SomeStruct{
Src: netip.Addr{},
DstNet: netip.MustParsePrefix("203.0.113.0/24"),
Nothing: netip.Addr{},
},
Error: true,
}, {
Description: "Missing netip.Prefix",
Value: SomeStruct{
Src: netip.MustParseAddr("203.0.113.14"),
DstNet: netip.Prefix{},
Nothing: netip.Addr{},
},
Error: true,
}, {
Description: "Non-default netip.Addr",
Value: SomeStruct{
Src: netip.MustParseAddr("203.0.113.14"),
DstNet: netip.MustParsePrefix("203.0.113.0/24"),
Nothing: netip.MustParseAddr("2001:db8::1"),
},
Error: true,
},
}
for _, tc := range cases {
t.Run(tc.Description, func(t *testing.T) {
err := helpers.Validate.Struct(tc.Value)
if err != nil && !tc.Error {
t.Fatalf("Validate() error:\n%+v", err)
} else if err == nil && tc.Error {
t.Fatal("Validate() did not error")
}
})
}
}

View File

@@ -4,6 +4,7 @@
package flows package flows
import ( import (
"net/netip"
"testing" "testing"
"time" "time"
@@ -19,6 +20,8 @@ func TestDefaultConfiguration(t *testing.T) {
OutIfIndex: []int{2}, OutIfIndex: []int{2},
PeakHour: 21 * time.Hour, PeakHour: 21 * time.Hour,
Multiplier: 3.0, Multiplier: 3.0,
SrcNet: netip.MustParsePrefix("2001:db8:1::/64"),
DstNet: netip.MustParsePrefix("2001:db8:2::/64"),
SrcAS: []uint32{2906}, SrcAS: []uint32{2906},
DstAS: []uint32{12322}, DstAS: []uint32{12322},
SrcPort: []uint16{443}, SrcPort: []uint16{443},