Repository: scionproto/scion
Pull request #3842 (2020-07-28 13:52:35)

dispatcher v2 support


<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/scionproto/scion/3842)
<!-- Reviewable:end -->



Patch:
diff --git a/go/dispatcher/dispatcher/BUILD.bazel b/go/dispatcher/dispatcher/BUILD.bazel
index 096cccdad..adfbe31cf 100644
--- a/go/dispatcher/dispatcher/BUILD.bazel
+++ b/go/dispatcher/dispatcher/BUILD.bazel
@@ -22,8 +22,10 @@ go_library(
         "//go/lib/ringbuf:go_default_library",
         "//go/lib/scmp:go_default_library",
         "//go/lib/serrors:go_default_library",
+        "//go/lib/slayers:go_default_library",
         "//go/lib/spkt:go_default_library",
         "//go/lib/underlay/conn:go_default_library",
+        "@com_github_google_gopacket//:go_default_library",
     ],
 )
 
@@ -32,14 +34,20 @@ go_test(
     srcs = ["underlay_test.go"],
     embed = [":go_default_library"],
     deps = [
+        "//go/dispatcher/internal/respool:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/l4:go_default_library",
         "//go/lib/l4/mock_l4:go_default_library",
         "//go/lib/scmp:go_default_library",
+        "//go/lib/slayers:go_default_library",
+        "//go/lib/slayers/path:go_default_library",
+        "//go/lib/slayers/path/scion:go_default_library",
         "//go/lib/spkt:go_default_library",
         "//go/lib/xtest:go_default_library",
         "@com_github_golang_mock//gomock:go_default_library",
+        "@com_github_google_gopacket//:go_default_library",
+        "@com_github_google_gopacket//layers:go_default_library",
         "@com_github_stretchr_testify//assert:go_default_library",
         "@com_github_stretchr_testify//require:go_default_library",
     ],
diff --git a/go/dispatcher/dispatcher/dispatcher.go b/go/dispatcher/dispatcher/dispatcher.go
index ad5d7603f..787ca32eb 100644
--- a/go/dispatcher/dispatcher/dispatcher.go
+++ b/go/dispatcher/dispatcher/dispatcher.go
@@ -11,11 +11,11 @@ import (
 	"github.com/scionproto/scion/go/dispatcher/internal/respool"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
-	"github.com/scionproto/scion/go/lib/hpkt"
 	"github.com/scionproto/scion/go/lib/log"
 	"github.com/scionproto/scion/go/lib/ringbuf"
 	"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/spkt"
 	"github.com/scionproto/scion/go/lib/underlay/conn"
 )
@@ -127,32 +127,21 @@ type Conn struct {
 	ring *ringbuf.Ring
 	// regReference is the reference to the registration in the routing table.
 	regReference registration.RegReference
-	// HeaderV2 indicates whether the new header format is used.
-	HeaderV2 bool
 }
 
 func (ac *Conn) WriteTo(p []byte, addr net.Addr) (int, error) {
-	var info spkt.ScnPkt
-	if ac.HeaderV2 {
-		if err := hpkt.ParseScnPkt2(&info, p); err != nil {
-			return 0, err
-		}
-	} else {
-		if err := hpkt.ParseScnPkt(&info, p); err != nil {
-			return 0, err
-		}
-	}
-	if err := registerIfSCMPRequest(ac.regReference, &info); err != nil {
-		log.Info("SCMP Request ID error, packet still sent", "err", err)
-	}
-	return ac.conn.WriteTo(p, addr)
+	panic("not implemented")
 }
 
 // Write is optimized for the use by ConnHandler (avoids reparsing the packet).
 func (ac *Conn) Write(pkt *respool.Packet) (int, error) {
+	// FIXME(roosd): Drop this with header v1.
 	if err := registerIfSCMPRequest(ac.regReference, &pkt.Info); err != nil {
 		log.Info("SCMP Request ID error, packet still sent", "err", err)
 	}
+	if err := registerIfSCMPInfo(ac.regReference, pkt); err != nil {
+		log.Info("SCMP Request ID error, packet still sent", "err", err)
+	}
 	return pkt.SendOnConn(ac.conn, pkt.UnderlayRemote)
 }
 
@@ -257,6 +246,24 @@ func registerIfSCMPRequest(ref registration.RegReference, packet *spkt.ScnPkt) e
 	return nil
 }
 
+// registerIfSCMPInfo registers the ID of the SCMP request if it is an echo or
+// traceroute message.
+func registerIfSCMPInfo(ref registration.RegReference, pkt *respool.Packet) error {
+	if pkt.L4 != slayers.LayerTypeSCMP {
+		return nil
+	}
+	t := pkt.SCMP.TypeCode.Type()
+	if t != slayers.SCMPTypeEchoRequest && t != slayers.SCMPTypeTracerouteRequest {
+		return nil
+	}
+	id, err := extractSCMPIdentifier(&pkt.SCMP)
+	if err != nil {
+		return err
+	}
+	// FIXME(roosd): add metrics again.
+	return ref.RegisterID(uint64(id))
+}
+
 // underlayConnWrapper wraps a specialized underlay conn into a net.PacketConn
 // implementation. Only *net.UDPAddr addressing is supported.
 type underlayConnWrapper struct {
diff --git a/go/dispatcher/dispatcher/underlay.go b/go/dispatcher/dispatcher/underlay.go
index 9abf856c5..9a5224fbd 100644
--- a/go/dispatcher/dispatcher/underlay.go
+++ b/go/dispatcher/dispatcher/underlay.go
@@ -1,4 +1,5 @@
 // Copyright 2018 ETH Zurich
+// Copyright 2020 ETH Zurich, Anapaya Systems
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,6 +18,8 @@ package dispatcher
 import (
 	"net"
 
+	"github.com/google/gopacket"
+
 	"github.com/scionproto/scion/go/dispatcher/internal/metrics"
 	"github.com/scionproto/scion/go/dispatcher/internal/respool"
 	"github.com/scionproto/scion/go/lib/addr"
@@ -26,6 +29,8 @@ import (
 	"github.com/scionproto/scion/go/lib/log"
 	"github.com/scionproto/scion/go/lib/ringbuf"
 	"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/spkt"
 )
 
@@ -50,6 +55,33 @@ type NetToRingDataplane struct {
 }
 
 func (dp *NetToRingDataplane) Run() error {
+	if !dp.HeaderV2 {
+		return dp.runLegacy()
+	}
+	for {
+		pkt := respool.GetPacket(true)
+		// XXX(scrye): we don't release the reference on error conditions, and
+		// let the GC take care of this situation as they should be fairly
+		// rare.
+
+		if err := pkt.DecodeFromConn(dp.UnderlayConn); err != nil {
+			log.Debug("error receiving next packet from underlay conn", "err", err)
+			continue
+		}
+		dst, err := getDst(pkt)
+		if err != nil {
+			log.Debug("unable to route packet", "err", err)
+			metrics.M.NetReadPkts(
+				metrics.IncomingPacket{Result: metrics.PacketResultRouteNotFound},
+			).Inc()
+			continue
+		}
+		metrics.M.NetReadPkts(metrics.IncomingPacket{Result: metrics.PacketResultOk}).Inc()
+		dst.Send(dp, pkt)
+	}
+}
+
+func (dp *NetToRingDataplane) runLegacy() error {
 	for {
 		pkt := respool.GetPacket(dp.HeaderV2)
 		// XXX(scrye): we don't release the reference on error conditions, and
@@ -61,7 +93,7 @@ func (dp *NetToRingDataplane) Run() error {
 			continue
 		}
 
-		dst, err := ComputeDestination(&pkt.Info, dp.HeaderV2)
+		dst, err := ComputeDestination(&pkt.Info)
 		if err != nil {
 			log.Debug("unable to route packet", "err", err)
 			metrics.M.NetReadPkts(
@@ -74,25 +106,342 @@ func (dp *NetToRingDataplane) Run() error {
 	}
 }
 
-func ComputeDestination(packet *spkt.ScnPkt, headerV2 bool) (Destination, error) {
+func getDst(pkt *respool.Packet) (Destination, error) {
+	switch pkt.L4 {
+	case slayers.LayerTypeSCIONUDP:
+		return getDstUDP(pkt)
+	case slayers.LayerTypeSCMP:
+		return getDstSCMP(pkt)
+	default:
+		return nil, serrors.WithCtx(ErrUnsupportedL4, "type", pkt.L4)
+	}
+}
+
+func getDstUDP(pkt *respool.Packet) (Destination, error) {
+	dst, err := pkt.SCION.DstAddr()
+	if err != nil {
+		return nil, err
+	}
+	switch d := dst.(type) {
+	case *net.IPAddr:
+		return UDPDestination{
+			IA: pkt.SCION.DstIA,
+			Public: &net.UDPAddr{
+				IP:   d.IP,
+				Port: int(pkt.UDP.DstPort),
+			},
+		}, nil
+	case addr.HostSVC:
+		return SVCDestination{
+			IA:  pkt.SCION.DstIA,
+			Svc: d,
+		}, nil
+	default:
+		return nil, serrors.WithCtx(ErrUnsupportedDestination, "type", common.TypeOf(dst))
+	}
+}
+
+func getDstSCMP(pkt *respool.Packet) (Destination, error) {
+	if !pkt.SCMP.TypeCode.InfoMsg() {
+		dst, err := getDstSCMPErr(pkt)
+		if err != nil {
+			return nil, serrors.WrapStr("delivering SCMP error message", err)
+		}
+		return dst, nil
+	}
+	return getDstSCMPInfo(pkt)
+}
+
+func getDstSCMPInfo(pkt *respool.Packet) (Destination, error) {
+	t := pkt.SCMP.TypeCode.Type()
+	if t == slayers.SCMPTypeEchoRequest || t == slayers.SCMPTypeTracerouteRequest {
+		return SCMPHandler{}, nil
+	}
+	if t == slayers.SCMPTypeEchoReply || t == slayers.SCMPTypeTracerouteReply {
+		id, err := extractSCMPIdentifier(&pkt.SCMP)
+		if err != nil {
+			return nil, err
+		}
+		return SCMPDestination{IA: pkt.SCION.DstIA, ID: id}, nil
+	}
+	return nil, serrors.New("unsupported SCMP info message", "type", t)
+}
+
+func getDstSCMPErr(pkt *respool.Packet) (Destination, error) {
+	// Drop unknown SCMP error messages.
+	if pkt.SCMP.NextLayerType() == gopacket.LayerTypePayload {
+		return nil, serrors.New("unsupported SCMP error message", "type", pkt.SCMP.TypeCode.Type())
+	}
+	l, err := decodeSCMP(&pkt.SCMP)
+	if err != nil {
+		return nil, err
+	}
+	if len(l) != 2 {
+		return nil, serrors.New("SCMP error message without payload")
+	}
+	gpkt := gopacket.NewPacket(*l[1].(*gopacket.Payload), slayers.LayerTypeSCION,
+		gopacket.DecodeOptions{
+			NoCopy: true,
+		},
+	)
+
+	// If the offending packet was UDP/SCION, use the source port to deliver.
+	if udp := gpkt.Layer(slayers.LayerTypeSCIONUDP); udp != nil {
+		port := int(udp.(*slayers.UDP).SrcPort)
+		// XXX(roosd): We assume that the zero value means the UDP header is
+		// truncated. This flags packets of misbehaving senders as truncated, if
+		// they set the source port to 0. But there is no harm, since those
+		// packets are destined to be dropped anyway.
+		if port == 0 {
+			return nil, serrors.New("SCMP error with truncated UDP header")
+		}
+		dst, err := pkt.SCION.DstAddr()
+		if err != nil {
+			return nil, err
+		}
+		ipAddr, ok := dst.(*net.IPAddr)
+		if !ok {
+			return nil, serrors.WithCtx(ErrUnsupportedDestination, "type", common.TypeOf(dst))
+		}
+		return UDPDestination{
+			IA: pkt.SCION.DstIA,
+			Public: &net.UDPAddr{
+				IP:   ipAddr.IP,
+				Port: port,
+			},
+		}, nil
+	}
+
+	// If the offending packet was SCMP/SCION, and it is an echo or traceroute,
+	// use the Identifier to deliver. In all other cases, the message is dropped.
+	if scmp := gpkt.Layer(slayers.LayerTypeSCMP); scmp != nil {
+
+		tc := scmp.(*slayers.SCMP).TypeCode
+		// SCMP Error messages in response to an SCMP error message are not allowed.
+		if !tc.InfoMsg() {
+			return nil, serrors.New("SCMP error message in response to SCMP error message",
+				"type", tc.Type())
+		}
+		// We only support echo and traceroute requests.
+		t := tc.Type()
+		if t != slayers.SCMPTypeEchoRequest && t != slayers.SCMPTypeTracerouteRequest {
+			return nil, serrors.New("unsupported SCMP info message", "type", t)
+		}
+
+		var id uint16
+		// Extract the ID from the echo or traceroute layer.
+		if echo := gpkt.Layer(slayers.LayerTypeSCMPEcho); echo != nil {
+			id = echo.(*slayers.SCMPEcho).Identifier
+		} else if tr := gpkt.Layer(slayers.LayerTypeSCMPTraceroute); tr != nil {
+			id = tr.(*slayers.SCMPTraceroute).Identifier
+		} else {
+			return nil, serrors.New("SCMP error with truncated payload")
+		}
+		return SCMPDestination{
+			IA: pkt.SCION.DstIA,
+			ID: id,
+		}, nil
+	}
+	return nil, ErrUnsupportedL4
+}
+
+// UDPDestination delivers packets to the app that registered for the configured
+// public address.
+type UDPDestination struct {
+	IA     addr.IA
+	Public *net.UDPAddr
+}
+
+func (d UDPDestination) Send(dp *NetToRingDataplane, pkt *respool.Packet) {
+	routingEntry, ok := dp.RoutingTable.LookupPublic(d.IA, d.Public)
+	if !ok {
+		metrics.M.AppNotFoundErrors().Inc()
+		log.Debug("destination address not found", "isd_as", d.IA, "udp_addr", d.Public)
+		return
+	}
+	sendPacket(routingEntry, pkt)
+}
+
+// SVCDestination delivers packets to apps that registered for the configured
+// service.
+type SVCDestination struct {
+	IA  addr.IA
+	Svc addr.HostSVC
+}
+
+func (d SVCDestination) Send(dp *NetToRingDataplane, pkt *respool.Packet) {
+	// FIXME(scrye): This should deliver to the correct IP address, based on
+	// information found in the underlay IP header.
+	routingEntries := dp.RoutingTable.LookupService(d.IA, d.Svc, nil)
+	if len(routingEntries) == 0 {
+		metrics.M.AppNotFoundErrors().Inc()
+		log.Debug("destination address not found", "isd_as", d.IA, "svc", d.Svc)
+		return
+	}
+	// Increase reference count for all extra copies
+	for i := 0; i < len(routingEntries)-1; i++ {
+		pkt.Dup()
+	}
+	for _, routingEntry := range routingEntries {
+		metrics.M.AppWriteSVCPkts(metrics.SVC{Type: d.Svc.String()}).Inc()
+		sendPacket(routingEntry, pkt)
+	}
+}
+
+type SCMPDestination struct {
+	IA addr.IA
+	ID uint16
+}
+
+func (d SCMPDestination) Send(dp *NetToRingDataplane, pkt *respool.Packet) {
+	routingEntry, ok := dp.RoutingTable.LookupID(d.IA, uint64(d.ID))
+	if !ok {
+		metrics.M.AppNotFoundErrors().Inc()
+		log.Debug("destination address not found", "SCMP", d.ID)
+		return
+	}
+	sendPacket(routingEntry, pkt)
+}
+
+// SCMPHandler replies to SCMP echo and traceroute requests.
+type SCMPHandler struct{}
+
+func (h SCMPHandler) Send(dp *NetToRingDataplane, pkt *respool.Packet) {
+	// FIXME(roosd): introduce metrics again.
+	raw, err := h.reverse(pkt)
+	if err != nil {
+		log.Info("Failed to reverse SCMP packet, dropping", "err", err)
+		return
+	}
+	_, err = dp.UnderlayConn.WriteTo(raw, pkt.UnderlayRemote)
+	if err != nil {
+		log.Info("Unable to write to underlay socket", "err", err)
+		return
+	}
+	pkt.Free()
+}
+
+func (h SCMPHandler) reverse(pkt *respool.Packet) ([]byte, error) {
+	l, err := decodeSCMP(&pkt.SCMP)
+	if err != nil {
+		return nil, err
+	}
+	// Translate request to a reply.
+	switch l[0].LayerType() {
+	case slayers.LayerTypeSCMPEcho:
+		pkt.SCMP.TypeCode = slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0)
+	case slayers.LayerTypeSCMPTraceroute:
+		pkt.SCMP.TypeCode = slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0)
+	default:
+		return nil, serrors.New("unsupported SCMP informational message")
+	}
+	if err := h.reverseSCION(pkt); err != nil {
+		return nil, err
+	}
+	// FIXME(roosd): Consider moving this to a resource pool.
+	buf := gopacket.NewSerializeBuffer()
+	if err := pkt.SCMP.SetNetworkLayerForChecksum(&pkt.SCION); err != nil {
+		return nil, err
+	}
+	err = gopacket.SerializeLayers(
+		buf,
+		gopacket.SerializeOptions{
+			ComputeChecksums: true,
+			FixLengths:       true,
+		},
+		append([]gopacket.SerializableLayer{&pkt.SCION, &pkt.SCMP}, l...)...,
+	)
+	if err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
+
+func (h SCMPHandler) reverseSCION(pkt *respool.Packet) error {
+	// Reverse the SCION packet.
+	pkt.SCION.DstIA, pkt.SCION.SrcIA = pkt.SCION.SrcIA, pkt.SCION.DstIA
+	src, err := pkt.SCION.SrcAddr()
+	if err != nil {
+		return serrors.WrapStr("parsing source address", err)
+	}
+	dst, err := pkt.SCION.DstAddr()
+	if err != nil {
+		return serrors.WrapStr("parsing destination address", err)
+	}
+	if err := pkt.SCION.SetSrcAddr(dst); err != nil {
+		return serrors.WrapStr("setting source address", err)
+	}
+	if err := pkt.SCION.SetDstAddr(src); err != nil {
+		return serrors.WrapStr("setting destination address", err)
+	}
+	if err := pkt.SCION.Path.Reverse(); err != nil {
+		return serrors.WrapStr("reversing path", err)
+	}
+	return nil
+}
+
+func extractIP(dst net.Addr, err error) (net.IP, error) {
+	if err != nil {
+		return nil, err
+	}
+	ipAddr, ok := dst.(*net.IPAddr)
+	if !ok {
+		return nil, serrors.New("unsupported address", "type", common.TypeOf(dst))
+	}
+	return ipAddr.IP, nil
+}
+
+func extractSCMPIdentifier(scmp *slayers.SCMP) (uint16, error) {
+	l, err := decodeSCMP(scmp)
+	if err != nil {
+		return 0, err
+	}
+	switch info := l[0].(type) {
+	case *slayers.SCMPEcho:
+		return info.Identifier, nil
+	case *slayers.SCMPTraceroute:
+		return info.Identifier, nil
+	default:
+		return 0, serrors.New("invalid SCMP info message", "type_code", scmp.TypeCode)
+	}
+}
+
+// decodeSCMP decodes the SCMP payload. WARNING: Decoding is done with NoCopy set.
+func decodeSCMP(scmp *slayers.SCMP) ([]gopacket.SerializableLayer, error) {
+	gpkt := gopacket.NewPacket(scmp.Payload, scmp.NextLayerType(),
+		gopacket.DecodeOptions{NoCopy: true})
+	layers := gpkt.Layers()
+	if len(layers) == 0 || len(layers) > 2 {
+		return nil, serrors.New("invalid number of SCMP layers", "count", len(layers))
+	}
+	ret := make([]gopacket.SerializableLayer, len(layers))
+	for i, l := range layers {
+		s, ok := l.(gopacket.SerializableLayer)
+		if !ok {
+			return nil, serrors.New("invalid SCMP layer, not serializable", "index", i)
+		}
+		ret[i] = s
+	}
+	return ret, nil
+}
+
+func ComputeDestination(packet *spkt.ScnPkt) (Destination, error) {
 	switch header := packet.L4.(type) {
 	case *l4.UDP:
-		return ComputeUDPDestination(packet, header, headerV2)
+		return ComputeUDPDestination(packet, header)
 	case *scmp.Hdr:
-		return ComputeSCMPDestination(packet, header, headerV2)
+		return ComputeSCMPDestination(packet, header)
 	default:
 		return nil, common.NewBasicError(ErrUnsupportedL4, nil, "type", header.L4Type())
 	}
 }
 
-func ComputeUDPDestination(packet *spkt.ScnPkt, header *l4.UDP,
-	headerV2 bool) (Destination, error) {
-
+func ComputeUDPDestination(packet *spkt.ScnPkt, header *l4.UDP) (Destination, error) {
 	switch packet.DstHost.Type() {
 	case addr.HostTypeIPv4, addr.HostTypeIPv6:
-		return &UDPDestination{IP: packet.DstHost.IP(), Port: int(header.DstPort)}, nil
+		return &UDPDestinationLegacy{IP: packet.DstHost.IP(), Port: int(header.DstPort)}, nil
 	case addr.HostTypeSVC:
-		return SVCDestination(packet.DstHost.(addr.HostSVC)), nil
+		return SVCDestinationLegacy(packet.DstHost.(addr.HostSVC)), nil
 	default:
 		return nil, common.NewBasicError(ErrUnsupportedDestination, nil,
 			"type", packet.DstHost.Type())
@@ -101,9 +450,7 @@ func ComputeUDPDestination(packet *spkt.ScnPkt, header *l4.UDP,
 
 // ComputeSCMPDestination decides which application to send the SCMP packet to. It also increments
 // SCMP-related metrics.
-func ComputeSCMPDestination(packet *spkt.ScnPkt, header *scmp.Hdr,
-	headerV2 bool) (Destination, error) {
-
+func ComputeSCMPDestination(packet *spkt.ScnPkt, header *scmp.Hdr) (Destination, error) {
 	metrics.M.SCMPReadPkts(
 		metrics.SCMP{
 			Class: header.Class.String(),
@@ -115,15 +462,13 @@ func ComputeSCMPDestination(packet *spkt.ScnPkt, header *scmp.Hdr,
 			"type", packet.DstHost.Type())
 	}
 	if header.Class == scmp.C_General {
-		return ComputeSCMPGeneralDestination(packet, header, headerV2)
+		return ComputeSCMPGeneralDestination(packet, header)
 	} else {
 		return ComputeSCMPErrorDestination(packet, header)
 	}
 }
 
-func ComputeSCMPGeneralDestination(s *spkt.ScnPkt, header *scmp.Hdr,
-	headerV2 bool) (Destination, error) {
-
+func ComputeSCMPGeneralDestination(s *spkt.ScnPkt, header *scmp.Hdr) (Destination, error) {
 	id := getSCMPGeneralID(s)
 	if id == 0 {
 		return nil, common.NewBasicError("Invalid SCMP ID", nil, "id", id)
@@ -131,7 +476,7 @@ func ComputeSCMPGeneralDestination(s *spkt.ScnPkt, header *scmp.Hdr,
 	switch {
 	case isSCMPGeneralRequest(header):
 		invertSCMPGeneralType(header)
-		return SCMPHandlerDestination{HeaderV2: headerV2}, nil
+		return SCMPHandlerDestination{HeaderV2: false}, nil
 	case isSCMPGeneralReply(header):
 		return &SCMPAppDestination{ID: id}, nil
 	default:
@@ -147,7 +492,10 @@ func ComputeSCMPErrorDestination(packet *spkt.ScnPkt, header *scmp.Hdr) (Destina
 		if err != nil {
 			return nil, common.NewBasicError(ErrMalformedL4Quote, nil, "err", err)
 		}
-		return &UDPDestination{IP: packet.DstHost.IP(), Port: int(quotedUDPHeader.SrcPort)}, nil
+		return &UDPDestinationLegacy{
+			IP:   packet.DstHost.IP(),
+			Port: int(quotedUDPHeader.SrcPort),
+		}, nil
 	case common.L4SCMP:
 
 		id, err := getQuotedSCMPGeneralID(scmpPayload)
@@ -167,11 +515,11 @@ type Destination interface {
 	Send(dp *NetToRingDataplane, pkt *respool.Packet)
 }
 
-var _ Destination = (*UDPDestination)(nil)
+var _ Destination = (*UDPDestinationLegacy)(nil)
 
-type UDPDestination net.UDPAddr
+type UDPDestinationLegacy net.UDPAddr
 
-func (d *UDPDestination) Send(dp *NetToRingDataplane, pkt *respool.Packet) {
+func (d *UDPDestinationLegacy) Send(dp *NetToRingDataplane, pkt *respool.Packet) {
 	routingEntry, ok := dp.RoutingTable.LookupPublic(pkt.Info.DstIA, (*net.UDPAddr)(d))
 	if !ok {
 		metrics.M.AppNotFoundErrors().Inc()
@@ -182,11 +530,11 @@ func (d *UDPDestination) Send(dp *NetToRingDataplane, pkt *respool.Packet) {
 	sendPacket(routingEntry, pkt)
 }
 
-var _ Destination = SVCDestination(addr.SvcNone)
+var _ Destination = SVCDestinationLegacy(addr.SvcNone)
 
-type SVCDestination addr.HostSVC
+type SVCDestinationLegacy addr.HostSVC
 
-func (d SVCDestination) Send(dp *NetToRingDataplane, pkt *respool.Packet) {
+func (d SVCDestinationLegacy) Send(dp *NetToRingDataplane, pkt *respool.Packet) {
 	// FIXME(scrye): This should deliver to the correct IP address, based on
 	// information found in the underlay IP header.
 	routingEntries := dp.RoutingTable.LookupService(pkt.Info.DstIA, addr.HostSVC(d), nil)
diff --git a/go/dispatcher/internal/respool/BUILD.bazel b/go/dispatcher/internal/respool/BUILD.bazel
index c37c9e713..4b4e23a4d 100644
--- a/go/dispatcher/internal/respool/BUILD.bazel
+++ b/go/dispatcher/internal/respool/BUILD.bazel
@@ -1,4 +1,4 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
@@ -13,6 +13,25 @@ go_library(
         "//go/lib/common:go_default_library",
         "//go/lib/hpkt:go_default_library",
         "//go/lib/serrors:go_default_library",
+        "//go/lib/slayers:go_default_library",
         "//go/lib/spkt:go_default_library",
+        "@com_github_google_gopacket//:go_default_library",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = ["packet_test.go"],
+    embed = [":go_default_library"],
+    deps = [
+        "//go/lib/common:go_default_library",
+        "//go/lib/slayers:go_default_library",
+        "//go/lib/slayers/path:go_default_library",
+        "//go/lib/slayers/path/scion:go_default_library",
+        "//go/lib/xtest:go_default_library",
+        "@com_github_google_gopacket//:go_default_library",
+        "@com_github_google_gopacket//layers:go_default_library",
+        "@com_github_stretchr_testify//assert:go_default_library",
+        "@com_github_stretchr_testify//require:go_default_library",
     ],
 )
diff --git a/go/dispatcher/internal/respool/packet.go b/go/dispatcher/internal/respool/packet.go
index 239341289..9872b1a3b 100644
--- a/go/dispatcher/internal/respool/packet.go
+++ b/go/dispatcher/internal/respool/packet.go
@@ -18,10 +18,13 @@ import (
 	"net"
 	"sync"
 
+	"github.com/google/gopacket"
+
 	"github.com/scionproto/scion/go/dispatcher/internal/metrics"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/hpkt"
 	"github.com/scionproto/scion/go/lib/serrors"
+	"github.com/scionproto/scion/go/lib/slayers"
 	"github.com/scionproto/scion/go/lib/spkt"
 )
 
@@ -47,6 +50,15 @@ type Packet struct {
 	// HeaderV2 indicates whether the new header format is used.
 	HeaderV2 bool
 
+	SCION slayers.SCION
+	// FIXME(roosd): currently no support for extensions.
+	UDP  slayers.UDP
+	SCMP slayers.SCMP
+	Pld  gopacket.Payload
+
+	// L4 indicates what type is at layer 4.
+	L4 gopacket.LayerType
+
 	// buffer contains the raw slice that other fields reference
 	buffer common.RawBytes
 
@@ -117,18 +129,7 @@ func (pkt *Packet) DecodeFromConn(conn net.PacketConn) error {
 	metrics.M.NetReadBytes().Add(float64(n))
 
 	pkt.UnderlayRemote = readExtra.(*net.UDPAddr)
-	if pkt.HeaderV2 {
-		if err = hpkt.ParseScnPkt2(&pkt.Info, pkt.buffer); err != nil {
-			metrics.M.NetReadPkts(
-				metrics.IncomingPacket{
-					Result: metrics.PacketResultParseError,
-				},
-			).Inc()
-			return err
-		}
-		return nil
-	}
-	if err = hpkt.ParseScnPkt(&pkt.Info, pkt.buffer); err != nil {
+	if err := pkt.decodeBuffer(); err != nil {
 		metrics.M.NetReadPkts(
 			metrics.IncomingPacket{
 				Result: metrics.PacketResultParseError,
@@ -150,11 +151,36 @@ func (pkt *Packet) DecodeFromReliableConn(conn net.PacketConn) error {
 		return serrors.New("missing next-hop")
 	}
 	pkt.UnderlayRemote = readExtra.(*net.UDPAddr)
+	return pkt.decodeBuffer()
+}
 
-	if pkt.HeaderV2 {
-		return hpkt.ParseScnPkt2(&pkt.Info, pkt.buffer)
+func (pkt *Packet) decodeBuffer() error {
+	if !pkt.HeaderV2 {
+		return hpkt.ParseScnPkt(&pkt.Info, pkt.buffer)
 	}
-	return hpkt.ParseScnPkt(&pkt.Info, pkt.buffer)
+	parser := gopacket.NewDecodingLayerParser(slayers.LayerTypeSCION,
+		&pkt.SCION, &pkt.UDP, &pkt.SCMP, &pkt.Pld,
+	)
+	decoded := make([]gopacket.LayerType, 3)
+
+	// Only return the error if it is not caused by the unregistered SCMP layers.
+	if err := parser.DecodeLayers(pkt.buffer, &decoded); err != nil {
+		if _, ok := err.(gopacket.UnsupportedLayerType); !ok {
+			return err
+		}
+		if len(decoded) == 0 || decoded[len(decoded)-1] != slayers.LayerTypeSCMP {
+			return err
+		}
+	}
+	if len(decoded) < 2 {
+		return serrors.New("L4 not decoded")
+	}
+	l4 := decoded[1]
+	if l4 != slayers.LayerTypeSCMP && l4 != slayers.LayerTypeSCIONUDP {
+		return serrors.New("unknown L4 layer decoded", "type", common.TypeOf(l4))
+	}
+	pkt.L4 = l4
+	return nil
 }
 
 func (pkt *Packet) SendOnConn(conn net.PacketConn, address net.Addr) (int, error) {
@@ -165,4 +191,5 @@ func (pkt *Packet) reset() {
 	pkt.buffer = pkt.buffer[:cap(pkt.buffer)]
 	pkt.Info = spkt.ScnPkt{}
 	pkt.UnderlayRemote = nil
+	pkt.L4 = 0
 }
diff --git a/go/lib/slayers/scmp.go b/go/lib/slayers/scmp.go
index ff90134e0..cb13336c2 100644
--- a/go/lib/slayers/scmp.go
+++ b/go/lib/slayers/scmp.go
@@ -61,6 +61,7 @@ func (s *SCMP) CanDecode() gopacket.LayerClass {
 }
 
 // NextLayerType use the typecode to select the right next decoder.
+// If the SCMP type is unknown, the next layer is gopacket.LayerTypePayload.
 func (s *SCMP) NextLayerType() gopacket.LayerType {
 	switch s.TypeCode.Type() {
 	case SCMPTypeDestinationUnreachable:
diff --git a/go/lib/slayers/scmp_typecode.go b/go/lib/slayers/scmp_typecode.go
index a26f7ba8d..e2510015d 100644
--- a/go/lib/slayers/scmp_typecode.go
+++ b/go/lib/slayers/scmp_typecode.go
@@ -88,6 +88,11 @@ func (a SCMPTypeCode) Code() uint8 {
 	return uint8(a)
 }
 
+// InfoMsg indicates if the SCMP message is an SCMP informational message.
+func (a SCMPTypeCode) InfoMsg() bool {
+	return a.Type() > 127
+}
+
 func (a SCMPTypeCode) String() string {
 	t, c := a.Type(), a.Code()
 	info, ok := scmpTypeCodeInfo[t]


Test patch:
diff --git a/go/dispatcher/dispatcher/underlay_test.go b/go/dispatcher/dispatcher/underlay_test.go
index d3684c327..f1ef0fd96 100644
--- a/go/dispatcher/dispatcher/underlay_test.go
+++ b/go/dispatcher/dispatcher/underlay_test.go
@@ -1,4 +1,5 @@
 // Copyright 2019 ETH Zurich
+// Copyright 2020 ETH Zurich, Anapaya Systems
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,22 +16,926 @@
 package dispatcher
 
 import (
+	"bytes"
 	"net"
 	"testing"
 
 	"github.com/golang/mock/gomock"
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
+	"github.com/scionproto/scion/go/dispatcher/internal/respool"
 	"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/l4/mock_l4"
 	"github.com/scionproto/scion/go/lib/scmp"
+	"github.com/scionproto/scion/go/lib/slayers"
+	"github.com/scionproto/scion/go/lib/slayers/path"
+	"github.com/scionproto/scion/go/lib/slayers/path/scion"
 	"github.com/scionproto/scion/go/lib/spkt"
 	"github.com/scionproto/scion/go/lib/xtest"
 )
 
+func TestGetDst(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	testCases := map[string]struct {
+		Pkt          func(t *testing.T) *respool.Packet
+		ExpectedDst  Destination
+		ErrAssertion assert.ErrorAssertionFunc
+	}{
+		"unsupported L4": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				return &respool.Packet{
+					L4: 1337,
+				}
+			},
+			ErrAssertion: assert.Error,
+		},
+		"UDP/SCION with IP destination is delivered by IP": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					UDP: slayers.UDP{
+						UDP: layers.UDP{
+							DstPort: 1337,
+						},
+					},
+					L4: slayers.LayerTypeSCIONUDP,
+				}
+				require.NoError(t, pkt.SCION.SetDstAddr(&net.IPAddr{IP: net.IP{192, 168, 0, 1}}))
+				return pkt
+			},
+			ExpectedDst: UDPDestination{
+				IA:     xtest.MustParseIA("1-ff00:0:110"),
+				Public: &net.UDPAddr{IP: net.IP{192, 168, 0, 1}, Port: 1337},
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"UDP/SCION with SVC destination is delivered by SVC": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					UDP: slayers.UDP{
+						UDP: layers.UDP{
+							DstPort: 1337,
+						},
+					},
+					L4: slayers.LayerTypeSCIONUDP,
+				}
+				require.NoError(t, pkt.SCION.SetDstAddr(addr.SvcCS))
+				return pkt
+			},
+			ExpectedDst: SVCDestination{
+				IA:  xtest.MustParseIA("1-ff00:0:110"),
+				Svc: addr.SvcCS,
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"SCMP/SCION EchoRequest, is sent to SCMP handler": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPEcho{
+						Identifier: 42,
+						SeqNumber:  13,
+					},
+				)
+				require.NoError(t, err)
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					SCMP: slayers.SCMP{
+						TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0),
+						BaseLayer: layers.BaseLayer{
+							Payload: buf.Bytes(),
+						},
+					},
+					L4: slayers.LayerTypeSCMP,
+				}
+				return pkt
+			},
+			ExpectedDst:  SCMPHandler{},
+			ErrAssertion: assert.NoError,
+		},
+		"SCMP/SCION EchoReply, is sent to SCMP destination": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPEcho{
+						Identifier: 42,
+						SeqNumber:  13,
+					},
+				)
+				require.NoError(t, err)
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					SCMP: slayers.SCMP{
+						TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0),
+						BaseLayer: layers.BaseLayer{
+							Payload: buf.Bytes(),
+						},
+					},
+					L4: slayers.LayerTypeSCMP,
+				}
+				return pkt
+			},
+			ExpectedDst: SCMPDestination{
+				IA: xtest.MustParseIA("1-ff00:0:110"),
+				ID: 42,
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"SCMP/SCION TracerouteRequest, is sent to SCMP handler": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPTraceroute{
+						Identifier: 42,
+					},
+				)
+				require.NoError(t, err)
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					SCMP: slayers.SCMP{
+						TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0),
+						BaseLayer: layers.BaseLayer{
+							Payload: buf.Bytes(),
+						},
+					},
+					L4: slayers.LayerTypeSCMP,
+				}
+				return pkt
+			},
+			ExpectedDst:  SCMPHandler{},
+			ErrAssertion: assert.NoError,
+		},
+		"SCMP/SCION TracerouteReply, is sent to SCMP destination": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPTraceroute{
+						Identifier: 42,
+					},
+				)
+				require.NoError(t, err)
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					SCMP: slayers.SCMP{
+						TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0),
+						BaseLayer: layers.BaseLayer{
+							Payload: buf.Bytes(),
+						},
+					},
+					L4: slayers.LayerTypeSCMP,
+				}
+				return pkt
+			},
+			ExpectedDst: SCMPDestination{
+				IA: xtest.MustParseIA("1-ff00:0:110"),
+				ID: 42,
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"SCMP/SCION Error with offending UDP/SCION is delivered by IP": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				// Construct offending packet.
+				scion := newSCIONHdr(t, common.L4UDP)
+				udp := &slayers.UDP{
+					UDP: layers.UDP{
+						SrcPort: 1337,
+						DstPort: 42,
+					},
+				}
+				udp.SetNetworkLayerForChecksum(scion)
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{
+						FixLengths:       true,
+						ComputeChecksums: true,
+					},
+					scion,
+					udp,
+					gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)),
+				)
+				require.NoError(t, err)
+
+				scmpPld := gopacket.NewSerializeBuffer()
+				err = gopacket.SerializeLayers(scmpPld,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPExternalInterfaceDown{
+						IA:   xtest.MustParseIA("1-ff00:0:111"),
+						IfID: 141,
+					},
+					gopacket.Payload(buf.Bytes()),
+				)
+				require.NoError(t, err)
+
+				// Construct packet received by dispatcher.
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					SCMP: slayers.SCMP{
+						TypeCode: slayers.CreateSCMPTypeCode(
+							slayers.SCMPTypeExternalInterfaceDown, 0),
+						BaseLayer: layers.BaseLayer{
+							Payload: scmpPld.Bytes(),
+						},
+					},
+					L4: slayers.LayerTypeSCMP,
+				}
+				require.NoError(t, pkt.SCION.SetDstAddr(&net.IPAddr{IP: net.IP{192, 168, 0, 1}}))
+				return pkt
+			},
+			ExpectedDst: UDPDestination{
+				IA:     xtest.MustParseIA("1-ff00:0:110"),
+				Public: &net.UDPAddr{IP: net.IP{192, 168, 0, 1}, Port: 1337},
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"SCMP/SCION Error with offending SCMP/SCION EchoRequest is delivered by ID": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				// Construct offending packet.
+				scion := newSCIONHdr(t, common.L4SCMP)
+				scmp := &slayers.SCMP{
+					TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0),
+				}
+				scmp.SetNetworkLayerForChecksum(scion)
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{
+						FixLengths:       true,
+						ComputeChecksums: true,
+					},
+					scion,
+					scmp,
+					&slayers.SCMPEcho{Identifier: 42, SeqNumber: 16},
+					gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)),
+				)
+				require.NoError(t, err)
+
+				scmpPld := gopacket.NewSerializeBuffer()
+				err = gopacket.SerializeLayers(scmpPld,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPInternalConnectivityDown{
+						IA:      xtest.MustParseIA("1-ff00:0:111"),
+						Ingress: 131,
+						Egress:  141,
+					},
+					gopacket.Payload(buf.Bytes()),
+				)
+				require.NoError(t, err)
+
+				// Construct packet received by dispatcher.
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					SCMP: slayers.SCMP{
+						TypeCode: slayers.CreateSCMPTypeCode(
+							slayers.SCMPTypeInternalConnectivityDown, 0),
+						BaseLayer: layers.BaseLayer{
+							Payload: scmpPld.Bytes(),
+						},
+					},
+					L4: slayers.LayerTypeSCMP,
+				}
+				require.NoError(t, pkt.SCION.SetDstAddr(&net.IPAddr{IP: net.IP{192, 168, 0, 1}}))
+				return pkt
+			},
+			ExpectedDst: SCMPDestination{
+				IA: xtest.MustParseIA("1-ff00:0:110"),
+				ID: 42,
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"SCMP/SCION Error with offending SCMP/SCION TracerouteRequest is delivered by ID": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				// Construct offending packet.
+				scion := newSCIONHdr(t, common.L4SCMP)
+				scmp := &slayers.SCMP{
+					TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0),
+				}
+				scmp.SetNetworkLayerForChecksum(scion)
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{
+						FixLengths:       true,
+						ComputeChecksums: true,
+					},
+					scion,
+					scmp,
+					&slayers.SCMPTraceroute{Identifier: 42},
+					gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)),
+				)
+				require.NoError(t, err)
+
+				scmpPld := gopacket.NewSerializeBuffer()
+				err = gopacket.SerializeLayers(scmpPld,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPInternalConnectivityDown{
+						IA:      xtest.MustParseIA("1-ff00:0:111"),
+						Ingress: 131,
+						Egress:  141,
+					},
+					gopacket.Payload(buf.Bytes()),
+				)
+				require.NoError(t, err)
+
+				// Construct packet received by dispatcher.
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					SCMP: slayers.SCMP{
+						TypeCode: slayers.CreateSCMPTypeCode(
+							slayers.SCMPTypeInternalConnectivityDown, 0),
+						BaseLayer: layers.BaseLayer{
+							Payload: scmpPld.Bytes(),
+						},
+					},
+					L4: slayers.LayerTypeSCMP,
+				}
+				require.NoError(t, pkt.SCION.SetDstAddr(&net.IPAddr{IP: net.IP{192, 168, 0, 1}}))
+				return pkt
+			},
+			ExpectedDst: SCMPDestination{
+				IA: xtest.MustParseIA("1-ff00:0:110"),
+				ID: 42,
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"SCMP/SCION Error with truncated UDP/SCION payload is delivered by IP": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				// Construct offending packet.
+				scion := newSCIONHdr(t, common.L4UDP)
+				udp := &slayers.UDP{
+					UDP: layers.UDP{
+						SrcPort: 1337,
+						DstPort: 42,
+					},
+				}
+				udp.SetNetworkLayerForChecksum(scion)
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{
+						FixLengths:       true,
+						ComputeChecksums: true,
+					},
+					scion,
+					udp,
+					gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)),
+				)
+				require.NoError(t, err)
+
+				scmpPld := gopacket.NewSerializeBuffer()
+				err = gopacket.SerializeLayers(scmpPld,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPExternalInterfaceDown{
+						IA:   xtest.MustParseIA("1-ff00:0:111"),
+						IfID: 141,
+					},
+					gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-20]),
+				)
+				require.NoError(t, err)
+
+				// Construct packet received by dispatcher.
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					SCMP: slayers.SCMP{
+						TypeCode: slayers.CreateSCMPTypeCode(
+							slayers.SCMPTypeExternalInterfaceDown, 0),
+						BaseLayer: layers.BaseLayer{
+							Payload: scmpPld.Bytes(),
+						},
+					},
+					L4: slayers.LayerTypeSCMP,
+				}
+				require.NoError(t, pkt.SCION.SetDstAddr(&net.IPAddr{IP: net.IP{192, 168, 0, 1}}))
+				return pkt
+			},
+			ExpectedDst: UDPDestination{
+				IA:     xtest.MustParseIA("1-ff00:0:110"),
+				Public: &net.UDPAddr{IP: net.IP{192, 168, 0, 1}, Port: 1337},
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"SCMP/SCION Error with offending truncated EchoRequest is delivered by ID": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				// Construct offending packet.
+				scion := newSCIONHdr(t, common.L4SCMP)
+				scmp := &slayers.SCMP{
+					TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0),
+				}
+				scmp.SetNetworkLayerForChecksum(scion)
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{
+						FixLengths:       true,
+						ComputeChecksums: true,
+					},
+					scion,
+					scmp,
+					&slayers.SCMPEcho{Identifier: 42, SeqNumber: 16},
+					gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)),
+				)
+				require.NoError(t, err)
+
+				scmpPld := gopacket.NewSerializeBuffer()
+				err = gopacket.SerializeLayers(scmpPld,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPInternalConnectivityDown{
+						IA:      xtest.MustParseIA("1-ff00:0:111"),
+						Ingress: 131,
+						Egress:  141,
+					},
+					// Truncate the SCMP Echo data.
+					gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-20]),
+				)
+				require.NoError(t, err)
+
+				// Construct packet received by dispatcher.
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					SCMP: slayers.SCMP{
+						TypeCode: slayers.CreateSCMPTypeCode(
+							slayers.SCMPTypeInternalConnectivityDown, 0),
+						BaseLayer: layers.BaseLayer{
+							Payload: scmpPld.Bytes(),
+						},
+					},
+					L4: slayers.LayerTypeSCMP,
+				}
+				require.NoError(t, pkt.SCION.SetDstAddr(&net.IPAddr{IP: net.IP{192, 168, 0, 1}}))
+				return pkt
+			},
+			ExpectedDst: SCMPDestination{
+				IA: xtest.MustParseIA("1-ff00:0:110"),
+				ID: 42,
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"SCMP/SCION Error with offending truncated TracerouteRequest is delivered by ID": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				// Construct offending packet.
+				scion := newSCIONHdr(t, common.L4SCMP)
+				scmp := &slayers.SCMP{
+					TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0),
+				}
+				scmp.SetNetworkLayerForChecksum(scion)
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{
+						FixLengths:       true,
+						ComputeChecksums: true,
+					},
+					scion,
+					scmp,
+					&slayers.SCMPTraceroute{Identifier: 42},
+					gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)),
+				)
+				require.NoError(t, err)
+
+				scmpPld := gopacket.NewSerializeBuffer()
+				err = gopacket.SerializeLayers(scmpPld,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPInternalConnectivityDown{
+						IA:      xtest.MustParseIA("1-ff00:0:111"),
+						Ingress: 131,
+						Egress:  141,
+					},
+					// Truncate the SCMP Traceroute data.
+					gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-20]),
+				)
+				require.NoError(t, err)
+
+				// Construct packet received by dispatcher.
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					SCMP: slayers.SCMP{
+						TypeCode: slayers.CreateSCMPTypeCode(
+							slayers.SCMPTypeInternalConnectivityDown, 0),
+						BaseLayer: layers.BaseLayer{
+							Payload: scmpPld.Bytes(),
+						},
+					},
+					L4: slayers.LayerTypeSCMP,
+				}
+				require.NoError(t, pkt.SCION.SetDstAddr(&net.IPAddr{IP: net.IP{192, 168, 0, 1}}))
+				return pkt
+			},
+			ExpectedDst: SCMPDestination{
+				IA: xtest.MustParseIA("1-ff00:0:110"),
+				ID: 42,
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"SCMP/SCION Error with partial UDP/SCION header is dropped": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				// Construct offending packet.
+				scion := newSCIONHdr(t, common.L4UDP)
+				udp := &slayers.UDP{
+					UDP: layers.UDP{
+						SrcPort: 1337,
+						DstPort: 42,
+					},
+				}
+				udp.SetNetworkLayerForChecksum(scion)
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{
+						FixLengths:       true,
+						ComputeChecksums: true,
+					},
+					scion,
+					udp,
+					gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)),
+				)
+				require.NoError(t, err)
+
+				scmpPld := gopacket.NewSerializeBuffer()
+				err = gopacket.SerializeLayers(scmpPld,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPExternalInterfaceDown{
+						IA:   xtest.MustParseIA("1-ff00:0:111"),
+						IfID: 141,
+					},
+					gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-21]),
+				)
+				require.NoError(t, err)
+
+				// Construct packet received by dispatcher.
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					SCMP: slayers.SCMP{
+						TypeCode: slayers.CreateSCMPTypeCode(
+							slayers.SCMPTypeExternalInterfaceDown, 0),
+						BaseLayer: layers.BaseLayer{
+							Payload: scmpPld.Bytes(),
+						},
+					},
+					L4: slayers.LayerTypeSCMP,
+				}
+				require.NoError(t, pkt.SCION.SetDstAddr(&net.IPAddr{IP: net.IP{192, 168, 0, 1}}))
+				return pkt
+			},
+			ErrAssertion: assert.Error,
+		},
+		"SCMP/SCION Error with partial EchoRequest is dropped": {
+			Pkt: func(t *testing.T) *respool.Packet {
+				// Construct offending packet.
+				scion := newSCIONHdr(t, common.L4SCMP)
+				scmp := &slayers.SCMP{
+					TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0),
+				}
+				scmp.SetNetworkLayerForChecksum(scion)
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{
+						FixLengths:       true,
+						ComputeChecksums: true,
+					},
+					scion,
+					scmp,
+					&slayers.SCMPEcho{Identifier: 42, SeqNumber: 16},
+					gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)),
+				)
+				require.NoError(t, err)
+
+				scmpPld := gopacket.NewSerializeBuffer()
+				err = gopacket.SerializeLayers(scmpPld,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPInternalConnectivityDown{
+						IA:      xtest.MustParseIA("1-ff00:0:111"),
+						Ingress: 131,
+						Egress:  141,
+					},
+					// Only partially include the echo request information.
+					gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-21]),
+				)
+				require.NoError(t, err)
+
+				// Construct packet received by dispatcher.
+				pkt := &respool.Packet{
+					SCION: slayers.SCION{
+						DstIA: xtest.MustParseIA("1-ff00:0:110"),
+					},
+					SCMP: slayers.SCMP{
+						TypeCode: slayers.CreateSCMPTypeCode(
+							slayers.SCMPTypeInternalConnectivityDown, 0),
+						BaseLayer: layers.BaseLayer{
+							Payload: scmpPld.Bytes(),
+						},
+					},
+					L4: slayers.LayerTypeSCMP,
+				}
+				require.NoError(t, pkt.SCION.SetDstAddr(&net.IPAddr{IP: net.IP{192, 168, 0, 1}}))
+				return pkt
+			},
+			ErrAssertion: assert.Error,
+		},
+	}
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			destination, err := getDst(tc.Pkt(t))
+			tc.ErrAssertion(t, err)
+			assert.Equal(t, tc.ExpectedDst, destination)
+		})
+	}
+}
+
+func TestSCMPHandlerReverse(t *testing.T) {
+	testCases := map[string]struct {
+		L4               func(t *testing.T) slayers.SCMP
+		ExpectedTypeCode slayers.SCMPTypeCode
+		ExpectedL4       func(t *testing.T) []gopacket.SerializableLayer
+	}{
+		"echo without data": {
+			L4: func(t *testing.T) slayers.SCMP {
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPEcho{
+						Identifier: 42,
+						SeqNumber:  12,
+					},
+				)
+				require.NoError(t, err)
+				return slayers.SCMP{
+					TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0),
+					Checksum: 1337,
+					BaseLayer: layers.BaseLayer{
+						Payload: buf.Bytes(),
+					},
+				}
+			},
+			ExpectedTypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0),
+			ExpectedL4: func(t *testing.T) []gopacket.SerializableLayer {
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPEcho{
+						Identifier: 42,
+						SeqNumber:  12,
+					},
+				)
+				require.NoError(t, err)
+				pkt := gopacket.NewPacket(buf.Bytes(), slayers.LayerTypeSCMPEcho,
+					gopacket.DecodeOptions{})
+				echo := pkt.Layer(slayers.LayerTypeSCMPEcho)
+				require.NotNil(t, echo)
+				return []gopacket.SerializableLayer{echo.(gopacket.SerializableLayer)}
+			},
+		},
+		"echo with data": {
+			L4: func(t *testing.T) slayers.SCMP {
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPEcho{
+						Identifier: 42,
+						SeqNumber:  12,
+					},
+					gopacket.Payload("I am the payload, please don't forget about me :)"),
+				)
+				require.NoError(t, err)
+				return slayers.SCMP{
+					TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0),
+					Checksum: 1337,
+					BaseLayer: layers.BaseLayer{
+						Payload: buf.Bytes(),
+					},
+				}
+			},
+			ExpectedTypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0),
+			ExpectedL4: func(t *testing.T) []gopacket.SerializableLayer {
+				pld := gopacket.Payload("I am the payload, please don't forget about me :)")
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPEcho{
+						Identifier: 42,
+						SeqNumber:  12,
+					},
+					pld,
+				)
+				require.NoError(t, err)
+				pkt := gopacket.NewPacket(buf.Bytes(), slayers.LayerTypeSCMPEcho,
+					gopacket.DecodeOptions{})
+				echo := pkt.Layer(slayers.LayerTypeSCMPEcho)
+				require.NotNil(t, echo)
+				return []gopacket.SerializableLayer{echo.(gopacket.SerializableLayer), &pld}
+			},
+		},
+		"traceroute without data": {
+			L4: func(t *testing.T) slayers.SCMP {
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPTraceroute{
+						Identifier: 42,
+						IA:         xtest.MustParseIA("1-ff00:0:110"),
+						Interface:  12,
+					},
+				)
+				require.NoError(t, err)
+				return slayers.SCMP{
+					TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0),
+					Checksum: 1337,
+					BaseLayer: layers.BaseLayer{
+						Payload: buf.Bytes(),
+					},
+				}
+			},
+			ExpectedTypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0),
+			ExpectedL4: func(t *testing.T) []gopacket.SerializableLayer {
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPTraceroute{
+						Identifier: 42,
+						IA:         xtest.MustParseIA("1-ff00:0:110"),
+						Interface:  12,
+					},
+				)
+				require.NoError(t, err)
+				pkt := gopacket.NewPacket(buf.Bytes(), slayers.LayerTypeSCMPTraceroute,
+					gopacket.DecodeOptions{})
+				tr := pkt.Layer(slayers.LayerTypeSCMPTraceroute)
+				require.NotNil(t, tr)
+				return []gopacket.SerializableLayer{tr.(gopacket.SerializableLayer)}
+			},
+		},
+		"traceroute with data": {
+			L4: func(t *testing.T) slayers.SCMP {
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPTraceroute{
+						Identifier: 42,
+						IA:         xtest.MustParseIA("1-ff00:0:110"),
+						Interface:  12,
+					},
+					gopacket.Payload("I am the payload, please don't forget about me :)"),
+				)
+				require.NoError(t, err)
+				return slayers.SCMP{
+					TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0),
+					Checksum: 1337,
+					BaseLayer: layers.BaseLayer{
+						Payload: buf.Bytes(),
+					},
+				}
+			},
+			ExpectedTypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0),
+			ExpectedL4: func(t *testing.T) []gopacket.SerializableLayer {
+				pld := gopacket.Payload("I am the payload, please don't forget about me :)")
+				buf := gopacket.NewSerializeBuffer()
+				err := gopacket.SerializeLayers(buf,
+					gopacket.SerializeOptions{},
+					&slayers.SCMPTraceroute{
+						Identifier: 42,
+						IA:         xtest.MustParseIA("1-ff00:0:110"),
+						Interface:  12,
+					},
+					pld,
+				)
+				require.NoError(t, err)
+				pkt := gopacket.NewPacket(buf.Bytes(), slayers.LayerTypeSCMPTraceroute,
+					gopacket.DecodeOptions{})
+				tr := pkt.Layer(slayers.LayerTypeSCMPTraceroute)
+				require.NotNil(t, tr)
+				return []gopacket.SerializableLayer{tr.(gopacket.SerializableLayer), &pld}
+			},
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			// Prepare original packet
+			pkt := &respool.Packet{
+				SCION: slayers.SCION{
+					Version:      0,
+					TrafficClass: 0xb8,
+					FlowID:       0xdead,
+					NextHdr:      common.L4SCMP,
+					PathType:     slayers.PathTypeSCION,
+					SrcIA:        xtest.MustParseIA("1-ff00:0:110"),
+					DstIA:        xtest.MustParseIA("1-ff00:0:112"),
+					Path: &scion.Decoded{
+						Base: scion.Base{
+							PathMeta: scion.MetaHdr{
+								CurrHF: 2,
+								SegLen: [3]uint8{3, 0, 0},
+							},
+							NumINF:  1,
+							NumHops: 3,
+						},
+						InfoFields: []*path.InfoField{
+							{SegID: 0x111, ConsDir: true, Timestamp: 0x100},
+						},
+						HopFields: []*path.HopField{
+							{ConsIngress: 0, ConsEgress: 311, Mac: bytes.Repeat([]byte{0x00}, 6)},
+							{ConsIngress: 131, ConsEgress: 141, Mac: bytes.Repeat([]byte{0x01}, 6)},
+							{ConsIngress: 411, ConsEgress: 0, Mac: bytes.Repeat([]byte{0x02}, 6)},
+						},
+					},
+				},
+				SCMP: tc.L4(t),
+				L4:   slayers.LayerTypeSCMP,
+			}
+			require.NoError(t, pkt.SCION.SetSrcAddr(&net.IPAddr{IP: net.IP{127, 0, 0, 1}}))
+			require.NoError(t, pkt.SCION.SetDstAddr(&net.IPAddr{IP: net.IP{127, 0, 0, 2}}))
+
+			// Reverse packet
+			raw, err := SCMPHandler{}.reverse(pkt)
+			require.NoError(t, err)
+
+			gpkt := gopacket.NewPacket(raw, slayers.LayerTypeSCION, gopacket.DecodeOptions{})
+
+			t.Run("check SCION header", func(t *testing.T) {
+				scionL := gpkt.Layer(slayers.LayerTypeSCION).(*slayers.SCION)
+				expected := &slayers.SCION{
+					Version:      0,
+					TrafficClass: 0xb8,
+					FlowID:       0xdead,
+					HdrLen:       21,
+					NextHdr:      common.L4SCMP,
+					PayloadLen:   uint16(4 + len(pkt.SCMP.Payload)),
+					PathType:     slayers.PathTypeSCION,
+					SrcIA:        xtest.MustParseIA("1-ff00:0:112"),
+					DstIA:        xtest.MustParseIA("1-ff00:0:110"),
+					Path: &scion.Decoded{
+						Base: scion.Base{
+							PathMeta: scion.MetaHdr{
+								CurrHF: 0,
+								SegLen: [3]uint8{3, 0, 0},
+							},
+							NumINF:  1,
+							NumHops: 3,
+						},
+						InfoFields: []*path.InfoField{
+							{SegID: 0x111, ConsDir: false, Timestamp: 0x100},
+						},
+						HopFields: []*path.HopField{
+							{ConsIngress: 411, ConsEgress: 0, Mac: bytes.Repeat([]byte{0x02}, 6)},
+							{ConsIngress: 131, ConsEgress: 141, Mac: bytes.Repeat([]byte{0x01}, 6)},
+							{ConsIngress: 0, ConsEgress: 311, Mac: bytes.Repeat([]byte{0x00}, 6)},
+						},
+					},
+				}
+				require.NoError(t, expected.SetSrcAddr(&net.IPAddr{IP: net.IP{127, 0, 0, 2}}))
+				require.NoError(t, expected.SetDstAddr(&net.IPAddr{IP: net.IP{127, 0, 0, 1}}))
+
+				scionL.BaseLayer = layers.BaseLayer{}
+				var decodedPath scion.Decoded
+				require.NoError(t, decodedPath.DecodeFromBytes(scionL.Path.(*scion.Raw).Raw))
+				scionL.Path = &decodedPath
+
+				assert.Equal(t, expected, scionL)
+			})
+			t.Run("check L4", func(t *testing.T) {
+				scmp := gpkt.Layer(slayers.LayerTypeSCMP)
+				require.NotNil(t, scmp)
+				assert.Equal(t, tc.ExpectedTypeCode, scmp.(*slayers.SCMP).TypeCode)
+				assert.NotZero(t, scmp.(*slayers.SCMP).Checksum)
+
+				for _, l := range tc.ExpectedL4(t) {
+					assert.Equal(t, l, gpkt.Layer(l.LayerType()), l.LayerType().String())
+				}
+			})
+		})
+	}
+}
+
 func TestComputeDestination(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
@@ -59,7 +964,7 @@ func TestComputeDestination(t *testing.T) {
 				DstHost: addr.HostFromIP(net.IP{192, 168, 0, 1}),
 				L4:      &l4.UDP{DstPort: 1002},
 			},
-			ExpectedDst: &UDPDestination{IP: net.IP{192, 168, 0, 1}, Port: 1002},
+			ExpectedDst: &UDPDestinationLegacy{IP: net.IP{192, 168, 0, 1}, Port: 1002},
 		},
 		{
 			Description: "SCION/UDP with SVC destination is delivered by SVC",
@@ -67,7 +972,7 @@ func TestComputeDestination(t *testing.T) {
 				DstHost: addr.SvcPS,
 				L4:      &l4.UDP{DstPort: 1002},
 			},
-			ExpectedDst: SVCDestination(addr.SvcPS),
+			ExpectedDst: SVCDestinationLegacy(addr.SvcPS),
 		},
 		{
 			Description: "SCION/UDP without SVC or IP destination returns error",
@@ -178,7 +1083,7 @@ func TestComputeDestination(t *testing.T) {
 					}),
 				},
 			},
-			ExpectedDst: &UDPDestination{IP: net.IP{192, 168, 0, 1}, Port: 1002},
+			ExpectedDst: &UDPDestinationLegacy{IP: net.IP{192, 168, 0, 1}, Port: 1002},
 		},
 		{
 			Description: "SCION/SCMP with Non-General class and bad quoted L4 type returns error",
@@ -211,7 +1116,7 @@ func TestComputeDestination(t *testing.T) {
 	}
 	for _, test := range testCases {
 		t.Run(test.Description, func(t *testing.T) {
-			destination, err := ComputeDestination(test.Packet, false)
+			destination, err := ComputeDestination(test.Packet)
 			xtest.AssertErrorsIs(t, err, test.ExpectedErr)
 			assert.Equal(t, test.ExpectedDst, destination)
 		})
@@ -223,3 +1128,33 @@ func MustPackL4Header(t *testing.T, header l4.L4Header) common.RawBytes {
 	require.NoError(t, err)
 	return b
 }
+
+func newSCIONHdr(t *testing.T, l4 common.L4ProtocolType) *slayers.SCION {
+	scion := &slayers.SCION{
+		NextHdr:  l4,
+		PathType: slayers.PathTypeSCION,
+		SrcIA:    xtest.MustParseIA("1-ff00:0:110"),
+		DstIA:    xtest.MustParseIA("1-ff00:0:112"),
+		Path: &scion.Decoded{
+			Base: scion.Base{
+				PathMeta: scion.MetaHdr{
+					CurrHF: 2,
+					SegLen: [3]uint8{3, 0, 0},
+				},
+				NumINF:  1,
+				NumHops: 3,
+			},
+			InfoFields: []*path.InfoField{
+				{SegID: 0x111, ConsDir: true, Timestamp: 0x100},
+			},
+			HopFields: []*path.HopField{
+				{ConsIngress: 0, ConsEgress: 311},
+				{ConsIngress: 131, ConsEgress: 141},
+				{ConsIngress: 411, ConsEgress: 0},
+			},
+		},
+	}
+	require.NoError(t, scion.SetSrcAddr(&net.IPAddr{IP: net.IP{192, 168, 0, 1}}))
+	require.NoError(t, scion.SetDstAddr(&net.IPAddr{IP: net.IP{192, 168, 0, 2}}))
+	return scion
+}
diff --git a/go/dispatcher/internal/respool/packet_test.go b/go/dispatcher/internal/respool/packet_test.go
new file mode 100644
index 000000000..ee0061e44
--- /dev/null
+++ b/go/dispatcher/internal/respool/packet_test.go
@@ -0,0 +1,144 @@
+// Copyright 2020 Anapaya Systems
+//
+// 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 respool
+
+import (
+	"net"
+	"testing"
+
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/slayers"
+	"github.com/scionproto/scion/go/lib/slayers/path"
+	"github.com/scionproto/scion/go/lib/slayers/path/scion"
+	"github.com/scionproto/scion/go/lib/xtest"
+)
+
+func TestDecodeBuffer(t *testing.T) {
+	testCases := map[string]struct {
+		Layers       func(t *testing.T) []gopacket.SerializableLayer
+		Check        func(t *testing.T, pkt *Packet)
+		ErrAssertion assert.ErrorAssertionFunc
+	}{
+		"UDP": {
+			Layers: func(t *testing.T) []gopacket.SerializableLayer {
+				scion := scionLayer(t, common.L4UDP)
+				udp := &slayers.UDP{
+					UDP: layers.UDP{
+						SrcPort: 1337,
+						DstPort: 42,
+					},
+				}
+				udp.SetNetworkLayerForChecksum(scion)
+				pld := gopacket.Payload("I am a payload")
+				return []gopacket.SerializableLayer{scion, udp, pld}
+			},
+			Check: func(t *testing.T, pkt *Packet) {
+				assert.Equal(t, xtest.MustParseIA("1-ff00:0:110"), pkt.SCION.SrcIA)
+				assert.Equal(t, 1337, int(pkt.UDP.SrcPort))
+				assert.Equal(t, slayers.LayerTypeSCIONUDP, pkt.L4)
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"SCMP": {
+			Layers: func(t *testing.T) []gopacket.SerializableLayer {
+				scion := scionLayer(t, common.L4SCMP)
+				scmp := &slayers.SCMP{
+					TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeExternalInterfaceDown, 0),
+				}
+				scmp.SetNetworkLayerForChecksum(scion)
+				scmpMsg := &slayers.SCMPExternalInterfaceDown{
+					IA:   xtest.MustParseIA("1-ff00:0:110"),
+					IfID: 42,
+				}
+				pld := gopacket.Payload("offending packet")
+				return []gopacket.SerializableLayer{scion, scmp, scmpMsg, pld}
+			},
+			Check: func(t *testing.T, pkt *Packet) {
+				assert.Equal(t, xtest.MustParseIA("1-ff00:0:110"), pkt.SCION.SrcIA)
+				assert.Equal(t, slayers.SCMPTypeExternalInterfaceDown,
+					int(pkt.SCMP.TypeCode.Type()))
+				assert.Equal(t, slayers.LayerTypeSCMP, pkt.L4)
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"TCP": {
+			Layers: func(t *testing.T) []gopacket.SerializableLayer {
+				scion := scionLayer(t, common.L4TCP)
+				pld := gopacket.Payload("offending packet")
+				return []gopacket.SerializableLayer{scion, pld}
+			},
+			ErrAssertion: assert.Error,
+		},
+	}
+	for name, tc := range testCases {
+		name, tc := name, tc
+		t.Run(name, func(t *testing.T) {
+			opts := gopacket.SerializeOptions{
+				ComputeChecksums: true,
+				FixLengths:       true,
+			}
+			buf := gopacket.NewSerializeBuffer()
+			require.NoError(t, gopacket.SerializeLayers(buf, opts, tc.Layers(t)...))
+			pkt := &Packet{
+				HeaderV2: true,
+				buffer:   buf.Bytes(),
+			}
+			err := pkt.decodeBuffer()
+			tc.ErrAssertion(t, err)
+			if err != nil {
+				return
+			}
+			tc.Check(t, pkt)
+		})
+	}
+}
+
+func scionLayer(t *testing.T, l4 common.L4ProtocolType) *slayers.SCION {
+	scion := &slayers.SCION{
+		Version:      0,
+		TrafficClass: 0xb8,
+		FlowID:       0xdead,
+		NextHdr:      l4,
+		PathType:     slayers.PathTypeSCION,
+		SrcIA:        xtest.MustParseIA("1-ff00:0:110"),
+		DstIA:        xtest.MustParseIA("1-ff00:0:112"),
+		Path: &scion.Decoded{
+			Base: scion.Base{
+				PathMeta: scion.MetaHdr{
+					CurrHF: 2,
+					SegLen: [3]uint8{3, 0, 0},
+				},
+				NumINF:  1,
+				NumHops: 3,
+			},
+			InfoFields: []*path.InfoField{
+				{SegID: 0x111, ConsDir: true, Timestamp: 0x100},
+			},
+			HopFields: []*path.HopField{
+				{ConsIngress: 0, ConsEgress: 311},
+				{ConsIngress: 131, ConsEgress: 141},
+				{ConsIngress: 411, ConsEgress: 0},
+			},
+		},
+	}
+	require.NoError(t, scion.SetSrcAddr(&net.IPAddr{IP: net.IP{127, 0, 0, 1}}))
+	require.NoError(t, scion.SetDstAddr(&net.IPAddr{IP: net.IP{127, 0, 0, 2}}))
+	return scion
+}
diff --git a/go/dispatcher/main_test.go b/go/dispatcher/main_test.go
index c84f780e9..d7eba18c7 100644
--- a/go/dispatcher/main_test.go
+++ b/go/dispatcher/main_test.go
@@ -23,6 +23,7 @@ import (
 	"os"
 	"path/filepath"
 	"reflect"
+	"strings"
 	"testing"
 	"time"
 
@@ -483,6 +484,9 @@ func RunTestCase(t *testing.T, tc *TestCase, settings *TestSettings) {
 	defer conn.Close()
 
 	for _, packet := range tc.TestPackets {
+		if settings.HeaderV2 && strings.Contains(tc.Name, "SCMP") {
+			t.Skip("Skipping SCMP test", tc.Name)
+		}
 		sendBuffer := make([]byte, common.MaxMTU)
 		var n int
 		var err error