Merge branch 'feature/blackhole-communities' into develop
This commit is contained in:
commit
b47769817e
@ -95,6 +95,11 @@ load_on_demand = true # Default: false
|
||||
|
||||
23:46:1 = Some other made up reason
|
||||
|
||||
[blackhole_communities]
|
||||
65535:666
|
||||
12345:1105-1189:*
|
||||
12345:1111:10-90
|
||||
rt:1234:4200000000-4200010000
|
||||
|
||||
[rpki]
|
||||
# shows rpki validation status in the client, based on the presence of a large
|
||||
|
@ -173,3 +173,33 @@ func (c BGPCommunityMap) Communities() Communities {
|
||||
}
|
||||
return communities
|
||||
}
|
||||
|
||||
// BGPCommunity types: Standard, Extended and Large
|
||||
const (
|
||||
BGPCommunityTypeStd = iota
|
||||
BGPCommunityTypeExt
|
||||
BGPCommunityTypeLarge
|
||||
)
|
||||
|
||||
// BGPCommunityRange is a list of tuples with the start and end
|
||||
// of the range defining a community.
|
||||
type BGPCommunityRange []interface{}
|
||||
|
||||
// Type classifies the BGP Ranged BGP Community into: std, large, ext
|
||||
func (c BGPCommunityRange) Type() int {
|
||||
if len(c) == 2 {
|
||||
return BGPCommunityTypeStd
|
||||
}
|
||||
if _, ok := c[0].([]string); ok {
|
||||
return BGPCommunityTypeExt
|
||||
}
|
||||
return BGPCommunityTypeLarge
|
||||
}
|
||||
|
||||
// A BGPCommunitiesSet is a set of communities, large and extended.
|
||||
// The communities are described as ranges.
|
||||
type BGPCommunitiesSet struct {
|
||||
Standard []BGPCommunityRange `json:"standard"`
|
||||
Extended []BGPCommunityRange `json:"extended"`
|
||||
Large []BGPCommunityRange `json:"large"`
|
||||
}
|
||||
|
@ -38,7 +38,8 @@ type ConfigResponse struct {
|
||||
|
||||
Rpki Rpki `json:"rpki"`
|
||||
|
||||
BGPCommunities map[string]interface{} `json:"bgp_communities"`
|
||||
BGPCommunities map[string]interface{} `json:"bgp_communities"`
|
||||
BGPBlackholeCommunities BGPCommunitiesSet `json:"bgp_blackhole_communities"`
|
||||
|
||||
NeighborsColumns map[string]string `json:"neighbors_columns"`
|
||||
NeighborsColumnsOrder []string `json:"neighbors_columns_order"`
|
||||
|
121
pkg/config/bgp_communities.go
Normal file
121
pkg/config/bgp_communities.go
Normal file
@ -0,0 +1,121 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alice-lg/alice-lg/pkg/api"
|
||||
"github.com/alice-lg/alice-lg/pkg/decoders"
|
||||
)
|
||||
|
||||
// ErrInvalidCommunity creates an invalid community error
|
||||
func ErrInvalidCommunity(s string) error {
|
||||
return fmt.Errorf("invalid community: %s", s)
|
||||
}
|
||||
|
||||
// Helper parse communities from a section body
|
||||
func parseAndMergeCommunities(
|
||||
communities api.BGPCommunityMap, body string,
|
||||
) api.BGPCommunityMap {
|
||||
|
||||
// Parse and merge communities
|
||||
lines := strings.Split(body, "\n")
|
||||
for _, line := range lines {
|
||||
kv := strings.SplitN(line, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
log.Println("Skipping malformed BGP community:", line)
|
||||
continue
|
||||
}
|
||||
|
||||
community := strings.TrimSpace(kv[0])
|
||||
label := strings.TrimSpace(kv[1])
|
||||
communities.Set(community, label)
|
||||
}
|
||||
|
||||
return communities
|
||||
}
|
||||
|
||||
// Parse a communities set with ranged communities
|
||||
func parseRangeCommunitiesSet(body string) (*api.BGPCommunitiesSet, error) {
|
||||
comms := []api.BGPCommunityRange{}
|
||||
large := []api.BGPCommunityRange{}
|
||||
ext := []api.BGPCommunityRange{}
|
||||
|
||||
lines := strings.Split(body, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue // Empty
|
||||
}
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue // Comment
|
||||
}
|
||||
comm, err := parseRangeCommunity(line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch comm.Type() {
|
||||
case api.BGPCommunityTypeStd:
|
||||
comms = append(comms, comm)
|
||||
case api.BGPCommunityTypeLarge:
|
||||
large = append(large, comm)
|
||||
case api.BGPCommunityTypeExt:
|
||||
ext = append(ext, comm)
|
||||
}
|
||||
}
|
||||
|
||||
set := &api.BGPCommunitiesSet{
|
||||
Standard: comms,
|
||||
Large: large,
|
||||
Extended: ext,
|
||||
}
|
||||
return set, nil
|
||||
}
|
||||
|
||||
func parseRangeCommunity(s string) (api.BGPCommunityRange, error) {
|
||||
tokens := strings.Split(s, ":")
|
||||
if len(tokens) < 2 {
|
||||
return nil, ErrInvalidCommunity(s)
|
||||
}
|
||||
|
||||
// Extract ranges and make uniform structure
|
||||
parts := make([][]string, 0, len(tokens))
|
||||
for _, t := range tokens {
|
||||
values := strings.SplitN(t, "-", 2)
|
||||
if len(values) == 0 {
|
||||
return nil, ErrInvalidCommunity(s)
|
||||
}
|
||||
if len(values) == 1 {
|
||||
parts = append(parts, []string{values[0], values[0]})
|
||||
} else {
|
||||
parts = append(parts, []string{values[0], values[1]})
|
||||
}
|
||||
}
|
||||
if len(parts) <= 1 {
|
||||
return nil, ErrInvalidCommunity(s)
|
||||
}
|
||||
|
||||
// Check if this might be an ext community
|
||||
isExt := false
|
||||
if _, err := strconv.Atoi(parts[0][0]); err != nil {
|
||||
isExt = true // At least it looks like...
|
||||
}
|
||||
|
||||
if isExt && len(parts) != 3 {
|
||||
return nil, ErrInvalidCommunity(s)
|
||||
}
|
||||
if isExt {
|
||||
return api.BGPCommunityRange{
|
||||
[]string{parts[0][0], parts[0][0]},
|
||||
decoders.IntListFromStrings(parts[1]),
|
||||
decoders.IntListFromStrings(parts[2]),
|
||||
}, nil
|
||||
}
|
||||
comm := api.BGPCommunityRange{}
|
||||
for _, p := range parts {
|
||||
comm = append(comm, decoders.IntListFromStrings(p))
|
||||
}
|
||||
return comm, nil
|
||||
}
|
@ -146,8 +146,9 @@ type UIConfig struct {
|
||||
RoutesNoexports NoexportsConfig
|
||||
RoutesRejectCandidates RejectCandidatesConfig
|
||||
|
||||
BGPCommunities api.BGPCommunityMap
|
||||
Rpki RpkiConfig
|
||||
BGPCommunities api.BGPCommunityMap
|
||||
BGPBlackholeCommunities api.BGPCommunitiesSet
|
||||
Rpki RpkiConfig
|
||||
|
||||
Theme ThemeConfig
|
||||
|
||||
@ -387,28 +388,6 @@ func getLookupColumns(config *ini.File) (
|
||||
return columns, order, nil
|
||||
}
|
||||
|
||||
// Helper parse communities from a section body
|
||||
func parseAndMergeCommunities(
|
||||
communities api.BGPCommunityMap, body string,
|
||||
) api.BGPCommunityMap {
|
||||
|
||||
// Parse and merge communities
|
||||
lines := strings.Split(body, "\n")
|
||||
for _, line := range lines {
|
||||
kv := strings.SplitN(line, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
log.Println("Skipping malformed BGP community:", line)
|
||||
continue
|
||||
}
|
||||
|
||||
community := strings.TrimSpace(kv[0])
|
||||
label := strings.TrimSpace(kv[1])
|
||||
communities.Set(community, label)
|
||||
}
|
||||
|
||||
return communities
|
||||
}
|
||||
|
||||
// Get UI config: BGP Communities
|
||||
func getBGPCommunityMap(config *ini.File) api.BGPCommunityMap {
|
||||
// Load defaults
|
||||
@ -459,6 +438,25 @@ func getRoutesNoexports(config *ini.File) (NoexportsConfig, error) {
|
||||
return noexportsConfig, nil
|
||||
}
|
||||
|
||||
// Get UI config: blackhole communities
|
||||
func getBlackholeCommunities(config *ini.File) (api.BGPCommunitiesSet, error) {
|
||||
section := config.Section("blackhole_communities")
|
||||
defaultBlackholes := api.BGPCommunitiesSet{
|
||||
Standard: []api.BGPCommunityRange{
|
||||
{[]interface{}{65535, 65535}, []interface{}{666, 666}},
|
||||
},
|
||||
}
|
||||
if section == nil {
|
||||
return defaultBlackholes, nil
|
||||
}
|
||||
set, err := parseRangeCommunitiesSet(section.Body())
|
||||
if err != nil {
|
||||
return defaultBlackholes, err
|
||||
}
|
||||
set.Standard = append(set.Standard, defaultBlackholes.Standard...)
|
||||
return *set, nil
|
||||
}
|
||||
|
||||
// Get UI config: Reject candidates
|
||||
func getRejectCandidatesConfig(config *ini.File) (RejectCandidatesConfig, error) {
|
||||
candidateCommunities := config.Section(
|
||||
@ -618,6 +616,12 @@ func getUIConfig(config *ini.File) (UIConfig, error) {
|
||||
return uiConfig, err
|
||||
}
|
||||
|
||||
// Blackhole communities
|
||||
blackholeCommunities, err := getBlackholeCommunities(config)
|
||||
if err != nil {
|
||||
return uiConfig, err
|
||||
}
|
||||
|
||||
// Theme configuration: Theming is optional, if no settings
|
||||
// are found, it will be ignored
|
||||
themeConfig := getThemeConfig(config)
|
||||
@ -640,8 +644,9 @@ func getUIConfig(config *ini.File) (UIConfig, error) {
|
||||
RoutesNoexports: noexports,
|
||||
RoutesRejectCandidates: rejectCandidates,
|
||||
|
||||
BGPCommunities: getBGPCommunityMap(config),
|
||||
Rpki: rpki,
|
||||
BGPBlackholeCommunities: blackholeCommunities,
|
||||
BGPCommunities: getBGPCommunityMap(config),
|
||||
Rpki: rpki,
|
||||
|
||||
Theme: themeConfig,
|
||||
|
||||
@ -830,6 +835,7 @@ func LoadConfig(file string) (*Config, error) {
|
||||
parsedConfig, err := ini.LoadSources(ini.LoadOptions{
|
||||
UnparseableSections: []string{
|
||||
"bgp_communities",
|
||||
"blackhole_communities",
|
||||
"rejection_reasons",
|
||||
"noexport_reasons",
|
||||
},
|
||||
|
@ -227,7 +227,7 @@ func TestRejectCandidatesConfig(t *testing.T) {
|
||||
func TestDefaultHTTPTimeout(t *testing.T) {
|
||||
config, err := LoadConfig("testdata/alice.conf")
|
||||
if err != nil {
|
||||
t.Error("Could not load test config:", err)
|
||||
t.Fatal("Could not load test config:", err)
|
||||
}
|
||||
|
||||
if config.Server.HTTPTimeout != DefaultHTTPTimeout {
|
||||
@ -249,3 +249,19 @@ func TestPostgresStoreConfig(t *testing.T) {
|
||||
}
|
||||
t.Log(config.Postgres)
|
||||
}
|
||||
|
||||
func TestGetBlackholeCommunities(t *testing.T) {
|
||||
config, _ := LoadConfig("testdata/alice.conf")
|
||||
comms := config.UI.BGPBlackholeCommunities
|
||||
|
||||
if comms.Standard[0][0].([]int)[0] != 1337 {
|
||||
t.Error("unexpected community:", comms.Standard[0])
|
||||
}
|
||||
if len(comms.Extended) != 1 {
|
||||
t.Error("unexpected communities:", comms.Extended)
|
||||
}
|
||||
if len(comms.Large) != 1 {
|
||||
t.Error("unexpected communities:", comms.Large)
|
||||
}
|
||||
t.Log(comms)
|
||||
}
|
||||
|
5
pkg/config/testdata/alice.conf
vendored
5
pkg/config/testdata/alice.conf
vendored
@ -88,6 +88,10 @@ load_on_demand = true # Default: false
|
||||
|
||||
23:46:1 = Some other made up reason
|
||||
|
||||
[blackhole_communities]
|
||||
1337:666
|
||||
rt:1324:4200000000-4200010000
|
||||
2342:65530-65535:665-667
|
||||
|
||||
[rpki]
|
||||
# shows rpki validation status in the client, based on the presence of a large
|
||||
@ -128,6 +132,7 @@ invalid = 23042:1000:4-*
|
||||
# Uptime Displays the relative uptime of this neighbour
|
||||
# Description The neighbour's description with link to routes page
|
||||
#
|
||||
#
|
||||
|
||||
[neighbours_columns]
|
||||
address = Neighbour
|
||||
|
@ -51,9 +51,10 @@ func (s *Server) apiConfigShow(
|
||||
_params httprouter.Params,
|
||||
) (response, error) {
|
||||
result := api.ConfigResponse{
|
||||
Asn: s.cfg.Server.Asn,
|
||||
BGPCommunities: s.cfg.UI.BGPCommunities,
|
||||
RejectReasons: s.cfg.UI.RoutesRejections.Reasons,
|
||||
Asn: s.cfg.Server.Asn,
|
||||
BGPCommunities: s.cfg.UI.BGPCommunities,
|
||||
BGPBlackholeCommunities: s.cfg.UI.BGPBlackholeCommunities,
|
||||
RejectReasons: s.cfg.UI.RoutesRejections.Reasons,
|
||||
Noexport: api.Noexport{
|
||||
LoadOnDemand: s.cfg.UI.RoutesNoexports.LoadOnDemand,
|
||||
},
|
||||
|
@ -6,26 +6,53 @@ import { faCircle }
|
||||
|
||||
import { useRouteServer }
|
||||
from 'app/context/route-servers';
|
||||
import { matchCommunityRange
|
||||
, useBlackholeCommunities
|
||||
}
|
||||
from 'app/context/bgp';
|
||||
|
||||
|
||||
const BlackholeIndicator = ({route}) => {
|
||||
const routeServer = useRouteServer(); // blackholes are store per RS
|
||||
const blackholeCommunities = useBlackholeCommunities();
|
||||
|
||||
const blackholes = routeServer?.blackholes || [];
|
||||
const communities = route?.bgp?.communities || [];
|
||||
const nextHop = route?.bgp?.next_hop;
|
||||
const routeStandard = route?.bgp?.communities || [];
|
||||
const routeExtended = route?.bgp?.ext_communities || [];
|
||||
const routeLarge = route?.bgp?.large_communities || [];
|
||||
|
||||
// Check if next hop is a known blackhole
|
||||
let isBlackhole = blackholes.includes(nextHop);
|
||||
|
||||
// Check if BGP community 65535:666 is set
|
||||
for (const c of communities) {
|
||||
if (c[0] === 65535 && c[1] === 666) {
|
||||
isBlackhole = true;
|
||||
break;
|
||||
// Check standard communities
|
||||
for (const c of blackholeCommunities.standard) {
|
||||
for (const r of routeStandard) {
|
||||
if (matchCommunityRange(r, c)) {
|
||||
isBlackhole = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check large communities
|
||||
for (const c of blackholeCommunities.large) {
|
||||
for (const r of routeLarge) {
|
||||
if (matchCommunityRange(r, c)) {
|
||||
isBlackhole = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check extended
|
||||
for (const c of blackholeCommunities.extended) {
|
||||
for (const r of routeExtended) {
|
||||
if (matchCommunityRange(r, c)) {
|
||||
isBlackhole = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isBlackhole) {
|
||||
return(
|
||||
<span className="route-prefix-flag blackhole-route is-blackhole-route">
|
||||
|
@ -114,3 +114,34 @@ export const useReadableCommunity = (community) => {
|
||||
community, getLabel,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get blackhole communities from config
|
||||
*/
|
||||
export const useBlackholeCommunities = () => {
|
||||
let config = useConfig();
|
||||
return config.bgp_blackhole_communities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match community ranges. When doing so, make sure
|
||||
* you compare the right communities. e.g. comparing
|
||||
* a large and an extended community will lead to unexpected
|
||||
* results.
|
||||
*/
|
||||
export const matchCommunityRange = (community, range) => {
|
||||
if (community.length !== range.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i in community) {
|
||||
let c = community[i];
|
||||
let rs = range[i][0];
|
||||
let re = range[i][1];
|
||||
if ((c < rs) || (c > re)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user