-
Notifications
You must be signed in to change notification settings - Fork 178
daemon: refactoring to enable applications running in-library self-contained daemon logic (a.k.a standalone mode) #4853
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
base: master
Are you sure you want to change the base?
Conversation
JordiSubira
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tackling some questions in the description:
RevNotification handling: RevNotification should of course trigger NotifyInterfaceDown, but should NotifyInterfaceDown also trigger RevNotification, or should this be handled differently?
I do not completely undestand. So far, applications used RevNotification() "reactively" after receiving a "PathDown" SCMP message. The API factory internally used the daemon gRPC endpoint NotifyInterfaceDown() to inform the daemon service about it. Then the server added this information on the RevCache. If we keep the same definition of daemon.Connector, how can it be triggered a NotificationDown without a RevNotification() ?
In addition to running the Acceptance Tests, I spun up a fresh Kathara setup with the new binaries and no caches to see if I could ping a neighbor, which works without issues.
If I am not wrong, you have not changed the acceptance test setup, right? This means that the acceptance/integration test keep running with daemons for all cases right? I would at least create a new acceptance test that runs an end2end_integration test with standalone client, or even better, making this the default behavior and having one test with "non-standalone" clients and the rest runs without daemons. At the end, we are moving to this deployment model in most scenarios.
After putting the mentioned functions under a bit more scrutiny, I think that the splitting (the first commit of this branch) was performed correctly for this functionality. The existing daemon grpc stub contstructs a _, err := client.NotifyInterfaceDown(ctx, &sdpb.NotifyInterfaceDownRequest{
Id: uint64(revInfo.IfID),
IsdAs: uint64(revInfo.RawIsdas),
})The daemon then reconstructs the revInfo := &path_mgmt.RevInfo{
RawIsdas: addr.IA(req.IsdAs),
IfID: iface.ID(req.Id),
LinkType: proto.LinkType_core,
RawTTL: 10,
RawTimestamp: util.TimeToSecs(time.Now()),
}
_, err := s.RevCache.Insert(ctx, revInfo)However, if the new backend is plugged in as the connector direcly, this replacement never happens, and the // no LinkType is set here!
return h.handleSCMPRev(typeCode, &path_mgmt.RevInfo{
IfID: iface.ID(msg.Interface),
RawIsdas: msg.IA,
RawTimestamp: util.TimeToSecs(time.Now()),
RawTTL: 10,
})Im not sure of the consequences of this and would love some additional thoughts on this. |
6f73c82 to
07020c4
Compare
|
Btw force pushing is really not great if we use github review... It loses all context in that case. |
e6616a6 to
bcaf6a0
Compare
|
The diff is currently unnecessary large since it's not up to date with |
I will of course rebase before merging.
What changes do you mean concretely? I always try to not change anything unnecessary. For the few whitespace changes, these were mostly needed due to lines reaching the 100 char mark after renaming types, failing the linter test. |
Continuously rebasing, not just before merging, would help with reviewing the PR.
Looking for example at scion/cmd/scion/ping.go, we have many changes like from to |
|
Happy New Year! 🎉 After further discussing the old split of the gRPC and backend/DaemonEngine, we came to the conclusion that it's actually a bad idea to have the engine conform to Connector. So I reset the gRPC server to master and re-split it into gRPC and engine - and I have to say you were right, it works out quite a bit nicer without trying to force it into Connector. The StandaloneDaemon now serves a similar purpose as the gRPC server: being an adapter on top of the engine and implementing the Connector interface. It also handles closing all the closable resources.
|
I guess after refactoring to only use the |
Found the culprit! While troubleshooting the CI linter failures, I accidentally enabled an auto-formatting option in my IDE that went rogue without me noticing. I've reverted all the unnecessary changes I could find. Let me know if you spot any others I missed. Apologies for the noise. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, aside from the few nitpicks.
my only concern was #4853 (comment)
P.S. I'm not too familiar with the daemon at this point.
0d6896a to
f43025e
Compare
| if !srcCore { | ||
| reqs = append(reqs, Requests{{Src: src, Dst: toWildCard(src), SegType: Up}, | ||
| {Src: toWildCard(src), Dst: toWildCard(dst), SegType: Core}, | ||
| {Src: toWildCard(dst), Dst: dst, SegType: Core}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| {Src: toWildCard(dst), Dst: dst, SegType: Core}, | |
| {Src: toWildCard(src), Dst: dst, SegType: Core}, |
Seems this is wrong.
This seg request is used when the source is not a core AS, and the destination IS a core AS, we then need Up segments to our local ISD's core AS and a core segments from the source ISD to the destination AS.
The current code will get core segments from the source ISD to the desination ISD. These segments will likely anyways contain the destination AS, as it is a core AS, but technically does not have to, so I think that request should be made explicit.
Requesting core segments from the destination ISD's core ASes to the destination core AS does not make a lot of sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this still apply on the current HEAD?
if s.Inspector == nil { // In case inspector is not set, fall back to basic splitting
if srcCore {
return Requests{
{SegType: Down, Src: src, Dst: dst},
{SegType: Core, Src: src, Dst: dst},
{SegType: Core, Src: src, Dst: toWildCard(dst)},
{SegType: Down, Src: toWildCard(dst), Dst: dst},
}, nil
}
reqs := Requests{
{SegType: Up, Src: src, Dst: toWildCard(src)},
{SegType: Core, Src: toWildCard(src), Dst: toWildCard(dst)},
{SegType: Core, Src: toWildCard(dst), Dst: dst},
{SegType: Down, Src: toWildCard(dst), Dst: dst},
}
if src.ISD() == dst.ISD() && dst.IsWildcard() {
reqs = append(reqs, Request{SegType: Up, Src: src, Dst: dst})
}
return reqs, nil
}EDIT:
does this fix it?
if s.Inspector == nil { // In case inspector is not set, fall back to basic splitting
if srcCore {
return Requests{
{SegType: Down, Src: src, Dst: dst},
{SegType: Core, Src: src, Dst: dst},
{SegType: Core, Src: src, Dst: toWildCard(dst)},
{SegType: Down, Src: toWildCard(dst), Dst: dst},
}, nil
}
reqs := Requests{
{SegType: Up, Src: src, Dst: toWildCard(src)},
{SegType: Core, Src: toWildCard(src), Dst: toWildCard(dst)},
{SegType: Core, Src: toWildCard(src), Dst: dst},
{SegType: Down, Src: toWildCard(dst), Dst: dst},
}
if src.ISD() == dst.ISD() && dst.IsWildcard() {
reqs = append(reqs, Request{SegType: Up, Src: src, Dst: dst})
}
return reqs, nil
}committed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you (@jeltevanbommel ) also confirm that:
{SegType: Up, Src: src, Dst: toWildCard(src)},
and
if src.ISD() == dst.ISD() && dst.IsWildcard() {
reqs = append(reqs, Request{SegType: Up, Src: src, Dst: dst})
}
are redundant? If affirmative, we can remove the latter. This would also simplify the readability (related to Roman's comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, they should be redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jeltevanbommel The SCION book suggests using at most three queries, one UP, one CORE and one DOWN.
Should we add some documentation here why four (or more?) requests are made?
I suspect that it has to do with a query returning a maximum of 20 segments, so the CORE(wildcard(src), dst) query is necessary because CORE(wildcard(src), wildcard(dst)) may not return any segments that end at dst if dst is a core?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the regular Daemon implementation for path lookup we rely on having the TRCs to determine which ASes are core and which are not. We can then optimize the query to the path server as then we'll know whether we need up or down segments.
In the daemonless case, we do not want to rely on the TRCs, as it's an extra storage requirement (or requires fetching before we can do the path lookup) and hence we look up as if the destination is both a core AS and not a core AS, meaning you have to do an extra requests.
Example: Assuming we are not a core AS. If the destination is not a core AS, you'll need a core path from src ISD to dst ISD, and then a down path from dst ISD wildcard to dst AS => {CORE, wildcard(src), wildcard(dst)}, {DOWN, wildcard(dst), dst}
If the destination is a core AS, you'll need a core path from src ISD to dst ISD,AS. So you'll request {CORE, wildcard(src), dst}.
We cannot assume anything, so we'll request both cases, and let the combinator figure it out:
{CORE, wildcard(src), wildcard(dst)}, {DOWN, wildcard(dst), dst}, {CORE, wildcard(src), dst}.
Maybe adding this as some docs is good indeed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot assume anything, so we'll request both cases, and let the combinator figure it out:
{CORE, wildcard(src), wildcard(dst)}, {DOWN, wildcard(dst), dst}, {CORE, wildcard(src), dst}.
But if we didn't have the 20 segment limit, then the CORE(wildcard, wildcard) query would always contain the correct CORE segment, no need to ask twice? Also, if our dst-AS is one of the endpoints in the CORE segments, then it must be a core AS, no need to ask the TRC?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, if the path server would return all path segments then we would not need to ask twice. And yes we can, based on the replies we get, also determine whether we would need any subsequent requests (i.e. not have to request the DOWN segment if we see the AS as a CORE AS). However, that is not how the current system is implemented and changing that would require a lot more changes. At that point I think it'd be better to rethink this in general, and for instance consider a mode at the path server where one can just provide src,dst and the path server handles this splitting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At that point I think it'd be better to rethink this in general, and for instance consider a mode at the path server where one can just provide src,dst and the path server handles this splitting.
That would be the new endhost API :-)
|
So, given that there are no more open points, can I now rebase it into a clean git history? Or do you want to keep it as it is? |
8d83ef0 to
ff6b95e
Compare
This PR refactors the daemon codebase to prepare for standalone operation support. It separates concerns and moves reusable components to `pkg/daemon`. This is part 1 of a series splitting #4853 into smaller, reviewable PRs. The original PR will be closed. **Main Changes** - **Split daemon server**: Separated the existing daemon gRPC server into transport (`daemon/grpc/server.go`) and core logic (`DaemonEngine` in `pkg/daemon/private/engine`) components. The engine contains the business logic, independent of transport. - **Move fetcher to pkg**: Relocated `daemon/fetcher` to `pkg/daemon/fetcher` to make it accessible for in-process usage. - **Move drkey to private**: Relocated `daemon/drkey` to `private/drkey` as it is shared infrastructure. - **Add ASInfo abstraction**: Introduced `pkg/daemon/asinfo` to provide local AS information from topology files. - **Add trust utilities**: Created `pkg/daemon/private/trust` with helpers for trust verification setup. - **Add AcceptAll verifier**: Added `private/segment/verifier/acceptall.go` for scenarios where segment verification should be skipped. - **Rename daemon.go to connector.go**: Clarified the purpose of the `Connector` interface in `pkg/daemon`.
**Summary** This PR implements standalone daemon functionality, allowing applications to communicate with the control service directly and run daemon functionality in-process without requiring a separate SCION daemon. This is part 2 of a series splitting #4853 into smaller, reviewable PRs. The original PR will be closed. **Main Changes** - **Implement standalone connector**: Added `NewStandaloneConnector()` in `pkg/daemon/standalone.go` to create a `Connector` with in-process pathdb, trust verification, and revocation cache. - **Add auto connector**: Implemented `NewAutoConnector()` in `pkg/daemon/auto.go` that selects between standalone and remote daemon based on availability: - Priority 1: Use supplied daemon address (gRPC) - Priority 2: Load from topology file if exists (standalone) - **Add documentation**: Created `pkg/daemon/doc.go` documenting the public API and usage patterns. **Follow-up Tasks** - **Bootstrapping functionality**: The `NewAutoConnector()` function could implement options to automatically bootstrap SCION connectivity from bootstrap servers / DNS records. - **DRKey in Standalone Mode**: The `NewStandaloneConnector()` should set up and manage a `DRKeyEngine` appropriately configured for standalone mode. Currently DRKey is explicitly disabled.
Summary
This PR allows applications to run without a SCION daemon by communicating with the control service directly and running daemon functionality in-process.
Main Changes
Split daemon server: Separated the existing daemon gRPC server into gRPC (
DaemonServer) and core logic (DaemonEngine) components. The engine contains the business logic, independent of transport.Move engine to pkg: Moved the
DaemonEngineintopkg/daemon/private/engineto make it accessible for in-process usage.Implement standalone connector: Added
NewStandaloneConnector()to create aConnectorwith in-process pathdb, trust verification, revocation cache, etc.Add DefaultConnector helper: Implemented
NewDefaultConnector()that selects between standalone and remote daemon based on availability:Update scion-cli: Adapted the CLI to use the new connector system via
--sciondflag. If supplied, a remote daemon is used, by default the standalone is used.Testing
Added
daemon_vs_standaloneacceptance test that runs e2e integration test with both modes. The test are already integrated in the CI/CDAT: daemon_vs_standalone_daemonandAT: daemon_vs_standalone_standalone.One can alternatively run both test using the Bazel framework provided that the local development is installed (https://docs.scion.org/en/latest/dev/setup.html#setup). The commands are respectively:
Testing scion utility in your SCION environment
If not having or not wanting to use the development setup, one can also directly try out the standalone feature by compiling the
scionutility. Thescionutility is an endhost CLI tool that can ping or retrieve SCION path when run in an endhost connected to a SCION network (production network, local setup, container network, etc.).To compile the tool, check out to this PR and:
For example, if you want to try out the feature in the production network, you can try to fetch paths to the ETHZ AS, as follows.
The binary needs a valid
topology.jsonfile. By default it will try to find it under/etc/scion/topology.jsonif defined elsewhere please, specify the parent directory with the--configflag.Follow-up Tasks (not considered in this PR)
NewAutoConnector()function should implement options to automatically bootstrap SCION connectivity from bootstrap servers / DNS records similar to JPAN.NewStandaloneConnector()should set up and manage aDRKeyEngineappropriately configured for standalone mode. Currently DRKey is explicitly disabled by setting the engine tonil.Open Discussion Points
Naming/API
daemon.NewService()be renamed todaemon.NewRemoteService()for symmetry withNewStandaloneConnector(), breaking the API?LocalASInfo(formerlyCPInfoandTopology) now ok?verifier.AcceptAllbe renamed toAcceptAllVerifier,TrustAllVerifier, orNoOpVerifier?SuppliedOptionbe renamed? Options:DefaultOption,connectorOption. Should it be exported? Should it match the pattern ofstandaloneOption?Logic/Design
Code Organization
pkg/daemon/private/typesmove topkg/daemon/types, breaking the API?pkg/daemon/private/drkeymove toprivate/drkey?pkg/daemonAPI minimal and appropriate?Documentation
pkg/daemondocumenting the public API and usage patterns (once other points resolved)