diff --git a/bwtester/bwtestclient/bwtestclient.go b/bwtester/bwtestclient/bwtestclient.go index 6013e29b..ef84af07 100644 --- a/bwtester/bwtestclient/bwtestclient.go +++ b/bwtester/bwtestclient/bwtestclient.go @@ -30,6 +30,8 @@ import ( "time" "unicode" + "github.com/scionproto/scion/private/path/fabridquery" + "github.com/netsec-ethz/scion-apps/bwtester/bwtest" "github.com/netsec-ethz/scion-apps/pkg/pan" ) @@ -267,6 +269,7 @@ func main() { interactive bool sequence string preference string + fabridQuery string ) flag.Usage = printUsage @@ -279,6 +282,7 @@ func main() { flag.StringVar(&preference, "preference", "", "Preference sorting order for paths. "+ "Comma-separated list of available sorting options: "+ strings.Join(pan.AvailablePreferencePolicies, "|")) + flag.StringVar(&fabridQuery, "fabridquery", "", "Query for FABRID policies") flag.Parse() flagset := make(map[string]bool) @@ -320,7 +324,7 @@ func main() { fmt.Printf("server->client: %d seconds, %d bytes, %d packets\n", int(serverBwp.BwtestDuration/time.Second), serverBwp.PacketSize, serverBwp.NumPackets) - clientRes, serverRes, err := runBwtest(local.Get(), serverCCAddr, policy, clientBwp, serverBwp) + clientRes, serverRes, err := runBwtest(local.Get(), serverCCAddr, policy, clientBwp, serverBwp, fabridQuery) bwtest.Check(err) fmt.Println("\nS->C results") @@ -330,8 +334,7 @@ func main() { } // runBwtest runs the bandwidth test with the given parameters against the server at serverCCAddr. -func runBwtest(local netip.AddrPort, serverCCAddr pan.UDPAddr, policy pan.Policy, - clientBwp, serverBwp bwtest.Parameters) (clientRes, serverRes bwtest.Result, err error) { +func runBwtest(local netip.AddrPort, serverCCAddr pan.UDPAddr, policy pan.Policy, clientBwp, serverBwp bwtest.Parameters, fabridQuery string) (clientRes, serverRes bwtest.Result, err error) { // Control channel connection ccSelector := pan.NewDefaultSelector() @@ -345,7 +348,16 @@ func runBwtest(local netip.AddrPort, serverCCAddr pan.UDPAddr, policy pan.Policy serverDCAddr := serverCCAddr.WithPort(serverCCAddr.Port + 1) // Data channel connection - dcConn, err := pan.DialUDP(context.Background(), dcLocal, serverDCAddr, policy, nil) + var selector pan.Selector + if fabridQuery != "" { + query, err := fabridquery.ParseFabridQuery(fabridQuery) + if err != nil { + return bwtest.Result{}, bwtest.Result{}, err + } + selector = pan.NewFabridSelector(query, context.Background()) + } + + dcConn, err := pan.DialUDP(context.Background(), dcLocal, serverDCAddr, policy, selector) if err != nil { return } diff --git a/bwtester/bwtestserver/bwtestserver.go b/bwtester/bwtestserver/bwtestserver.go index 4656cc5f..8a66d77c 100644 --- a/bwtester/bwtestserver/bwtestserver.go +++ b/bwtester/bwtestserver/bwtestserver.go @@ -36,13 +36,14 @@ const ( func main() { var listen pan.IPPortValue kingpin.Flag("listen", "Address to listen on").Default(":40002").SetValue(&listen) + fabrid := kingpin.Flag("fabrid", "Enable FABRID").Bool() kingpin.Parse() - err := runServer(listen.Get()) + err := runServer(listen.Get(), *fabrid) bwtest.Check(err) } -func runServer(listen netip.AddrPort) error { +func runServer(listen netip.AddrPort, enableFabrid bool) error { receivePacketBuffer := make([]byte, 2500) var currentBwtest string @@ -57,6 +58,7 @@ func runServer(listen netip.AddrPort) error { return err } serverCCAddr := ccConn.LocalAddr().(pan.UDPAddr) + fmt.Println("Server listening on ", serverCCAddr, " fabrid:", enableFabrid) for { // Handle client requests n, clientCCAddr, err := ccConn.ReadFrom(receivePacketBuffer) @@ -103,7 +105,7 @@ func runServer(listen netip.AddrPort) error { } path := ccSelector.Path(clientCCAddr.(pan.UDPAddr)) finishTime, err := startBwtestBackground(serverCCAddr, clientCCAddr.(pan.UDPAddr), path, - clientBwp, serverBwp, currentResult) + clientBwp, serverBwp, currentResult, enableFabrid) if err != nil { // Ask the client to try again in 1 second writeResponseN(ccConn, clientCCAddr, 1) @@ -134,7 +136,7 @@ func runServer(listen netip.AddrPort) error { // startBwtestBackground starts a bandwidth test, in the background. // Returns the expected finish time of the test, or any error during the setup. func startBwtestBackground(serverCCAddr pan.UDPAddr, clientCCAddr pan.UDPAddr, - path *pan.Path, clientBwp, serverBwp bwtest.Parameters, res chan<- bwtest.Result) (time.Time, error) { + path *pan.Path, clientBwp, serverBwp bwtest.Parameters, res chan<- bwtest.Result, enableFabrid bool) (time.Time, error) { // Data Connection addresses: clientDCAddr := clientCCAddr @@ -143,7 +145,7 @@ func startBwtestBackground(serverCCAddr pan.UDPAddr, clientCCAddr pan.UDPAddr, // Open Data Connection dcSelector := initializedReplySelector(clientDCAddr, path) - dcConn, err := listenConnected(serverDCAddr, clientDCAddr, dcSelector) + dcConn, err := listenConnected(serverDCAddr, clientDCAddr, dcSelector, enableFabrid) if err != nil { return time.Time{}, err } @@ -286,8 +288,15 @@ func (r resultsMap) purgeExpired() { } } -func listenConnected(local netip.AddrPort, remote pan.UDPAddr, selector pan.ReplySelector) (net.Conn, error) { - conn, err := pan.ListenUDP(context.Background(), local, selector) +func listenConnected(local netip.AddrPort, remote pan.UDPAddr, selector pan.ReplySelector, enableFabrid bool) (net.Conn, error) { + var err error + var conn pan.ListenConn + if enableFabrid { + conn, err = pan.ListenUDPWithFabrid(context.Background(), local, remote, selector) + + } else { + conn, err = pan.ListenUDP(context.Background(), local, selector) + } return connectedPacketConn{ ListenConn: conn, remote: remote, diff --git a/pkg/pan/fabrid_server.go b/pkg/pan/fabrid_server.go new file mode 100644 index 00000000..c7c6dfa6 --- /dev/null +++ b/pkg/pan/fabrid_server.go @@ -0,0 +1,86 @@ +// Copyright 2021 ETH Zurich +// +// 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 pan + +import ( + "context" + "fmt" + "time" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/drkey" + "github.com/scionproto/scion/pkg/experimental/fabrid/crypto" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/slayers/extension" +) + +type FabridServer struct { + Local UDPAddr + Source UDPAddr + tmpBuffer []byte + pathKey *drkey.HostHostKey + ctx context.Context +} + +func NewFabridServer(ctx context.Context, local UDPAddr, remote UDPAddr) *FabridServer { + server := &FabridServer{ + Local: local, + Source: remote, + tmpBuffer: make([]byte, 192), + ctx: ctx, + } + err := server.refreshPathKey(time.Now()) + if err != nil { + fmt.Println("Failed to fetch path key. Does your local IP differ from your SD?\nError:", err) + return nil + } + return server +} + +func (s *FabridServer) refreshPathKey(validity time.Time) error { + if s.pathKey == nil || !s.pathKey.Epoch.Contains(validity) { + meta := drkey.HostHostMeta{ + Validity: validity, + SrcIA: addr.IA(s.Local.IA), + SrcHost: s.Local.IP.String(), + DstIA: addr.IA(s.Source.IA), + DstHost: s.Source.IP.String(), + ProtoId: drkey.FABRID, + } + hostHostKey, err := host().drkeyGetHostHostKey(s.ctx, meta) + if err != nil { + return serrors.WrapStr("getting host key", err) + } + s.pathKey = &hostHostKey + + } + return nil +} + +func (s *FabridServer) HandleFabridPacket(fabridOption *extension.FabridOption, + identifierOption *extension.IdentifierOption) error { + err := s.refreshPathKey(identifierOption.Timestamp) + if err != nil { + fmt.Println("Failed to fetch path key. Does your local IP differ from your SD?\nError:", err) + return err + } + _, err = crypto.VerifyPathValidator(fabridOption, + s.tmpBuffer, s.pathKey.Key[:]) + if err != nil { + fmt.Println("Failed to verify FABRID packet. Error:", err) + return err + } + return nil +} diff --git a/pkg/pan/path_metadata.go b/pkg/pan/path_metadata.go index 1b384542..dd9421e8 100644 --- a/pkg/pan/path_metadata.go +++ b/pkg/pan/path_metadata.go @@ -75,10 +75,14 @@ type PathMetadata struct { // Notes contains the notes added by ASes on the path, in the order of occurrence. // Entry i is the note of AS i on the path. Notes []string + + // FabridInfo contains information about the FABRID policies and support for each hop. + FabridInfo []FabridInfo } type GeoCoordinates = snet.GeoCoordinates type LinkType = snet.LinkType +type FabridInfo = snet.FabridInfo func (pm *PathMetadata) Copy() *PathMetadata { if pm == nil { @@ -93,6 +97,7 @@ func (pm *PathMetadata) Copy() *PathMetadata { LinkType: append(pm.LinkType[:0:0], pm.LinkType...), InternalHops: append(pm.InternalHops[:0:0], pm.InternalHops...), Notes: append(pm.Notes[:0:0], pm.Notes...), + FabridInfo: append(pm.FabridInfo[:0:0], pm.FabridInfo...), } } diff --git a/pkg/pan/raw.go b/pkg/pan/raw.go index 2a48ad99..f7bc70fc 100644 --- a/pkg/pan/raw.go +++ b/pkg/pan/raw.go @@ -110,7 +110,7 @@ func (c *baseUDPConn) writeMsg(src, dst UDPAddr, path *Path, b []byte) (int, err // readMsg is a helper for reading a single packet. // Internally invokes the configured SCMP handler. // Ignores non-UDP packets. -func (c *baseUDPConn) readMsg(b []byte) (int, UDPAddr, ForwardingPath, error) { +func (c *baseUDPConn) readMsg(b []byte) (int, UDPAddr, ForwardingPath, *slayers.HopByHopExtn, *slayers.EndToEndExtn, error) { c.readMutex.Lock() defer c.readMutex.Unlock() if c.readBuffer == nil { @@ -124,7 +124,7 @@ func (c *baseUDPConn) readMsg(b []byte) (int, UDPAddr, ForwardingPath, error) { var lastHop net.UDPAddr err := c.raw.ReadFrom(&pkt, &lastHop) if err != nil { - return 0, UDPAddr{}, ForwardingPath{}, err + return 0, UDPAddr{}, ForwardingPath{}, nil, nil, err } udp, ok := pkt.Payload.(snet.UDPPayload) if !ok { @@ -144,7 +144,7 @@ func (c *baseUDPConn) readMsg(b []byte) (int, UDPAddr, ForwardingPath, error) { underlay: underlay, } n := copy(b, udp.Payload) - return n, remote, fw, nil + return n, remote, fw, pkt.HbhExtension, pkt.E2eExtension, nil } } diff --git a/pkg/pan/sciond.go b/pkg/pan/sciond.go index 3e3b9d1f..6684405b 100644 --- a/pkg/pan/sciond.go +++ b/pkg/pan/sciond.go @@ -25,6 +25,7 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/daemon" + "github.com/scionproto/scion/pkg/drkey" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/addrutil" ) @@ -146,6 +147,7 @@ func (h *hostContext) queryPaths(ctx context.Context, dst IA) ([]*Path, error) { LinkType: snetMetadata.LinkType, InternalHops: snetMetadata.InternalHops, Notes: snetMetadata.Notes, + FabridInfo: snetMetadata.FabridInfo, } underlay := p.UnderlayNextHop().AddrPort() paths[i] = &Path{ @@ -163,6 +165,14 @@ func (h *hostContext) queryPaths(ctx context.Context, dst IA) ([]*Path, error) { return paths, nil } +func (h *hostContext) drkeyGetHostHostKey(ctx context.Context, meta drkey.HostHostMeta) (drkey.HostHostKey, error) { + return h.sciond.DRKeyGetHostHostKey(ctx, meta) +} + +func (h *hostContext) fabridKeys() func(ctx context.Context, meta drkey.FabridKeysMeta) (drkey.FabridKeysResponse, error) { + return h.sciond.FabridKeys +} + func convertPathInterfaceSlice(spis []snet.PathInterface) []PathInterface { pis := make([]PathInterface, len(spis)) for i, spi := range spis { diff --git a/pkg/pan/selector.go b/pkg/pan/selector.go index 9a2fac34..c03a7514 100644 --- a/pkg/pan/selector.go +++ b/pkg/pan/selector.go @@ -23,6 +23,10 @@ import ( "time" "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/private/common" + "github.com/scionproto/scion/pkg/snet" + snetpath "github.com/scionproto/scion/pkg/snet/path" + "github.com/scionproto/scion/private/path/fabridquery" "github.com/netsec-ethz/scion-apps/pkg/pan/internal/ping" ) @@ -322,3 +326,122 @@ func (s *PingingSelector) Close() error { s.pingerCancel() return s.pinger.Close() } + +// FabridSelector is a Selector for a single dialed socket. +type FabridSelector struct { + mutex sync.Mutex + paths []*Path + current int + fabridQuery fabridquery.Expressions + fabridConfig snetpath.FabridConfig + ctx context.Context +} + +func NewFabridSelector(query fabridquery.Expressions, ctx context.Context) *FabridSelector { + return &FabridSelector{ + fabridQuery: query, + ctx: ctx, + } +} + +func (s *FabridSelector) Path() *Path { + s.mutex.Lock() + defer s.mutex.Unlock() + + if len(s.paths) == 0 { + return nil + } + return s.paths[s.current] +} + +func convertInterfaces(interfaces []PathInterface) []snet.PathInterface { + snetInterfaces := make([]snet.PathInterface, len(interfaces)) + for i, pathInterface := range interfaces { + snetInterfaces[i] = snet.PathInterface{ + ID: common.IFIDType(pathInterface.IfID), + IA: addr.IA(pathInterface.IA), + } + } + return snetInterfaces +} + +func (s *FabridSelector) Initialize(local, remote UDPAddr, paths []*Path) { + s.mutex.Lock() + defer s.mutex.Unlock() + + s.fabridConfig = snetpath.FabridConfig{ + LocalIA: addr.IA(local.IA), + LocalAddr: local.IP.String(), + DestinationIA: addr.IA(remote.IA), + DestinationAddr: remote.IP.String(), + } + + fabridPaths := []*Path{} + for _, p := range paths { + scionPath, isSCIONPath := p.ForwardingPath.dataplanePath.(snetpath.SCION) + if !isSCIONPath { + continue + } + snetMetadata := snet.PathMetadata{ + Interfaces: convertInterfaces(p.Metadata.Interfaces), + FabridInfo: p.Metadata.FabridInfo} + hopIntfs := snetMetadata.Hops() + ml := fabridquery.MatchList{ + SelectedPolicies: make([]*fabridquery.Policy, len(hopIntfs)), + } + _, pols := s.fabridQuery.Evaluate(hopIntfs, &ml) + + if !pols.Accepted() { + continue + } + fabridPath, err := snetpath.NewFABRIDDataplanePath(scionPath, hopIntfs, + pols.Policies(), &s.fabridConfig) + if err != nil { + return + } + p.ForwardingPath.dataplanePath = fabridPath + fabridPath.RegisterDRKeyFetcher(host().fabridKeys()) + + fabridPaths = append(fabridPaths, p) + } + + s.paths = fabridPaths + s.current = 0 +} + +func (s *FabridSelector) Refresh(paths []*Path) { + s.mutex.Lock() + defer s.mutex.Unlock() + + newcurrent := 0 + if len(s.paths) > 0 { + currentFingerprint := s.paths[s.current].Fingerprint + for i, p := range paths { + if p.Fingerprint == currentFingerprint { + newcurrent = i + break + } + } + } + s.paths = paths + s.current = newcurrent +} + +func (s *FabridSelector) PathDown(pf PathFingerprint, pi PathInterface) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if current := s.paths[s.current]; isInterfaceOnPath(current, pi) || pf == current.Fingerprint { + fmt.Println("down:", s.current, len(s.paths)) + better := stats.FirstMoreAlive(current, s.paths) + if better >= 0 { + // Try next path. Note that this will keep cycling if we get down notifications + s.current = better + fmt.Println("failover:", s.current, len(s.paths)) + } + } +} + +func (s *FabridSelector) Close() error { + return nil +} diff --git a/pkg/pan/udp_dial.go b/pkg/pan/udp_dial.go index 2f90af8f..b482c748 100644 --- a/pkg/pan/udp_dial.go +++ b/pkg/pan/udp_dial.go @@ -137,7 +137,7 @@ func (c *dialedConn) WriteVia(path *Path, b []byte) (int, error) { func (c *dialedConn) Read(b []byte) (int, error) { for { - n, remote, _, err := c.baseUDPConn.readMsg(b) + n, remote, _, _, _, err := c.baseUDPConn.readMsg(b) if err != nil { return n, err } @@ -150,7 +150,7 @@ func (c *dialedConn) Read(b []byte) (int, error) { func (c *dialedConn) ReadVia(b []byte) (int, *Path, error) { for { - n, remote, fwPath, err := c.baseUDPConn.readMsg(b) + n, remote, fwPath, _, _, err := c.baseUDPConn.readMsg(b) if err != nil { return n, nil, err } diff --git a/pkg/pan/udp_listen.go b/pkg/pan/udp_listen.go index b7923d49..76939a17 100644 --- a/pkg/pan/udp_listen.go +++ b/pkg/pan/udp_listen.go @@ -24,6 +24,9 @@ import ( "sync" "time" + "github.com/scionproto/scion/pkg/slayers" + "github.com/scionproto/scion/pkg/slayers/extension" + "github.com/scionproto/scion/pkg/slayers/path/scion" "github.com/scionproto/scion/pkg/snet" ) @@ -99,6 +102,51 @@ func ListenUDP(ctx context.Context, local netip.AddrPort, }, nil } +func ListenUDPWithFabrid(ctx context.Context, local netip.AddrPort, remote UDPAddr, + selector ReplySelector) (ListenConn, error) { + + local, err := defaultLocalAddr(local) + if err != nil { + return nil, err + } + + if selector == nil { + selector = NewDefaultReplySelector() + } + stats.subscribe(selector) + sn := snet.SCIONNetwork{ + Topology: host().sciond, + SCMPHandler: scmpHandler{}, + } + conn, err := sn.OpenRaw(ctx, net.UDPAddrFromAddrPort(local)) + if err != nil { + return nil, err + } + ipport := conn.LocalAddr().(*net.UDPAddr).AddrPort() + localUDPAddr := UDPAddr{ + IA: host().ia, + IP: ipport.Addr(), + Port: ipport.Port(), + } + selector.Initialize(localUDPAddr) + + if len(os.Getenv("SCION_GO_INTEGRATION")) > 0 { + fmt.Printf("Listening addr=%s\n", localUDPAddr) + } + + server := NewFabridServer(ctx, localUDPAddr, remote) + return &fabridListenConn{ + listenConn: listenConn{ + baseUDPConn: baseUDPConn{ + raw: conn, + }, + local: localUDPAddr, + selector: selector, + }, + fabridServer: server, + }, nil +} + type listenConn struct { baseUDPConn @@ -106,6 +154,12 @@ type listenConn struct { selector ReplySelector } +type fabridListenConn struct { + listenConn + + fabridServer *FabridServer +} + func (c *listenConn) LocalAddr() net.Addr { return c.local } @@ -116,7 +170,7 @@ func (c *listenConn) ReadFrom(b []byte) (int, net.Addr, error) { } func (c *listenConn) ReadFromVia(b []byte) (int, UDPAddr, *Path, error) { - n, remote, fwPath, err := c.baseUDPConn.readMsg(b) + n, remote, fwPath, _, _, err := c.baseUDPConn.readMsg(b) if err != nil { return n, UDPAddr{}, nil, err } @@ -162,6 +216,56 @@ func NewDefaultReplySelector() *DefaultReplySelector { } } +func (c *fabridListenConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, remote, _, err := c.ReadFromVia(b) + return n, remote, err +} + +func (c *fabridListenConn) ReadFromVia(b []byte) (int, UDPAddr, *Path, error) { + n, panRemote, fwPath, hbhExt, _, err := c.baseUDPConn.readMsg(b) + if err != nil { + return n, UDPAddr{}, nil, err + } + + path, err := reversePathFromForwardingPath(panRemote.IA, c.local.IA, fwPath) + c.selector.Record(panRemote, path) + + // Check extensions for relevant options + var identifierOption *extension.IdentifierOption + var fabridOption *extension.FabridOption + if hbhExt != nil { + for _, opt := range hbhExt.Options { + switch opt.OptType { + case slayers.OptTypeIdentifier: + decoded := scion.Decoded{} + err = decoded.DecodeFromBytes(fwPath.dataplanePath.(snet.RawPath).Raw) + if err != nil { + return 0, UDPAddr{}, nil, err + } + baseTimestamp := decoded.InfoFields[0].Timestamp + identifierOption, err = extension.ParseIdentifierOption(opt, baseTimestamp) + if err != nil { + return 0, UDPAddr{}, nil, err + } + case slayers.OptTypeFabrid: + fabridOption, err = extension.ParseFabridOptionFullExtension(opt, (opt.OptDataLen-4)/4) + if err != nil { + return 0, UDPAddr{}, nil, err + } + } + } + } + if fabridOption != nil && identifierOption != nil { + + err = c.fabridServer.HandleFabridPacket(fabridOption, identifierOption) + if err != nil { + return 0, UDPAddr{}, nil, err + } + } + + return n, panRemote, path, err +} + func (s *DefaultReplySelector) Initialize(local UDPAddr) { }