Permalink
Browse files

Initial implementation

  • Loading branch information...
1 parent 9a6a927 commit 19b094a883f38c0cb7a6ada78a22d1ff4b8604d7 @abh committed Jan 6, 2013
Showing with 409 additions and 3 deletions.
  1. +3 −3 README.md
  2. +12 −0 bgpapi.go
  3. +191 −0 bgpreader.go
  4. +103 −0 http.go
  5. +2 −0 templates/footer.html
  6. +13 −0 templates/header.html
  7. +41 −0 templates/home.html
  8. +44 −0 types.go
View
@@ -1,7 +1,7 @@
# BGP API
-`bgpapi` runs as a "parsed-route-backend" to [ExaBGP]() and provides
-an HTTP API to some BGP data.
+`bgpapi` runs as a "parsed-route-backend" to [ExaBGP](http://code.google.com/p/exabgp/)
+and provides an HTTP API to some BGP data.
## Compilation
@@ -14,7 +14,7 @@ After installing Go (golang) and configuring the development environment, you ca
## Configuration
bgpapi itself doesn't currently take any configuration. Configure a
-`neighbor` in ExaBGP with a `parsed-route-backend` process, like:
+`neighbor` in ExaBGP and a `parsed-route-backend` process, like:
process parsed-route-backend {
parse-routes;
View
@@ -0,0 +1,12 @@
+package main
+
+import (
+ "runtime"
+)
+
+func main() {
+ runtime.GOMAXPROCS(4)
+
+ go bgpReader()
+ httpServer()
+}
View
@@ -0,0 +1,191 @@
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "strconv"
+ "strings"
+)
+
+var neighbors Neighbors
+
+func bgpReader() {
+
+ neighbors = make(Neighbors)
+
+ r := bufio.NewReader(os.Stdin)
+
+ var err error
+ for line, err := r.ReadString('\n'); err == nil; line, err = r.ReadString('\n') {
+ line = strings.TrimSpace(line)
+ if len(line) > 0 {
+ // fmt.Println("X", line)
+ f := strings.SplitN(line, " ", 4)
+ // fmt.Printf("%#v\n", f)
+
+ neighbor_ip := f[1]
+ command := f[2]
+
+ if neighbors[neighbor_ip] == nil {
+ neighbor := new(Neighbor)
+ neighbors[neighbor_ip] = neighbor
+ // neighbor.AsnPrefix = new(map[ASN]Prefixes)
+ // neighbor.PrefixAsn = make(map[string]ASN)
+ }
+
+ neighbor := neighbors[neighbor_ip]
+
+ switch command {
+ case "up", "connected":
+ neighbor.State = command
+ case "update":
+ neighbor.State = "update " + f[3]
+ case "announced":
+ // fmt.Printf("R: %#v\n", r)
+
+ neighbor.Updates++
+
+ route := parseRoute(f[3])
+
+ if ones, _ := route.Prefix.Mask.Size(); ones < 8 || ones > 25 {
+ // fmt.Println("prefix mask too big or small", route.Prefix)
+ } else {
+ if neighbor.AsnPrefix == nil {
+ neighbor.AsnPrefix = make(map[ASN]Prefixes)
+ }
+ if neighbor.PrefixAsn == nil {
+ neighbor.PrefixAsn = make(Prefixes)
+ }
+
+ if neighbor.AsnPrefix[route.PrimaryASN] == nil {
+ neighbor.AsnPrefix[route.PrimaryASN] = make(Prefixes)
+ }
+
+ neighbor.AsnPrefix[route.PrimaryASN][route.Prefix.String()] = 0
+ neighbor.PrefixAsn[route.Prefix.String()] = route.PrimaryASN
+ }
+ case "withdrawn":
+
+ neighbor.Updates++
+
+ // fmt.Println("withdraw", f[3])
+ route := parseRoute(f[3])
+
+ // x, y := neighbor.PrefixAsn[route.Prefix.String()]
+ // fmt.Println("X/Y", x, y)
+
+ if asn, exists := neighbor.PrefixAsn[route.Prefix.String()]; exists {
+ // fmt.Println("Removing ASN from prefix", asn, route.Prefix)
+ delete(neighbor.PrefixAsn, route.Prefix.String())
+ delete(neighbor.AsnPrefix[asn], route.Prefix.String())
+ } else {
+ fmt.Println("Could not find prefix in PrefixAsn")
+ fmt.Println("%#v", neighbor.PrefixAsn)
+ }
+
+ default:
+ fmt.Println("Unknown command:", command)
+ fmt.Println("LINE:", line)
+ panic("Command not implemented:")
+ }
+ }
+ }
+
+ if err != nil && err != io.EOF {
+ fmt.Println(err)
+ return
+ } else {
+ fmt.Println("EOF")
+ }
+}
+
+func parseRoute(input string) *Route {
+
+ r := strings.Split(input, " ")
+
+ route := new(Route)
+ route.Options = make(map[string]string)
+ aspath := make(ASPath, 0)
+
+ var key string
+
+ state := parseKey
+
+ for _, v := range r {
+ // fmt.Printf("k: %s, v: %s, state: %#v\n", key, v, state)
+
+ switch state {
+ case parseKey:
+ {
+ state = parseValue
+ key = v
+ continue
+ }
+ case parseValue:
+ if v == "[" {
+ state = parseList
+ continue
+ }
+ state = parseKey
+
+ if key == "as-path" {
+ addASPath(&aspath, v)
+ }
+ route.Options[key] = v
+ continue
+ case parseList:
+ {
+ if v == "]" {
+ state = parseKey
+ continue
+ }
+ if key != "as-path" {
+ fmt.Printf("key: %s, v: %s\n\n", key, v)
+ panic("can only do list for as-path")
+ }
+ if v == "(" {
+ state = parseSkip
+ continue
+ }
+
+ addASPath(&aspath, v)
+
+ }
+ case parseSkip:
+ if v == ")" {
+ state = parseList
+ }
+ }
+ }
+ // fmt.Printf("%#v / %#v\n", route, aspath)
+
+ _, prefix, err := net.ParseCIDR(route.Options["route"])
+ if err != nil {
+ fmt.Printf("Could not parse prefix %s %e\n", route.Options["route"], err)
+ panic("bad prefix")
+ }
+ route.Prefix = prefix
+ // fmt.Printf("IP: %s, PREFIX: %s\n", ip, prefix)
+
+ if len(aspath) > 0 {
+ route.PrimaryASN = ASN(aspath[len(aspath)-1])
+ }
+
+ if DEBUG {
+ fmt.Println("PREFIX", route.Prefix)
+ }
+
+ return route
+}
+
+func addASPath(aspath *ASPath, v string) {
+ asn, err := strconv.Atoi(v)
+ if err != nil {
+ fmt.Println("Could not parse number", v)
+ panic("Bad as-path")
+ }
+ *aspath = append(*aspath, ASN(asn))
+}
View
103 http.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/gorilla/mux"
+ "html/template"
+ "net/http"
+ "strconv"
+)
+
+type homePageData struct {
+ Title string
+ Neighbors Neighbors
+ Data map[string]string
+}
+
+func HomeHandler(w http.ResponseWriter, r *http.Request) {
+
+ tmpl, err := template.ParseFiles("templates/home.html", "templates/header.html", "templates/footer.html")
+ if err != nil {
+ fmt.Println("Could not parse templates", err)
+ fmt.Fprintln(w, "Problem parsing templates", err)
+ return
+ }
+
+ data := new(homePageData)
+ data.Title = "BGP Status"
+ data.Neighbors = neighbors
+
+ // fmt.Fprintf(w, "%s\t%s\t%v\n", neighbor, data.State, data.Updates)
+ // fmt.Printf("TMPL %s %#v\n", tmpl, tmpl)
+
+ tmpl.Execute(w, data)
+
+}
+
+func StatusHandler(w http.ResponseWriter, r *http.Request) {
+ for neighbor, data := range neighbors {
+ fmt.Fprintf(w, "%s\t%s\t%v\n", neighbor, data.State, data.Updates)
+ }
+}
+
+func ApiHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/json")
+
+ vars := mux.Vars(r)
+
+ neighbor_ip := vars["neighbor"]
+ neighbor, ok := neighbors[neighbor_ip]
+ if !ok {
+ w.WriteHeader(404)
+ return
+ }
+
+ // fmt.Printf("VARS: %#v\n", vars)
+
+ switch vars["method"] {
+ case "asn":
+ asn, err := strconv.Atoi(vars["id"])
+ if err != nil {
+ fmt.Fprintln(w, "Could not parse AS number")
+ return
+ }
+ prefixes := neighbor.AsnPrefix[ASN(asn)]
+
+ strPrefixes := make([]string, 0)
+
+ for prefix, _ := range prefixes {
+ strPrefixes = append(strPrefixes, prefix)
+ }
+
+ json, err := json.Marshal(map[string][]string{"prefixes": strPrefixes})
+ // json, err := json.Marshal(prefixes)
+
+ fmt.Fprint(w, string(json))
+
+ case "ip":
+ fmt.Fprintf(w, "/api/ip/%s not implemented\n", vars["id"])
+ return
+ case "prefixes":
+ prefixes := neighbor.PrefixAsn
+ json, err := json.Marshal(map[string]Prefixes{"prefixes": prefixes})
+ if err != nil {
+ fmt.Fprint(w, "error generating json", err)
+ }
+ fmt.Fprint(w, string(json))
+ default:
+ w.WriteHeader(404)
+ }
+ fmt.Fprint(w, "\n")
+}
+
+func httpServer() {
+ r := mux.NewRouter()
+ r.HandleFunc("/", HomeHandler)
+ r.HandleFunc("/api/{neighbor:[0-9.:]+}/{method:asn}/{id:[0-9]+}", ApiHandler)
+ r.HandleFunc("/api/{neighbor:[0-9.:]+}/{method:ip}/{id:[0-9.:]+}", ApiHandler)
+ r.HandleFunc("/api/{neighbor:[0-9.:]+}/{method:prefixes}", ApiHandler)
+ r.HandleFunc("/status", StatusHandler)
+ http.Handle("/", r)
+ http.ListenAndServe(":8080", nil)
+}
View
@@ -0,0 +1,2 @@
+ </body>
+</html>
View
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head><title>BGP Status</title>
+ <link href="http://st.pimg.net/cdn/libs/bootstrap/2/css/bootstrap.min.css" rel="stylesheet">
+ <style>
+ html,
+ body {
+ margin: 10px;
+ margin-top: 20px;
+ }
+ </style>
+ </head>
+ <body>
View
@@ -0,0 +1,41 @@
+{{template "header.html"}}
+
+<h1>{{.Title}}</h1>
+
+ <table class="table table-striped table-hover table-condensed">
+ <thead>
+ <tr>
+ <th>IP</th>
+ <th>State</th>
+ <th>Prefixes</th>
+ <th>ASNs</th>
+ <th>Updates</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {{range $ip, $data := .Neighbors}}
+ <tr>
+ <td>
+ {{$ip}}
+ </td>
+ <td>
+ {{$data.State}}
+ </td>
+ <td>
+ <a href="/api/{{$ip}}/prefixes">{{$data.PrefixCount}}</a>
+ </td>
+ <td>
+ {{$data.AsnCount}}
+ </td>
+ <td>
+ {{$data.Updates}}
+ </td>
+ <tr>
+ {{else}}
+ <tr><td>No neighbors</td></tr>
+ {{end}}
+ </tbody>
+ </table>
+
+{{template "footer.html"}}
Oops, something went wrong.

0 comments on commit 19b094a

Please sign in to comment.