1
mirror of https://github.com/xddxdd/bird-lg-go synced 2025-11-11 21:57:34 +01:00

5 Commits

Author SHA1 Message Date
Lan Tian
e7010f75f8 release: v1.2.0 2023-01-06 23:05:05 -06:00
Yuhui Xu
dba2af7634 proxy: fix description for --traceroute_flags (#70) 2022-12-27 15:38:41 -06:00
Yuhui Xu
049775319b proxy: autodetect traceroute args on startup (#69) 2022-12-25 15:41:29 -06:00
Lan Tian
47c66b125c release: v1.1.1 2022-12-18 16:26:47 -06:00
Yuhui Xu
9e17b116f1 frontend: refactor bgpmap and fix node colors (#67)
* frontend: refactor bgpmap and fix node colors

* frontend: alternative way to test bgpmap
2022-12-07 16:30:19 -06:00
7 changed files with 211 additions and 98 deletions

View File

@@ -58,8 +58,6 @@ Configuration can be set in:
Configuration is handled by [viper](https://github.com/spf13/viper), any config format supported by it can be used.
> Note: the config system is replaced with viper only recently (2022-07-08). If some config items do not work, please open an issue, and use commit [892a7bee22a1bb02d3b4da6d270c65b6e4e1321a](https://github.com/xddxdd/bird-lg-go/tree/892a7bee22a1bb02d3b4da6d270c65b6e4e1321a) (last version before config system replace) for the time being.
| Config Key | Parameter | Environment Variable | Description |
| ---------- | --------- | -------------------- | ----------- |
| servers | --servers | BIRDLG_SERVERS | server name prefixes, separated by comma |
@@ -79,6 +77,8 @@ Configuration is handled by [viper](https://github.com/spf13/viper), any config
| name_filter | --name-filter | BIRDLG_NAME_FILTER | protocol names to hide in summary tables (RE2 syntax); defaults to none if not set |
| timeout | --time-out | BIRDLG_TIMEOUT | time before request timed out, in seconds; defaults to 120 if not set |
### Examples
Example: the following command starts the frontend with 2 BIRD nodes, with domain name "gigsgigscloud.dn42.lantian.pub" and "hostdare.dn42.lantian.pub", and proxies are running on port 8000 on both nodes.
```bash
@@ -122,16 +122,32 @@ Configuration can be set in:
Configuration is handled by [viper](https://github.com/spf13/viper), any config format supported by it can be used.
> Note: the config system is replaced with viper only recently (2022-07-08). If some config items do not work, please open an issue, and use commit [892a7bee22a1bb02d3b4da6d270c65b6e4e1321a](https://github.com/xddxdd/bird-lg-go/tree/892a7bee22a1bb02d3b4da6d270c65b6e4e1321a) (last version before config system replace) for the time being.
| Config Key | Parameter | Environment Variable | Description |
| ---------- | --------- | -------------------- | ----------- |
| allowed_ips | --allowed | ALLOWED_IPS | IPs allowed to access this proxy, separated by commas. Don't set to allow all IPs. (default "") |
| bird_socket | --bird | BIRD_SOCKET | socket file for bird, set either in parameter or environment variable BIRD_SOCKET (default "/var/run/bird/bird.ctl") |
| listen | --listen | BIRDLG_PROXY_PORT | listen address, set either in parameter or environment variable BIRDLG_PROXY_PORT(default "8000") |
| traceroute_bin | --traceroute_bin | BIRDLG_TRACEROUTE_BIN | traceroute binary file, set either in parameter or environment variable BIRDLG_TRACEROUTE_BIN(default "traceroute") |
| traceroute_bin | --traceroute_bin | BIRDLG_TRACEROUTE_BIN | traceroute binary file, set either in parameter or environment variable BIRDLG_TRACEROUTE_BIN |
| traceroute_flags | --traceroute_flags | BIRDLG_TRACEROUTE_FLAGS | traceroute flags, supports multiple flags separated with space. |
| traceroute_raw | --traceroute_raw | BIRDLG_TRACEROUTE_RAW | whether to display traceroute outputs raw (default false) |
### Traceroute Binary Autodetection
If `traceroute_bin` or `traceroute_flags` is not set, then on startup, the proxy will try to `traceroute 127.0.0.1` with different traceroute binaries and arguments, in order to use the most optimized setting available, while maintaining compatibility with multiple variants of traceroute binaries.
Traceroute binaries will be autodetected in the following order:
1. If `traceroute_bin` is set:
1. `[traceroute_bin] -q1 -N32 -w1 127.0.0.1` (Corresponds to Traceroute on Debian)
2. `[traceroute_bin] -q1 -w1 127.0.0.1` (Corresponds to Traceroute on FreeBSD)
3. `[traceroute_bin] 127.0.0.1` (Corresponds to Busybox Traceroute)
2. `mtr -w -c1 -Z1 -G1 -b 127.0.0.1` (MTR)
3. `traceroute -q1 -N32 -w1 127.0.0.1` (Corresponds to Traceroute on Debian)
4. `traceroute -q1 -w1 127.0.0.1` (Corresponds to Traceroute on FreeBSD)
5. `traceroute 127.0.0.1` (Corresponds to Busybox Traceroute)
### Examples
Example: start proxy with default configuration, should work "out of the box" on Debian 9 with BIRDv1:
```bash

View File

@@ -1 +1 @@
v1.1.0
v1.2.0

View File

@@ -1,13 +1,22 @@
package main
import (
"encoding/json"
"fmt"
"html"
"net"
"regexp"
"strings"
)
func graphvizEscape(s string) string {
result, err := json.Marshal(s)
if err != nil {
return err.Error()
} else {
return string(result)
}
}
func getASNRepresentation(asn string) string {
if setting.dnsInterface != "" {
// get ASN representation using DNS
@@ -15,9 +24,9 @@ func getASNRepresentation(asn string) string {
if err == nil {
result := strings.Join(records, " ")
if resultSplit := strings.Split(result, " | "); len(resultSplit) > 1 {
result = strings.Join(resultSplit[1:], "\\n")
result = strings.Join(resultSplit[1:], "\n")
}
return fmt.Sprintf("AS%s\\n%s", asn, result)
return fmt.Sprintf("AS%s\n%s", asn, result)
}
}
@@ -67,26 +76,28 @@ func getASNRepresentation(asn string) string {
}
func birdRouteToGraphviz(servers []string, responses []string, target string) string {
graph := make(map[string]string)
graph := make(map[string](map[string]string))
// Helper to add an edge
addEdge := func(src string, dest string, attr string) {
key := "\"" + html.EscapeString(src) + "\" -> \"" + html.EscapeString(dest) + "\""
addEdge := func(src string, dest string, attrKey string, attrValue string) {
key := graphvizEscape(src) + " -> " + graphvizEscape(dest)
_, present := graph[key]
// If there are multiple edges / routes between 2 nodes, only pick the first one
if present {
return
if !present {
graph[key] = map[string]string{}
}
if attrKey != "" {
graph[key][attrKey] = attrValue
}
graph[key] = attr
}
// Helper to set attribute for a point in graph
addPoint := func(name string, attr string) {
key := "\"" + html.EscapeString(name) + "\""
addPoint := func(name string, attrKey string, attrValue string) {
key := graphvizEscape(name)
_, present := graph[key]
// Do not remove point's attributes if it's already present
if present && len(attr) == 0 {
return
if !present {
graph[key] = map[string]string{}
}
if attrKey != "" {
graph[key][attrKey] = attrValue
}
graph[key] = attr
}
// The protocol name for each route (e.g. "ibgp_sea02") is encoded in the form:
// unicast [ibgp_sea02 2021-08-27 from fd86:bad:11b7:1::1] * (100/1015) [i]
@@ -95,13 +106,16 @@ func birdRouteToGraphviz(servers []string, responses []string, target string) st
// Possible values are defined at https://gitlab.nic.cz/labs/bird/-/blob/v2.0.8/nest/rt-attr.c#L81-87
routeSplitRe := regexp.MustCompile("(unicast|blackhole|unreachable|prohibited)")
addPoint("Target: "+target, "[color=red,shape=diamond]")
addPoint("Target: "+target, "color", "red")
addPoint("Target: "+target, "shape", "diamond")
for serverID, server := range servers {
response := responses[serverID]
if len(response) == 0 {
continue
}
addPoint(server, "[color=blue,shape=box]")
addPoint(server, "color", "blue")
addPoint(server, "shape", "box")
routes := routeSplitRe.Split(response, -1)
targetNodeName := "Target: " + target
@@ -153,15 +167,16 @@ func birdRouteToGraphviz(servers []string, responses []string, target string) st
// First step starting from originating server
if len(paths) > 0 {
attrs := []string{"fontsize=12.0"}
edgeTarget := getASNRepresentation(paths[0])
addEdge(server, edgeTarget, "fontsize", "12.0")
if routePreferred {
attrs = append(attrs, "color=red")
addEdge(server, edgeTarget, "color", "red")
// Only set color for next step, origin color is set to blue above
addPoint(edgeTarget, "color", "red")
}
if len(routeNexthop) > 0 {
attrs = append(attrs, fmt.Sprintf("label=\"%s\\n%s\"", protocolName, routeNexthop))
addEdge(server, edgeTarget, "label", protocolName + "\n" + routeNexthop)
}
formattedAttr := fmt.Sprintf("[%s]", strings.Join(attrs, ","))
addEdge(server, getASNRepresentation(paths[0]), formattedAttr)
}
// Following steps, edges between AS
@@ -169,29 +184,51 @@ func birdRouteToGraphviz(servers []string, responses []string, target string) st
if pathIndex == 0 {
continue
}
addEdge(getASNRepresentation(paths[pathIndex-1]), getASNRepresentation(paths[pathIndex]), (map[bool]string{true: "[color=red]"})[routePreferred])
if routePreferred {
addEdge(getASNRepresentation(paths[pathIndex-1]), getASNRepresentation(paths[pathIndex]), "color", "red")
// Only set color for next step, origin color is set to blue above
addPoint(getASNRepresentation(paths[pathIndex]), "color", "red")
} else {
addEdge(getASNRepresentation(paths[pathIndex-1]), getASNRepresentation(paths[pathIndex]), "", "")
}
}
// Last AS to destination
addEdge(getASNRepresentation(paths[len(paths)-1]), targetNodeName, (map[bool]string{true: "[color=red]"})[routePreferred])
if routePreferred {
addEdge(getASNRepresentation(paths[len(paths)-1]), targetNodeName, "color", "red")
} else {
addEdge(getASNRepresentation(paths[len(paths)-1]), targetNodeName, "", "")
}
}
if len(nonBGPRoutes) > 0 {
protocolsForRoute := fmt.Sprintf("label=\"%s\"", strings.Join(nonBGPRoutes, "\\n"))
attrs := []string{protocolsForRoute, "fontsize=12.0"}
addEdge(server, targetNodeName, "label", strings.Join(nonBGPRoutes, "\n"))
addEdge(server, targetNodeName, "fontsize", "12.0")
if nonBGPRoutePreferred {
attrs = append(attrs, "color=red")
addEdge(server, targetNodeName, "color", "red")
}
formattedAttr := fmt.Sprintf("[%s]", strings.Join(attrs, ","))
addEdge(server, targetNodeName, formattedAttr)
}
}
// Combine all graphviz commands
var result string
for edge, attr := range graph {
result += edge + " " + attr + ";\n"
result += edge;
if len(attr) != 0 {
result += " ["
isFirst := true
for k, v := range attr {
if isFirst {
isFirst = false
} else {
result += ","
}
result += graphvizEscape(k) + "=" + graphvizEscape(v) + "";
}
result += "]"
}
result += ";\n"
}
return "digraph {\n" + result + "}\n"
}

View File

@@ -5,6 +5,16 @@ import (
"testing"
)
func contains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}
func TestGetASNRepresentationDNS(t *testing.T) {
checkNetwork(t)
@@ -36,6 +46,7 @@ func TestGetASNRepresentationFallback(t *testing.T) {
}
}
// Broken due to random order of attributes
func TestBirdRouteToGraphviz(t *testing.T) {
setting.dnsInterface = ""
@@ -48,12 +59,13 @@ func TestBirdRouteToGraphviz(t *testing.T) {
BGP.as_path: 4242422601
BGP.next_hop: 172.18.0.2`
expectedResult := `digraph {
"Target: 192.168.0.1" [color=red,shape=diamond];
"alpha" [color=blue,shape=box];
"alpha" -> "AS4242422601" [fontsize=12.0,color=red,label="alpha*\n172.18.0.2"];
"AS4242422601" -> "Target: 192.168.0.1" [color=red];
}`
expectedLinesInResult := []string{
`"AS4242422601" [`,
`"AS4242422601" -> "Target: 192.168.0.1" [`,
`"Target: 192.168.0.1" [`,
`"alpha" [`,
`"alpha" -> "AS4242422601" [`,
}
result := birdRouteToGraphviz([]string{
"alpha",
@@ -61,9 +73,10 @@ func TestBirdRouteToGraphviz(t *testing.T) {
fakeResult,
}, "192.168.0.1")
for _, line := range strings.Split(result, "\n") {
if !strings.Contains(expectedResult, line) {
t.Errorf("Unexpected line in result: %s", line)
for _, line := range expectedLinesInResult {
if !strings.Contains(result, line) {
t.Errorf("Expected line in result not found: %s", line)
}
}
}

View File

@@ -1,6 +1,7 @@
package main
import (
"fmt"
"net"
"net/http"
"os"
@@ -54,6 +55,7 @@ type settingType struct {
listen string
allowedIPs []string
tr_bin string
tr_flags []string
tr_raw bool
}
@@ -62,6 +64,9 @@ var setting settingType
// Wrapper of tracer
func main() {
parseSettings()
tracerouteAutodetect()
fmt.Printf("Listening on %s...\n", setting.listen)
var l net.Listener
var err error

View File

@@ -4,16 +4,18 @@ import (
"fmt"
"strings"
"github.com/google/shlex"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
type viperSettingType struct {
BirdSocket string `mapstructure:"bird_socket"`
Listen string `mapstructure:"listen"`
AllowedIPs string `mapstructure:"allowed_ips"`
TracerouteBin string `mapstructure:"traceroute_bin"`
TracerouteRaw bool `mapstructure:"traceroute_raw"`
BirdSocket string `mapstructure:"bird_socket"`
Listen string `mapstructure:"listen"`
AllowedIPs string `mapstructure:"allowed_ips"`
TracerouteBin string `mapstructure:"traceroute_bin"`
TracerouteFlags string `mapstructure:"traceroute_flags"`
TracerouteRaw bool `mapstructure:"traceroute_raw"`
}
// Parse settings with viper, and convert to legacy setting format
@@ -39,9 +41,12 @@ func parseSettings() {
pflag.String("allowed", "", "IPs allowed to access this proxy, separated by commas. Don't set to allow all IPs.")
viper.BindPFlag("allowed_ips", pflag.Lookup("allowed"))
pflag.String("traceroute_bin", "traceroute", "traceroute binary file, set either in parameter or environment variable BIRDLG_TRACEROUTE_BIN")
pflag.String("traceroute_bin", "", "traceroute binary file, set either in parameter or environment variable BIRDLG_TRACEROUTE_BIN")
viper.BindPFlag("traceroute_bin", pflag.Lookup("traceroute_bin"))
pflag.String("traceroute_flags", "", "traceroute flags, supports multiple flags separated with space.")
viper.BindPFlag("traceroute_flags", pflag.Lookup("traceroute_flags"))
pflag.Bool("traceroute_raw", false, "whether to display traceroute outputs raw; set via parameter or environment variable BIRDLG_TRACEROUTE_RAW")
viper.BindPFlag("traceroute_raw", pflag.Lookup("traceroute_raw"))
@@ -65,7 +70,13 @@ func parseSettings() {
setting.allowedIPs = []string{""}
}
var err error
setting.tr_bin = viperSettings.TracerouteBin
setting.tr_flags, err = shlex.Split(viperSettings.TracerouteFlags)
if err != nil {
panic(err)
}
setting.tr_raw = viperSettings.TracerouteRaw
fmt.Printf("%#v\n", setting)

View File

@@ -5,28 +5,81 @@ import (
"net/http"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/google/shlex"
)
func tracerouteTryExecute(cmd []string, args [][]string) ([]byte, string) {
var output []byte
var errString = ""
for i := range cmd {
var err error
var cmdCombined = cmd[i] + " " + strings.Join(args[i], " ")
func tracerouteArgsToString(cmd string, args []string, target []string) string {
var cmdCombined = append([]string{cmd}, args...)
cmdCombined = append(cmdCombined, target...)
return strings.Join(cmdCombined, " ")
}
instance := exec.Command(cmd[i], args[i]...)
output, err = instance.CombinedOutput()
if err == nil {
return output, ""
}
errString += fmt.Sprintf("+ (Try %d) %s\n%s\n\n", (i + 1), cmdCombined, output)
func tracerouteTryExecute(cmd string, args []string, target []string) ([]byte, error) {
instance := exec.Command(cmd, append(args, target...)...)
output, err := instance.CombinedOutput()
if err == nil {
return output, nil
}
return nil, errString
return output, err
}
func tracerouteDetect(cmd string, args []string) bool {
target := []string{"127.0.0.1"}
success := false
if result, err := tracerouteTryExecute(cmd, args, target); err == nil {
setting.tr_bin = cmd
setting.tr_flags = args
success = true
fmt.Printf("Traceroute autodetect success: %s\n", tracerouteArgsToString(cmd, args, target))
} else {
fmt.Printf("Traceroute autodetect fail, continuing: %s (%s)\n%s", tracerouteArgsToString(cmd, args, target), err.Error(), result)
}
return success
}
func tracerouteAutodetect() {
if setting.tr_bin != "" && setting.tr_flags != nil {
return
}
// Traceroute (custom binary)
if setting.tr_bin != "" {
if tracerouteDetect(setting.tr_bin, []string{"-q1", "-N32", "-w1"}) {
return
}
if tracerouteDetect(setting.tr_bin, []string{"-q1", "-w1"}) {
return
}
if tracerouteDetect(setting.tr_bin, []string{}) {
return
}
}
// MTR
if tracerouteDetect("mtr", []string{"-w", "-c1", "-Z1", "-G1", "-b"}) {
return
}
// Traceroute
if tracerouteDetect("traceroute", []string{"-q1", "-N32", "-w1"}) {
return
}
if tracerouteDetect("traceroute", []string{"-q1", "-w1"}) {
return
}
if tracerouteDetect("traceroute", []string{}) {
return
}
// Unsupported
setting.tr_bin = ""
setting.tr_flags = nil
println("Traceroute autodetect failed! Traceroute will be disabled")
}
func tracerouteHandler(httpW http.ResponseWriter, httpR *http.Request) {
@@ -44,52 +97,30 @@ func tracerouteHandler(httpW http.ResponseWriter, httpR *http.Request) {
}
var result []byte
var errString string
skippedCounter := 0
if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" || runtime.GOOS == "openbsd" {
result, errString = tracerouteTryExecute(
[]string{
setting.tr_bin,
setting.tr_bin,
},
[][]string{
append([]string{"-q1", "-w1"}, args...),
args,
},
)
} else if runtime.GOOS == "linux" {
result, errString = tracerouteTryExecute(
[]string{
setting.tr_bin,
setting.tr_bin,
setting.tr_bin,
},
[][]string{
append([]string{"-q1", "-N32", "-w1"}, args...),
append([]string{"-q1", "-w1"}, args...),
args,
},
)
} else {
if setting.tr_bin == "" {
httpW.WriteHeader(http.StatusInternalServerError)
httpW.Write([]byte("traceroute not supported on this node.\n"))
return
}
if errString != "" {
result, err = tracerouteTryExecute(setting.tr_bin, setting.tr_flags, args)
if err != nil {
httpW.WriteHeader(http.StatusInternalServerError)
httpW.Write([]byte(errString))
httpW.Write([]byte(fmt.Sprintf("Error executing traceroute: %s\n\n", err.Error())))
}
if result != nil {
if setting.tr_raw {
httpW.Write(result)
} else {
errString = string(result)
errString = regexp.MustCompile(`(?m)^\s*(\d*)\s*\*\n`).ReplaceAllStringFunc(errString, func(w string) string {
resultString := string(result)
resultString = regexp.MustCompile(`(?m)^\s*(\d*)\s*\*\n`).ReplaceAllStringFunc(resultString, func(w string) string {
skippedCounter++
return ""
})
httpW.Write([]byte(strings.TrimSpace(errString)))
httpW.Write([]byte(strings.TrimSpace(resultString)))
if skippedCounter > 0 {
httpW.Write([]byte("\n\n" + strconv.Itoa(skippedCounter) + " hops not responding."))
}