diff --git a/dispatcher/underlay.go b/dispatcher/underlay.go index 4a09cc22b8..2734055963 100644 --- a/dispatcher/underlay.go +++ b/dispatcher/underlay.go @@ -354,6 +354,7 @@ func (h SCMPHandler) reverseSCION(pkt *respool.Packet) error { if pkt.SCION.Path, err = pkt.SCION.Path.Reverse(); err != nil { return serrors.WrapStr("reversing path", err) } + pkt.SCION.PathType = pkt.SCION.Path.Type() return nil } diff --git a/private/app/path/BUILD.bazel b/private/app/path/BUILD.bazel index d6df278e2a..37df2f913e 100644 --- a/private/app/path/BUILD.bazel +++ b/private/app/path/BUILD.bazel @@ -8,6 +8,7 @@ go_library( deps = [ "//pkg/addr:go_default_library", "//pkg/daemon:go_default_library", + "//pkg/private/common:go_default_library", "//pkg/private/serrors:go_default_library", "//pkg/snet:go_default_library", "//pkg/snet/path:go_default_library", diff --git a/private/app/path/path.go b/private/app/path/path.go index 6b8bffacbc..42e60288a7 100644 --- a/private/app/path/path.go +++ b/private/app/path/path.go @@ -18,6 +18,7 @@ import ( "bufio" "context" "fmt" + "hash" "math/rand" "net" "os" @@ -30,8 +31,10 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/daemon" + "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/path" snetpath "github.com/scionproto/scion/pkg/snet/path" "github.com/scionproto/scion/private/app/path/pathprobe" "github.com/scionproto/scion/private/path/pathpol" @@ -79,6 +82,28 @@ func Choose( ) (snet.Path, error) { o := applyOption(opts) + + if o.ohCfg != nil { + // Only one-hop paths are requested. + p, err := path.NewOneHop(o.ohCfg.Egress, time.Now(), 63, o.ohCfg.Mac) + if err != nil { + return nil, err + } + + return path.Path{ + Src: o.ohCfg.Source, + Dst: o.ohCfg.Destination, + DataplanePath: p, + NextHop: o.ohCfg.NextHop, + Meta: snet.PathMetadata{ + Interfaces: []snet.PathInterface{ + {ID: common.IFIDType(o.ohCfg.Egress), IA: o.ohCfg.Source}, + {ID: 0, IA: o.ohCfg.Destination}, + }, + }, + }, nil + } + paths, err := fetchPaths(ctx, conn, remote, o.refresh, o.seq) if err != nil { return nil, serrors.WrapStr("fetching paths", err) @@ -313,6 +338,14 @@ type ProbeConfig struct { SCIONPacketConnMetrics snet.SCIONPacketConnMetrics } +type OneHopConfig struct { + Source addr.IA + Destination addr.IA + Egress uint16 + Mac hash.Hash + NextHop *net.UDPAddr +} + type options struct { interactive bool refresh bool @@ -320,6 +353,7 @@ type options struct { colorScheme ColorScheme probeCfg *ProbeConfig epic bool + ohCfg *OneHopConfig } type Option func(o *options) @@ -367,3 +401,9 @@ func WithEPIC(epic bool) Option { o.epic = epic } } + +func WithOneHopConfig(cfg *OneHopConfig) Option { + return func(o *options) { + o.ohCfg = cfg + } +} diff --git a/private/keyconf/keyconf.go b/private/keyconf/keyconf.go index 5acccbf2fd..99011f8192 100644 --- a/private/keyconf/keyconf.go +++ b/private/keyconf/keyconf.go @@ -39,6 +39,10 @@ const ( ErrUnknown common.ErrMsg = "Unknown algorithm" ) +func LoadKey(file string) ([]byte, error) { + return loadKey(file, RawKey) +} + // loadKey decodes a base64 encoded key stored in file and returns the raw bytes. func loadKey(file string, algo string) ([]byte, error) { b, err := os.ReadFile(file) diff --git a/scion/cmd/scion/BUILD.bazel b/scion/cmd/scion/BUILD.bazel index 4e60f134cb..9e072cf70b 100644 --- a/scion/cmd/scion/BUILD.bazel +++ b/scion/cmd/scion/BUILD.bazel @@ -21,6 +21,7 @@ go_library( "//pkg/log:go_default_library", "//pkg/private/common:go_default_library", "//pkg/private/serrors:go_default_library", + "//pkg/scrypto:go_default_library", "//pkg/snet:go_default_library", "//pkg/snet/addrutil:go_default_library", "//pkg/snet/path:go_default_library", @@ -30,6 +31,7 @@ go_library( "//private/app/flag:go_default_library", "//private/app/path:go_default_library", "//private/env:go_default_library", + "//private/keyconf:go_default_library", "//private/path/pathpol:go_default_library", "//private/topology:go_default_library", "//private/tracing:go_default_library", diff --git a/scion/cmd/scion/ping.go b/scion/cmd/scion/ping.go index df6bf02ac1..846ca4dd27 100644 --- a/scion/cmd/scion/ping.go +++ b/scion/cmd/scion/ping.go @@ -30,6 +30,7 @@ import ( "github.com/scionproto/scion/pkg/daemon" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/scrypto" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/addrutil" snetpath "github.com/scionproto/scion/pkg/snet/path" @@ -37,6 +38,7 @@ import ( "github.com/scionproto/scion/private/app" "github.com/scionproto/scion/private/app/flag" "github.com/scionproto/scion/private/app/path" + "github.com/scionproto/scion/private/keyconf" "github.com/scionproto/scion/private/path/pathpol" "github.com/scionproto/scion/private/tracing" "github.com/scionproto/scion/scion/ping" @@ -71,22 +73,25 @@ type PingUpdate struct { func newPing(pather CommandPather) *cobra.Command { var envFlags flag.SCIONEnvironment var flags struct { - count uint16 - features []string - interactive bool - interval time.Duration - logLevel string - maxMTU bool - noColor bool - refresh bool - healthyOnly bool - sequence string - size uint - pktSize uint - timeout time.Duration - tracer string - epic bool - format string + count uint16 + features []string + interactive bool + interval time.Duration + logLevel string + maxMTU bool + noColor bool + refresh bool + healthyOnly bool + sequence string + size uint + pktSize uint + timeout time.Duration + tracer string + epic bool + format string + egress uint16 + nextHop flag.UDPAddr + forwardingKey string } var cmd = &cobra.Command{ @@ -165,6 +170,32 @@ On other errors, ping will exit with code 2. path.WithColorScheme(path.DefaultColorScheme(flags.noColor)), path.WithEPIC(flags.epic), } + if flags.egress != 0 || flags.nextHop.IP != nil || flags.forwardingKey != "" { + if flags.egress == 0 { + return serrors.New("egress must be set") + } + if flags.nextHop.IP == nil { + return serrors.New("next hop must be set") + } + if flags.forwardingKey == "" { + return serrors.New("forwarding key must be set") + } + forwardingKey, err := keyconf.LoadKey(flags.forwardingKey) + if err != nil { + return serrors.WrapStr("loading forwarding key", err) + } + mac, err := scrypto.HFMacFactory(forwardingKey) + if err != nil { + return err + } + opts = append(opts, path.WithOneHopConfig(&path.OneHopConfig{ + Source: info.IA, + Destination: remote.IA, + Egress: flags.egress, + NextHop: (*net.UDPAddr)(&flags.nextHop), + Mac: mac(), + })) + } if flags.healthyOnly { opts = append(opts, path.WithProbing(&path.ProbeConfig{ LocalIA: info.IA, @@ -363,6 +394,11 @@ SCMP echo header and payload are equal to the MTU of the path. This flag overrid cmd.Flags().BoolVar(&flags.epic, "epic", false, "Enable EPIC for path probing.") cmd.Flags().StringVar(&flags.format, "format", "human", "Specify the output format (human|json|yaml)") + cmd.Flags().Uint16Var(&flags.egress, "onehop.egress", 0, "Egress interface for one-hop path") + cmd.Flags().Var(&flags.nextHop, "onehop.next-hop", "NextHop for one-hop path") + cmd.Flags().StringVar(&flags.forwardingKey, "onehop.forwarding-key", "", + "Forwarding key for one-hop path", + ) return cmd }