Merge branch 'develop' of ssh://github.com/alice-lg/alice-lg into develop

This commit is contained in:
Annika Hannig 2023-11-24 15:30:07 +01:00
commit cd8fdfa610
8 changed files with 495 additions and 107 deletions

View File

@ -14,9 +14,9 @@ enable_prefix_lookup = true
# Try to refresh the neighbor status on every request to /neighbors # Try to refresh the neighbor status on every request to /neighbors
enable_neighbors_status_refresh = false enable_neighbors_status_refresh = false
# this ASN is used as a fallback value in the RPKI feature and for route # This default ASN is used as a fallback value in the RPKI feature.
# filtering evaluation with large BGP communities # Setting it is optional.
asn = 9033 asn = 9999
# Use an alternative store backend. The default is `memory`. # Use an alternative store backend. The default is `memory`.
# store_backend = postgres # store_backend = postgres
@ -36,9 +36,9 @@ neighbors_store_refresh_interval = 5
# Add a delay to the stream parser in order to reduce # Add a delay to the stream parser in order to reduce
# CPU load while ingesting routes. Route refreshs will take # CPU load while ingesting routes. Route refreshs will take
# a bit longer. The value is in nanoseconds. # 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. # parsing a master4 table will take about 2.5 instead of 1.25 minutes.
stream_parser_throttle = 30000 stream_parser_throttle = 10000
# [postgres] # [postgres]
# url = "postgres://postgres:postgres@localhost:5432/alice" # url = "postgres://postgres:postgres@localhost:5432/alice"

View File

@ -5,6 +5,7 @@
package config package config
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"log" "log"
@ -80,7 +81,7 @@ type ServerConfig struct {
RoutesStoreRefreshInterval int `ini:"routes_store_refresh_interval"` RoutesStoreRefreshInterval int `ini:"routes_store_refresh_interval"`
RoutesStoreRefreshParallelism int `ini:"routes_store_refresh_parallelism"` RoutesStoreRefreshParallelism int `ini:"routes_store_refresh_parallelism"`
StoreBackend string `ini:"store_backend"` StoreBackend string `ini:"store_backend"`
Asn int `ini:"asn"` DefaultAsn int `ini:"asn"`
EnableNeighborsStatusRefresh bool `ini:"enable_neighbors_status_refresh"` EnableNeighborsStatusRefresh bool `ini:"enable_neighbors_status_refresh"`
StreamParserThrottle int `ini:"stream_parser_throttle"` StreamParserThrottle int `ini:"stream_parser_throttle"`
} }
@ -125,11 +126,11 @@ type RejectCandidatesConfig struct {
// validation state. // validation state.
type RpkiConfig struct { type RpkiConfig struct {
// Define communities // Define communities
Enabled bool `ini:"enabled"` Enabled bool `ini:"enabled"`
Valid []string `ini:"valid"` Valid [][]string `ini:"valid"`
Unknown []string `ini:"unknown"` Unknown [][]string `ini:"unknown"`
NotChecked []string `ini:"not_checked"` NotChecked [][]string `ini:"not_checked"`
Invalid []string `ini:"invalid"` Invalid [][]string `ini:"invalid"`
} }
// UIConfig holds runtime settings for the web client // UIConfig holds runtime settings for the web client
@ -485,51 +486,88 @@ func getRpkiConfig(config *ini.File) (RpkiConfig, error) {
// Defaults taken from: // Defaults taken from:
// https://www.euro-ix.net/en/forixps/large-bgp-communities/ // https://www.euro-ix.net/en/forixps/large-bgp-communities/
section := config.Section("rpki") 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 { if err := section.MapTo(&rpki); err != nil {
return rpki, err return rpki, err
} }
hasDefaultASN := true
fallbackAsn, err := getOwnASN(config) asn, err := getDefaultASN(config)
if err != nil { if err != nil {
log.Println( hasDefaultASN = false
"Own ASN is not configured.",
"This might lead to unexpected behaviour with BGP large communities",
)
} }
ownAsn := fmt.Sprintf("%d", fallbackAsn)
// Fill in defaults or postprocess config value // Fill in defaults or postprocess config value
if len(rpki.Valid) == 0 { if len(rpki.Valid) == 0 && !hasDefaultASN && rpki.Enabled {
rpki.Valid = []string{ownAsn, "1000", "1"} return rpki, fmt.Errorf(
} else { "rpki.valid must be set if no server.asn is configured")
rpki.Valid = strings.SplitN(rpki.Valid[0], ":", 3) }
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 { if len(rpki.Unknown) == 0 && !hasDefaultASN && rpki.Enabled {
rpki.Unknown = []string{ownAsn, "1000", "2"} return rpki, fmt.Errorf(
} else { "rpki.unknown must be set if no server.asn is configured")
rpki.Unknown = strings.SplitN(rpki.Unknown[0], ":", 3) }
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 { if len(rpki.NotChecked) == 0 {
rpki.NotChecked = []string{ownAsn, "1000", "3"} log.Printf("Using default rpki.not_checked: %s:1000:3\n", asn)
} else { rpki.NotChecked = [][]string{{asn, "1000", "3"}}
rpki.NotChecked = strings.SplitN(rpki.NotChecked[0], ":", 3)
} }
// As the euro-ix document states, this can be a range. // As the euro-ix document states, this can be a range.
if len(rpki.Invalid) == 0 { for i, com := range rpki.Invalid {
rpki.Invalid = []string{ownAsn, "1000", "4", "*"} if len(com) != 3 {
} else { return rpki, fmt.Errorf("Invalid rpki.invalid config: %v", com)
// 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)
} }
tokens := strings.Split(rpki.Invalid[2], "-") tokens := strings.Split(com[2], "-")
rpki.Invalid = append([]string{rpki.Invalid[0], rpki.Invalid[1]}, tokens...) 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 return rpki, nil
@ -538,12 +576,12 @@ func getRpkiConfig(config *ini.File) (RpkiConfig, error) {
// Helper: Get own ASN from ini // Helper: Get own ASN from ini
// This is now easy, since we enforce an ASN in // This is now easy, since we enforce an ASN in
// the [server] section. // the [server] section.
func getOwnASN(config *ini.File) (int, error) { func getDefaultASN(config *ini.File) (string, error) {
server := config.Section("server") server := config.Section("server")
asn := server.Key("asn").MustInt(-1) asn := server.Key("asn").MustString("")
if asn == -1 { if asn == "" {
return 0, fmt.Errorf("could not get own ASN from config") return "", fmt.Errorf("could not get default ASN from config")
} }
return asn, nil return asn, nil
@ -822,15 +860,52 @@ func getSources(config *ini.File) ([]*SourceConfig, error) {
return sources, nil 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. // LoadConfig reads a configuration from a file.
func LoadConfig(file string) (*Config, error) { func LoadConfig(file string) (*Config, error) {
// Try to get config file, fallback to alternatives // Try to get config file, fallback to alternatives
file, err := getConfigFile(file) file, err := getConfigFile(file)
if err != nil { if err != nil {
return nil, err 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 // Load configuration, but handle bgp communities section
// with our own parser // with our own parser
parsedConfig, err := ini.LoadSources(ini.LoadOptions{ parsedConfig, err := ini.LoadSources(ini.LoadOptions{
@ -839,8 +914,9 @@ func LoadConfig(file string) (*Config, error) {
"blackhole_communities", "blackhole_communities",
"rejection_reasons", "rejection_reasons",
"noexport_reasons", "noexport_reasons",
"rpki",
}, },
}, file) }, configData)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -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) { func TestRpkiConfig(t *testing.T) {
config, err := LoadConfig("testdata/alice.conf") config, err := LoadConfig("testdata/alice.conf")
if err != nil { if err != nil {
t.Fatal("Could not load test config:", err) 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) 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) t.Fatal("Unexpected RPKI:INVALID,", config.UI.Rpki.Invalid)
} }
// Check fallback // Check fallback
if config.UI.Rpki.NotChecked[0] != "9033" { if config.UI.Rpki.NotChecked[0][0] != "9999" {
t.Error( t.Error(
"Expected NotChecked to fall back to defaults, got:", "Expected NotChecked to fall back to defaults, got:",
config.UI.Rpki.NotChecked, config.UI.Rpki.NotChecked,
@ -200,7 +189,7 @@ func TestRpkiConfig(t *testing.T) {
} }
// Check range postprocessing // Check range postprocessing
if config.UI.Rpki.Invalid[3] != "*" { if config.UI.Rpki.Invalid[0][3] != "*" {
t.Error("Missing wildcard from config") t.Error("Missing wildcard from config")
} }

187
pkg/config/expand.go Normal file
View 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
View 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)
}

View File

@ -2,6 +2,13 @@
# Alice-LG configuration example # Alice-LG configuration example
# ====================================== # ======================================
$ASN01 = 1111
$ASN02 = 2222
$SW1001 = switch01.dc01
$SW1002 = switch02.dc01
$SW2023 = switch23.dc02
[server] [server]
# configures the built-in webserver and provides global application settings # configures the built-in webserver and provides global application settings
listen_http = 127.0.0.1:7340 listen_http = 127.0.0.1:7340
@ -9,7 +16,6 @@ listen_http = 127.0.0.1:7340
enable_prefix_lookup = true enable_prefix_lookup = true
# Try to refresh the neighbor status on every request to /neighbors # Try to refresh the neighbor status on every request to /neighbors
enable_neighbors_status_refresh = false enable_neighbors_status_refresh = false
asn = 9033
# this ASN is used as a fallback value in the RPKI feature and for route # this ASN is used as a fallback value in the RPKI feature and for route
# filtering evaluation with large BGP communities # filtering evaluation with large BGP communities
@ -29,6 +35,8 @@ stream_parser_throttle = 2342
store_backend = postgres store_backend = postgres
asn = 9999
[postgres] [postgres]
url = "postgres://postgres:postgres@localhost:5432/alice" url = "postgres://postgres:postgres@localhost:5432/alice"
min_connections = 10 min_connections = 10
@ -55,16 +63,16 @@ routes_not_exported_page_size = 250
[rejection_reasons] [rejection_reasons]
# a pair of a large BGP community value and a string to signal the processing # a pair of a large BGP community value and a string to signal the processing
# results of route filtering # results of route filtering
9033:65666:1 = An IP Bogon was detected {{ASN*}}:65666:1 = An IP Bogon was detected
9033:65666:2 = Prefix is longer than 64 {{ASN*}}:65666:2 = Prefix is longer than 64
9033:65666:3 = Prefix is longer than 24 {{ASN*}}:65666:3 = Prefix is longer than 24
9033:65666:4 = AS path contains a bogon AS {{ASN*}}:65666:4 = AS path contains a bogon AS
9033:65666:5 = AS path length is longer than 64 {{ASN*}}:65666:5 = AS path length is longer than 64
9033:65666:6 = First AS in path is not the same as the Peer AS {{ASN*}}:65666:6 = First AS in path is not the same as the Peer AS
9033:65666:7 = ECIX prefix hijack {{ASN*}}:65666:7 = ECIX prefix hijack
9033:65666:8 = Origin AS not found in IRRDB for Peer AS-SET {{ASN*}}:65666:8 = Origin AS not found in IRRDB for Peer AS-SET
9033:65666:9 = Prefix not found in IRRDB for Origin AS {{ASN*}}: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:10 = Advertised nexthop address is not the same as the peer
23:42:1 = Some made up reason 23:42:1 = Some made up reason
@ -80,13 +88,13 @@ load_on_demand = true # Default: false
[noexport_reasons] [noexport_reasons]
# a pair of a large BGP community value and a string to signal the processing # 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 # 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 {{ASN*}}: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 {{ASN*}}: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 {{ASN*}}: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 {{ASN*}}: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 {{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
9033:65667:6 = The Sender has set (peerRTTHigherDeny:ms) and the targets RTT ms >= then the ms in the community {{ASN*}}: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: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 23:46:1 = Some other made up reason
@ -102,10 +110,10 @@ enabled = true
# Optional, falling back to defaults as defined in: # Optional, falling back to defaults as defined in:
# https://www.euro-ix.net/en/forixps/large-bgp-communities/ # https://www.euro-ix.net/en/forixps/large-bgp-communities/
valid = 23042:1000:1 valid = {{ASN*}}:1000:1
unknown = 23042:1000:2 unknown = {{ASN*}}:1000:2
# not_checked = 23042:1000:3 # not_checked = 23042:1000:3
invalid = 23042:1000:4-* invalid = {{ASN*}}:1000:4-*
# Define other known bgp communities # Define other known bgp communities
@ -115,6 +123,8 @@ invalid = 23042:1000:4-*
# Wildcards are supported aswell: # Wildcards are supported aswell:
0:* = do not redistribute to AS$1 0:* = do not redistribute to AS$1
{{ASN*}}:911:{SW*} = Redistribute to {{SW*}}
# #
# Define columns for neighbours and routes table, # Define columns for neighbours and routes table,
# with <key> = <Table Header> # with <key> = <Table Header>

View File

@ -27,11 +27,16 @@ const RpkiIndicator = ({route}) => {
const rpkiInvalid = rpki.invalid; const rpkiInvalid = rpki.invalid;
const communities = route?.bgp?.large_communities || []; 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) { for (const com of communities) {
// RPKI VALID // RPKI VALID
if (com[0].toFixed() === rpkiValid[0] && if (matchCommunity(com, rpkiValid)) {
com[1].toFixed() === rpkiValid[1] &&
com[2].toFixed() === rpkiValid[2]) {
return ( return (
<span className="route-prefix-flag rpki-route rpki-valid"> <span className="route-prefix-flag rpki-route rpki-valid">
<FlagIcon icon={faCircleCheck} tooltip="RPKI Valid" /> <FlagIcon icon={faCircleCheck} tooltip="RPKI Valid" />
@ -40,9 +45,7 @@ const RpkiIndicator = ({route}) => {
} }
// RPKI UNKNOWN // RPKI UNKNOWN
if (com[0].toFixed() === rpkiUnknown[0] && if (matchCommunity(com, rpkiUnknown)) {
com[1].toFixed() === rpkiUnknown[1] &&
com[2].toFixed() === rpkiUnknown[2]) {
return ( return (
<span className="route-prefix-flag rpki-route rpki-unknown"> <span className="route-prefix-flag rpki-route rpki-unknown">
<FlagIcon icon={faCircleQuestion} tooltip="RPKI Unknown" /> <FlagIcon icon={faCircleQuestion} tooltip="RPKI Unknown" />
@ -51,9 +54,7 @@ const RpkiIndicator = ({route}) => {
} }
// RPKI NOT CHECKED // RPKI NOT CHECKED
if (com[0].toFixed() === rpkiNotChecked[0] && if (matchCommunity(com, rpkiNotChecked)) {
com[1].toFixed() === rpkiNotChecked[1] &&
com[2].toFixed() === rpkiNotChecked[2]) {
return ( return (
<span className="route-prefix-flag rpki-route rpki-not-checked"> <span className="route-prefix-flag rpki-route rpki-not-checked">
<FlagIcon icon={faCircle} tooltip="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 // Depending on the configration this can either be a
// single flag or a range with a given reason. // single flag or a range with a given reason.
let rpkiInvalidReason = 0; let rpkiInvalidReason = 0;
if (com[0].toFixed() === rpkiInvalid[0] && for (const invalid of rpkiInvalid) {
com[1].toFixed() === rpkiInvalid[1]) { if (com[0].toFixed() === invalid[0] &&
com[1].toFixed() === invalid[1]) {
// This needs to be considered invalid, now try to detect why // This needs to be considered invalid, now try to detect why
if (rpkiInvalid.length > 3 && rpkiInvalid[3] === "*") { if (invalid.length > 3 && invalid[3] === "*") {
// Check if token falls within range // Check if token falls within range
const start = parseInt(rpkiInvalid[2], 10); const start = parseInt(invalid[2], 10);
if (com[2] >= start) { if (com[2] >= start) {
rpkiInvalidReason = com[2]; rpkiInvalidReason = com[2];
} }
} else { } else {
if (com[2].toFixed() === rpkiInvalid[2]) { if (com[2].toFixed() === invalid[2]) {
rpkiInvalidReason = 1; rpkiInvalidReason = 1;
}
} }
break; // We found a match, stop searching
} }
} }

View File

@ -10,10 +10,10 @@ import RpkiIndicator
const config = { const config = {
rpki: { rpki: {
enabled: true, enabled: true,
valid: ["1234", "1111", "1"], valid: [["1234", "1111", "1"]],
unknown: ["1234", "1111", "0"], unknown: [["1234", "1111", "0"]],
not_checked: ["1234", "1111", "10"], not_checked: [["1234", "1111", "10"]],
invalid: ["1234", "1111", "100"], invalid: [["1234", "1111", "100"]],
}, },
}; };