Merge branch 'develop' of ssh://github.com/alice-lg/alice-lg into develop
This commit is contained in:
commit
cd8fdfa610
@ -14,9 +14,9 @@ enable_prefix_lookup = true
|
||||
# Try to refresh the neighbor status on every request to /neighbors
|
||||
enable_neighbors_status_refresh = false
|
||||
|
||||
# this ASN is used as a fallback value in the RPKI feature and for route
|
||||
# filtering evaluation with large BGP communities
|
||||
asn = 9033
|
||||
# This default ASN is used as a fallback value in the RPKI feature.
|
||||
# Setting it is optional.
|
||||
asn = 9999
|
||||
|
||||
# Use an alternative store backend. The default is `memory`.
|
||||
# store_backend = postgres
|
||||
@ -36,9 +36,9 @@ neighbors_store_refresh_interval = 5
|
||||
# Add a delay to the stream parser in order to reduce
|
||||
# CPU load while ingesting routes. Route refreshs will take
|
||||
# a bit longer. The value is in nanoseconds.
|
||||
# A value of 30000 will keep the cpu load at roughly 60% and
|
||||
# A value of 10000 will keep the cpu load at roughly 70% and
|
||||
# parsing a master4 table will take about 2.5 instead of 1.25 minutes.
|
||||
stream_parser_throttle = 30000
|
||||
stream_parser_throttle = 10000
|
||||
|
||||
# [postgres]
|
||||
# url = "postgres://postgres:postgres@localhost:5432/alice"
|
||||
|
@ -5,6 +5,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
@ -80,7 +81,7 @@ type ServerConfig struct {
|
||||
RoutesStoreRefreshInterval int `ini:"routes_store_refresh_interval"`
|
||||
RoutesStoreRefreshParallelism int `ini:"routes_store_refresh_parallelism"`
|
||||
StoreBackend string `ini:"store_backend"`
|
||||
Asn int `ini:"asn"`
|
||||
DefaultAsn int `ini:"asn"`
|
||||
EnableNeighborsStatusRefresh bool `ini:"enable_neighbors_status_refresh"`
|
||||
StreamParserThrottle int `ini:"stream_parser_throttle"`
|
||||
}
|
||||
@ -125,11 +126,11 @@ type RejectCandidatesConfig struct {
|
||||
// validation state.
|
||||
type RpkiConfig struct {
|
||||
// Define communities
|
||||
Enabled bool `ini:"enabled"`
|
||||
Valid []string `ini:"valid"`
|
||||
Unknown []string `ini:"unknown"`
|
||||
NotChecked []string `ini:"not_checked"`
|
||||
Invalid []string `ini:"invalid"`
|
||||
Enabled bool `ini:"enabled"`
|
||||
Valid [][]string `ini:"valid"`
|
||||
Unknown [][]string `ini:"unknown"`
|
||||
NotChecked [][]string `ini:"not_checked"`
|
||||
Invalid [][]string `ini:"invalid"`
|
||||
}
|
||||
|
||||
// UIConfig holds runtime settings for the web client
|
||||
@ -485,51 +486,88 @@ func getRpkiConfig(config *ini.File) (RpkiConfig, error) {
|
||||
// Defaults taken from:
|
||||
// https://www.euro-ix.net/en/forixps/large-bgp-communities/
|
||||
section := config.Section("rpki")
|
||||
lines := strings.Split(section.Body(), "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
l := strings.TrimSpace(line)
|
||||
if !strings.Contains(l, "=") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(l, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return rpki, fmt.Errorf("invalid rpki config line: %s", line)
|
||||
}
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.Split(strings.TrimSpace(parts[1]), ":")
|
||||
|
||||
if key == "enabled" {
|
||||
rpki.Enabled = parts[1] == "true"
|
||||
} else if key == "valid" {
|
||||
rpki.Valid = append(rpki.Valid, value)
|
||||
} else if key == "not_checked" {
|
||||
rpki.NotChecked = append(rpki.NotChecked, value)
|
||||
} else if key == "invalid" {
|
||||
rpki.Invalid = append(rpki.Invalid, value)
|
||||
} else if key == "unknown" {
|
||||
rpki.Unknown = append(rpki.Unknown, value)
|
||||
} else {
|
||||
return rpki, fmt.Errorf("invalid rpki config line: %s", line)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err := section.MapTo(&rpki); err != nil {
|
||||
return rpki, err
|
||||
}
|
||||
|
||||
fallbackAsn, err := getOwnASN(config)
|
||||
hasDefaultASN := true
|
||||
asn, err := getDefaultASN(config)
|
||||
if err != nil {
|
||||
log.Println(
|
||||
"Own ASN is not configured.",
|
||||
"This might lead to unexpected behaviour with BGP large communities",
|
||||
)
|
||||
hasDefaultASN = false
|
||||
}
|
||||
ownAsn := fmt.Sprintf("%d", fallbackAsn)
|
||||
|
||||
// Fill in defaults or postprocess config value
|
||||
if len(rpki.Valid) == 0 {
|
||||
rpki.Valid = []string{ownAsn, "1000", "1"}
|
||||
} else {
|
||||
rpki.Valid = strings.SplitN(rpki.Valid[0], ":", 3)
|
||||
if len(rpki.Valid) == 0 && !hasDefaultASN && rpki.Enabled {
|
||||
return rpki, fmt.Errorf(
|
||||
"rpki.valid must be set if no server.asn is configured")
|
||||
}
|
||||
if len(rpki.Valid) == 0 && rpki.Enabled {
|
||||
log.Printf("Using default rpki.valid: %s:1000:1\n", asn)
|
||||
rpki.Valid = [][]string{{asn, "1000", "1"}}
|
||||
}
|
||||
|
||||
if len(rpki.Unknown) == 0 {
|
||||
rpki.Unknown = []string{ownAsn, "1000", "2"}
|
||||
} else {
|
||||
rpki.Unknown = strings.SplitN(rpki.Unknown[0], ":", 3)
|
||||
if len(rpki.Unknown) == 0 && !hasDefaultASN && rpki.Enabled {
|
||||
return rpki, fmt.Errorf(
|
||||
"rpki.unknown must be set if no server.asn is configured")
|
||||
}
|
||||
if len(rpki.Unknown) == 0 && rpki.Enabled {
|
||||
log.Printf("Using default rpki.unknown: %s:1000:2\n", asn)
|
||||
rpki.Unknown = [][]string{{asn, "1000", "2"}}
|
||||
}
|
||||
|
||||
if len(rpki.NotChecked) == 0 && !hasDefaultASN && rpki.Enabled {
|
||||
return rpki, fmt.Errorf(
|
||||
"rpki.not_checked must be set if no server.asn is set")
|
||||
}
|
||||
if len(rpki.NotChecked) == 0 {
|
||||
rpki.NotChecked = []string{ownAsn, "1000", "3"}
|
||||
} else {
|
||||
rpki.NotChecked = strings.SplitN(rpki.NotChecked[0], ":", 3)
|
||||
log.Printf("Using default rpki.not_checked: %s:1000:3\n", asn)
|
||||
rpki.NotChecked = [][]string{{asn, "1000", "3"}}
|
||||
}
|
||||
|
||||
// As the euro-ix document states, this can be a range.
|
||||
if len(rpki.Invalid) == 0 {
|
||||
rpki.Invalid = []string{ownAsn, "1000", "4", "*"}
|
||||
} else {
|
||||
// Preprocess
|
||||
rpki.Invalid = strings.SplitN(rpki.Invalid[0], ":", 3)
|
||||
if len(rpki.Invalid) != 3 {
|
||||
// This is wrong, we should have three parts (RS):1000:[range]
|
||||
return rpki, fmt.Errorf(
|
||||
"unexpected rpki.Invalid configuration: %v", rpki.Invalid)
|
||||
for i, com := range rpki.Invalid {
|
||||
if len(com) != 3 {
|
||||
return rpki, fmt.Errorf("Invalid rpki.invalid config: %v", com)
|
||||
}
|
||||
tokens := strings.Split(rpki.Invalid[2], "-")
|
||||
rpki.Invalid = append([]string{rpki.Invalid[0], rpki.Invalid[1]}, tokens...)
|
||||
tokens := strings.Split(com[2], "-")
|
||||
rpki.Invalid[i] = append([]string{com[0], com[1]}, tokens...)
|
||||
}
|
||||
if len(rpki.Invalid) == 0 && !hasDefaultASN && rpki.Enabled {
|
||||
return rpki, fmt.Errorf(
|
||||
"rpki.invalid must be set if no server.asn is configured")
|
||||
}
|
||||
if len(rpki.Invalid) == 0 && rpki.Enabled {
|
||||
log.Printf("Using default rpki.invalid: %s:1000:4-*\n", asn)
|
||||
rpki.Invalid = [][]string{{asn, "1000", "4", "*"}}
|
||||
}
|
||||
|
||||
return rpki, nil
|
||||
@ -538,12 +576,12 @@ func getRpkiConfig(config *ini.File) (RpkiConfig, error) {
|
||||
// Helper: Get own ASN from ini
|
||||
// This is now easy, since we enforce an ASN in
|
||||
// the [server] section.
|
||||
func getOwnASN(config *ini.File) (int, error) {
|
||||
func getDefaultASN(config *ini.File) (string, error) {
|
||||
server := config.Section("server")
|
||||
asn := server.Key("asn").MustInt(-1)
|
||||
asn := server.Key("asn").MustString("")
|
||||
|
||||
if asn == -1 {
|
||||
return 0, fmt.Errorf("could not get own ASN from config")
|
||||
if asn == "" {
|
||||
return "", fmt.Errorf("could not get default ASN from config")
|
||||
}
|
||||
|
||||
return asn, nil
|
||||
@ -822,15 +860,52 @@ func getSources(config *ini.File) ([]*SourceConfig, error) {
|
||||
return sources, nil
|
||||
}
|
||||
|
||||
// preprocessConfig parses the variables in the config
|
||||
// and applies it to the rest of the config.
|
||||
func preprocessConfig(data []byte) []byte {
|
||||
lines := bytes.Split(data, []byte("\n"))
|
||||
config := make([][]byte, 0, len(lines))
|
||||
|
||||
expMap := ExpandMap{}
|
||||
for _, line := range lines {
|
||||
l := strings.TrimSpace(string(line))
|
||||
if strings.HasPrefix(l, "$") {
|
||||
expMap.AddExpr(l[1:])
|
||||
continue
|
||||
}
|
||||
config = append(config, line)
|
||||
}
|
||||
|
||||
// Now apply to config
|
||||
configLines := []string{}
|
||||
for _, line := range config {
|
||||
l := string(line)
|
||||
exp, err := expMap.Expand(l)
|
||||
if err != nil {
|
||||
log.Fatal("Error expanding expression in config:", l, err)
|
||||
}
|
||||
for _, e := range exp {
|
||||
configLines = append(configLines, e)
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(configLines, "\n"))
|
||||
}
|
||||
|
||||
// LoadConfig reads a configuration from a file.
|
||||
func LoadConfig(file string) (*Config, error) {
|
||||
|
||||
// Try to get config file, fallback to alternatives
|
||||
file, err := getConfigFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read the config file and preprocess it
|
||||
configData, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configData = preprocessConfig(configData)
|
||||
|
||||
// Load configuration, but handle bgp communities section
|
||||
// with our own parser
|
||||
parsedConfig, err := ini.LoadSources(ini.LoadOptions{
|
||||
@ -839,8 +914,9 @@ func LoadConfig(file string) (*Config, error) {
|
||||
"blackhole_communities",
|
||||
"rejection_reasons",
|
||||
"noexport_reasons",
|
||||
"rpki",
|
||||
},
|
||||
}, file)
|
||||
}, configData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -167,32 +167,21 @@ func TestBlackholeParsing(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOwnASN(t *testing.T) {
|
||||
config, err := LoadConfig("testdata/alice.conf")
|
||||
if err != nil {
|
||||
t.Fatal("Could not load test config:", err)
|
||||
}
|
||||
|
||||
if config.Server.Asn != 9033 {
|
||||
t.Error("Expected a set server asn")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRpkiConfig(t *testing.T) {
|
||||
config, err := LoadConfig("testdata/alice.conf")
|
||||
if err != nil {
|
||||
t.Fatal("Could not load test config:", err)
|
||||
}
|
||||
|
||||
if len(config.UI.Rpki.Valid) != 3 {
|
||||
if len(config.UI.Rpki.Valid[0]) != 3 {
|
||||
t.Error("Unexpected RPKI:VALID,", config.UI.Rpki.Valid)
|
||||
}
|
||||
if len(config.UI.Rpki.Invalid) != 4 {
|
||||
if len(config.UI.Rpki.Invalid[0]) != 4 {
|
||||
t.Fatal("Unexpected RPKI:INVALID,", config.UI.Rpki.Invalid)
|
||||
}
|
||||
|
||||
// Check fallback
|
||||
if config.UI.Rpki.NotChecked[0] != "9033" {
|
||||
if config.UI.Rpki.NotChecked[0][0] != "9999" {
|
||||
t.Error(
|
||||
"Expected NotChecked to fall back to defaults, got:",
|
||||
config.UI.Rpki.NotChecked,
|
||||
@ -200,7 +189,7 @@ func TestRpkiConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check range postprocessing
|
||||
if config.UI.Rpki.Invalid[3] != "*" {
|
||||
if config.UI.Rpki.Invalid[0][3] != "*" {
|
||||
t.Error("Missing wildcard from config")
|
||||
}
|
||||
|
||||
|
187
pkg/config/expand.go
Normal file
187
pkg/config/expand.go
Normal file
@ -0,0 +1,187 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Compile input pattern regex
|
||||
var (
|
||||
expandMatchPlaceholder = regexp.MustCompile(`(?U:{.*}+?)`)
|
||||
expandMatchWildcardShorthard = regexp.MustCompile(`(?U:{{.*\*}}+?)`)
|
||||
)
|
||||
|
||||
// Extract all matches from the input string.
|
||||
// The pattern to find is {INPUT}. The input string
|
||||
// itself can contain new matches.
|
||||
func expandFindPlaceholders(s string) []string {
|
||||
|
||||
// Find all matches
|
||||
results := expandMatchPlaceholder.FindAllString(s, -1)
|
||||
if len(results) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
matches := []string{}
|
||||
for _, result := range results {
|
||||
key := expandGetKey(result)
|
||||
subP := expandFindPlaceholders(key)
|
||||
matches = append(matches, result)
|
||||
matches = append(matches, subP...)
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
// Extract the key from the placeholder
|
||||
func expandGetKey(s string) string {
|
||||
// Strip the enclosing curly braces
|
||||
s = strings.TrimPrefix(s, "{")
|
||||
s = strings.TrimSuffix(s, "}")
|
||||
return s
|
||||
}
|
||||
|
||||
// ExpandMap holds the current state of variables
|
||||
type ExpandMap map[string]string
|
||||
|
||||
// Retrieve a set of matching variables, by iterating variables.
|
||||
// Whenever a key matches the wildcard, the prefix is removed.
|
||||
// Example:
|
||||
// pattern = "AS*", key = "AS2342", value = "2342"
|
||||
func (e ExpandMap) matchWildcard(pattern string) []string {
|
||||
matches := []string{}
|
||||
|
||||
// Strip the wildcard from the pattern.
|
||||
pattern = strings.TrimSuffix(pattern, "*")
|
||||
|
||||
// Iterate variables and add match to result set
|
||||
for k, _ := range e {
|
||||
if strings.HasPrefix(k, pattern) {
|
||||
key := strings.TrimPrefix(k, pattern)
|
||||
matches = append(matches, key)
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
// Get all substitutions for a given key.
|
||||
// This method will return an error, if a placeholder
|
||||
// does not match.
|
||||
func (e ExpandMap) getSubstitutions(key string) []string {
|
||||
// Check if the placeholder is a wildcard
|
||||
if strings.HasSuffix(key, "*") {
|
||||
return e.matchWildcard(key)
|
||||
}
|
||||
|
||||
// Check if the placeholder is direct match
|
||||
if val, ok := e[key]; ok {
|
||||
return []string{val}
|
||||
}
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Get placeholder level. This is the number of opening
|
||||
// curly braces in the placeholder.
|
||||
func expandGetLevel(s string) int {
|
||||
level := 0
|
||||
for _, c := range s {
|
||||
if c == '{' {
|
||||
level++
|
||||
}
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
// Preprocess input string and resolve syntactic sugar.
|
||||
// Replace {{VAR}} with {VAR{VAR}} to make it easier
|
||||
// to access the wildcard value.
|
||||
func expandPreprocess(s string) string {
|
||||
// Find all access shorthands and replace them
|
||||
// with the full syntax
|
||||
results := expandMatchWildcardShorthard.FindAllString(s, -1)
|
||||
for _, match := range results {
|
||||
// Wildcard {{KEY*}} -> KEY
|
||||
key := match[2 : len(match)-3]
|
||||
expr := fmt.Sprintf("{%s{%s*}}", key, key)
|
||||
s = strings.Replace(s, match, expr, -1)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Expand variables by recursive substitution and expansion
|
||||
func (e ExpandMap) Expand(s string) ([]string, error) {
|
||||
// Preprocess syntactic sugar: replace {{VAR}}
|
||||
// with {VAR{VAR}}
|
||||
s = expandPreprocess(s)
|
||||
|
||||
// Find all placeholders and substitute them
|
||||
placeholders := expandFindPlaceholders(s)
|
||||
if len(placeholders) == 0 {
|
||||
return []string{s}, nil
|
||||
}
|
||||
|
||||
// Find substitutions for each placeholder
|
||||
substitutions := map[string][]string{}
|
||||
for _, p := range placeholders {
|
||||
key := expandGetKey(p)
|
||||
subs := e.getSubstitutions(key)
|
||||
if len(subs) == 0 {
|
||||
level := expandGetLevel(p)
|
||||
if level == 1 {
|
||||
err := fmt.Errorf("No substitution for %s in '%s'", p, s)
|
||||
return []string{}, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
substitutions[p] = subs
|
||||
}
|
||||
|
||||
// Apply substitutions
|
||||
subsRes := []string{s}
|
||||
for p, subs := range substitutions {
|
||||
subsExp := []string{}
|
||||
for _, s := range subsRes {
|
||||
for _, sub := range subs {
|
||||
res := strings.Replace(s, p, sub, -1)
|
||||
subsExp = append(subsExp, res)
|
||||
}
|
||||
}
|
||||
subsRes = subsExp
|
||||
}
|
||||
|
||||
// Expand recursively
|
||||
results := []string{}
|
||||
for _, s := range subsRes {
|
||||
res, err := e.Expand(s)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
results = append(results, res...)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Add a new variable to the map. Key and value are
|
||||
// expanded.
|
||||
func (e ExpandMap) AddExpr(expr string) error {
|
||||
// Expand expression
|
||||
res, err := e.Expand(expr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, exp := range res {
|
||||
// Split key and value
|
||||
parts := strings.SplitN(exp, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("Invalid expression '%s'", expr)
|
||||
}
|
||||
key := strings.TrimSpace(parts[0])
|
||||
val := strings.TrimSpace(parts[1])
|
||||
e[key] = val
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
122
pkg/config/expand_test.go
Normal file
122
pkg/config/expand_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Text variable pattern matching
|
||||
func TestExpandMatch(t *testing.T) {
|
||||
exp := ExpandMap{
|
||||
"AS2342": "",
|
||||
"AS1111": "",
|
||||
"FOOBAR": "foo",
|
||||
}
|
||||
|
||||
matches := exp.matchWildcard("AS*")
|
||||
if len(matches) != 2 {
|
||||
t.Errorf("Expected 2 matches, got %d", len(matches))
|
||||
}
|
||||
|
||||
for _, m := range matches {
|
||||
t.Log("Match wildcard:", m)
|
||||
}
|
||||
}
|
||||
|
||||
// Test variable expansion / substitution
|
||||
func TestFindPlaceholders(t *testing.T) {
|
||||
s := "{FOO} BAR {AS{AS*}}"
|
||||
placeholders := expandFindPlaceholders(s)
|
||||
if len(placeholders) != 3 {
|
||||
t.Errorf("Expected 3 placeholders, got %d", len(placeholders))
|
||||
}
|
||||
t.Log(placeholders)
|
||||
}
|
||||
|
||||
// Test variable expansion / substitution
|
||||
func TestExpand(t *testing.T) {
|
||||
s := "{FOO} BAR {AS{AS*}} AS {AS*}"
|
||||
exp := ExpandMap{
|
||||
"AS2342": "AS2342",
|
||||
"AS1111": "AS1111",
|
||||
"FOO": "foo",
|
||||
}
|
||||
|
||||
results, err := exp.Expand(s)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(results)
|
||||
}
|
||||
|
||||
func TestExpandErr(t *testing.T) {
|
||||
s := "{FOO} BAR {AS{AS*}} AS {AS*} {UNKNOWN}"
|
||||
exp := ExpandMap{
|
||||
"AS2342": "AS2342",
|
||||
"AS1111": "AS1111",
|
||||
"FOO": "foo",
|
||||
"FN": "fn",
|
||||
"FA": "fa",
|
||||
}
|
||||
|
||||
_, err := exp.Expand(s)
|
||||
t.Log(err)
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandPreprocess(t *testing.T) {
|
||||
s := "FOO {FOO} {{AS*}} {F*} {{F*}} {X{X*}}"
|
||||
expect := "FOO {FOO} {AS{AS*}} {F*} {F{F*}} {X{X*}}"
|
||||
s = expandPreprocess(s)
|
||||
if s != expect {
|
||||
t.Errorf("Expected '%s', got '%s'", expect, s)
|
||||
}
|
||||
t.Log(s)
|
||||
|
||||
s = "TEST {{FN}}"
|
||||
s = expandPreprocess(s)
|
||||
t.Log(s)
|
||||
|
||||
}
|
||||
|
||||
func TestExpandAddExpr(t *testing.T) {
|
||||
e := ExpandMap{
|
||||
"FOO": "foo23",
|
||||
"BAR": "bar42",
|
||||
"bar42": "BAM",
|
||||
}
|
||||
|
||||
if err := e.AddExpr("FOOBAR = {FOO}{BAR}{{BAR}}"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(e)
|
||||
|
||||
if e["FOOBAR"] != "foo23bar42BAM" {
|
||||
t.Error("Expected 'foo23bar42BAM', got", e["FOOBAR"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandBgpCommunities(t *testing.T) {
|
||||
e := ExpandMap{
|
||||
"ASRS01": "6695",
|
||||
"ASRS02": "4617",
|
||||
"SW1001": "edge01.fra2",
|
||||
"SW1002": "edge01.fra6",
|
||||
"SW2038": "edge01.nyc1",
|
||||
"RDCTL911": "Redistribute",
|
||||
"RDCTL922": "Do not redistribute",
|
||||
}
|
||||
|
||||
// Some large communities:
|
||||
expr := "{{AS*}}:{RDCTL*}:{SW*} = {{RDCTL*}} to {{SW*}}"
|
||||
exp, err := e.Expand(expr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := 2 * 3 * 2
|
||||
if len(exp) != expected {
|
||||
t.Errorf("Expected %d results, got %d", expected, len(exp))
|
||||
}
|
||||
t.Log(exp)
|
||||
}
|
52
pkg/config/testdata/alice.conf
vendored
52
pkg/config/testdata/alice.conf
vendored
@ -2,6 +2,13 @@
|
||||
# Alice-LG configuration example
|
||||
# ======================================
|
||||
|
||||
$ASN01 = 1111
|
||||
$ASN02 = 2222
|
||||
|
||||
$SW1001 = switch01.dc01
|
||||
$SW1002 = switch02.dc01
|
||||
$SW2023 = switch23.dc02
|
||||
|
||||
[server]
|
||||
# configures the built-in webserver and provides global application settings
|
||||
listen_http = 127.0.0.1:7340
|
||||
@ -9,7 +16,6 @@ listen_http = 127.0.0.1:7340
|
||||
enable_prefix_lookup = true
|
||||
# Try to refresh the neighbor status on every request to /neighbors
|
||||
enable_neighbors_status_refresh = false
|
||||
asn = 9033
|
||||
# this ASN is used as a fallback value in the RPKI feature and for route
|
||||
# filtering evaluation with large BGP communities
|
||||
|
||||
@ -29,6 +35,8 @@ stream_parser_throttle = 2342
|
||||
|
||||
store_backend = postgres
|
||||
|
||||
asn = 9999
|
||||
|
||||
[postgres]
|
||||
url = "postgres://postgres:postgres@localhost:5432/alice"
|
||||
min_connections = 10
|
||||
@ -55,16 +63,16 @@ routes_not_exported_page_size = 250
|
||||
[rejection_reasons]
|
||||
# a pair of a large BGP community value and a string to signal the processing
|
||||
# results of route filtering
|
||||
9033:65666:1 = An IP Bogon was detected
|
||||
9033:65666:2 = Prefix is longer than 64
|
||||
9033:65666:3 = Prefix is longer than 24
|
||||
9033:65666:4 = AS path contains a bogon AS
|
||||
9033:65666:5 = AS path length is longer than 64
|
||||
9033:65666:6 = First AS in path is not the same as the Peer AS
|
||||
9033:65666:7 = ECIX prefix hijack
|
||||
9033:65666:8 = Origin AS not found in IRRDB for Peer AS-SET
|
||||
9033:65666:9 = Prefix not found in IRRDB for Origin AS
|
||||
9033:65666:10 = Advertised nexthop address is not the same as the peer
|
||||
{{ASN*}}:65666:1 = An IP Bogon was detected
|
||||
{{ASN*}}:65666:2 = Prefix is longer than 64
|
||||
{{ASN*}}:65666:3 = Prefix is longer than 24
|
||||
{{ASN*}}:65666:4 = AS path contains a bogon AS
|
||||
{{ASN*}}:65666:5 = AS path length is longer than 64
|
||||
{{ASN*}}:65666:6 = First AS in path is not the same as the Peer AS
|
||||
{{ASN*}}:65666:7 = ECIX prefix hijack
|
||||
{{ASN*}}:65666:8 = Origin AS not found in IRRDB for Peer AS-SET
|
||||
{{ASN*}}:65666:9 = Prefix not found in IRRDB for Origin AS
|
||||
{{ASN*}}:65666:10 = Advertised nexthop address is not the same as the peer
|
||||
|
||||
23:42:1 = Some made up reason
|
||||
|
||||
@ -80,13 +88,13 @@ load_on_demand = true # Default: false
|
||||
[noexport_reasons]
|
||||
# a pair of a large BGP community value and a string to signal the processing
|
||||
# results of route distribution and the distribution policy applied to a route
|
||||
9033:65667:1 = The target peer policy is Fairly-open and the sender ASN is an exception
|
||||
9033:65667:2 = The target peer policy is Selective and the sender ASN is no exception
|
||||
9033:65667:3 = The target peer policy is set to restrictive
|
||||
9033:65667:4 = The sender has specifically refused export to the target peer, either through sending 65000:AS, or through the portal
|
||||
9033:65667:5 = The sender has refused export to all peers and the target is no exception, either through sending 65000:0, or through the portal
|
||||
9033:65667:6 = The Sender has set (peerRTTHigherDeny:ms) and the targets RTT ms >= then the ms in the community
|
||||
9033:65667:7 = The Sender has set (peerRTTLowerDeny:ms) and the targets RTT ms <= then the ms in the community
|
||||
{{ASN*}}:65667:1 = The target peer policy is Fairly-open and the sender ASN is an exception
|
||||
{{ASN*}}:65667:2 = The target peer policy is Selective and the sender ASN is no exception
|
||||
{{ASN*}}:65667:3 = The target peer policy is set to restrictive
|
||||
{{ASN*}}:65667:4 = The sender has specifically refused export to the target peer, either through sending 65000:AS, or through the portal
|
||||
{{ASN*}}:65667:5 = The sender has refused export to all peers and the target is no exception, either through sending 65000:0, or through the portal
|
||||
{{ASN*}}:65667:6 = The Sender has set (peerRTTHigherDeny:ms) and the targets RTT ms >= then the ms in the community
|
||||
{{ASN*}}:65667:7 = The Sender has set (peerRTTLowerDeny:ms) and the targets RTT ms <= then the ms in the community
|
||||
|
||||
23:46:1 = Some other made up reason
|
||||
|
||||
@ -102,10 +110,10 @@ enabled = true
|
||||
|
||||
# Optional, falling back to defaults as defined in:
|
||||
# https://www.euro-ix.net/en/forixps/large-bgp-communities/
|
||||
valid = 23042:1000:1
|
||||
unknown = 23042:1000:2
|
||||
valid = {{ASN*}}:1000:1
|
||||
unknown = {{ASN*}}:1000:2
|
||||
# not_checked = 23042:1000:3
|
||||
invalid = 23042:1000:4-*
|
||||
invalid = {{ASN*}}:1000:4-*
|
||||
|
||||
|
||||
# Define other known bgp communities
|
||||
@ -115,6 +123,8 @@ invalid = 23042:1000:4-*
|
||||
# Wildcards are supported aswell:
|
||||
0:* = do not redistribute to AS$1
|
||||
|
||||
{{ASN*}}:911:{SW*} = Redistribute to {{SW*}}
|
||||
|
||||
#
|
||||
# Define columns for neighbours and routes table,
|
||||
# with <key> = <Table Header>
|
||||
|
@ -27,11 +27,16 @@ const RpkiIndicator = ({route}) => {
|
||||
const rpkiInvalid = rpki.invalid;
|
||||
|
||||
const communities = route?.bgp?.large_communities || [];
|
||||
|
||||
const matchCommunity = (com, coms) =>
|
||||
coms.some((match) =>
|
||||
(com[0].toFixed() === match[0] &&
|
||||
com[1].toFixed() === match[1] &&
|
||||
com[2].toFixed() === match[2]));
|
||||
|
||||
for (const com of communities) {
|
||||
// RPKI VALID
|
||||
if (com[0].toFixed() === rpkiValid[0] &&
|
||||
com[1].toFixed() === rpkiValid[1] &&
|
||||
com[2].toFixed() === rpkiValid[2]) {
|
||||
if (matchCommunity(com, rpkiValid)) {
|
||||
return (
|
||||
<span className="route-prefix-flag rpki-route rpki-valid">
|
||||
<FlagIcon icon={faCircleCheck} tooltip="RPKI Valid" />
|
||||
@ -40,9 +45,7 @@ const RpkiIndicator = ({route}) => {
|
||||
}
|
||||
|
||||
// RPKI UNKNOWN
|
||||
if (com[0].toFixed() === rpkiUnknown[0] &&
|
||||
com[1].toFixed() === rpkiUnknown[1] &&
|
||||
com[2].toFixed() === rpkiUnknown[2]) {
|
||||
if (matchCommunity(com, rpkiUnknown)) {
|
||||
return (
|
||||
<span className="route-prefix-flag rpki-route rpki-unknown">
|
||||
<FlagIcon icon={faCircleQuestion} tooltip="RPKI Unknown" />
|
||||
@ -51,9 +54,7 @@ const RpkiIndicator = ({route}) => {
|
||||
}
|
||||
|
||||
// RPKI NOT CHECKED
|
||||
if (com[0].toFixed() === rpkiNotChecked[0] &&
|
||||
com[1].toFixed() === rpkiNotChecked[1] &&
|
||||
com[2].toFixed() === rpkiNotChecked[2]) {
|
||||
if (matchCommunity(com, rpkiNotChecked)) {
|
||||
return (
|
||||
<span className="route-prefix-flag rpki-route rpki-not-checked">
|
||||
<FlagIcon icon={faCircle} tooltip="RPKI Not Checked" />
|
||||
@ -65,20 +66,23 @@ const RpkiIndicator = ({route}) => {
|
||||
// Depending on the configration this can either be a
|
||||
// single flag or a range with a given reason.
|
||||
let rpkiInvalidReason = 0;
|
||||
if (com[0].toFixed() === rpkiInvalid[0] &&
|
||||
com[1].toFixed() === rpkiInvalid[1]) {
|
||||
for (const invalid of rpkiInvalid) {
|
||||
if (com[0].toFixed() === invalid[0] &&
|
||||
com[1].toFixed() === invalid[1]) {
|
||||
|
||||
// This needs to be considered invalid, now try to detect why
|
||||
if (rpkiInvalid.length > 3 && rpkiInvalid[3] === "*") {
|
||||
// Check if token falls within range
|
||||
const start = parseInt(rpkiInvalid[2], 10);
|
||||
if (com[2] >= start) {
|
||||
rpkiInvalidReason = com[2];
|
||||
}
|
||||
} else {
|
||||
if (com[2].toFixed() === rpkiInvalid[2]) {
|
||||
rpkiInvalidReason = 1;
|
||||
// This needs to be considered invalid, now try to detect why
|
||||
if (invalid.length > 3 && invalid[3] === "*") {
|
||||
// Check if token falls within range
|
||||
const start = parseInt(invalid[2], 10);
|
||||
if (com[2] >= start) {
|
||||
rpkiInvalidReason = com[2];
|
||||
}
|
||||
} else {
|
||||
if (com[2].toFixed() === invalid[2]) {
|
||||
rpkiInvalidReason = 1;
|
||||
}
|
||||
}
|
||||
break; // We found a match, stop searching
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,10 +10,10 @@ import RpkiIndicator
|
||||
const config = {
|
||||
rpki: {
|
||||
enabled: true,
|
||||
valid: ["1234", "1111", "1"],
|
||||
unknown: ["1234", "1111", "0"],
|
||||
not_checked: ["1234", "1111", "10"],
|
||||
invalid: ["1234", "1111", "100"],
|
||||
valid: [["1234", "1111", "1"]],
|
||||
unknown: [["1234", "1111", "0"]],
|
||||
not_checked: [["1234", "1111", "10"]],
|
||||
invalid: [["1234", "1111", "100"]],
|
||||
},
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user