Skip to content

Commit

Permalink
epic: endhost logic
Browse files Browse the repository at this point in the history
This PR implements the EPIC endhost logic.
Applications can now send packets using the EPIC path type header.

Major changes:
- The gRPC communication between the SCION daemon and the application is extended, so that the application gets the two authenticators necessary to calculate the PHVF and LHVF when fetching paths from the daemon.
- `snet` contains the major logic behind the creation of EPIC packets, where `snet/path` keeps the necessary state and uses it to create the PHVF and LHVF when the packet is serialized.
- There is an additional argument "--epic" for the ping, traceroute, showpaths, end2end_integration, and scion_integration tools. The argument enables those tools to send EPIC-HP path type packets (if paths with the necessary EPIC authenticators are available).

Closes #4079

GitOrigin-RevId: f7436eb98840064fc905d56602ae8d65f7f44106
  • Loading branch information
mawyss authored and oncilla committed Mar 12, 2022
1 parent dd97571 commit e9f9b49
Show file tree
Hide file tree
Showing 30 changed files with 717 additions and 240 deletions.
3 changes: 3 additions & 0 deletions go/cs/config/bs_sample.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ propagation_interval = "5s"
# The interval between registering beacons. (default 5s)
registration_interval = "5s"
# Add EPIC authenticators to the beacons. (default false)
epic = false
`

const policiesSample = `
Expand Down
2 changes: 2 additions & 0 deletions go/cs/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ type BSConfig struct {
RegistrationInterval util.DurWrap `toml:"registration_interval,omitempty"`
// Policies contains the policy files.
Policies Policies `toml:"policies,omitempty"`
// EPIC specifies whether the EPIC authenticators should be added to the beacons.
EPIC bool `toml:"epic,omitempty" default:"false"`
}

// InitDefaults the default values for the durations that are equal to zero.
Expand Down
1 change: 1 addition & 0 deletions go/cs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,7 @@ func realMain(ctx context.Context) error {
RegistrationInterval: globalCfg.BS.RegistrationInterval.Duration,
HiddenPathRegistrationCfg: hpWriterCfg,
AllowIsdLoop: isdLoopAllowed,
EPIC: globalCfg.BS.EPIC,
})
if err != nil {
return serrors.WrapStr("starting periodic tasks", err)
Expand Down
2 changes: 2 additions & 0 deletions go/dispatcher/dispatcher/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ go_library(
"//go/lib/ringbuf:go_default_library",
"//go/lib/serrors:go_default_library",
"//go/lib/slayers:go_default_library",
"//go/lib/slayers/path/epic:go_default_library",
"//go/lib/slayers/path/scion:go_default_library",
"//go/lib/underlay/conn:go_default_library",
"@com_github_google_gopacket//:go_default_library",
],
Expand Down
11 changes: 11 additions & 0 deletions go/dispatcher/dispatcher/underlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import (
"github.com/scionproto/scion/go/lib/ringbuf"
"github.com/scionproto/scion/go/lib/serrors"
"github.com/scionproto/scion/go/lib/slayers"
"github.com/scionproto/scion/go/lib/slayers/path/epic"
"github.com/scionproto/scion/go/lib/slayers/path/scion"
)

const (
Expand Down Expand Up @@ -343,6 +345,15 @@ func (h SCMPHandler) reverseSCION(pkt *respool.Packet) error {
if err := pkt.SCION.SetDstAddr(src); err != nil {
return serrors.WrapStr("setting destination address", err)
}
if pkt.SCION.PathType == epic.PathType {
// Received packet with EPIC path type, hence extract the SCION path
epicPath, ok := pkt.SCION.Path.(*epic.Path)
if !ok {
return serrors.New("path type and path data do not match")
}
pkt.SCION.Path = epicPath.ScionPath
pkt.SCION.PathType = scion.PathType
}
if pkt.SCION.Path, err = pkt.SCION.Path.Reverse(); err != nil {
return serrors.WrapStr("reversing path", err)
}
Expand Down
23 changes: 19 additions & 4 deletions go/integration/end2end/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ var (
timeout = &util.DurWrap{Duration: 10 * time.Second}
scionPacketConnMetrics = metrics.NewSCIONPacketConnMetrics()
scmpErrorsCounter = scionPacketConnMetrics.SCMPErrors
epic bool
)

func main() {
Expand Down Expand Up @@ -98,6 +99,7 @@ func realMain() int {
func addFlags() {
flag.Var(&remote, "remote", "(Mandatory for clients) address to connect to")
flag.Var(timeout, "timeout", "The timeout for each attempt")
flag.BoolVar(&epic, "epic", false, "Enable EPIC.")
}

func validateFlags() {
Expand Down Expand Up @@ -361,11 +363,11 @@ func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) {
if err != nil {
return nil, withTag(serrors.WrapStr("requesting paths", err))
}
// if all paths had an error, let's try them again.
// If all paths had an error, let's try them again.
if len(paths) <= len(c.errorPaths) {
c.errorPaths = make(map[snet.PathFingerprint]struct{})
}
// select first path that didn't error before.
// Select first path that didn't error before.
var path snet.Path
for _, p := range paths {
if _, ok := c.errorPaths[snet.Fingerprint(p)]; ok {
Expand All @@ -380,8 +382,21 @@ func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) {
"errors", len(c.errorPaths),
))
}
// Extract forwarding path from the SCION Daemon response
remote.Path = path.Dataplane()
// Extract forwarding path from the SCION Daemon response.
// If the epic flag is set, try to use the EPIC path type header.
if epic {
scionPath, ok := path.Dataplane().(snetpath.SCION)
if !ok {
return nil, serrors.New("provided path must be of type scion")
}
epicPath, err := snetpath.NewEPICDataplanePath(scionPath, path.Metadata().EpicAuths)
if err != nil {
return nil, err
}
remote.Path = epicPath
} else {
remote.Path = path.Dataplane()
}
remote.NextHop = path.UnderlayNextHop()
return path, nil
}
Expand Down
4 changes: 4 additions & 0 deletions go/integration/end2end_integration/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ var (
name string
cmd string
features string
epic bool
)

func getCmd() (string, bool) {
Expand Down Expand Up @@ -74,6 +75,7 @@ func realMain() int {
"-sciond", integration.Daemon,
"-local", integration.SrcAddrPattern + ":0",
"-remote", integration.DstAddrPattern + ":" + integration.ServerPortReplace,
fmt.Sprintf("-epic=%t", epic),
}
serverArgs := []string{
"-mode", "server",
Expand Down Expand Up @@ -111,6 +113,7 @@ func addFlags() {
flag.IntVar(&parallelism, "parallelism", 1, "How many end2end tests run in parallel.")
flag.StringVar(&features, "features", "",
fmt.Sprintf("enable development features (%v)", feature.String(&feature.Default{}, "|")))
flag.BoolVar(&epic, "epic", false, "Enable EPIC.")
}

// runTests runs the end2end tests for all pairs. In case of an error the
Expand Down Expand Up @@ -275,6 +278,7 @@ func clientTemplate(progressSock string) integration.Cmd {
"-sciond", integration.Daemon,
"-local", integration.SrcAddrPattern + ":0",
"-remote", integration.DstAddrPattern + ":" + integration.ServerPortReplace,
fmt.Sprintf("-epic=%t", epic),
},
}
if len(features) != 0 {
Expand Down
4 changes: 3 additions & 1 deletion go/integration/scion_integration/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
var (
features string
test string
epic bool
)

func main() {
Expand All @@ -54,6 +55,7 @@ func realMain() int {
"--timeout", "4s",
"--sciond", integration.Daemon,
"--log.level", "debug",
fmt.Sprintf("--epic=%t", epic),
}
if *integration.Docker {
cmnArgs = append(cmnArgs,
Expand Down Expand Up @@ -123,5 +125,5 @@ func addFlags() {
fmt.Sprintf("enable development features (%v)", feature.String(&feature.Default{}, "|")))
flag.StringVar(&test, "test", "",
"Test to run. If empty, all tests are run.")

flag.BoolVar(&epic, "epic", false, "Enable EPIC.")
}
13 changes: 11 additions & 2 deletions go/lib/daemon/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func convertPath(p *sdpb.Path, dst addr.IA) (path.Path, error) {
linkType[i] = linkTypeFromPB(v)
}

return path.Path{
res := path.Path{
Src: interfaces[0].IA,
Dst: dst,
DataplanePath: path.SCION{
Expand All @@ -230,7 +230,16 @@ func convertPath(p *sdpb.Path, dst addr.IA) (path.Path, error) {
InternalHops: p.InternalHops,
Notes: p.Notes,
},
}, nil
}

if p.EpicAuths == nil {
return res, nil
}
res.Meta.EpicAuths = snet.EpicAuths{
AuthPHVF: append([]byte(nil), p.EpicAuths.AuthPhvf...),
AuthLHVF: append([]byte(nil), p.EpicAuths.AuthLhvf...),
}
return res, nil
}

func linkTypeFromPB(lt sdpb.LinkType) snet.LinkType {
Expand Down
2 changes: 1 addition & 1 deletion go/lib/epic/epic.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func VerifyHVF(auth []byte, pktID epic.PktID, s *slayers.SCION,

if subtle.ConstantTimeCompare(hvf, mac) == 0 {
return serrors.New("epic hop validation field verification failed",
"hvf in packet", hvf, "calculated mac", mac)
"hvf in packet", hvf, "calculated mac", mac, "auth", auth)
}
return nil
}
Expand Down
58 changes: 57 additions & 1 deletion go/lib/infra/modules/combinator/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,12 @@ type pathSolution struct {
func (solution *pathSolution) Path() Path {
mtu := ^uint16(0)
var segments segmentList
var epicPathAuths [][]byte
for _, solEdge := range solution.edges {
var hops []path.HopField
var intfs []snet.PathInterface
var pathASEntries []seg.ASEntry // ASEntries that on the path, eventually in path order.
var epicSegAuths [][]byte

// Go through each ASEntry, starting from the last one, until we
// find a shortcut (which can be 0, meaning the end of the segment).
Expand All @@ -324,6 +326,7 @@ func (solution *pathSolution) Path() Path {

var hopField path.HopField
var forwardingLinkMtu int
var epicAuth []byte
if !isPeer {
// Regular hop field.
entry := asEntry.HopEntry
Expand All @@ -334,6 +337,7 @@ func (solution *pathSolution) Path() Path {
Mac: entry.HopField.MAC,
}
forwardingLinkMtu = entry.IngressMTU
epicAuth = getAuth(&asEntry)
} else {
// We've reached the ASEntry where we want to switch
// segments on a peering link.
Expand All @@ -345,6 +349,7 @@ func (solution *pathSolution) Path() Path {
Mac: peer.HopField.MAC,
}
forwardingLinkMtu = peer.PeerMTU
epicAuth = getAuthPeer(&asEntry, solEdge.edge.Peer-1)
}

// Segment is traversed in reverse construction direction.
Expand All @@ -364,6 +369,7 @@ func (solution *pathSolution) Path() Path {
}
hops = append(hops, hopField)
pathASEntries = append(pathASEntries, asEntry)
epicSegAuths = append(epicSegAuths, epicAuth)

mtu = minUint16(mtu, uint16(asEntry.MTU))
if forwardingLinkMtu != 0 {
Expand All @@ -376,6 +382,7 @@ func (solution *pathSolution) Path() Path {
reverseHops(hops)
reverseIntfs(intfs)
reverseASEntries(pathASEntries)
reverseEpicAuths(epicSegAuths)
}

segments = append(segments, segment{
Expand All @@ -389,13 +396,14 @@ func (solution *pathSolution) Path() Path {
Interfaces: intfs,
ASEntries: pathASEntries,
})
epicPathAuths = append(epicPathAuths, epicSegAuths...)
}

interfaces := segments.Interfaces()
asEntries := segments.ASEntries()
staticInfo := collectMetadata(interfaces, asEntries)

return Path{
path := Path{
SCIONPath: segments.ScionPath(),
Metadata: snet.PathMetadata{
Interfaces: interfaces,
Expand All @@ -410,6 +418,48 @@ func (solution *pathSolution) Path() Path {
},
Weight: solution.cost,
}

if authPHVF, authLHVF, ok := isEpicAvailable(epicPathAuths); ok {
path.Metadata.EpicAuths = snet.EpicAuths{
AuthPHVF: authPHVF,
AuthLHVF: authLHVF,
}
}

return path
}

func getAuth(a *seg.ASEntry) []byte {
if a.UnsignedExtensions.EpicDetached == nil {
return nil
}

auth := make([]byte, 16)
copy(auth[0:6], a.HopEntry.HopField.MAC[:])
copy(auth[6:16], a.UnsignedExtensions.EpicDetached.AuthHopEntry)
return auth
}

func getAuthPeer(a *seg.ASEntry, i int) []byte {
if a.UnsignedExtensions.EpicDetached == nil {
return nil
}

auth := make([]byte, 16)
copy(auth[0:6], a.HopEntry.HopField.MAC[:])
copy(auth[6:16], a.UnsignedExtensions.EpicDetached.AuthPeerEntries[i])
return auth
}

func isEpicAvailable(epicPathAuths [][]byte) ([]byte, []byte, bool) {
l := len(epicPathAuths)
if l < 2 {
return nil, nil, false
}
if epicPathAuths[l-1] == nil || epicPathAuths[l-2] == nil {
return nil, nil, false
}
return epicPathAuths[l-2], epicPathAuths[l-1], true
}

func reverseHops(s []path.HopField) {
Expand All @@ -430,6 +480,12 @@ func reverseASEntries(s []seg.ASEntry) {
}
}

func reverseEpicAuths(s [][]byte) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}

func calculateBeta(se *solutionEdge) uint16 {
var index int
if se.segment.IsDownSeg() {
Expand Down
1 change: 1 addition & 0 deletions go/lib/snet/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ go_library(
"//go/lib/serrors:go_default_library",
"//go/lib/slayers:go_default_library",
"//go/lib/slayers/path:go_default_library",
"//go/lib/slayers/path/epic:go_default_library",
"//go/lib/sock/reliable:go_default_library",
"//go/lib/topology/underlay:go_default_library",
"//go/lib/util:go_default_library",
Expand Down
20 changes: 20 additions & 0 deletions go/lib/snet/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ func (iface PathInterface) String() string {
return fmt.Sprintf("%s#%d", iface.IA, iface.ID)
}

// EpicAuths is a container for the EPIC hop authenticators.
type EpicAuths struct {
// AuthPHVF is the authenticator for the penultimate hop.
AuthPHVF []byte
// AuthLHVF is the authenticator for the last hop
AuthLHVF []byte
}

func (ea *EpicAuths) SupportsEpic() bool {
return (len(ea.AuthPHVF) == 16 && len(ea.AuthLHVF) == 16)
}

// PathMetadata contains supplementary information about a path.
//
// The information about MTU, Latency, Bandwidth etc. are based solely on data
Expand Down Expand Up @@ -125,12 +137,16 @@ 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

// EpicAuths contains the EPIC authenticators.
EpicAuths EpicAuths
}

func (pm *PathMetadata) Copy() *PathMetadata {
if pm == nil {
return nil
}

return &PathMetadata{
Interfaces: append(pm.Interfaces[:0:0], pm.Interfaces...),
MTU: pm.MTU,
Expand All @@ -141,6 +157,10 @@ 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...),
EpicAuths: EpicAuths{
AuthPHVF: append([]byte(nil), pm.EpicAuths.AuthPHVF...),
AuthLHVF: append([]byte(nil), pm.EpicAuths.AuthLHVF...),
},
}
}

Expand Down
Loading

0 comments on commit e9f9b49

Please sign in to comment.