From 3132e6bda038a369b788b7c4d1c15c61e4d2fd57 Mon Sep 17 00:00:00 2001 From: Martin Sustrik Date: Mon, 12 Nov 2018 10:17:20 +0100 Subject: [PATCH] Path test in showpaths tool (#2108) Fixes #2039 --- go/tools/showpaths/paths.go | 123 ++++++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 7 deletions(-) diff --git a/go/tools/showpaths/paths.go b/go/tools/showpaths/paths.go index dfb1c424ff..9f13bfec84 100644 --- a/go/tools/showpaths/paths.go +++ b/go/tools/showpaths/paths.go @@ -18,23 +18,31 @@ package main import ( "flag" "fmt" + "net" "os" "time" "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" "github.com/scionproto/scion/go/lib/log" "github.com/scionproto/scion/go/lib/sciond" + "github.com/scionproto/scion/go/lib/scmp" + "github.com/scionproto/scion/go/lib/snet" + "github.com/scionproto/scion/go/lib/sock/reliable" + "github.com/scionproto/scion/go/lib/spath" ) var ( dstIAStr = flag.String("dstIA", "", "Destination IA address: ISD-AS") srcIAStr = flag.String("srcIA", "", "Source IA address: ISD-AS") sciondPath = flag.String("sciond", "", "SCIOND socket path") - timeout = flag.Duration("timeout", 2*time.Second, "SCIOND connection timeout") + timeout = flag.Duration("timeout", 5*time.Second, "Timeout in seconds") maxPaths = flag.Int("maxpaths", 10, "Maximum number of paths") sciondFromIA = flag.Bool("sciondFromIA", false, "SCIOND socket path from IA address:ISD-AS") expiration = flag.Bool("expiration", false, "Show path expiration timestamps") refresh = flag.Bool("refresh", false, "Set refresh flag for SCIOND path request") + status = flag.Bool("p", false, "Probe the paths and print out the statuses") + Local snet.Addr ) var ( @@ -45,6 +53,8 @@ var ( func main() { var err error + flag.Var((*snet.Addr)(&Local), "local", "Local address to use for health checks") + log.AddLogConsFlags() validateFlags() @@ -61,16 +71,22 @@ func main() { if reply.ErrorCode != sciond.ErrorOk { LogFatal("SCIOND unable to retrieve paths: %s\n", reply.ErrorCode) } + fmt.Println("Available paths to", dstIA) - i := 0 - for _, path := range reply.Entries { + var pathStatuses map[string]string + if *status { + pathStatuses = getStatuses(reply.Entries) + } + for i, path := range reply.Entries { + fmt.Printf("[%2d] %s", i, path.Path.String()) if *expiration { - fmt.Printf("[%2d] %s Expires: %s (%s)\n", i, path.Path.String(), path.Path.Expiry(), + fmt.Printf(" Expires: %s (%s)", path.Path.Expiry(), time.Until(path.Path.Expiry()).Truncate(time.Second)) - } else { - fmt.Printf("[%2d] %s\n", i, path.Path.String()) } - i++ + if *status { + fmt.Printf(" Status: %s", pathStatuses[string(path.Path.FwdPath)]) + } + fmt.Printf("\n") } } @@ -78,6 +94,7 @@ func validateFlags() { var err error flag.Parse() + log.SetupFromFlags("") dstIA, err = addr.IAFromString(*dstIAStr) if err != nil { @@ -107,3 +124,95 @@ func LogFatal(msg string, a ...interface{}) { fmt.Fprintf(os.Stderr, msg, a...) os.Exit(1) } + +func getStatuses(paths []sciond.PathReplyEntry) map[string]string { + // Check whether paths are alive. This is done by sending a packet + // with invalid address via the path. The border router at the destination + // is going to reply with SCMP error. Receiving the error means that + // the path is alive. + if err := snet.Init(srcIA, "", reliable.DefaultDispPath); err != nil { + LogFatal("Initializing SNET: %v\n", err) + } + snetConn, err := snet.ListenSCION("udp4", &Local) + if err != nil { + LogFatal("Listening failed: %v\n", err) + } + scionConn := snetConn.(*snet.SCIONConn) + err = scionConn.SetReadDeadline(time.Now().Add(*timeout)) + if err != nil { + LogFatal("Cannot set deadline: %v\n", err) + } + pathStatuses := make(map[string]string) + for _, path := range paths { + sendTestPacket(scionConn, path) + pathStatuses[string(path.Path.FwdPath)] = "Timeout" + } + for i := len(pathStatuses); i > 0; i-- { + path, status := receiveTestReply(scionConn) + if path == nil { + break + } + if pathStatuses[*path] != "Timeout" { + // Two replies received for the same path. + pathStatuses[*path] = "Unknown" + continue + } + pathStatuses[*path] = status + } + return pathStatuses +} + +func sendTestPacket(scionConn *snet.SCIONConn, path sciond.PathReplyEntry) { + sPath := spath.New(path.Path.FwdPath) + if err := sPath.InitOffsets(); err != nil { + LogFatal("Unable to initialize path: %v\n", err) + } + nextHop, err := path.HostInfo.Overlay() + if err != nil { + LogFatal("Cannot get overlay info: %v\n", err) + } + addr := &snet.Addr{ + IA: dstIA, + Host: &addr.AppAddr{ + L3: addr.HostSVCFromString("NONE"), + L4: addr.NewL4UDPInfo(0), + }, + NextHop: nextHop, + Path: sPath, + } + log.Debug("Sending test packet.", "path", path.Path.String()) + _, err = scionConn.WriteTo([]byte{}, addr) + if err != nil { + LogFatal("Cannot send packet: %v\n", err) + } +} + +func receiveTestReply(scionConn *snet.SCIONConn) (*string, string) { + b := make([]byte, 1500, 1500) + _, addr, err := scionConn.ReadFromSCION(b) + if addr == nil { + if basicErr, ok := err.(common.BasicError); ok { + if netErr, ok := basicErr.Err.(net.Error); ok && netErr.Timeout() { + // Timeout expired before all replies were received. + return nil, "" + } + } + if err != nil { + LogFatal("Cannot read packet: %v\n", err) + } + LogFatal("Packet without an address received: %v\n", err) + } + path := string(addr.Path.Raw) + if err == nil { + // We've got an actual reply instead of SCMP error. This should not happen. + return &path, "Unknown" + } + if opErr, ok := err.(*snet.OpError); ok { + if opErr.SCMP().Class == scmp.C_Routing && opErr.SCMP().Type == scmp.T_R_BadHost { + // Expected outcome. The peer complains about SvcNone being an invalid address. + return &path, "Alive" + } + } + // All other errors are just reported alongside the path. + return &path, err.Error() +}