1
mirror of https://github.com/xddxdd/bird-lg-go synced 2025-11-15 00:57:35 +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. 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 | | Config Key | Parameter | Environment Variable | Description |
| ---------- | --------- | -------------------- | ----------- | | ---------- | --------- | -------------------- | ----------- |
| servers | --servers | BIRDLG_SERVERS | server name prefixes, separated by comma | | 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 | | 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 | | 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. 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 ```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. 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 | | 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 "") | | 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") | | 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") | | 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_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: Example: start proxy with default configuration, should work "out of the box" on Debian 9 with BIRDv1:
```bash ```bash

View File

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

View File

@@ -1,13 +1,22 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"html"
"net" "net"
"regexp" "regexp"
"strings" "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 { func getASNRepresentation(asn string) string {
if setting.dnsInterface != "" { if setting.dnsInterface != "" {
// get ASN representation using DNS // get ASN representation using DNS
@@ -15,9 +24,9 @@ func getASNRepresentation(asn string) string {
if err == nil { if err == nil {
result := strings.Join(records, " ") result := strings.Join(records, " ")
if resultSplit := strings.Split(result, " | "); len(resultSplit) > 1 { 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 { 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 // Helper to add an edge
addEdge := func(src string, dest string, attr string) { addEdge := func(src string, dest string, attrKey string, attrValue string) {
key := "\"" + html.EscapeString(src) + "\" -> \"" + html.EscapeString(dest) + "\"" key := graphvizEscape(src) + " -> " + graphvizEscape(dest)
_, present := graph[key] _, present := graph[key]
// If there are multiple edges / routes between 2 nodes, only pick the first one if !present {
if present { graph[key] = map[string]string{}
return }
if attrKey != "" {
graph[key][attrKey] = attrValue
} }
graph[key] = attr
} }
// Helper to set attribute for a point in graph // Helper to set attribute for a point in graph
addPoint := func(name string, attr string) { addPoint := func(name string, attrKey string, attrValue string) {
key := "\"" + html.EscapeString(name) + "\"" key := graphvizEscape(name)
_, present := graph[key] _, present := graph[key]
// Do not remove point's attributes if it's already present if !present {
if present && len(attr) == 0 { graph[key] = map[string]string{}
return }
if attrKey != "" {
graph[key][attrKey] = attrValue
} }
graph[key] = attr
} }
// The protocol name for each route (e.g. "ibgp_sea02") is encoded in the form: // 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] // 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 // 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)") 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 { for serverID, server := range servers {
response := responses[serverID] response := responses[serverID]
if len(response) == 0 { if len(response) == 0 {
continue continue
} }
addPoint(server, "[color=blue,shape=box]") addPoint(server, "color", "blue")
addPoint(server, "shape", "box")
routes := routeSplitRe.Split(response, -1) routes := routeSplitRe.Split(response, -1)
targetNodeName := "Target: " + target targetNodeName := "Target: " + target
@@ -153,15 +167,16 @@ func birdRouteToGraphviz(servers []string, responses []string, target string) st
// First step starting from originating server // First step starting from originating server
if len(paths) > 0 { if len(paths) > 0 {
attrs := []string{"fontsize=12.0"} edgeTarget := getASNRepresentation(paths[0])
addEdge(server, edgeTarget, "fontsize", "12.0")
if routePreferred { 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 { 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 // Following steps, edges between AS
@@ -169,29 +184,51 @@ func birdRouteToGraphviz(servers []string, responses []string, target string) st
if pathIndex == 0 { if pathIndex == 0 {
continue 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 // 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 { if len(nonBGPRoutes) > 0 {
protocolsForRoute := fmt.Sprintf("label=\"%s\"", strings.Join(nonBGPRoutes, "\\n")) addEdge(server, targetNodeName, "label", strings.Join(nonBGPRoutes, "\n"))
addEdge(server, targetNodeName, "fontsize", "12.0")
attrs := []string{protocolsForRoute, "fontsize=12.0"}
if nonBGPRoutePreferred { 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 // Combine all graphviz commands
var result string var result string
for edge, attr := range graph { 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" return "digraph {\n" + result + "}\n"
} }

View File

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

View File

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

View File

@@ -4,16 +4,18 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/google/shlex"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
type viperSettingType struct { type viperSettingType struct {
BirdSocket string `mapstructure:"bird_socket"` BirdSocket string `mapstructure:"bird_socket"`
Listen string `mapstructure:"listen"` Listen string `mapstructure:"listen"`
AllowedIPs string `mapstructure:"allowed_ips"` AllowedIPs string `mapstructure:"allowed_ips"`
TracerouteBin string `mapstructure:"traceroute_bin"` TracerouteBin string `mapstructure:"traceroute_bin"`
TracerouteRaw bool `mapstructure:"traceroute_raw"` TracerouteFlags string `mapstructure:"traceroute_flags"`
TracerouteRaw bool `mapstructure:"traceroute_raw"`
} }
// Parse settings with viper, and convert to legacy setting format // 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.") 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")) 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")) 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") 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")) viper.BindPFlag("traceroute_raw", pflag.Lookup("traceroute_raw"))
@@ -65,7 +70,13 @@ func parseSettings() {
setting.allowedIPs = []string{""} setting.allowedIPs = []string{""}
} }
var err error
setting.tr_bin = viperSettings.TracerouteBin setting.tr_bin = viperSettings.TracerouteBin
setting.tr_flags, err = shlex.Split(viperSettings.TracerouteFlags)
if err != nil {
panic(err)
}
setting.tr_raw = viperSettings.TracerouteRaw setting.tr_raw = viperSettings.TracerouteRaw
fmt.Printf("%#v\n", setting) fmt.Printf("%#v\n", setting)

View File

@@ -5,28 +5,81 @@ import (
"net/http" "net/http"
"os/exec" "os/exec"
"regexp" "regexp"
"runtime"
"strconv" "strconv"
"strings" "strings"
"github.com/google/shlex" "github.com/google/shlex"
) )
func tracerouteTryExecute(cmd []string, args [][]string) ([]byte, string) { func tracerouteArgsToString(cmd string, args []string, target []string) string {
var output []byte var cmdCombined = append([]string{cmd}, args...)
var errString = "" cmdCombined = append(cmdCombined, target...)
for i := range cmd { return strings.Join(cmdCombined, " ")
var err error }
var cmdCombined = cmd[i] + " " + strings.Join(args[i], " ")
instance := exec.Command(cmd[i], args[i]...) func tracerouteTryExecute(cmd string, args []string, target []string) ([]byte, error) {
output, err = instance.CombinedOutput() instance := exec.Command(cmd, append(args, target...)...)
if err == nil { output, err := instance.CombinedOutput()
return output, "" if err == nil {
} return output, nil
errString += fmt.Sprintf("+ (Try %d) %s\n%s\n\n", (i + 1), cmdCombined, output)
} }
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) { func tracerouteHandler(httpW http.ResponseWriter, httpR *http.Request) {
@@ -44,52 +97,30 @@ func tracerouteHandler(httpW http.ResponseWriter, httpR *http.Request) {
} }
var result []byte var result []byte
var errString string
skippedCounter := 0 skippedCounter := 0
if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" || runtime.GOOS == "openbsd" { if setting.tr_bin == "" {
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 {
httpW.WriteHeader(http.StatusInternalServerError) httpW.WriteHeader(http.StatusInternalServerError)
httpW.Write([]byte("traceroute not supported on this node.\n")) httpW.Write([]byte("traceroute not supported on this node.\n"))
return return
} }
if errString != "" {
result, err = tracerouteTryExecute(setting.tr_bin, setting.tr_flags, args)
if err != nil {
httpW.WriteHeader(http.StatusInternalServerError) 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 result != nil {
if setting.tr_raw { if setting.tr_raw {
httpW.Write(result) httpW.Write(result)
} else { } else {
errString = string(result) resultString := string(result)
errString = regexp.MustCompile(`(?m)^\s*(\d*)\s*\*\n`).ReplaceAllStringFunc(errString, func(w string) string { resultString = regexp.MustCompile(`(?m)^\s*(\d*)\s*\*\n`).ReplaceAllStringFunc(resultString, func(w string) string {
skippedCounter++ skippedCounter++
return "" return ""
}) })
httpW.Write([]byte(strings.TrimSpace(errString))) httpW.Write([]byte(strings.TrimSpace(resultString)))
if skippedCounter > 0 { if skippedCounter > 0 {
httpW.Write([]byte("\n\n" + strconv.Itoa(skippedCounter) + " hops not responding.")) httpW.Write([]byte("\n\n" + strconv.Itoa(skippedCounter) + " hops not responding."))
} }