diff --git a/control/cmd/control/main.go b/control/cmd/control/main.go index cb9db22c48..7df6f2db15 100644 --- a/control/cmd/control/main.go +++ b/control/cmd/control/main.go @@ -566,16 +566,22 @@ func realMain(ctx context.Context) error { healthpb.RegisterHealthServer(tcpServer, dsHealth) hpCfg := cs.HiddenPathConfigurator{ - LocalIA: topo.IA(), - Verifier: verifier, - Signer: signer, - PathDB: pathDB, - Dialer: dialer, - FetcherConfig: fetcherCfg, - IntraASTCPServer: tcpServer, - InterASQUICServer: quicServer, - } - hpWriterCfg, err := hpCfg.Setup(globalCfg.PS.HiddenPathsCfg) + LocalIA: topo.IA(), + Verifier: verifier, + Signer: signer, + PathDB: pathDB, + Dialer: dialer, + FetcherConfig: fetcherCfg, + IntraASTCPServer: tcpServer, + } + // (XXX)JordiSubira: We should revisit how we want to handle HP service, + // right now it only seems to be used within the CS. So perhaps we should treat + // it as a part of the CS (same as BS, CertServ, DRKey, etc). For the moment, we + // create a different grpc.Server endpoint that will be located behind a different + // quic socket using the IP:port in the topology.json file. This is required + // because the client side, uses the DS to discover the address of the remote + // HP server, thus it should use whatever is written in the topology file. + hpInterASServer, hpWriterCfg, err := hpCfg.Setup(globalCfg.PS.HiddenPathsCfg) if err != nil { return err } @@ -677,6 +683,26 @@ func realMain(ctx context.Context) error { return nil }) cleanup.Add(func() error { tcpServer.GracefulStop(); return nil }) + if hpInterASServer != nil { + a, err := topo.HiddenSegmentRegistrationAddresses() + if err != nil { + return err + } + if len(a) == 0 { + return serrors.New("Hidden path registration address expected and not found") + } + // XXX(JordiSubira): Just take the first address, we only use topology.json with + // information for one unique AS. + hpListener, err := nc.OpenListener(a[0].String()) + g.Go(func() error { + defer log.HandlePanic() + if err := hpInterASServer.Serve(hpListener); err != nil { + return serrors.WrapStr("serving Hidden Path API", err) + } + return nil + }) + cleanup.Add(func() error { hpInterASServer.GracefulStop(); return nil }) + } if globalCfg.API.Addr != "" { r := chi.NewRouter() diff --git a/control/hiddenpaths.go b/control/hiddenpaths.go index 299d265871..3d57af693f 100644 --- a/control/hiddenpaths.go +++ b/control/hiddenpaths.go @@ -31,31 +31,31 @@ import ( // HiddenPathConfigurator can be used to configure the hidden path servers. type HiddenPathConfigurator struct { - LocalIA addr.IA - Verifier infra.Verifier - Signer hpgrpc.Signer - PathDB pathdb.DB - Dialer libgrpc.Dialer - FetcherConfig segreq.FetcherConfig - IntraASTCPServer *grpc.Server - InterASQUICServer *grpc.Server + LocalIA addr.IA + Verifier infra.Verifier + Signer hpgrpc.Signer + PathDB pathdb.DB + Rewriter libgrpc.AddressRewriter + Dialer libgrpc.Dialer + FetcherConfig segreq.FetcherConfig + IntraASTCPServer *grpc.Server } // Setup sets up the hidden paths servers using the configuration at the given // location. An empty location will not enable any hidden path behavior. It // returns the configuration for the hidden segment writer. The return value can // be nil if this AS isn't a writer. -func (c HiddenPathConfigurator) Setup(location string) (*HiddenPathRegistrationCfg, error) { +func (c HiddenPathConfigurator) Setup(location string) (*grpc.Server, *HiddenPathRegistrationCfg, error) { if location == "" { - return nil, nil + return nil, nil, nil } groups, regPolicy, err := hiddenpath.LoadConfiguration(location) if err != nil { - return nil, err + return nil, nil, err } roles := groups.Roles(c.LocalIA) if roles.None() { - return nil, nil + return nil, nil, nil } log.Info("Starting hidden path forward server") hspb.RegisterHiddenSegmentLookupServiceServer(c.IntraASTCPServer, &hpgrpc.SegmentServer{ @@ -67,26 +67,33 @@ func (c HiddenPathConfigurator) Setup(location string) (*HiddenPathRegistrationC Dialer: c.Dialer, Signer: c.Signer, }, - Resolver: hiddenpath.LookupResolver{ + HPResolver: hiddenpath.LookupResolver{ Router: segreq.NewRouter(c.FetcherConfig), Discoverer: &hpgrpc.Discoverer{ Dialer: c.Dialer, }, }, + CSResolver: hiddenpath.CSResolver{ + Router: segreq.NewRouter(c.FetcherConfig), + Rewriter: c.Rewriter, + }, Verifier: hiddenpath.VerifierAdapter{ Verifier: c.Verifier, }, }, }) + var interASServer *grpc.Server if roles.Registry { + interASServer = grpc.NewServer( + libgrpc.UnaryServerInterceptor(), + ) log.Info("Starting hidden path authoritative and registration server") - hspb.RegisterAuthoritativeHiddenSegmentLookupServiceServer(c.InterASQUICServer, + hspb.RegisterAuthoritativeHiddenSegmentLookupServiceServer(interASServer, &hpgrpc.AuthoritativeSegmentServer{ Lookup: c.localAuthServer(groups), Verifier: c.Verifier, - }, - ) - hspb.RegisterHiddenSegmentRegistrationServiceServer(c.InterASQUICServer, + }) + hspb.RegisterHiddenSegmentRegistrationServiceServer(interASServer, &hpgrpc.RegistrationServer{ Registry: hiddenpath.RegistryServer{ Groups: groups, @@ -103,21 +110,22 @@ func (c HiddenPathConfigurator) Setup(location string) (*HiddenPathRegistrationC ) } if !roles.Writer { - return nil, nil + return interASServer, nil, nil } log.Info("Using hidden path beacon writer") - return &HiddenPathRegistrationCfg{ - Policy: regPolicy, - Router: segreq.NewRouter(c.FetcherConfig), - Discoverer: &hpgrpc.Discoverer{ - Dialer: c.Dialer, - }, - RPC: &hpgrpc.Registerer{ - Dialer: c.Dialer, - RegularRegistration: beaconinggrpc.Registrar{Dialer: c.Dialer}, - Signer: c.Signer, - }, - }, nil + return interASServer, + &HiddenPathRegistrationCfg{ + Policy: regPolicy, + Router: segreq.NewRouter(c.FetcherConfig), + Discoverer: &hpgrpc.Discoverer{ + Dialer: c.Dialer, + }, + RPC: &hpgrpc.Registerer{ + Dialer: c.Dialer, + RegularRegistration: beaconinggrpc.Registrar{Dialer: c.Dialer}, + Signer: c.Signer, + }, + }, nil } func (c HiddenPathConfigurator) localAuthServer(groups hiddenpath.Groups) hiddenpath.Lookuper { diff --git a/pkg/experimental/hiddenpath/BUILD.bazel b/pkg/experimental/hiddenpath/BUILD.bazel index 3962a4665e..909f9853ea 100644 --- a/pkg/experimental/hiddenpath/BUILD.bazel +++ b/pkg/experimental/hiddenpath/BUILD.bazel @@ -19,6 +19,7 @@ go_library( "//control/beaconing:go_default_library", "//control/ifstate:go_default_library", "//pkg/addr:go_default_library", + "//pkg/grpc:go_default_library", "//pkg/log:go_default_library", "//pkg/metrics:go_default_library", "//pkg/private/prom:go_default_library", diff --git a/pkg/experimental/hiddenpath/discovery.go b/pkg/experimental/hiddenpath/discovery.go index f336a152c0..2ff44f0e28 100644 --- a/pkg/experimental/hiddenpath/discovery.go +++ b/pkg/experimental/hiddenpath/discovery.go @@ -19,6 +19,7 @@ import ( "net" "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/grpc" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/path" @@ -61,6 +62,32 @@ func (r RegistrationResolver) Resolve(ctx context.Context, ia addr.IA) (net.Addr }) } +// CSResolver resolves the address of a Control Service +// server in an IA. This is needed to get the needed +// certificates from the Register to verify the segments. +type CSResolver struct { + Router snet.Router + Rewriter grpc.AddressRewriter +} + +// Resolve resolves the CS server in the remote IA. +func (r CSResolver) Resolve(ctx context.Context, ia addr.IA) (net.Addr, error) { + // TODO(JordiSubira): Put path failover mechanism in-place + path, err := r.Router.Route(ctx, ia) + if err != nil { + return nil, serrors.WrapStr("looking up path", err) + } + if path == nil { + return nil, serrors.WrapStr("no path found to remote", err) + } + return &snet.SVCAddr{ + IA: ia, + NextHop: path.UnderlayNextHop(), + Path: path.Dataplane(), + SVC: addr.SvcCS, + }, nil +} + // LookupResolver resolves the address of a hidden segment lookup // server in an IA. type LookupResolver struct { diff --git a/pkg/experimental/hiddenpath/forwarder.go b/pkg/experimental/hiddenpath/forwarder.go index b7cfaf300c..884b525e85 100644 --- a/pkg/experimental/hiddenpath/forwarder.go +++ b/pkg/experimental/hiddenpath/forwarder.go @@ -46,12 +46,13 @@ type Verifier interface { // For each group id of the request, it requests the segments at the the // respective autoritative registry. type ForwardServer struct { - Groups map[GroupID]*Group - LocalAuth Lookuper - LocalIA addr.IA - RPC RPC - Resolver AddressResolver - Verifier Verifier + Groups map[GroupID]*Group + LocalAuth Lookuper + LocalIA addr.IA + RPC RPC + HPResolver AddressResolver + CSResolver AddressResolver + Verifier Verifier } // Segments serves segments for the given request. It finds per group ID @@ -104,7 +105,7 @@ func (s ForwardServer) Segments(ctx context.Context, replies <- segsOrErr{segs: reply, err: err} return } - a, err := s.Resolver.Resolve(ctx, r) + a, err := s.HPResolver.Resolve(ctx, r) if err != nil { replies <- segsOrErr{err: err} return @@ -114,9 +115,14 @@ func (s ForwardServer) Segments(ctx context.Context, replies <- segsOrErr{err: err} return } + a, err = s.CSResolver.Resolve(ctx, r) + if err != nil { + replies <- segsOrErr{err: err} + return + } if err := s.Verifier.Verify(ctx, reply, a); err != nil { replies <- segsOrErr{ - err: serrors.New("can not verify segments", "crypto-source", r, + err: serrors.WrapStr("verifying segment", err, "crypto-source", r, "server", a), } return diff --git a/pkg/experimental/hiddenpath/forwarder_test.go b/pkg/experimental/hiddenpath/forwarder_test.go index 6d5d54a287..f68aaca26b 100644 --- a/pkg/experimental/hiddenpath/forwarder_test.go +++ b/pkg/experimental/hiddenpath/forwarder_test.go @@ -115,12 +115,13 @@ func TestForwardServerSegments(t *testing.T) { AnyTimes() server := hiddenpath.ForwardServer{ - Groups: tc.groups(), - RPC: tc.rpc(ctrl), - LocalAuth: tc.lookuper(ctrl), - LocalIA: local, - Verifier: tc.verifier(ctrl), - Resolver: resolver, + Groups: tc.groups(), + RPC: tc.rpc(ctrl), + LocalAuth: tc.lookuper(ctrl), + LocalIA: local, + Verifier: tc.verifier(ctrl), + HPResolver: resolver, + CSResolver: resolver, } got, err := server.Segments(context.Background(), tc.request) tc.assertErr(t, err) diff --git a/private/app/appnet/infraenv.go b/private/app/appnet/infraenv.go index 424224257b..5e03a21687 100644 --- a/private/app/appnet/infraenv.go +++ b/private/app/appnet/infraenv.go @@ -232,6 +232,37 @@ func (nc *NetworkConfig) AddressRewriter( } } +func (nc *NetworkConfig) OpenListener(a string) (*squic.ConnListener, error) { + scionNet := &snet.SCIONNetwork{ + LocalIA: nc.IA, + Connector: &snet.DefaultConnector{ + // XXX(roosd): This is essential, the server must not read SCMP + // errors. Otherwise, the accept loop will always return that error + // on every subsequent call to accept. + SCMPHandler: ignoreSCMP{}, + Metrics: nc.SCIONPacketConnMetrics, + }, + Metrics: nc.SCIONNetworkMetrics, + } + udpAddr, err := net.ResolveUDPAddr("udp", a) + if err != nil { + return nil, serrors.WrapStr("parsing server QUIC address", err) + } + server, err := scionNet.Listen(context.Background(), "udp", udpAddr, addr.SvcNone) + if err != nil { + return nil, serrors.WrapStr("creating server connection", err) + } + serverTLSConfig, err := GenerateTLSConfig() + if err != nil { + return nil, err + } + listener, err := quic.Listen(server, serverTLSConfig, nil) + if err != nil { + return nil, err + } + return squic.NewConnListener(listener), nil +} + // initSvcRedirect creates the main control-plane UDP socket. SVC anycasts will be // delivered to this socket, which replies to SVC resolution requests. The // address will be included as the QUIC address in SVC resolution replies.