diff --git a/pkg/validator/validator.go b/pkg/validator/validator.go index 01e9fa3..54ebc0b 100644 --- a/pkg/validator/validator.go +++ b/pkg/validator/validator.go @@ -15,6 +15,7 @@ package validator import ( + "errors" "fmt" "net" "strings" @@ -25,6 +26,8 @@ import ( "go.uber.org/multierr" ) +var errUnparsableNetworkAddr = errors.New("unparsable network address") + func LoadAndValidate(filename string) (*model.RoutemapRoot, model.RoutemapSummary, error) { var ( rmap *model.RoutemapRoot @@ -50,7 +53,8 @@ func isAsciiOnly(s string) bool { return true } -func validateProperCIDR(ip net.IP, ipnet *net.IPNet) error { +// ValidateProperCIDR verifies that the IP address corresponds to the network. +func ValidateProperCIDR(ip net.IP, ipnet *net.IPNet) error { if !ip.Equal(ipnet.IP) { return fmt.Errorf("network address not properly masked") } @@ -58,7 +62,9 @@ func validateProperCIDR(ip net.IP, ipnet *net.IPNet) error { return nil } -func validateNetmaskLen(ipnet *net.IPNet) error { +// ValidateNetmaskLen verifies that the mask length does not exceeds the maximum +// allowed value. +func ValidateNetmaskLen(ipnet *net.IPNet) error { numOnes, numBits := ipnet.Mask.Size() switch { case numOnes == 0 && numBits == 0: @@ -72,36 +78,61 @@ func validateNetmaskLen(ipnet *net.IPNet) error { } } -func validateNetworks(nets []string, mapIdx int, summary *model.RoutemapSummary) error { - var allErrs error +// ValidateNetwork takes an string representing a network from the JSON source +// file and validates it. +// Returns the parsed IP and IPNet along with an error instance that can possibly +// be a multierr instance. +// +// If the network can not be parsed, only the error instance will contain values. +// The correctness of the network depends on the returned error having a nil value. +func ValidateNetwork(network string) (net.IP, *net.IPNet, error) { + var errs []error + + ip, ipnet, err := net.ParseCIDR(network) + if err != nil { + return nil, nil, errUnparsableNetworkAddr + } + + if err = ValidateProperCIDR(ip, ipnet); err != nil { + errs = append(errs, err) + } + if err = ValidateNetmaskLen(ipnet); err != nil { + errs = append(errs, err) + } + + return ip, ipnet, multierr.Combine(errs...) +} + +func ValidateNetworks(nets []string, mapIdx int, summary *model.RoutemapSummary) error { + var ( + allErrs error + err error + ipnet *net.IPNet + ) summary.NumNetworks += len(nets) for idx, n := range nets { lg.Tracef("visiting networks at index=%d, map segment index=%d...", idx, mapIdx) - ip, ipnet, err := net.ParseCIDR(n) + _, ipnet, err = ValidateNetwork(n) if err != nil { - multierr.AppendInto(&allErrs, - fmt.Errorf("unparsable network address (for CIDR \"%s\" at index=%d, map segment index=%d)", n, idx, mapIdx)) + for _, e := range multierr.Errors(err) { + // Rehydrate the packed errors so we can set the proper individual + // error messages. + multierr.AppendInto(&allErrs, + fmt.Errorf("%v (for CIDR \"%s\" at index=%d, map segment index=%d)", + e, n, idx, mapIdx)) + } continue } - if _, bits := ipnet.Mask.Size(); bits == 32 { - summary.NumIPv4 += 1 - } else { - summary.NumIPv6 += 1 - } - - var errs []error - - errs = append(errs, validateProperCIDR(ip, ipnet)) - errs = append(errs, validateNetmaskLen(ipnet)) - - for _, e := range errs { - if e != nil { - multierr.AppendInto(&allErrs, - fmt.Errorf("%v (for CIDR \"%s\" at index=%d, map segment index=%d)", e, n, idx, mapIdx)) + // ValidateNetwork will return a nil ipnet if the string was unparsable. + if ipnet != nil { + if _, bits := ipnet.Mask.Size(); bits == 32 { + summary.NumIPv4 += 1 + } else { + summary.NumIPv6 += 1 } } } @@ -109,7 +140,8 @@ func validateNetworks(nets []string, mapIdx int, summary *model.RoutemapSummary) return allErrs } -func validateLabels(labels []string, mapIdx int, summary *model.RoutemapSummary) error { +// ValidateLabels validates the set of labels of the route map. +func ValidateLabels(labels []string, mapIdx int, summary *model.RoutemapSummary) error { var allErrs error if len(labels) == 0 { @@ -147,7 +179,7 @@ func validateLabels(labels []string, mapIdx int, summary *model.RoutemapSummary) return allErrs } -func validateVersion(version int) error { +func ValidateVersion(version int) error { if version < 1 { return fmt.Errorf("invalid or missing meta/version") } else if version != 1 { @@ -158,7 +190,7 @@ func validateVersion(version int) error { } func startValidate(root *model.RoutemapRoot, summary *model.RoutemapSummary) error { - if err := validateVersion(root.MetaVersion()); err != nil { + if err := ValidateVersion(root.MetaVersion()); err != nil { return err } @@ -181,8 +213,8 @@ func startValidate(root *model.RoutemapRoot, summary *model.RoutemapSummary) err continue } - multierr.AppendInto(&allErrs, validateNetworks(m.Networks, idx, summary)) - multierr.AppendInto(&allErrs, validateLabels(m.Labels, idx, summary)) + multierr.AppendInto(&allErrs, ValidateNetworks(m.Networks, idx, summary)) + multierr.AppendInto(&allErrs, ValidateLabels(m.Labels, idx, summary)) if lg.EnabledFor(lg.LevelDebug) && (summary.NumNetworks-lastProgressReport) > 500000 { numErrs := len(multierr.Errors(allErrs)) diff --git a/pkg/validator/validator_test.go b/pkg/validator/validator_test.go index 6e1f197..476d40e 100644 --- a/pkg/validator/validator_test.go +++ b/pkg/validator/validator_test.go @@ -39,7 +39,7 @@ func Test_validateProperCIDR(t *testing.T) { ip, ipnet, err := net.ParseCIDR(fx.addr) assert.NoError(t, err, fx.addr) - err = validateProperCIDR(ip, ipnet) + err = ValidateProperCIDR(ip, ipnet) if fx.valid { assert.NoError(t, err, fx.addr) } else { @@ -64,7 +64,7 @@ func Test_validateNetmaskLen(t *testing.T) { _, ipnet, err := net.ParseCIDR(fx.addr) assert.NoError(t, err, fx.addr) - err = validateNetmaskLen(ipnet) + err = ValidateNetmaskLen(ipnet) if fx.valid { assert.NoError(t, err, fx.addr) } else { @@ -88,7 +88,7 @@ func Test_validateLabels(t *testing.T) { summary := model.NewRoutemapSummary() for _, fx := range fixtures { - err := validateLabels(fx.labels, 1, &summary) + err := ValidateLabels(fx.labels, 1, &summary) if fx.valid { assert.NoError(t, err, fx.labels) } else {