Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

showpaths: Fix probing #3127

Merged
merged 5 commits into from
Sep 11, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions acceptance/showpaths_status_acceptance/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env python3

# Copyright 2019 Anapaya Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import logging

from plumbum import local

from acceptance.common.log import LogExec, init_log
from acceptance.common.base import CmdBase, TestBase, set_name


set_name(__file__)
logger = logging.getLogger(__name__)


class Test(TestBase):
"""
Test that showpaths with the -p option works as intended.
"""


class Base(CmdBase):
pass


@Test.subcommand('setup')
class TestSetup(Base):

@LogExec(logger, 'setup')
def main(self):
self.scion.topology('topology/Tiny.topo')
self.scion.run()
if not self.no_docker:
self.tools_dc('start', 'tester*')
self.docker_status()


@Test.subcommand("run")
class TestRun(Base):
showpaths = local['./bin/showpaths']

@LogExec(logger, "run")
def main(self):
if self.no_docker:
out = self.showpaths('-srcIA', '1-ff00:0:112', '-sciondFromIA',
'-dstIA', '1-ff00:0:110', '-p', '-local',
'1-ff00:0:112,[127.0.0.1]')
else:
local_disp = local['docker']('inspect', '-f',
'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}',
'scion_disp_1-ff00_0_112')
local_disp = local_disp.strip()
out = self.tools_dc('exec_tester', '1-ff00_0_112', './bin/showpaths',
'-srcIA', '1-ff00:0:112', '-sciondFromIA',
'-dstIA', '1-ff00:0:110', '-p', '-local',
'1-ff00:0:112,[%s]' % local_disp)

if 'Alive' not in out:
logger.error("Alive not found in output, output=%s", out)
return 1
logger.info("successful")


if __name__ == '__main__':
init_log()
Test.run()
13 changes: 13 additions & 0 deletions acceptance/sig_short_exp_time_acceptance/test
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
#!/usr/bin/env python3

# Copyright 2019 Anapaya Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import logging
import os
Expand Down
19 changes: 19 additions & 0 deletions go/lib/sciond/pathprobe/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")

go_library(
name = "go_default_library",
srcs = ["paths.go"],
importpath = "github.com/scionproto/scion/go/lib/sciond/pathprobe",
visibility = ["//visibility:public"],
deps = [
"//go/lib/addr:go_default_library",
"//go/lib/common:go_default_library",
"//go/lib/log:go_default_library",
"//go/lib/sciond:go_default_library",
"//go/lib/scmp:go_default_library",
"//go/lib/snet:go_default_library",
"//go/lib/sock/reliable:go_default_library",
"//go/lib/spath:go_default_library",
"@org_golang_x_xerrors//:go_default_library",
],
)
212 changes: 212 additions & 0 deletions go/lib/sciond/pathprobe/paths.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Copyright 2019 Anapaya Systems
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pathprobe

import (
"context"
"errors"
"fmt"
"sync"
"time"

"golang.org/x/xerrors"

"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"
)

// StatusName defines the different states a path can be in.
type StatusName string

const (
// StatusUnknown indicates that it is not clear what state the path is in.
StatusUnknown StatusName = "Unknown"
// StatusTimeout indicates that a reply did come back in time for the path.
StatusTimeout StatusName = "Timeout"
// StatusAlive indicates that the expected reply did come back in time.
StatusAlive StatusName = "Alive"
// StatusSCMP indicates that an unexpected SCMP packet came in the reply.
StatusSCMP StatusName = "SCMP"
)

// Status indicates the state a path is in.
type Status struct {
Status StatusName
AdditionalInfo string
}

// Predefined path status
var (
unknown = Status{Status: StatusUnknown}
timeout = Status{Status: StatusTimeout}
alive = Status{Status: StatusAlive}
)

func (s Status) String() string {
if s.AdditionalInfo == "" {
return string(s.Status)
}
return fmt.Sprintf("%s(%s)", s.Status, s.AdditionalInfo)
}

// PathKey is the mapping of a path reply entry to a key that is returned in
// GetStatuses.
func PathKey(path sciond.PathReplyEntry) string {
return string(path.Path.FwdPath)
}

// Prober can be used to get the status of a path.
type Prober struct {
DstIA addr.IA
Local snet.Addr
DispPath string
}

// GetStatuses probes the paths and returns the statuses of the paths. The
// returned map is keyed with path.Path.FwdPath.
func (p Prober) GetStatuses(ctx context.Context,
paths []sciond.PathReplyEntry) (map[string]Status, error) {

deadline, ok := ctx.Deadline()
if !ok {
return nil, common.NewBasicError("deadline required on ctx", nil)
}
// 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.
pathStatuses := make(map[string]Status, len(paths))
scmpH := &scmpHandler{statuses: pathStatuses}
network := snet.NewCustomNetworkWithPR(p.Local.IA,
&snet.DefaultPacketDispatcherService{
Dispatcher: reliable.NewDispatcherService(p.DispPath),
SCMPHandler: scmpH,
},
nil,
)
snetConn, err := network.ListenSCION("udp4", &p.Local, deadline.Sub(time.Now()))
if err != nil {
return nil, common.NewBasicError("listening failed", err)
}
defer snetConn.Close()
var sendErrors common.MultiError
for _, path := range paths {
scmpH.setStatus(PathKey(path), timeout)
if err := p.send(snetConn, path); err != nil {
sendErrors = append(sendErrors, err)
}
}
if err := sendErrors.ToError(); err != nil {
return nil, err
}
var receiveErrors common.MultiError
for i := len(scmpH.statuses); i > 0; i-- {
if err := p.receive(snetConn); err != nil {
receiveErrors = append(receiveErrors, err)
}
}
if err := receiveErrors.ToError(); err != nil {
return nil, err
}
return scmpH.statuses, nil
}

func (p Prober) send(scionConn snet.Conn, path sciond.PathReplyEntry) error {
sPath := spath.New(path.Path.FwdPath)
if err := sPath.InitOffsets(); err != nil {
return common.NewBasicError("unable to initialize path", err)
}
nextHop, err := path.HostInfo.Overlay()
if err != nil {
return common.NewBasicError("unable to get overlay info", err)
}
addr := &snet.Addr{
IA: p.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 {
return common.NewBasicError("cannot send packet", err)
}
return nil
}

func (p Prober) receive(scionConn snet.Conn) error {
b := make([]byte, 1500, 1500)
_, _, err := scionConn.ReadFromSCION(b)
if err == nil {
// We've got an actual reply instead of SCMP error. This should not happen.
return nil
}
if xerrors.Is(err, errBadHost) || xerrors.Is(err, errSCMP) {
return nil
}
if common.IsTimeoutErr(err) {
// Timeout expired before all replies were received.
return nil
}
return common.NewBasicError("failed to read packet", err)
}

var errBadHost = errors.New("scmp: bad host")
var errSCMP = errors.New("scmp: other")

type scmpHandler struct {
mtx sync.Mutex
statuses map[string]Status
}

func (h *scmpHandler) Handle(pkt *snet.SCIONPacket) error {
hdr, ok := pkt.L4Header.(*scmp.Hdr)
if ok {
path, err := h.path(pkt)
if err != nil {
return err
}
if hdr.Class == scmp.C_Routing && hdr.Type == scmp.T_R_BadHost {
h.setStatus(path, alive)
return errBadHost
}
h.setStatus(path, Status{Status: StatusSCMP, AdditionalInfo: hdr.String()})
return errSCMP
}
return nil
}

func (h *scmpHandler) path(pkt *snet.SCIONPacket) (string, error) {
path := pkt.Path.Copy()
if err := path.Reverse(); err != nil {
return "", common.NewBasicError("unable to reverse path on received packet", err)
}
return string(path.Raw), nil
}

func (h *scmpHandler) setStatus(path string, status Status) {
h.mtx.Lock()
defer h.mtx.Unlock()
h.statuses[path] = status
}
5 changes: 1 addition & 4 deletions go/tools/showpaths/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@ go_library(
visibility = ["//visibility:private"],
deps = [
"//go/lib/addr:go_default_library",
"//go/lib/common:go_default_library",
"//go/lib/env:go_default_library",
"//go/lib/log:go_default_library",
"//go/lib/sciond:go_default_library",
"//go/lib/scmp:go_default_library",
"//go/lib/sciond/pathprobe:go_default_library",
"//go/lib/snet:go_default_library",
"//go/lib/sock/reliable:go_default_library",
"//go/lib/spath:go_default_library",
],
)

Expand Down
Loading