Add bgpmap feature

This commit is contained in:
Lan Tian 2020-03-26 02:29:57 +08:00
parent 19fd44c28e
commit 96cca1adb6
No known key found for this signature in database
GPG Key ID: 27F31700E751EC22
4 changed files with 140 additions and 15 deletions

View File

@ -14,9 +14,6 @@ Features implemented:
- Query route (`show route for ...`, `show route where net ~ [ ... ]`)
- Whois and traceroute
- Work with both Python proxy (lgproxy.py) and Go proxy (proxy dir of this project)
Features not implemented yet:
- Visualize AS paths as picture (bgpmap feature)
Usage: all configuration is done via commandline parameters or environment variables, no config file.
@ -33,6 +30,7 @@ Example: the following command starts the frontend with 2 BIRD nodes, with domai
Example: the following docker-compose.yml entry does the same as above, but by starting a Docker container:
services:
bird-lg:
image: xddxdd/bird-lg-go
container_name: bird-lg
@ -66,7 +64,7 @@ Usage: all configuration is done via commandline parameters or environment varia
- --bird6 / BIRD6_SOCKET: socket file for bird6, set either in parameter or environment variable BIRD6_SOCKET (default "/var/run/bird/bird6.ctl")
- --listen / BIRDLG_LISTEN: listen address, set either in parameter or environment variable BIRDLG_LISTEN (default ":8000")
Example: start proxy with default configuration, should work "out of the box" on Debian 9:
Example: start proxy with default configuration, should work "out of the box" on Debian 9 with BIRDv1:
./proxy

78
frontend/bgpmap.go Normal file
View File

@ -0,0 +1,78 @@
package main
import (
"sort"
"strings"
)
func birdRouteToGraphviz(servers []string, responses []string, target string) string {
var edges string
edges += "\"Target: " + target + "\" [color=red,shape=diamond];\n"
for serverID, server := range servers {
response := responses[serverID]
if len(response) == 0 {
continue
}
edges += "\"" + server + "\" [color=blue,shape=box];\n"
routes := strings.Split(response, "\tvia ")
for routeIndex, route := range routes {
var routeNexthop string
var routeASPath string
var routePreferred bool = routeIndex > 0 && strings.Contains(routes[routeIndex-1], "*")
// Have to look at previous slice to determine if route is preferred, due to bad split point selection
for _, routeParameter := range strings.Split(route, "\n") {
if strings.HasPrefix(routeParameter, "\tBGP.next_hop: ") {
routeNexthop = strings.TrimPrefix(routeParameter, "\tBGP.next_hop: ")
} else if strings.HasPrefix(routeParameter, "\tBGP.as_path: ") {
routeASPath = strings.TrimPrefix(routeParameter, "\tBGP.as_path: ")
}
}
if len(routeNexthop) == 0 || len(routeASPath) == 0 {
continue
}
// Connect each node on AS path
paths := strings.Split(strings.TrimSpace(routeASPath), " ")
// First step starting from originating server
if len(paths) > 0 {
if len(routeNexthop) > 0 {
// Edge from originating server to nexthop
edges += "\"" + server + "\" -> \"Nexthop:\\n" + routeNexthop + "\"" + (map[bool]string{true: " [color=red]"})[routePreferred] + ";\n"
// and from nexthop to AS
edges += "\"Nexthop:\\n" + routeNexthop + "\" -> \"AS" + paths[0] + "\"" + (map[bool]string{true: " [color=red]"})[routePreferred] + ";\n"
edges += "\"Nexthop:\\n" + routeNexthop + "\" [shape=diamond];\n"
} else {
// Edge from originating server to AS
edges += "\"" + server + "\" -> \"AS" + paths[0] + "\"" + (map[bool]string{true: " [color=red]"})[routePreferred] + ";\n"
}
}
// Following steps, edges between AS
for pathIndex := range paths {
if pathIndex == 0 {
continue
}
edges += "\"AS" + paths[pathIndex-1] + "\" -> \"AS" + paths[pathIndex] + "\"" + (map[bool]string{true: " [color=red]"})[routePreferred] + ";\n"
}
// Last AS to destination
edges += "\"AS" + paths[len(paths)-1] + "\" -> \"Target: " + target + "\"" + (map[bool]string{true: " [color=red]"})[routePreferred] + ";\n"
}
if !strings.Contains(edges, "\""+server+"\" ->") {
// Cannot get path information from bird
edges += "\"" + server + "\" -> \"Target: " + target + "\" [color=gray,label=\"?\"]"
}
}
// Deduplication of edges: sort, then remove if current entry is prefix of next entry
var result string
edgesSorted := strings.Split(edges, ";\n")
sort.Strings(edgesSorted)
for edgeIndex, edge := range edgesSorted {
if edgeIndex >= len(edgesSorted)-1 || !strings.HasPrefix(edgesSorted[edgeIndex+1], edge) {
result += edge + ";\n"
}
}
return "digraph {\n" + result + "}\n"
}

View File

@ -69,14 +69,27 @@ func templateHeader(w http.ResponseWriter, r *http.Request, title string) {
}
// Add the options in navbar form, and check if they are active
var optionKeys = []string{"summary", "detail", "route", "route_all", "route_where", "route_where_all", "whois", "traceroute"}
var optionKeys = []string{
"summary",
"detail",
"route",
"route_all",
"route_bgpmap",
"route_where",
"route_where_all",
"route_where_bgpmap",
"whois",
"traceroute",
}
var optionDisplays = []string{
"show protocol",
"show protocol all",
"show route for ...",
"show route for ... all",
"show route for ... (bgpmap)",
"show route where net ~ [ ... ]",
"show route where net ~ [ ... ] all",
"show route where net ~ [ ... ] (bgpmap)",
"whois ...",
"traceroute ...",
}
@ -109,7 +122,9 @@ func templateHeader(w http.ResponseWriter, r *http.Request, title string) {
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/>
<meta name="renderer" content="webkit"/>
<title>` + title + `</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.2.1/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/viz.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/lite.render.js" crossorigin="anonymous"></script>
</head>
<body>
@ -143,6 +158,7 @@ func templateHeader(w http.ResponseWriter, r *http.Request, title string) {
func templateFooter(w http.ResponseWriter) {
w.Write([]byte(`
</div>
<div id="graphviz" class="overflow-auto"></div>
</body>
</html>
`))

View File

@ -49,6 +49,35 @@ func webBackendCommunicator(w http.ResponseWriter, r *http.Request, endpoint str
templateFooter(w)
}
func webHandlerBGPMap(w http.ResponseWriter, r *http.Request, endpoint string, command string) {
split := strings.Split(r.URL.Path[1:], "/")
urlCommands := strings.Join(split[3:], "/")
command = (map[string]string{
"route_bgpmap": "show route for " + urlCommands + " all",
"route_where_bgpmap": "show route where net ~ [ " + urlCommands + " ] all",
})[command]
templateHeader(w, r, "Bird-lg Go - "+html.EscapeString(endpoint+" "+command))
var servers []string = strings.Split(split[2], "+")
var responses []string = batchRequest(servers, endpoint, command)
w.Write([]byte(`
<script>
var viz = new Viz();
viz.renderSVGElement(` + "`" + birdRouteToGraphviz(servers, responses, urlCommands) + "`" + `)
.then(function(element) {
document.body.appendChild(element);
})
.catch(error => {
document.body.appendChild("<pre>"+error+"</pre>")
});
</script>`))
templateFooter(w)
}
func webHandlerNavbarFormRedirect(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
if query.Get("action") == "whois" {
@ -73,10 +102,14 @@ func webServerStart() {
http.HandleFunc("/ipv6/route/", func(w http.ResponseWriter, r *http.Request) { webBackendCommunicator(w, r, "bird6", "route") })
http.HandleFunc("/ipv4/route_all/", func(w http.ResponseWriter, r *http.Request) { webBackendCommunicator(w, r, "bird", "route_all") })
http.HandleFunc("/ipv6/route_all/", func(w http.ResponseWriter, r *http.Request) { webBackendCommunicator(w, r, "bird6", "route_all") })
http.HandleFunc("/ipv4/route_bgpmap/", func(w http.ResponseWriter, r *http.Request) { webHandlerBGPMap(w, r, "bird", "route_bgpmap") })
http.HandleFunc("/ipv6/route_bgpmap/", func(w http.ResponseWriter, r *http.Request) { webHandlerBGPMap(w, r, "bird6", "route_bgpmap") })
http.HandleFunc("/ipv4/route_where/", func(w http.ResponseWriter, r *http.Request) { webBackendCommunicator(w, r, "bird", "route_where") })
http.HandleFunc("/ipv6/route_where/", func(w http.ResponseWriter, r *http.Request) { webBackendCommunicator(w, r, "bird6", "route_where") })
http.HandleFunc("/ipv4/route_where_all/", func(w http.ResponseWriter, r *http.Request) { webBackendCommunicator(w, r, "bird", "route_where_all") })
http.HandleFunc("/ipv6/route_where_all/", func(w http.ResponseWriter, r *http.Request) { webBackendCommunicator(w, r, "bird6", "route_where_all") })
http.HandleFunc("/ipv4/route_where_bgpmap/", func(w http.ResponseWriter, r *http.Request) { webHandlerBGPMap(w, r, "bird", "route_where_bgpmap") })
http.HandleFunc("/ipv6/route_where_bgpmap/", func(w http.ResponseWriter, r *http.Request) { webHandlerBGPMap(w, r, "bird6", "route_where_bgpmap") })
http.HandleFunc("/ipv4/traceroute/", func(w http.ResponseWriter, r *http.Request) { webBackendCommunicator(w, r, "traceroute", "traceroute") })
http.HandleFunc("/ipv6/traceroute/", func(w http.ResponseWriter, r *http.Request) {
webBackendCommunicator(w, r, "traceroute6", "traceroute")