Skip to content

Commit

Permalink
Add adapters for converting slayers data to app data (#3809)
Browse files Browse the repository at this point in the history
Add decoders and serializers going from SCION v2 header
raw data to spkt.ScnPkt and spath.Path.The APIs
are identical to the SCION v1 header raw data decoders and
serializers, making switching to them trivial.

Also adds the header_v2 feature flag, which should be
used by application that want to switch in code between SCION
header V1 and V2.

Performance is not optimized, and the new adapters
are expected to put a fair bit of pressure on the GC.

Also:
- Make decoding benchmarks in slayers stop on errors; this
fixes an issue where decoding was silently stopping very early
(after the SCION header) due to a missing CanDecode property;
the performance impact of the additional parsing is negligible
- slayers SCION/UDP layer now parses the SCION/UDP gopacket layer
instead of UDP
- slayers documentation includes payload extraction

Co-authored-by: Sergiu Costea <sergiu.costea@gmail.com>
  • Loading branch information
oncilla and scrye authored Jun 30, 2020
1 parent 93f1d1c commit 908eda0
Show file tree
Hide file tree
Showing 12 changed files with 330 additions and 18 deletions.
1 change: 1 addition & 0 deletions go/lib/env/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var _ config.Config = (*Features)(nil)
type Features struct {
AllowRunAsRoot bool `toml:"allow_run_as_root,omitempty"`
UseSIGEgress2 bool `toml:"sig_egress_v2,omitempty"`
HeaderV2 bool `toml:"header_v2,omitempty"`
}

func (cfg *Features) InitDefaults() {
Expand Down
3 changes: 3 additions & 0 deletions go/lib/hpkt/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ go_library(
"//go/lib/layers:go_default_library",
"//go/lib/scmp:go_default_library",
"//go/lib/serrors:go_default_library",
"//go/lib/slayers:go_default_library",
"//go/lib/slayers/path/scion:go_default_library",
"//go/lib/spath:go_default_library",
"//go/lib/spkt:go_default_library",
"//go/lib/util:go_default_library",
"@com_github_google_gopacket//:go_default_library",
"@com_github_google_gopacket//layers:go_default_library",
],
)

Expand Down
81 changes: 81 additions & 0 deletions go/lib/hpkt/hpkt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,84 @@ func TestParseMalformedPkts(t *testing.T) {
})
}
}

func TestParseScnPkt2(t *testing.T) {
raw := xtest.MustReadFromFile(t, "udp-scion-v2.bin")

s := &spkt.ScnPkt{}
require.NoError(t, ParseScnPkt2(s, raw), "Should parse without error")

assert.Equal(t, addr.ISD(2), s.SrcIA.I, "SrcIA.I")
assert.Equal(t, xtest.MustParseAS("ff00:0:222"), s.SrcIA.A, "SrcIA.A")

assert.Equal(t, addr.ISD(1), s.DstIA.I, "DstIA.I")
assert.Equal(t, xtest.MustParseAS("ff00:0:111"), s.DstIA.A, "DstIA.A")

assert.Equal(t, net.IP{10, 0, 0, 100}, s.SrcHost.IP(), "SrcHostAddr")
assert.Equal(t, net.ParseIP("2001:db8::68"), s.DstHost.IP(), "DstHostAddr")

assert.Equal(
t,
common.RawBytes(generatePath()),
s.Path.Raw,
"Path",
)
udpHdr, ok := s.L4.(*l4.UDP)
require.True(t, ok, "L4Hdr - Bad header")
assert.Equal(t, uint16(1280), udpHdr.SrcPort, "UDP.SrcPort")
assert.Equal(t, uint16(80), udpHdr.DstPort, "UDP.DstPort")
assert.Equal(t, uint16(0x408), udpHdr.TotalLen, "UDP.TotalLen")

assert.Equal(
t,
common.RawBytes(generatePayload()),
s.Pld,
"Payload",
)
}

func TestScnPktWrite2(t *testing.T) {
expectedPacket := &spkt.ScnPkt{
SrcIA: xtest.MustParseIA("2-ff00:0:222"),
DstIA: xtest.MustParseIA("1-ff00:0:111"),
SrcHost: addr.HostFromIP(net.IP{10, 0, 0, 100}),
DstHost: addr.HostFromIP(net.IP{0x20, 0x1, 0xd, 0xb8, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x68}),
Path: spath.NewV2(generatePath()),
L4: &l4.UDP{SrcPort: 1280, DstPort: 80, TotalLen: 1032, Checksum: []byte{0xda, 0xbb}},
Pld: common.RawBytes(generatePayload()),
}

b := make(common.RawBytes, common.MaxMTU)
n, err := WriteScnPkt2(expectedPacket, b)
fmt.Println(b[:n])
assert.NoError(t, err, "Write error")

parsedPacket := &spkt.ScnPkt{}
err = ParseScnPkt2(parsedPacket, b[:n])
require.NoError(t, err, "Read error")

assert.Equal(t, expectedPacket, parsedPacket)
}

func generatePayload() []byte {
b := make([]byte, 4*256)
for i := 0; i < 4*256; i++ {
b[i] = byte(i)
}
return b
}

func generatePath() []byte {
return []byte{
0x0, 0x0, 0x20, 0x80, 0x0, 0x0, 0x1, 0x11,
0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x2, 0x22,
0x0, 0x0, 0x1, 0x0, 0x0, 0x3f, 0x0, 0x1,
0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x0,
0x3f, 0x0, 0x3, 0x0, 0x2, 0x1, 0x2, 0x3,
0x4, 0x5, 0x6, 0x0, 0x3f, 0x0, 0x0, 0x0,
0x2, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x0,
0x3f, 0x0, 0x1, 0x0, 0x0, 0x1, 0x2, 0x3,
0x4, 0x5, 0x6,
}
}
78 changes: 75 additions & 3 deletions go/lib/hpkt/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import (
"github.com/scionproto/scion/go/lib/addr"
"github.com/scionproto/scion/go/lib/common"
"github.com/scionproto/scion/go/lib/l4"
"github.com/scionproto/scion/go/lib/layers"
deprecatedlayers "github.com/scionproto/scion/go/lib/layers"
"github.com/scionproto/scion/go/lib/scmp"
"github.com/scionproto/scion/go/lib/serrors"
"github.com/scionproto/scion/go/lib/slayers"
"github.com/scionproto/scion/go/lib/spath"
"github.com/scionproto/scion/go/lib/spkt"
"github.com/scionproto/scion/go/lib/util"
Expand Down Expand Up @@ -137,13 +139,13 @@ func (p *parseCtx) parse() error {
func (p *parseCtx) parseExtensions() ([]common.Extension, []common.Extension, error) {
var extns []common.Extension
for p.nextHdr == common.HopByHopClass || p.nextHdr == common.End2EndClass {
var extn layers.Extension
var extn deprecatedlayers.Extension
err := extn.DecodeFromBytes(p.b[p.offset:], gopacket.NilDecodeFeedback)
if err != nil {
return nil, nil, common.NewBasicError("Unable to parse extensions", err)
}

extnData, err := layers.ExtensionFactory(p.nextHdr, &extn)
extnData, err := deprecatedlayers.ExtensionFactory(p.nextHdr, &extn)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -275,3 +277,73 @@ func (p *parseCtx) DefaultL4Parser() error {
}
return nil
}

// ParseScnPkt2 converts a raw SCION v2 header packet into ScnPkt data.
func ParseScnPkt2(s *spkt.ScnPkt, b []byte) error {
// XXX(scrye): Put these on the stack here, although this negates the gopacket speedups and puts
// pressure on the garbage collector.
var (
scionLayer slayers.SCION
udpLayer slayers.UDP
scmpLayer slayers.SCMP
// XXX(scrye): HBH and E2E are not needed yet, so we silently ignore them.
payloadLayer gopacket.Payload
)

parser := gopacket.NewDecodingLayerParser(
slayers.LayerTypeSCION, &scionLayer, &udpLayer, &scmpLayer, &payloadLayer,
)

decoded := []gopacket.LayerType{}
if err := parser.DecodeLayers(b, &decoded); err != nil {
return err
}

for _, layerType := range decoded {
switch layerType {
case slayers.LayerTypeSCION:
s.DstIA = scionLayer.DstIA
s.SrcIA = scionLayer.SrcIA
dstAddr, err := scionLayer.DstAddr()
if err != nil {
return serrors.WrapStr("unable to extract destination address", err)
}
s.DstHost, err = netAddrToHostAddr(dstAddr)
if err != nil {
return serrors.WrapStr("unable to convert address to HostAddr", err)
}
srcAddr, err := scionLayer.SrcAddr()
if err != nil {
return serrors.WrapStr("unable to extract source address", err)
}
s.SrcHost, err = netAddrToHostAddr(srcAddr)
if err != nil {
return serrors.WrapStr("unable to convert address to HostAddr", err)
}

pathCopy := make([]byte, scionLayer.Path.Len())
if err := scionLayer.Path.SerializeTo(pathCopy); err != nil {
return serrors.WrapStr("unable to extract path", err)
}
s.Path = spath.NewV2(pathCopy)
case slayers.LayerTypeSCIONUDP:
s.L4 = &l4.UDP{
SrcPort: uint16(udpLayer.SrcPort),
DstPort: uint16(udpLayer.DstPort),
TotalLen: udpLayer.Length,
Checksum: []byte{byte(udpLayer.Checksum % 256), byte(udpLayer.Checksum / 256)},
}
s.Pld = common.RawBytes(payloadLayer.Payload())
case slayers.LayerTypeSCMP:
s.L4 = &scmp.Hdr{
Class: scmpLayer.Class,
Type: scmpLayer.Type,
TotalLen: scmpLayer.TotalLen,
Checksum: scmpLayer.Checksum,
Timestamp: scmpLayer.Timestamp,
}
s.Pld = common.RawBytes(scmpLayer.Payload)
}
}
return nil
}
Binary file added go/lib/hpkt/testdata/udp-scion-v2.bin
Binary file not shown.
122 changes: 115 additions & 7 deletions go/lib/hpkt/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@
package hpkt

import (
"net"

"github.com/google/gopacket"
"github.com/google/gopacket/layers"

"github.com/scionproto/scion/go/lib/addr"
"github.com/scionproto/scion/go/lib/common"
"github.com/scionproto/scion/go/lib/l4"
"github.com/scionproto/scion/go/lib/layers"
deprecatedlayers "github.com/scionproto/scion/go/lib/layers"
"github.com/scionproto/scion/go/lib/scmp"
"github.com/scionproto/scion/go/lib/serrors"
"github.com/scionproto/scion/go/lib/slayers"
"github.com/scionproto/scion/go/lib/slayers/path/scion"
"github.com/scionproto/scion/go/lib/spkt"
"github.com/scionproto/scion/go/lib/util"
)
Expand Down Expand Up @@ -85,9 +91,9 @@ func WriteScnPkt(s *spkt.ScnPkt, b common.RawBytes) (int, error) {
buffer := gopacket.NewSerializeBuffer()
switch s.L4.L4Type() {
case common.L4UDP:
buffer.PushLayer(layers.LayerTypeSCIONUDP)
buffer.PushLayer(deprecatedlayers.LayerTypeSCIONUDP)
case common.L4SCMP:
buffer.PushLayer(layers.LayerTypeSCMP)
buffer.PushLayer(deprecatedlayers.LayerTypeSCMP)
default:
return 0, common.NewBasicError("Unsupported L4", nil, "type", s.L4.L4Type())
}
Expand Down Expand Up @@ -138,7 +144,7 @@ func writeExtensions(extensions []common.Extension, buffer gopacket.SerializeBuf
if err != nil {
return err
}
extn, err := layers.ExtensionDataToExtensionLayer(nextHeaderType, extensions[i])
extn, err := deprecatedlayers.ExtensionDataToExtensionLayer(nextHeaderType, extensions[i])
if err != nil {
return err
}
Expand All @@ -148,9 +154,9 @@ func writeExtensions(extensions []common.Extension, buffer gopacket.SerializeBuf
}
switch extensions[i].Class() {
case common.HopByHopClass:
buffer.PushLayer(layers.LayerTypeHopByHopExtension)
buffer.PushLayer(deprecatedlayers.LayerTypeHopByHopExtension)
case common.End2EndClass:
buffer.PushLayer(layers.LayerTypeEndToEndExtension)
buffer.PushLayer(deprecatedlayers.LayerTypeEndToEndExtension)
default:
return serrors.New("cannot push unknown layer")
}
Expand All @@ -161,7 +167,7 @@ func writeExtensions(extensions []common.Extension, buffer gopacket.SerializeBuf
func getNextHeaderType(buffer gopacket.SerializeBuffer) (common.L4ProtocolType, error) {
serializedLayers := buffer.Layers()
lastLayer := serializedLayers[len(serializedLayers)-1]
nextHdr, ok := layers.LayerToHeaderMap[lastLayer]
nextHdr, ok := deprecatedlayers.LayerToHeaderMap[lastLayer]
if !ok {
return 0, common.NewBasicError("unknown header", nil, "gopacket_type", lastLayer)
}
Expand All @@ -182,3 +188,105 @@ func zeroMemory(b common.RawBytes) {
b[i] = 0
}
}

// WriteScnPkt converts ScnPkt data into a raw SCION v2 header packet.
func WriteScnPkt2(s *spkt.ScnPkt, b []byte) (int, error) {
var packetLayers []gopacket.SerializableLayer

var scionLayer slayers.SCION
// XXX(scrye): Do not set TrafficClass and FlowID, even though the latter is mandatory,
// to keep things simple while we transition to HeaderV2. These should be added once
// the transition is complete.
scionLayer.DstIA = s.DstIA
scionLayer.SrcIA = s.SrcIA
netDstAddr, err := hostAddrToNetAddr(s.DstHost)
if err != nil {
return 0, serrors.WrapStr("unable to convert destination addr.HostAddr to net.Addr", err,
"address", s.DstHost)
}
if err := scionLayer.SetDstAddr(netDstAddr); err != nil {
return 0, serrors.WrapStr("unable to set destination address", err)
}
netSrcAddr, err := hostAddrToNetAddr(s.SrcHost)
if err != nil {
return 0, serrors.WrapStr("unable to convert source addr.HostAddr to net.Addr", err,
"address", s.SrcHost)
}
if err := scionLayer.SetSrcAddr(netSrcAddr); err != nil {
return 0, serrors.WrapStr("unable to set source address", err)
}
scionLayer.PathType = slayers.PathTypeSCION

// Use decoded for simplicity, easier to work with when debugging with delve.
var decodedPath scion.Decoded
if err := decodedPath.DecodeFromBytes(s.Path.Raw); err != nil {
return 0, nil
}
scionLayer.Path = &decodedPath
packetLayers = append(packetLayers, &scionLayer)

// XXX(scrye): No extensions are defined for the V2 header format. However,
// application code uses some V1 extensions like the One-Hop Path, and these
// will need to be converted for V2 to the new One-Hop path type.
if len(s.HBHExt) != 0 {
return 0, serrors.New("HBH extensions are not supported for Header V2")
}
if len(s.E2EExt) != 0 {
return 0, serrors.New("E2E extensions are not supported for Header V2")
}

switch layer := s.L4.(type) {
case *l4.UDP:
scionLayer.NextHdr = common.L4UDP
var udpLayer slayers.UDP
udpLayer.SrcPort = layers.UDPPort(layer.SrcPort)
udpLayer.DstPort = layers.UDPPort(layer.DstPort)
udpLayer.SetNetworkLayerForChecksum(&scionLayer)
packetLayers = append(packetLayers, &udpLayer)
case *scmp.Hdr:
scionLayer.NextHdr = common.L4SCMP
var scmpLayer slayers.SCMP
scmpLayer.Class = layer.Class
scmpLayer.Type = layer.Type
scmpLayer.TotalLen = layer.TotalLen
scmpLayer.SetNetworkLayerForChecksum(&scionLayer)
scmpLayer.Timestamp = layer.Timestamp
scmpLayer.Payload = []byte(s.Pld.(common.RawBytes))
packetLayers = append(packetLayers, &scmpLayer)
}
payloadLayer := gopacket.Payload(s.Pld.(common.RawBytes))
packetLayers = append(packetLayers, &payloadLayer)

buffer := gopacket.NewSerializeBuffer()
options := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
if err := gopacket.SerializeLayers(buffer, options, packetLayers...); err != nil {
return 0, err
}

return copy(b, buffer.Bytes()), nil
}

func netAddrToHostAddr(a net.Addr) (addr.HostAddr, error) {
switch aImpl := a.(type) {
case *net.IPAddr:
return addr.HostFromIP(aImpl.IP), nil
case addr.HostSVC:
return aImpl, nil
default:
return nil, serrors.New("address not supported", "a", a)
}
}

func hostAddrToNetAddr(a addr.HostAddr) (net.Addr, error) {
switch aImpl := a.(type) {
case addr.HostSVC:
return aImpl, nil
case addr.HostIPv4, addr.HostIPv6:
return &net.IPAddr{IP: aImpl.IP()}, nil
default:
return nil, serrors.New("address not supported", "a", a)
}
}
3 changes: 2 additions & 1 deletion go/lib/slayers/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ that have either a SCION/UDP or SCMP payload:
var e2e slayers.EndToEndExtn
var udp slayers.UDP
var scmp slayers.SCMP
parser := gopacket.NewDecodingLayerParser(LayerTypeSCION, &scn, &hbh, &e2e, &udp, &scmp)
var pld gopacket.Payload
parser := gopacket.NewDecodingLayerParser(LayerTypeSCION, &scn, &hbh, &e2e, &udp, &scmp, &pld)
decoded := []gopacket.LayerType{}
if err := parser.DecodeLayers(packetData, &decoded); err != nil {
// Handle error
Expand Down
Loading

0 comments on commit 908eda0

Please sign in to comment.