Skip to content

Commit

Permalink
Path test in showpaths tool (#2108)
Browse files Browse the repository at this point in the history
Fixes #2039
  • Loading branch information
sustrik authored Nov 12, 2018
1 parent ca75e24 commit 3132e6b
Showing 1 changed file with 116 additions and 7 deletions.
123 changes: 116 additions & 7 deletions go/tools/showpaths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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()

Expand All @@ -61,23 +71,30 @@ 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")
}
}

func validateFlags() {
var err error

flag.Parse()
log.SetupFromFlags("")

dstIA, err = addr.IAFromString(*dstIAStr)
if err != nil {
Expand Down Expand Up @@ -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()
}

0 comments on commit 3132e6b

Please sign in to comment.