diff --git a/pkg/config/config.go b/pkg/config/config.go index 603611f..3027c9a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -126,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 @@ -486,70 +486,68 @@ 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) - if err != nil { - log.Println( - "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 if len(rpki.Valid) == 0 { - rpki.Valid = []string{ownAsn, "1000", "1"} - } else { - rpki.Valid = strings.SplitN(rpki.Valid[0], ":", 3) + rpki.Valid = [][]string{{"*", "1000", "1"}} } if len(rpki.Unknown) == 0 { - rpki.Unknown = []string{ownAsn, "1000", "2"} - } else { - rpki.Unknown = strings.SplitN(rpki.Unknown[0], ":", 3) + rpki.Unknown = [][]string{{"*", "1000", "2"}} } if len(rpki.NotChecked) == 0 { - rpki.NotChecked = []string{ownAsn, "1000", "3"} - } else { - rpki.NotChecked = strings.SplitN(rpki.NotChecked[0], ":", 3) + rpki.NotChecked = [][]string{{"*", "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 { + rpki.Invalid = [][]string{{"*", "1000", "4", "*"}} } return rpki, nil } -// 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) { - server := config.Section("server") - asn := server.Key("asn").MustInt(-1) - - if asn == -1 { - return 0, fmt.Errorf("could not get own ASN from config") - } - - return asn, nil -} - // Get UI config: Theme settings func getThemeConfig(config *ini.File) ThemeConfig { baseConfig := config.Section("theme") @@ -823,18 +821,35 @@ func getSources(config *ini.File) ([]*SourceConfig, error) { return sources, nil } -// preprocessConfig parses the variables section of the config +// 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) } - return bytes.Join(config, []byte("\n")) + // 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. @@ -860,6 +875,7 @@ func LoadConfig(file string) (*Config, error) { "blackhole_communities", "rejection_reasons", "noexport_reasons", + "rpki", }, }, configData) if err != nil { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 6bdd956..370f233 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -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] != "*" { 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") } diff --git a/pkg/config/expand_test.go b/pkg/config/expand_test.go index 0d0d970..4b64a80 100644 --- a/pkg/config/expand_test.go +++ b/pkg/config/expand_test.go @@ -119,5 +119,4 @@ func TestExpandBgpCommunities(t *testing.T) { t.Errorf("Expected %d results, got %d", expected, len(exp)) } t.Log(exp) - } diff --git a/pkg/config/testdata/alice.conf b/pkg/config/testdata/alice.conf index 03fecff..824611d 100644 --- a/pkg/config/testdata/alice.conf +++ b/pkg/config/testdata/alice.conf @@ -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 @@ -55,16 +61,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 +86,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 +108,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 +121,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 =