commit a9a07d4de42c0038f19b85c18ac8eab7e8d6b679 Author: Lan Tian Date: Tue Jan 8 01:32:30 2019 +0800 Initial commit diff --git a/frontend/lgproxy.go b/frontend/lgproxy.go new file mode 100644 index 0000000..c10389c --- /dev/null +++ b/frontend/lgproxy.go @@ -0,0 +1,51 @@ +package main + +import ( + "net/http" + "net/url" + "io/ioutil" + "strconv" +) + +type channelData struct { + id int + data string +} + +func batchRequest(servers []string, endpoint string, command string) []string { + var ch chan channelData = make(chan channelData) + var response_array []string = make([]string, len(servers)) + + for i, server := range servers { + var isValidServer bool = false + for _, validServer := range settingServers { + if validServer == server { + isValidServer = true + break + } + } + if !isValidServer { + go func (i int) { + ch <- channelData{i, "request failed: invalid server\n"} + } (i) + } else { + url := "http://" + server + "." + settingServersDomain + ":" + strconv.Itoa(settingServersPort) + "/" + url.PathEscape(endpoint) + "?q=" + url.QueryEscape(command) + go func (url string, i int){ + response, err := http.Get(url) + if err != nil { + ch <- channelData{i, "request failed: " + err.Error() + "\n"} + return + } + text, _ := ioutil.ReadAll(response.Body) + ch <- channelData{i, string(text)} + } (url, i) + } + } + + for range servers { + var output channelData = <-ch + response_array[output.id] = output.data + } + + return response_array +} diff --git a/frontend/main.go b/frontend/main.go new file mode 100644 index 0000000..4850475 --- /dev/null +++ b/frontend/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "flag" + "strings" +) + +var settingServers []string +var settingServersDomain string +var settingServersPort int +var settingWhoisServer string + +func main() { + serversPtr := flag.String("servers", "", "server name prefixes, separated by comma") + domainPtr := flag.String("domain", "", "server name domain suffixes") + portPtr := flag.Int("port", 8000, "port bird-lgproxy is running on") + whoisPtr := flag.String("whois", "whois.verisign-grs.com", "whois server for queries") + flag.Parse() + + settingServers = strings.Split(*serversPtr, ",") + settingServersDomain = *domainPtr + settingServersPort = *portPtr + settingWhoisServer = *whoisPtr + webServerStart() +} diff --git a/frontend/template.go b/frontend/template.go new file mode 100644 index 0000000..a125524 --- /dev/null +++ b/frontend/template.go @@ -0,0 +1,243 @@ +package main + +import ( + "net" + "net/http" + "strings" + "strconv" +) + +func templateHeader(w http.ResponseWriter, r *http.Request, title string) { + path := r.URL.Path + if len(strings.Split(r.URL.Path, "/")) < 4 { + path = "/ipv4/summary/" + strings.Join(settingServers[:], "+") + "/" + } + + split := strings.Split(path, "/") + split[1] = "ipv4" + ipv4_url := strings.Join(split, "/") + + split = strings.Split(path, "/") + split[1] = "ipv6" + ipv6_url := strings.Join(split, "/") + + split = strings.Split(path, "/") + split[3] = strings.Join(settingServers[:], "+") + all_url := strings.Join(split, "/") + + split = strings.Split(path, "/") + var serverAllActive string + if split[3] == strings.Join(settingServers[:], "+") { + serverAllActive = " active" + } + + var serverNavigation string = ` + + + | + + ` + for _, server := range settingServers { + split = strings.Split(path, "/") + var serverActive string + if split[3] == server { + serverActive = " active" + } + split[3] = server + server_url := strings.Join(split, "/") + + serverNavigation += ` + + ` + } + + var options string + split = strings.Split(path, "/") + if split[2] == "summary" { + options += `` + } else { + options += `` + } + if split[2] == "route" { + options += `` + } else { + options += `` + } + if split[2] == "route_all" { + options += `` + } else { + options += `` + } + if split[2] == "whois" { + options += `` + } else { + options += `` + } + + var target string + if len(split) >= 5 { + target = split[4] + } + + w.Write([]byte(` + + + + + + + +` + title + ` + + + + + + +
+ `)) +} + +func templateFooter(w http.ResponseWriter) { + w.Write([]byte(` +
+ + + + `)) +} + +func isIP(s string) bool { + return nil != net.ParseIP(s) +} + +func isNumber(s string) bool { + _, err := strconv.Atoi(s) + return nil == err +} + +func smartWriter(w http.ResponseWriter, s string) { + w.Write([]byte("
"))
+    for _, line := range strings.Split(s, "\n") {
+        var tabPending bool = false
+        var isFirstWord bool = true
+        var isASes bool = false
+        for _, word := range strings.Split(line, " ") {
+            if len(word) == 0 {
+                tabPending = true
+            } else {
+                if isFirstWord {
+                    isFirstWord = false
+                } else if tabPending {
+                    w.Write([]byte("\t"))
+                    tabPending = false
+                } else {
+                    w.Write([]byte(" "))
+                }
+
+                if isIP(word) {
+                    w.Write([]byte("" + word + ""))
+                } else if len(strings.Split(word, "%")) == 2 && isIP(strings.Split(word, "%")[0]) {
+                    w.Write([]byte("" + strings.Split(word, "%")[0] + ""))
+                    w.Write([]byte("%" + strings.Split(word, "%")[1]))
+                } else if len(strings.Split(word, "/")) == 2 && isIP(strings.Split(word, "/")[0]) {
+                    w.Write([]byte("" + strings.Split(word, "/")[0] + ""))
+                    w.Write([]byte("/" + strings.Split(word, "/")[1]))
+                } else if word == "AS:" || word == "\tBGP.as_path:" {
+                    isASes = true
+                    w.Write([]byte(word))
+                } else if isASes && isNumber(word) {
+                    w.Write([]byte("" + word + ""))
+                } else {
+                    w.Write([]byte(word))
+                }
+            }
+        }
+        w.Write([]byte("\n"))
+    }
+    w.Write([]byte("
")) +} + +func summaryTable(w http.ResponseWriter, isIPv6 bool, data string, serverName string) { + w.Write([]byte("")) + for lineId, line := range strings.Split(data, "\n") { + var tabPending bool = false + var tableCells int = 0 + var row [6]string + for i, word := range strings.Split(line, " ") { + if len(word) == 0 { + tabPending = true + } else { + if i == 0 { + tabPending = true + } else if tabPending { + if tableCells < 5 { + tableCells++ + } else { + row[tableCells] += " " + } + tabPending = false + } else { + row[tableCells] += " " + } + row[tableCells] += word + } + } + + if len(row[0]) == 0 { + continue + } + if lineId == 0 { + w.Write([]byte("")) + for i := 0; i < 6; i++ { + w.Write([]byte("")) + } + w.Write([]byte("")) + } else { + if row[3] == "up" { + w.Write([]byte("")) + } else if lineId != 0 { + w.Write([]byte("")) + } + if isIPv6 { + w.Write([]byte("")) + } else { + w.Write([]byte("")) + } + for i := 1; i < 6; i++ { + w.Write([]byte("")) + } + w.Write([]byte("")) + } + } + w.Write([]byte("
" + row[i] + "
" + row[0] + "" + row[0] + "" + row[i] + "
")) +} diff --git a/frontend/webserver.go b/frontend/webserver.go new file mode 100644 index 0000000..24250ef --- /dev/null +++ b/frontend/webserver.go @@ -0,0 +1,110 @@ +package main + +import ( + "net/http" + "strings" + "html" +) + +func webDispatcherIPv4Summary(w http.ResponseWriter, r *http.Request) { + webHandler(w, r, false, r.URL.Path[len("/ipv4/summary/"):], "show protocols") +} + +func webDispatcherIPv6Summary(w http.ResponseWriter, r *http.Request) { + webHandler(w, r, true, r.URL.Path[len("/ipv6/summary/"):], "show protocols") +} + +func webDispatcherIPv4Detail(w http.ResponseWriter, r *http.Request) { + split := strings.Split(r.URL.Path[len("/ipv4/detail/"):], "/") + webHandler(w, r, false, split[0], "show protocols all " + split[1]) +} + +func webDispatcherIPv6Detail(w http.ResponseWriter, r *http.Request) { + split := strings.Split(r.URL.Path[len("/ipv6/detail/"):], "/") + webHandler(w, r, true, split[0], "show protocols all " + split[1]) +} + +func webDispatcherIPv4Route(w http.ResponseWriter, r *http.Request) { + split := strings.Split(r.URL.Path[len("/ipv4/route/"):], "/") + webHandler(w, r, false, split[0], "show route for " + strings.Join(split[1:], "/")) +} + +func webDispatcherIPv6Route(w http.ResponseWriter, r *http.Request) { + split := strings.Split(r.URL.Path[len("/ipv6/route/"):], "/") + webHandler(w, r, true, split[0], "show route for " + strings.Join(split[1:], "/")) +} + +func webDispatcherIPv4RouteAll(w http.ResponseWriter, r *http.Request) { + split := strings.Split(r.URL.Path[len("/ipv4/route_all/"):], "/") + webHandler(w, r, false, split[0], "show route for " + strings.Join(split[1:], "/") + " all") +} + +func webDispatcherIPv6RouteAll(w http.ResponseWriter, r *http.Request) { + split := strings.Split(r.URL.Path[len("/ipv6/route_all/"):], "/") + webHandler(w, r, true, split[0], "show route for " + strings.Join(split[1:], "/") + " all") +} + +func webDispatcherWhois(w http.ResponseWriter, r *http.Request) { + var target string = r.URL.Path[len("/whois/"):] + + templateHeader(w, r, "Bird-lg Go - WHOIS " + html.EscapeString(target)) + + w.Write([]byte("

whois " + html.EscapeString(target) + "

")) + smartWriter(w, whois(target)) + + templateFooter(w) +} + +func webHandler(w http.ResponseWriter, r *http.Request, isIPv6 bool, serverQuery string, command string) { + templateHeader(w, r, "Bird-lg Go - " + html.EscapeString(command)) + + var servers []string = strings.Split(serverQuery, "+") + + var responses []string + if isIPv6 { + responses = batchRequest(servers, "bird6", command) + } else { + responses = batchRequest(servers, "bird", command) + } + for i, response := range responses { + w.Write([]byte("

" + html.EscapeString(servers[i]) + ": " + html.EscapeString(command) + "

")) + if command == "show protocols" && response[0:4] == "name" { + summaryTable(w, isIPv6, response, servers[i]) + } else { + smartWriter(w, response) + } + } + + templateFooter(w) +} + +func defaultRedirect(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/ipv4/summary/" + strings.Join(settingServers[:], "+"), 302) +} + +func navbarFormRedirect(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + if query.Get("action") == "whois" { + http.Redirect(w, r, "/" + query.Get("action") + "/" + query.Get("target"), 302) + } else if query.Get("action") == "summary" { + http.Redirect(w, r, "/" + query.Get("proto") + "/" + query.Get("action") + "/" + query.Get("server"), 302) + } else { + http.Redirect(w, r, "/" + query.Get("proto") + "/" + query.Get("action") + "/" + query.Get("server") + "/" + query.Get("target"), 302) + } +} + +func webServerStart() { + // Start HTTP server + http.HandleFunc("/", defaultRedirect) + http.HandleFunc("/ipv4/summary/", webDispatcherIPv4Summary) + http.HandleFunc("/ipv6/summary/", webDispatcherIPv6Summary) + http.HandleFunc("/ipv4/detail/", webDispatcherIPv4Detail) + http.HandleFunc("/ipv6/detail/", webDispatcherIPv6Detail) + http.HandleFunc("/ipv4/route/", webDispatcherIPv4Route) + http.HandleFunc("/ipv6/route/", webDispatcherIPv6Route) + http.HandleFunc("/ipv4/route_all/", webDispatcherIPv4RouteAll) + http.HandleFunc("/ipv6/route_all/", webDispatcherIPv6RouteAll) + http.HandleFunc("/whois/", webDispatcherWhois) + http.HandleFunc("/redir/", navbarFormRedirect) + http.ListenAndServe(":5000", nil) +} diff --git a/frontend/whois.go b/frontend/whois.go new file mode 100644 index 0000000..327d220 --- /dev/null +++ b/frontend/whois.go @@ -0,0 +1,21 @@ +package main + +import ( + "net" + "io/ioutil" +) + +func whois(s string) string { + conn, err := net.Dial("tcp", settingWhoisServer + ":43") + if err != nil { + return err.Error() + } + defer conn.Close() + + conn.Write([]byte(s + "\r\n")) + result, err := ioutil.ReadAll(conn) + if err != nil { + return err.Error() + } + return string(result) +} diff --git a/proxy/main.go b/proxy/main.go new file mode 100644 index 0000000..113e60b --- /dev/null +++ b/proxy/main.go @@ -0,0 +1,200 @@ +package main + +import ( + "io" + "net" + "net/http" + "sync" + "runtime" + "os/exec" + "flag" +) + +// BIRDv4 connection & mutex lock +var bird net.Conn +var birdMutex = &sync.Mutex{} + +// BIRDv6 connection & mutex lock +var bird6 net.Conn +var bird6Mutex = &sync.Mutex{} + +// Check if a byte is character for number +func isNumeric(b byte) bool { + return b >= byte('0') && b <= byte('9') +} + +// Read a line from bird socket, removing preceding status number, output it. +// Returns if there are more lines. +func birdReadln(bird io.Reader, w io.Writer) bool { + // Read from socket byte by byte, until reaching newline character + c := make([]byte, 1024, 1024) + pos := 0 + for { + if pos >= 1024 { break } + _, err := bird.Read(c[pos:pos+1]) + if err != nil { + panic(err) + } + if c[pos] == byte('\n') { + break + } + pos++ + } + + c = c[:pos+1] + // print(string(c[:])) + + // Remove preceding status number, different situations + if pos < 4 { + // Line is too short to have a status number + if w != nil { + pos = 0 + for c[pos] == byte(' ') { pos++ } + w.Write(c[pos:]) + } + return true + } else if isNumeric(c[0]) && isNumeric(c[1]) && isNumeric(c[2]) && isNumeric(c[3]) { + // There is a status number at beginning, remove first 5 bytes + if w != nil && pos > 6 { + pos = 5 + for c[pos] == byte(' ') { pos++ } + w.Write(c[pos:]) + } + return c[0] != byte('0') && c[0] != byte('8') && c[0] != byte('9') + } else { + // There is no status number, only remove preceding spaces + if w != nil { + pos = 0 + for c[pos] == byte(' ') { pos++ } + w.Write(c[pos:]) + } + return true + } +} + +// Write a command to a bird socket +func birdWriteln(bird io.Writer, s string) { + bird.Write([]byte(s + "\n")) +} + +// Default handler, returns 500 Internal Server Error +func invalidHandler(httpW http.ResponseWriter, httpR *http.Request) { + httpW.WriteHeader(http.StatusInternalServerError) + httpW.Write([]byte("Invalid Request\n")) +} + +// Handles BIRDv4 queries +func birdHandler(httpW http.ResponseWriter, httpR *http.Request) { + query := string(httpR.URL.Query().Get("q")) + if query == "" { + invalidHandler(httpW, httpR) + } else { + birdMutex.Lock() + defer birdMutex.Unlock() + + println(query) + birdWriteln(bird, query) + for birdReadln(bird, httpW) {} + } +} + +// Handles BIRDv6 queries +func bird6Handler(httpW http.ResponseWriter, httpR *http.Request) { + query := string(httpR.URL.Query().Get("q")) + if query == "" { + invalidHandler(httpW, httpR) + } else { + bird6Mutex.Lock() + defer bird6Mutex.Unlock() + + println(query) + birdWriteln(bird6, query) + for birdReadln(bird6, httpW) {} + } +} + +// Wrapper of traceroute, IPv4 +func tracerouteIPv4Wrapper(httpW http.ResponseWriter, httpR *http.Request) { + tracerouteRealHandler(false, httpW, httpR) +} + +// Wrapper of traceroute, IPv6 +func tracerouteIPv6Wrapper(httpW http.ResponseWriter, httpR *http.Request) { + tracerouteRealHandler(true, httpW, httpR) +} + +// Real handler of traceroute requests +func tracerouteRealHandler(useIPv6 bool, httpW http.ResponseWriter, httpR *http.Request) { + query := string(httpR.URL.Query().Get("q")) + if query == "" { + invalidHandler(httpW, httpR) + } else { + var cmd string + var args []string + if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" { + if useIPv6 { cmd = "traceroute6" } else { cmd = "traceroute" } + args = []string{"-a", "-q1", "-w1", "-m15", query} + } else if runtime.GOOS == "openbsd" { + if useIPv6 { cmd = "traceroute6" } else { cmd = "traceroute" } + args = []string{"-A", "-q1", "-w1", "-m15", query} + } else if runtime.GOOS == "linux" { + cmd = "traceroute" + if useIPv6 { + args = []string{"-6", "-A", "-q1", "-N32", "-w1", "-m15", query} + } else { + args = []string{"-4", "-A", "-q1", "-N32", "-w1", "-m15", query} + } + } else { + httpW.WriteHeader(http.StatusInternalServerError) + httpW.Write([]byte("Traceroute Not Supported\n")) + return + } + instance := exec.Command(cmd, args...) + output, err := instance.Output() + if err != nil { + httpW.WriteHeader(http.StatusInternalServerError) + httpW.Write([]byte("Traceroute Execution Error: ")) + httpW.Write([]byte(err.Error() + "\n")) + return + } + httpW.Write(output) + } +} + +func main() { + var err error + + birdPtr := flag.String("bird", "/var/run/bird/bird.ctl", "socket file for bird") + bird6Ptr := flag.String("bird6", "/var/run/bird/bird6.ctl", "socket file for bird6") + flag.Parse() + + // Initialize BIRDv4 socket + bird, err = net.Dial("unix", *birdPtr) + if err != nil { + panic(err) + } + defer bird.Close() + + birdReadln(bird, nil) + birdWriteln(bird, "restrict") + birdReadln(bird, nil) + + // Initialize BIRDv6 socket + bird6, err = net.Dial("unix", *bird6Ptr) + if err != nil { + panic(err) + } + defer bird6.Close() + + birdReadln(bird6, nil) + birdWriteln(bird6, "restrict") + birdReadln(bird6, nil) + + // Start HTTP server + http.HandleFunc("/", invalidHandler) + http.HandleFunc("/bird", birdHandler) + http.HandleFunc("/bird6", bird6Handler) + http.HandleFunc("/traceroute", tracerouteIPv4Wrapper) + http.HandleFunc("/traceroute6", tracerouteIPv6Wrapper) + http.ListenAndServe(":8000", nil) +}