mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-11 22:14:02 +01:00
go-cmp is stricter and allow to catch more problems. Moreover, the output is a bit nicer.
154 lines
5.3 KiB
Go
154 lines
5.3 KiB
Go
// SPDX-FileCopyrightText: 2022 Free Mobile
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
"akvorado/common/helpers"
|
|
|
|
"github.com/go-viper/mapstructure/v2"
|
|
)
|
|
|
|
// Configuration describes the configuration for the core component.
|
|
type Configuration struct {
|
|
// ExporterClassifiers defines rules for exporter classification
|
|
ExporterClassifiers []ExporterClassifierRule
|
|
// InterfaceClassifiers defines rules for interface classification
|
|
InterfaceClassifiers []InterfaceClassifierRule
|
|
// ClassifierCacheDuration defines the default TTL for classifier cache
|
|
ClassifierCacheDuration time.Duration `validate:"min=1s"`
|
|
// DefaultSamplingRate defines the default sampling rate to use when the information is missing
|
|
DefaultSamplingRate *helpers.SubnetMap[uint]
|
|
// OverrideSamplingRate defines a sampling rate to use instead of the received on
|
|
OverrideSamplingRate *helpers.SubnetMap[uint]
|
|
// ASNProviders defines the source used to get AS numbers
|
|
ASNProviders []ASNProvider `validate:"dive"`
|
|
// NetProviders defines the source used to get Prefix/Network Information
|
|
NetProviders []NetProvider `validate:"dive"`
|
|
}
|
|
|
|
// DefaultConfiguration represents the default configuration for the core component.
|
|
func DefaultConfiguration() Configuration {
|
|
return Configuration{
|
|
ExporterClassifiers: []ExporterClassifierRule{},
|
|
InterfaceClassifiers: []InterfaceClassifierRule{},
|
|
ClassifierCacheDuration: 5 * time.Minute,
|
|
ASNProviders: []ASNProvider{ASNProviderFlow, ASNProviderRouting, ASNProviderGeoIP},
|
|
NetProviders: []NetProvider{NetProviderFlow, NetProviderRouting},
|
|
}
|
|
}
|
|
|
|
type (
|
|
// ASNProvider describes one AS number provider.
|
|
ASNProvider int
|
|
// NetProvider describes one network mask provider.
|
|
NetProvider int
|
|
)
|
|
|
|
const (
|
|
// ASNProviderFlow uses the AS number embedded in flows.
|
|
ASNProviderFlow ASNProvider = iota
|
|
// ASNProviderFlowExceptPrivate uses the AS number embedded in flows, except if this is a private AS.
|
|
ASNProviderFlowExceptPrivate
|
|
// ASNProviderGeoIP pulls the AS number from a GeoIP database.
|
|
ASNProviderGeoIP
|
|
// ASNProviderRouting uses the AS number from BMP
|
|
ASNProviderRouting
|
|
// ASNProviderRoutingExceptPrivate uses the AS number from BMP, except if this is a private AS.
|
|
ASNProviderRoutingExceptPrivate
|
|
)
|
|
|
|
const (
|
|
// NetProviderFlow uses the network mask embedded in flows, if any
|
|
NetProviderFlow NetProvider = iota
|
|
// NetProviderRouting uses looks the netmask up with BMP
|
|
NetProviderRouting
|
|
)
|
|
|
|
// ASNProviderUnmarshallerHook normalize a net provider configuration:
|
|
// - map bmp to routing
|
|
func ASNProviderUnmarshallerHook() mapstructure.DecodeHookFunc {
|
|
return func(from, to reflect.Value) (any, error) {
|
|
if from.Kind() != reflect.String || to.Type() != reflect.TypeOf(ASNProvider(0)) {
|
|
return from.Interface(), nil
|
|
}
|
|
if strings.ToLower(from.String()) == "bmp" {
|
|
return "routing", nil
|
|
}
|
|
if strings.ToLower(from.String()) == "bmp-except-private" {
|
|
return "routing-except-private", nil
|
|
}
|
|
return from.Interface(), nil
|
|
}
|
|
}
|
|
|
|
// NetProviderUnmarshallerHook normalize a net provider configuration:
|
|
// - map bmp to routing
|
|
func NetProviderUnmarshallerHook() mapstructure.DecodeHookFunc {
|
|
return func(from, to reflect.Value) (any, error) {
|
|
if from.Kind() != reflect.String || to.Type() != reflect.TypeOf(NetProvider(0)) {
|
|
return from.Interface(), nil
|
|
}
|
|
if strings.ToLower(from.String()) == "bmp" {
|
|
return "routing", nil
|
|
}
|
|
return from.Interface(), nil
|
|
}
|
|
}
|
|
|
|
// ConfigurationUnmarshallerHook normalize core configuration:
|
|
// - replace ignore-asn-from-flow by asn-providers
|
|
func ConfigurationUnmarshallerHook() mapstructure.DecodeHookFunc {
|
|
return func(from, to reflect.Value) (any, error) {
|
|
if from.Kind() != reflect.Map || from.IsNil() || to.Type() != reflect.TypeOf(Configuration{}) {
|
|
return from.Interface(), nil
|
|
}
|
|
|
|
// ignore-asn-from-flow → asn-providers
|
|
var oldKey, newKey *reflect.Value
|
|
fromMap := from.MapKeys()
|
|
for i, k := range fromMap {
|
|
k = helpers.ElemOrIdentity(k)
|
|
if k.Kind() != reflect.String {
|
|
return from.Interface(), nil
|
|
}
|
|
if helpers.MapStructureMatchName(k.String(), "IgnoreASNFromFlow") {
|
|
oldKey = &fromMap[i]
|
|
} else if helpers.MapStructureMatchName(k.String(), "ASNProviders") {
|
|
newKey = &fromMap[i]
|
|
}
|
|
}
|
|
if oldKey != nil && newKey != nil {
|
|
return nil, fmt.Errorf("cannot have both %q and %q", oldKey.String(), newKey.String())
|
|
}
|
|
if oldKey != nil {
|
|
oldValue := helpers.ElemOrIdentity(from.MapIndex(*oldKey))
|
|
if oldValue.Kind() == reflect.Bool && oldValue.Bool() {
|
|
newASNProviders := []ASNProvider{}
|
|
for _, p := range DefaultConfiguration().ASNProviders {
|
|
if p != ASNProviderFlow && p != ASNProviderFlowExceptPrivate {
|
|
newASNProviders = append(newASNProviders, p)
|
|
}
|
|
}
|
|
from.SetMapIndex(reflect.ValueOf("asn-providers"), reflect.ValueOf(newASNProviders))
|
|
}
|
|
from.SetMapIndex(*oldKey, reflect.Value{})
|
|
}
|
|
|
|
return from.Interface(), nil
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
helpers.RegisterMapstructureUnmarshallerHook(ConfigurationUnmarshallerHook())
|
|
helpers.RegisterMapstructureUnmarshallerHook(ASNProviderUnmarshallerHook())
|
|
helpers.RegisterMapstructureUnmarshallerHook(NetProviderUnmarshallerHook())
|
|
helpers.RegisterMapstructureUnmarshallerHook(helpers.SubnetMapUnmarshallerHook[uint]())
|
|
helpers.RegisterMapstructureDeprecatedFields[Configuration]("Workers", "ClassifierCacheSize")
|
|
}
|