diff --git a/go.mod b/go.mod index ebb9924..a64c825 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22 require ( github.com/prometheus/client_golang v1.19.1 github.com/rs/zerolog v1.33.0 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 ) require ( diff --git a/go.sum b/go.sum index 49ae820..f89fa2b 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3c github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/lg.go b/lg.go new file mode 100644 index 0000000..8e153ae --- /dev/null +++ b/lg.go @@ -0,0 +1,91 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "strconv" + "strings" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/rs/zerolog/log" + "golang.org/x/exp/slices" +) + +var ( + asPathGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "as_path", + Help: "AS PATH", + }, + []string{"prefix", "city", "mux"}, + ) + //bgpCommunitiesGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ + // Name: "bgp_communities", + // Help: "BGP Communities", + //}, + // []string{"prefix", "city", "mux", "communities"}, + //) +) + +func (p *PrefixState) checkLGState() { + log.Trace().Str("Prefix", p.Prefix).Msg("checking prefix state") + url := ripestatBase + "/data/looking-glass/data.json?resource=" + p.Prefix + "&sourceapp=" + appId + resp, err := http.Get(url) + if err != nil { + log.Error().Err(err).Msg("Fetching ripestat") + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Error().Err(err).Msg("reading ripestat resp") + } + defer resp.Body.Close() + + var ripeStatLookingGlassResp RIPEStatLookingGlassResp + json.Unmarshal(body, &ripeStatLookingGlassResp) + + if statusCode := ripeStatLookingGlassResp.StatusCode; statusCode != 200 { + log.Error().Int("status code", statusCode). + Str("status", ripeStatLookingGlassResp.Status). + Msg("ripestat(lg) resp status code != 200") + } + + p.Mu.Lock() + defer p.Mu.Unlock() + + for _, rrc := range ripeStatLookingGlassResp.Data.Rrcs { + upstreams := []int{} + //communities := []string{} + for _, peer := range rrc.Peers { + asPathSplit := strings.Split(peer.AsPath, " ") + upstream := 0 + if len(asPathSplit) >= 2 { + upstreamStr := asPathSplit[len(asPathSplit)-2] + upstream, err = strconv.Atoi(upstreamStr) + if err != nil { + log.Err(err).Msg("atoi fail") + } + } + upstreams = append(upstreams, upstream) + //communities = append(communities, peer.Community) + } + upstreams = slices.Compact(upstreams) + for _, upstream := range upstreams { + asPathGauge.WithLabelValues( + p.Prefix, + rrc.Location, + prefixes[p.Prefix], + ).Set(float64(upstream)) + } + //communities = slices.Compact(communities) + //for _, e := range communities { + // bgpCommunitiesGauge.WithLabelValues( + // p.Prefix, + // rrc.Location, + // prefixes[p.Prefix], + // e, + // ).Set(1) + //} + } +} diff --git a/main.go b/main.go index bb05627..fb6b428 100644 --- a/main.go +++ b/main.go @@ -2,15 +2,12 @@ package main import ( "context" - "encoding/json" "errors" "flag" - "io/ioutil" "net/http" "os" "os/signal" "strconv" - "strings" "sync" "syscall" "time" @@ -18,74 +15,16 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" ) -var prefixes = []string{ - // PEERING v6 - "2804:269c:fe41::/48", - "2804:269c:fe42::/48", - "2804:269c:fe44::/48", - "2804:269c:fe45::/48", - "2804:269c:fe47::/48", - "2804:269c:fe50::/48", - "2804:269c:fe51::/48", - "2804:269c:fe53::/48", - "2804:269c:fe56::/48", - "2804:269c:fe57::/48", - "2804:269c:fe58::/48", - "2804:269c:fe59::/48", - "2804:269c:fe5a::/48", - "2804:269c:fe5b::/48", - "2804:269c:fe5c::/48", - "2804:269c:fe5d::/48", - "2804:269c:fe5e::/48", - "2804:269c:fe5f::/48", - "2804:269c:fe60::/48", - "2804:269c:fe61::/48", - "2804:269c:fe62::/48", - "2804:269c:fe63::/48", - "2804:269c:fe64::/48", - "2804:269c:fe65::/48", - "2804:269c:fe66::/48", - "2804:269c:fe67::/48", - "2804:269c:fe68::/48", - "2804:269c:fe69::/48", - "2804:269c:fe6a::/48", - "2804:269c:fe6b::/48", - "2804:269c:fe6c::/48", - "2804:269c:fe6d::/48", - "2804:269c:fe6e::/48", - "2804:269c:fe6f::/48", - "2804:269c:fe70::/48", - "2804:269c:fe71::/48", - "2804:269c:fe72::/48", - "2804:269c:fe73::/48", - "2804:269c:fe74::/48", - "2804:269c:fe76::/48", - - // isbgpsafeyet.com valid - "104.17.224.0/20", - "2606:4700::/44", - - // isbgpsafeyet.com invalid - "103.21.244.0/24", - "2606:4700:7000::/48", -} - var prefixStates = []*PrefixState{} var port int var appId string +var debug bool -var ( - prefixStateGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Name: "prefix_visibility", - Help: "Visibility of the prefix", - }, []string{"prefix", "city"}) -) +const ripestatBase = "https://stat.ripe.net" type PrefixState struct { Prefix string @@ -93,68 +32,39 @@ type PrefixState struct { Mu sync.Mutex } -func (p *PrefixState) checkState() { - log.Trace().Str("Prefix", p.Prefix).Msg("checking prefix state") - url := "https://stat.ripe.net/data/visibility/data.json?data_overload_limit=ignore&include=peers_seeing&resource=" + p.Prefix + "&sourceapp=" + appId - resp, err := http.Get(url) - if err != nil { - log.Error().Err(err).Msg("Fetching ripestat") - } - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Error().Err(err).Msg("reading ripestat resp") - } - defer resp.Body.Close() - - var ripeStatResp RIPEStatResp - json.Unmarshal(body, &ripeStatResp) - - ipv6 := strings.Contains(p.Prefix, ":") - - p.Mu.Lock() - defer p.Mu.Unlock() - - for _, probe := range ripeStatResp.Data.Visibilities { - var vis float32 - if ipv6 { - vis = float32(len(probe.Ipv6FullTablePeersSeeing)) / - float32(probe.Ipv6FullTablePeerCount) - } else { - vis = float32(len(probe.Ipv4FullTablePeersSeeing)) / - float32(probe.Ipv4FullTablePeerCount) - - } - p.State[probe.Probe.City] = vis - prefixStateGauge.WithLabelValues(p.Prefix, probe.Probe.City).Set(float64(vis)) - } -} - func updateStates() { log.Debug().Msg("Updating Prefixes") for _, ps := range prefixStates { - go ps.checkState() + go ps.checkVisState() + go ps.checkLGState() } } func init() { zerolog.TimeFieldFormat = zerolog.TimeFormatUnix log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) - zerolog.SetGlobalLevel(zerolog.DebugLevel) flag.StringVar(&appId, "appid", "exporter", "provide a unique identifier to every data call") flag.IntVar(&port, "port", 2112, "port") + flag.BoolVar(&debug, "debug", false, "debug") } func main() { flag.Parse() + if debug { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + log.Debug().Msg("Debug log enabled") + } else { + zerolog.SetGlobalLevel(zerolog.InfoLevel) + } + log.Info(). Str("appID", appId). Str("Data Source", "RIPE RIS via RIPEstat API"). Msg("Starting PEERINGMON Exporter") - for _, prefix := range prefixes { + for prefix, _ := range prefixes { prefixStates = append(prefixStates, &PrefixState{ Prefix: prefix, State: make(map[string]float32), diff --git a/prefixes.go b/prefixes.go new file mode 100644 index 0000000..c93eb18 --- /dev/null +++ b/prefixes.go @@ -0,0 +1,54 @@ +package main + +var prefixes = map[string]string{ + "2804:269c:fe41::/48": "seattle01", + "2804:269c:fe42::/48": "isi01", + "2804:269c:fe45::/48": "amsterdam01", + "2804:269c:fe46::/48": "gatech01", + "2804:269c:fe47::/48": "ufmg01", + "2804:269c:fe49::/48": "grnet01", + "2804:269c:fe50::/48": "uw01", + "2804:269c:fe51::/48": "wisc01", + "2804:269c:fe54::/48": "neu01", + "2804:269c:fe56::/48": "clemson01", + "2804:269c:fe57::/48": "utah01", + "2804:269c:fe59::/48": "saopaulo01", + "2804:269c:fe63::/48": "vtrmiami", + "2804:269c:fe64::/48": "vtratlanta", + "2804:269c:fe65::/48": "vtramsterdam", + "2804:269c:fe66::/48": "vtrtokyo", + "2804:269c:fe67::/48": "vtrsydney", + "2804:269c:fe68::/48": "vtrfrankfurt", + "2804:269c:fe69::/48": "vtrseattle", + "2804:269c:fe70::/48": "vtrchicago", + "2804:269c:fe71::/48": "vtrparis", + "2804:269c:fe72::/48": "vtrsingapore", + "2804:269c:fe73::/48": "vtrwarsaw", + "2804:269c:fe74::/48": "vtrnewyork", + "2804:269c:fe75::/48": "vtrdallas", + "2804:269c:fe76::/48": "vtrmexico", + "2804:269c:fe77::/48": "vtrtoronto", + "2804:269c:fe78::/48": "vtrmadrid", + "2804:269c:fe79::/48": "vtrstockholm", + "2804:269c:fe80::/48": "vtrbangalore", + "2804:269c:fe81::/48": "vtrdelhi", + "2804:269c:fe82::/48": "vtrlosangelas", + "2804:269c:fe83::/48": "vtrsilicon", + "2804:269c:fe84::/48": "vtrlondon", + "2804:269c:fe85::/48": "vtrmumbai", + "2804:269c:fe86::/48": "vtrseoul", + "2804:269c:fe87::/48": "vtrmelbourne", + "2804:269c:fe88::/48": "vtrsaopaulo", + "2804:269c:fe89::/48": "vtrjohannesburg", + "2804:269c:fe90::/48": "vtrosaka", + "2804:269c:fe91::/48": "vtrsantiago", + "2804:269c:fe92::/48": "vtrmanchester", + "2804:269c:fe93::/48": "vtrtelaviv", + "2804:269c:fe94::/48": "vtrhonolulu", + + "104.17.224.0/20": "cf valid4", + "2606:4700::/44": "cf valid6", + + "103.21.244.0/24": "cf invalid4", + "2606:4700:7000::/48": "cf invalid6", +} diff --git a/ripeStruct.go b/ripeStruct.go index 931f3d9..231a636 100644 --- a/ripeStruct.go +++ b/ripeStruct.go @@ -1,6 +1,6 @@ package main -type RIPEStatResp struct { +type RIPEStatVisibilityResp struct { Messages [][]string `json:"messages"` SeeAlso []interface{} `json:"see_also"` Version string `json:"version"` @@ -48,3 +48,43 @@ type RIPEStatResp struct { StatusCode int `json:"status_code"` Time string `json:"time"` } + +type RIPEStatLookingGlassResp struct { + Messages []interface{} `json:"messages"` + SeeAlso []interface{} `json:"see_also"` + Version string `json:"version"` + DataCallName string `json:"data_call_name"` + DataCallStatus string `json:"data_call_status"` + Cached bool `json:"cached"` + Data struct { + Rrcs []struct { + Rrc string `json:"rrc"` + Location string `json:"location"` + Peers []struct { + AsnOrigin string `json:"asn_origin"` + AsPath string `json:"as_path"` + Community string `json:"community"` + LastUpdated string `json:"last_updated"` + Prefix string `json:"prefix"` + Peer string `json:"peer"` + Origin string `json:"origin"` + NextHop string `json:"next_hop"` + LatestTime string `json:"latest_time"` + } `json:"peers"` + } `json:"rrcs"` + QueryTime string `json:"query_time"` + LatestTime string `json:"latest_time"` + Parameters struct { + Resource string `json:"resource"` + LookBackLimit int `json:"look_back_limit"` + Cache interface{} `json:"cache"` + } `json:"parameters"` + } `json:"data"` + QueryID string `json:"query_id"` + ProcessTime int `json:"process_time"` + ServerID string `json:"server_id"` + BuildVersion string `json:"build_version"` + Status string `json:"status"` + StatusCode int `json:"status_code"` + Time string `json:"time"` +} diff --git a/vis.go b/vis.go new file mode 100644 index 0000000..8e5fd3c --- /dev/null +++ b/vis.go @@ -0,0 +1,66 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "strings" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/rs/zerolog/log" +) + +var ( + prefixStateGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "prefix_visibility", + Help: "Visibility of the prefix", + }, []string{"prefix", "city", "mux"}) +) + +func (p *PrefixState) checkVisState() { + log.Trace().Str("Prefix", p.Prefix).Msg("checking prefix state") + url := ripestatBase + "/data/visibility/data.json?data_overload_limit=ignore&include=peers_seeing&resource=" + p.Prefix + "&sourceapp=" + appId + resp, err := http.Get(url) + if err != nil { + log.Error().Err(err).Msg("Fetching ripestat") + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Error().Err(err).Msg("reading ripestat resp") + } + defer resp.Body.Close() + + var ripeStatVisibilityResp RIPEStatVisibilityResp + json.Unmarshal(body, &ripeStatVisibilityResp) + + if statusCode := ripeStatVisibilityResp.StatusCode; statusCode != 200 { + log.Error().Int("status code", statusCode). + Str("status", ripeStatVisibilityResp.Status). + Msg("ripestat(vis) resp status code != 200") + } + + ipv6 := strings.Contains(p.Prefix, ":") + + p.Mu.Lock() + defer p.Mu.Unlock() + + for _, probe := range ripeStatVisibilityResp.Data.Visibilities { + var vis float32 + if ipv6 { + vis = float32(len(probe.Ipv6FullTablePeersSeeing)) / + float32(probe.Ipv6FullTablePeerCount) + } else { + vis = float32(len(probe.Ipv4FullTablePeersSeeing)) / + float32(probe.Ipv4FullTablePeerCount) + + } + p.State[probe.Probe.City] = vis + prefixStateGauge.WithLabelValues( + p.Prefix, + probe.Probe.City, + prefixes[p.Prefix], + ).Set(float64(vis)) + } +}