diff --git a/proxy/bird.go b/proxy/bird.go new file mode 100644 index 0000000..acb83ec --- /dev/null +++ b/proxy/bird.go @@ -0,0 +1,100 @@ +package main + +import ( + "io" + "net" + "net/http" + "sync" +) + +// BIRDv4 connection & mutex lock +var bird net.Conn +var birdMutex = &sync.Mutex{} + +// BIRDv6 connection & mutex lock +var bird6 net.Conn +var bird6Mutex = &sync.Mutex{} + +// 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")) +} + +// 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) {} + } +} diff --git a/proxy/main.go b/proxy/main.go index f4835d6..bc8e76b 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -1,187 +1,50 @@ package main import ( - "io" "net" "net/http" - "sync" - "runtime" - "os/exec" "flag" + "os" ) -// 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 && runtime.GOOS == "linux" { - // Standard traceroute utility failed, maybe system using busybox - // Run with less parameters - cmd = "traceroute" - if useIPv6 { - args = []string{"-6", "-q1", "-w1", "-m15", query} - } else { - args = []string{"-4", "-q1", "-w1", "-m15", query} - } - 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) - } -} - +// Wrapper of tracer 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") + // Prepare default socket paths, use environment variable if possible + birdSocketDefault := "/var/run/bird/bird.ctl" + bird6SocketDefault := "/var/run/bird/bird6.ctl" + listenDefault := ":8000" + + if birdSocketEnv := os.Getenv("BIRD_SOCKET"); birdSocketEnv != "" { + birdSocketDefault = birdSocketEnv + } + if bird6SocketEnv := os.Getenv("BIRD6_SOCKET"); bird6SocketEnv != "" { + bird6SocketDefault = bird6SocketEnv + } + if listenEnv := os.Getenv("BIRDLG_LISTEN"); listenEnv != "" { + listenDefault = listenEnv + } + + // Allow parameters to override environment variables + birdParam := flag.String("bird", birdSocketDefault, "socket file for bird, set either in parameter or environment variable BIRD_SOCKET") + bird6Param := flag.String("bird6", bird6SocketDefault, "socket file for bird6, set either in parameter or environment variable BIRD6_SOCKET") + listenParam := flag.String("listen", listenDefault, "listen address, set either in parameter or environment variable BIRDLG_LISTEN") flag.Parse() // Initialize BIRDv4 socket - bird, err = net.Dial("unix", *birdPtr) + bird, err = net.Dial("unix", *birdParam) if err != nil { panic(err) } @@ -192,7 +55,7 @@ func main() { birdReadln(bird, nil) // Initialize BIRDv6 socket - bird6, err = net.Dial("unix", *bird6Ptr) + bird6, err = net.Dial("unix", *bird6Param) if err != nil { panic(err) } @@ -208,5 +71,5 @@ func main() { http.HandleFunc("/bird6", bird6Handler) http.HandleFunc("/traceroute", tracerouteIPv4Wrapper) http.HandleFunc("/traceroute6", tracerouteIPv6Wrapper) - http.ListenAndServe(":8000", nil) + http.ListenAndServe(*listenParam, nil) } diff --git a/proxy/traceroute.go b/proxy/traceroute.go new file mode 100644 index 0000000..61b0aa6 --- /dev/null +++ b/proxy/traceroute.go @@ -0,0 +1,67 @@ +package main + +import ( + "net/http" + "runtime" + "os/exec" +) + +// 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 && runtime.GOOS == "linux" { + // Standard traceroute utility failed, maybe system using busybox + // Run with less parameters + cmd = "traceroute" + if useIPv6 { + args = []string{"-6", "-q1", "-w1", "-m15", query} + } else { + args = []string{"-4", "-q1", "-w1", "-m15", query} + } + 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) + } +}