Repository: scionproto/scion
Pull request #3285 (2019-10-28 09:56:49)

braccept: more br_parent tests
Add test for the following cases:
- revoked parent interface
- svc tests
- revoked child (not owned) interface
- one-hop-path parent interface tests

Also fix some braccept issues when cloning packets.

<!-- 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/3285)
<!-- Reviewable:end -->



Patch:
diff --git a/acceptance/br_multi_acceptance/conf/topology.json b/acceptance/br_multi_acceptance/conf/topology.json
index bf01d4f03..c30daf463 100644
--- a/acceptance/br_multi_acceptance/conf/topology.json
+++ b/acceptance/br_multi_acceptance/conf/topology.json
@@ -108,11 +108,6 @@
       }
     }
   },
-  "PathService": {
-    "psA": { "Addrs": {
-        "IPv4": { "Public": { "L4Port": 20005, "Addr": "192.168.0.51" } }
-    } }
-  },
   "BeaconService": {
     "bsA": { "Addrs": {
         "IPv4": { "Public": { "L4Port": 20006, "Addr": "192.168.0.61" } }
@@ -121,6 +116,17 @@
   "CertificateService": {
     "csA": { "Addrs": {
         "IPv4": { "Public": { "L4Port": 20007, "Addr": "192.168.0.71" } }
+    } },
+    "csB": { "Addrs": {
+        "IPv4": { "Public": { "L4Port": 20007, "Addr": "192.168.0.72" } }
+    } }
+  },
+  "PathService": {
+    "psA": { "Addrs": {
+        "IPv4": { "Public": { "L4Port": 20005, "Addr": "192.168.0.51" } }
+    } },
+    "psB": { "Addrs": {
+        "IPv4": { "Public": { "L4Port": 20055, "Addr": "192.168.0.51" } }
     } }
   }
 }
diff --git a/acceptance/br_parent_acceptance/conf/topology.json b/acceptance/br_parent_acceptance/conf/topology.json
index f26a33cdd..8d419c09a 100644
--- a/acceptance/br_parent_acceptance/conf/topology.json
+++ b/acceptance/br_parent_acceptance/conf/topology.json
@@ -81,11 +81,6 @@
       }
     }
   },
-  "PathService": {
-    "psA": { "Addrs": {
-        "IPv4": { "Public": { "L4Port": 20005, "Addr": "192.168.0.51" } }
-    } }
-  },
   "BeaconService": {
     "bsA": { "Addrs": {
         "IPv4": { "Public": { "L4Port": 20006, "Addr": "192.168.0.61" } }
@@ -94,6 +89,17 @@
   "CertificateService": {
     "csA": { "Addrs": {
         "IPv4": { "Public": { "L4Port": 20007, "Addr": "192.168.0.71" } }
+    } },
+    "csB": { "Addrs": {
+        "IPv4": { "Public": { "L4Port": 20007, "Addr": "192.168.0.72" } }
+    } }
+  },
+  "PathService": {
+    "psA": { "Addrs": {
+        "IPv4": { "Public": { "L4Port": 20005, "Addr": "192.168.0.51" } }
+    } },
+    "psB": { "Addrs": {
+        "IPv4": { "Public": { "L4Port": 20055, "Addr": "192.168.0.51" } }
     } }
   }
 }
diff --git a/go/border/braccept/BUILD.bazel b/go/border/braccept/BUILD.bazel
index f43861ee0..3cf494af4 100644
--- a/go/border/braccept/BUILD.bazel
+++ b/go/border/braccept/BUILD.bazel
@@ -21,6 +21,7 @@ go_library(
         "revocation_tests.go",
         "send.go",
         "sleep.go",
+        "svc_tests.go",
     ],
     importpath = "github.com/scionproto/scion/go/border/braccept",
     visibility = ["//visibility:private"],
diff --git a/go/border/braccept/layers/addrhdr.go b/go/border/braccept/layers/addrhdr.go
index 494216032..ca22182b5 100644
--- a/go/border/braccept/layers/addrhdr.go
+++ b/go/border/braccept/layers/addrhdr.go
@@ -15,7 +15,9 @@
 package layers
 
 import (
+	"bytes"
 	"fmt"
+	"net"
 
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
@@ -129,3 +131,36 @@ func (a *AddrHdr) String() string {
 	return fmt.Sprintf("SrcIA: %s, SrcHost: %s, DstIA: %s, DstHost: %s",
 		a.SrcIA, a.SrcHost, a.DstIA, a.DstHost)
 }
+
+var _ addr.HostAddr = (HostBad)(nil)
+
+type HostBad common.RawBytes
+
+func (h HostBad) Size() int {
+	return len(h)
+}
+
+func (h HostBad) Type() addr.HostAddrType {
+	return addr.HostTypeNone
+}
+
+func (h HostBad) Pack() common.RawBytes {
+	return common.RawBytes(h)
+}
+
+func (h HostBad) IP() net.IP {
+	return nil
+}
+
+func (h HostBad) Copy() addr.HostAddr {
+	return HostBad(append(common.RawBytes(nil), h...))
+}
+
+func (h HostBad) Equal(o addr.HostAddr) bool {
+	hb, ok := o.(HostBad)
+	return ok && bytes.Equal(h, hb)
+}
+
+func (h HostBad) String() string {
+	return fmt.Sprintf("%s", common.RawBytes(h))
+}
diff --git a/go/border/braccept/layers/scmp.go b/go/border/braccept/layers/scmp.go
index dae5cfe33..8fdbe0874 100644
--- a/go/border/braccept/layers/scmp.go
+++ b/go/border/braccept/layers/scmp.go
@@ -108,6 +108,9 @@ func (s *SCMP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
 func (s *SCMP) String() string {
 	var str []string
 	str = append(str, fmt.Sprintf("%s", &s.Hdr))
+	if s.Pld == nil {
+		return strings.Join(str, " ")
+	}
 	if s.Meta != nil {
 		str = append(str, fmt.Sprintf("Meta={ %s }", s.Meta))
 	}
diff --git a/go/border/braccept/layers/scnpath.go b/go/border/braccept/layers/scnpath.go
index c9d00f3f6..24d18481d 100644
--- a/go/border/braccept/layers/scnpath.go
+++ b/go/border/braccept/layers/scnpath.go
@@ -27,6 +27,17 @@ type ScnPath struct {
 	raw  common.RawBytes
 }
 
+func (p *ScnPath) Clone() *ScnPath {
+	clone := &ScnPath{}
+	clone.raw = make(common.RawBytes, len(p.raw))
+	copy(clone.raw, p.raw)
+	clone.Segs = make([]*Segment, len(p.Segs))
+	for i := range p.Segs {
+		clone.Segs[i] = p.Segs[i].Clone()
+	}
+	return clone
+}
+
 func (p *ScnPath) Parse(b common.RawBytes) error {
 	if len(b) == 0 {
 		return nil
@@ -87,6 +98,18 @@ type Segment struct {
 	Hops []*spath.HopField
 }
 
+func (s *Segment) Clone() *Segment {
+	clone := &Segment{}
+	inf := *s.Inf
+	clone.Inf = &inf
+	clone.Hops = make([]*spath.HopField, len(s.Hops))
+	for i := range s.Hops {
+		hop := *s.Hops[i]
+		clone.Hops[i] = &hop
+	}
+	return clone
+}
+
 func (s *Segment) Parse(b common.RawBytes) (int, error) {
 	minSegLen := spath.InfoFieldLength + 2*spath.HopFieldLength
 	if minSegLen > len(b) {
diff --git a/go/border/braccept/parser/ctrl.go b/go/border/braccept/parser/ctrl.go
index 5b1acf06e..22a37cef4 100644
--- a/go/border/braccept/parser/ctrl.go
+++ b/go/border/braccept/parser/ctrl.go
@@ -151,6 +151,9 @@ func (i *IFStateInfoTaggedLayer) Update(lines []string) {
 		kvs := getKeyValueMap(kvStr)
 		i.RevInfo.updateFields(kvs)
 		i.SRevInfo = i.RevInfo.sign()
+	} else {
+		// If we are updating and the layer is not present, remove it.
+		i.SRevInfo = nil
 	}
 	infos := &path_mgmt.IFStateInfos{Infos: []*path_mgmt.IFStateInfo{&i.IFStateInfo}}
 	pmpld, err := path_mgmt.NewPld(infos, nil)
diff --git a/go/border/braccept/parser/extensions.go b/go/border/braccept/parser/extensions.go
index b7442021a..d2415089e 100644
--- a/go/border/braccept/parser/extensions.go
+++ b/go/border/braccept/parser/extensions.go
@@ -130,7 +130,7 @@ type hbh_ohp struct {
 
 func (ohp *hbh_ohp) updateFields(kvs propMap) {
 	if kvs != nil {
-		panic(fmt.Errorf("Unknown HBH_SCMP fields: %v", kvs))
+		panic(fmt.Errorf("Unknown HBH_OHP fields: %v", kvs))
 	}
 }
 
diff --git a/go/border/braccept/parser/helper.go b/go/border/braccept/parser/helper.go
index f1a92e5dd..5d06bd389 100644
--- a/go/border/braccept/parser/helper.go
+++ b/go/border/braccept/parser/helper.go
@@ -77,7 +77,7 @@ func HexToInt(s string) uint {
 func HexToBytes(s string) common.RawBytes {
 	b, err := hex.DecodeString(s)
 	if err != nil {
-		panic(fmt.Sprintf("HexToBytes error converting '%s'\n", b))
+		panic(fmt.Sprintf("HexToBytes error converting '%s'\n", s))
 	}
 	return b
 }
diff --git a/go/border/braccept/parser/scion.go b/go/border/braccept/parser/scion.go
index 6d07c4fcb..128f96d6c 100644
--- a/go/border/braccept/parser/scion.go
+++ b/go/border/braccept/parser/scion.go
@@ -37,15 +37,13 @@ type ScionTaggedLayer struct {
 	layers.Scion
 	tagged
 	options
-	// Tags is just a map of tag to SCION fields, use for GenerateMac or field update.
-	// This is slightly different than the packet layers where the order matters, hence the slice.
-	tags map[string]interface{}
+	tags scionTags
 }
 
 func ScionParser(lines []string) TaggedLayer {
 	// default Scion layer values
 	scn := &ScionTaggedLayer{}
-	scn.tags = make(map[string]interface{})
+	scn.tags = newScionTags()
 
 	//SerializeOptions
 	scn.opts.FixLengths = true
@@ -71,6 +69,20 @@ func (scn *ScionTaggedLayer) Layer() gopacket.Layer {
 
 func (scn *ScionTaggedLayer) Clone() TaggedLayer {
 	clone := *scn
+	// copy scion path
+	path := scn.Path.Clone()
+	clone.Path = *path
+	// update cloned tags to point to the cloned info/hop fields in the path
+	clone.tags = newScionTags()
+	for i := range scn.Path.Segs {
+		seg := scn.Path.Segs[i]
+		tag := scn.tags.getTag(seg.Inf)
+		clone.tags.add(tag, clone.Path.Segs[i].Inf)
+		for j := range seg.Hops {
+			tag := scn.tags.getTag(seg.Hops[j])
+			clone.tags.add(tag, clone.Path.Segs[i].Hops[j])
+		}
+	}
 	return &clone
 }
 
@@ -178,16 +190,17 @@ func (scn *ScionTaggedLayer) updateAddr(kvs propMap) {
 			var dst addr.HostAddr
 			dst = addr.HostSVCFromString(v)
 			if dst == addr.SvcNone {
+				// Try to parse IP address
 				dst = addr.HostFromIPStr(v)
 				if dst == nil {
-					panic(fmt.Errorf("invalid dst host address '%s'", v))
+					dst = layers.HostBad(HexToBytes(v))
 				}
 			}
 			scn.AddrHdr.DstHost = dst
 		case "Src":
 			src := addr.HostFromIPStr(v)
 			if src == nil {
-				panic(fmt.Errorf("invalid src host address '%s'", v))
+				src = layers.HostBad(HexToBytes(v))
 			}
 			scn.AddrHdr.SrcHost = src
 		default:
@@ -201,11 +214,11 @@ func (scn *ScionTaggedLayer) newIF(tag string) {
 	inf := &spath.InfoField{TsInt: shared.TsNow32}
 	seg.Inf = inf
 	scn.Path.Segs = append(scn.Path.Segs, seg)
-	scn.addTag(tag, inf)
+	scn.tags.add(tag, inf)
 }
 
 func (scn *ScionTaggedLayer) updateIF(tag string, kvs propMap) {
-	inf := scn.getTag(tag).(*spath.InfoField)
+	inf := scn.tags.get(tag).(*spath.InfoField)
 	if inf == nil {
 		panic(fmt.Errorf("Invalid IF tag '%s'\n", tag))
 	}
@@ -260,11 +273,11 @@ func (scn *ScionTaggedLayer) newHF(tag string) {
 	hf := &spath.HopField{}
 	hf.Mac = common.RawBytes{0xc0, 0xff, 0xee}
 	seg.Hops = append(seg.Hops, hf)
-	scn.addTag(tag, hf)
+	scn.tags.add(tag, hf)
 }
 
 func (scn *ScionTaggedLayer) updateHF(tag string, kvs propMap) {
-	hf := scn.getTag(tag).(*spath.HopField)
+	hf := scn.tags.get(tag).(*spath.HopField)
 	if hf == nil {
 		panic(fmt.Errorf("Invalid HF tag '%s'\n", tag))
 	}
@@ -314,12 +327,12 @@ func (scn *ScionTaggedLayer) GenerateMac(hMac hash.Hash, infTag, hfTag, hfMacTag
 	if hMac == nil {
 		panic(fmt.Errorf("GenerateMac: Invalid Mac %v", hMac))
 	}
-	inf := scn.getTag(infTag).(*spath.InfoField)
-	hf := scn.getTag(hfTag).(*spath.HopField)
+	inf := scn.tags.get(infTag).(*spath.InfoField)
+	hf := scn.tags.get(hfTag).(*spath.HopField)
 	var hfMac *spath.HopField
 	buf := make(common.RawBytes, spath.HopFieldLength)
 	if hfMacTag != "" {
-		hfMac = scn.getTag(hfMacTag).(*spath.HopField)
+		hfMac = scn.tags.get(hfMacTag).(*spath.HopField)
 		hfMac.Write(buf)
 	}
 	hMac.Reset()
@@ -328,19 +341,43 @@ func (scn *ScionTaggedLayer) GenerateMac(hMac hash.Hash, infTag, hfTag, hfMacTag
 	hf.Mac = hf.CalcMac(hMac, inf.TsInt, buf[1:])
 }
 
-func (scn *ScionTaggedLayer) addTag(tag string, v interface{}) {
-	if _, ok := scn.tags[tag]; ok {
+// Tags is just a map of tag to SCION fields, used for GenerateMac or field update.
+type scionTags map[string]interface{}
+
+func newScionTags() scionTags {
+	return make(map[string]interface{})
+}
+
+func (tags scionTags) add(tag string, v interface{}) {
+	if _, ok := tags[tag]; ok {
 		panic(fmt.Errorf("Duplicated tag '%s'", tag))
 	}
-	scn.tags[tag] = v
+	tags[tag] = v
 }
 
-func (scn *ScionTaggedLayer) getTag(tag string) interface{} {
+func (tags scionTags) get(tag string) interface{} {
 	if tag == "" {
 		panic(fmt.Errorf("Invalid empty tag"))
 	}
-	if _, ok := scn.tags[tag]; !ok {
+	v, ok := tags[tag]
+	if !ok {
 		panic(fmt.Errorf("Tag '%s' does not exists", tag))
 	}
-	return scn.tags[tag]
+	return v
+}
+
+func (tags scionTags) getTag(v interface{}) string {
+	matchedTags := []string{}
+	for tag, val := range tags {
+		if val == v {
+			matchedTags = append(matchedTags, tag)
+		}
+	}
+	if len(matchedTags) == 0 {
+		panic(fmt.Errorf("No tag for layer '%v'", v))
+	}
+	if len(matchedTags) > 1 {
+		panic(fmt.Errorf("Invalid! multiple tags found for layer '%v'", v))
+	}
+	return matchedTags[0]
 }


Test patch:
diff --git a/acceptance/br_multi_acceptance/test b/acceptance/br_multi_acceptance/test
index 5beed3607..433c1bd66 100755
--- a/acceptance/br_multi_acceptance/test
+++ b/acceptance/br_multi_acceptance/test
@@ -10,7 +10,7 @@ COMMAND="${1:?}"
 # This function is called from test_setup
 set_veths() {
     create_veth veth_int_host veth_int 192.168.0.11/24 f0:0d:ca:fe:00:01 \
-        192.168.0.12 192.168.0.13 192.168.0.14 192.168.0.51 192.168.0.61 192.168.0.71
+        192.168.0.12 192.168.0.13 192.168.0.14 192.168.0.51 192.168.0.61 192.168.0.71 192.168.0.72
     create_veth veth_121_host veth_121 192.168.12.2/31 f0:0d:ca:fe:00:12 192.168.12.3
     create_veth veth_131_host veth_131 192.168.13.2/31 f0:0d:ca:fe:00:13 192.168.13.3
     create_veth veth_141_host veth_141 192.168.14.2/31 f0:0d:ca:fe:00:14 192.168.14.3
diff --git a/acceptance/br_parent_acceptance/test b/acceptance/br_parent_acceptance/test
index 50fa21fc5..fa5c04f1f 100755
--- a/acceptance/br_parent_acceptance/test
+++ b/acceptance/br_parent_acceptance/test
@@ -10,7 +10,7 @@ COMMAND="${1:?}"
 # This function is called from test_setup
 set_veths() {
     create_veth veth_int_host veth_int 192.168.0.11/24 f0:0d:ca:fe:00:01 \
-        192.168.0.12 192.168.0.13 192.168.0.14 192.168.0.51 192.168.0.61 192.168.0.71
+        192.168.0.12 192.168.0.13 192.168.0.14 192.168.0.51 192.168.0.61 192.168.0.71 192.168.0.72
     create_veth veth_131_host veth_131 192.168.13.2/31 f0:0d:ca:fe:00:13 192.168.13.3
 }
 
diff --git a/go/border/braccept/br_tests.go b/go/border/braccept/br_tests.go
index f70010e83..729a40d29 100644
--- a/go/border/braccept/br_tests.go
+++ b/go/border/braccept/br_tests.go
@@ -72,5 +72,22 @@ func br_parent() int {
 	failures += parent_to_internal_child()
 	failures += internal_child_to_parent()
 
+	// XXX(sgmonroy) the following tests are only run for this specific BR configuration
+	// with a single parent interface. In the current implementation, the behavior would be
+	// the same regardless of the link type that the packet was recevied on.
+	failures += svc_anycast_parent_to_internal_host()
+	failures += svc_multicast_parent_to_internal_host()
+	failures += svc_multicast_same_host_parent_to_internal_host()
+
+	failures += revocation_owned_parent()
+	failures += revocation_not_owned_child_link()
+
+	failures += ohp_parent_to_internal_bs()
+	failures += ohp_udp_parent_to_internal_bs()
+	failures += ohp_udp_internal_bs_to_parent()
+	failures += ohp_internal_bs_to_parent()
+
+	failures += parent_scmp_routing_bad_host()
+
 	return failures
 }
diff --git a/go/border/braccept/parent_tests.go b/go/border/braccept/parent_tests.go
index 7abd76f49..e44a12903 100644
--- a/go/border/braccept/parent_tests.go
+++ b/go/border/braccept/parent_tests.go
@@ -14,6 +14,10 @@
 
 package main
 
+import (
+	"fmt"
+)
+
 func parent_to_internal_host() int {
 	pkt0 := AllocatePacket()
 	pkt0.ParsePacket(`
@@ -141,3 +145,181 @@ func internal_child_to_parent() int {
 
 	return ExpectedPackets("internal/child to parent", defaultTimeout, pkt1)
 }
+
+func ohp_parent_to_internal_bs() int {
+	pkt0 := AllocatePacket()
+	pkt0.ParsePacket(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:13 EthernetType=IPv4
+		IP4: Src=192.168.13.3 Dst=192.168.13.2 NextHdr=UDP Flags=DF
+		UDP: Src=40000 Dst=50000
+		SCION: NextHdr=HBH CurrInfoF=4 CurrHopF=6 SrcType=IPv4 DstType=SVC
+			ADDR: SrcIA=1-ff00:0:3 Src=172.16.3.1 DstIA=1-ff00:0:1 Dst=BS
+			IF_1: ISD=1 Hops=2 Flags=ConsDir
+				HF_1: ConsIngress=0 ConsEgress=311
+				HF_2: ConsIngress=0 ConsEgress=0 Mac=000000
+		HBH: NextHdr=HBH Type=OHP
+			HBH.OHP:
+	`)
+	// XXX HBH and None are the same NextHdr value
+	pkt0.SetDev("veth_131")
+	pkt0.SetChecksum("UDP", "IP4")
+
+	pkt1 := pkt0.CloneAndUpdate(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:00:01 DstMAC=f0:0d:ca:fe:be:ef
+		IP4: Src=192.168.0.11 Dst=192.168.0.61 Checksum=0
+		UDP: Src=30001 Dst=30041
+		SCION:
+			HF_2: ConsIngress=131 ConsEgress=0 ExpTime=63
+	`)
+	// XXX Go BR sets ExpTime to default, which is currently 63
+	pkt1.SetDev("veth_int")
+	pkt1.SetChecksum("UDP", "IP4")
+	pkt1.GenerateMac("SCION", "IF_1", "HF_2", "HF_1")
+
+	SendPackets(pkt0)
+
+	return ExpectedPackets("one-hop-path parent to internal/bs", defaultTimeout, pkt1)
+}
+
+func ohp_udp_parent_to_internal_bs() int {
+	pkt0 := AllocatePacket()
+	pkt0.ParsePacket(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:13 EthernetType=IPv4
+		IP4: Src=192.168.13.3 Dst=192.168.13.2 NextHdr=UDP Flags=DF
+		UDP: Src=40000 Dst=50000
+		SCION: NextHdr=HBH CurrInfoF=4 CurrHopF=6 SrcType=IPv4 DstType=SVC
+			ADDR: SrcIA=1-ff00:0:3 Src=172.16.3.1 DstIA=1-ff00:0:1 Dst=BS
+			IF_1: ISD=1 Hops=2 Flags=ConsDir
+				HF_1: ConsIngress=0 ConsEgress=311
+				HF_2: ConsIngress=0 ConsEgress=0 Mac=000000
+		HBH: NextHdr=UDP Type=OHP
+			HBH.OHP:
+		UDP_1: Src=40111 Dst=40222
+	`)
+	pkt0.SetDev("veth_131")
+	pkt0.SetChecksum("UDP", "IP4")
+	pkt0.SetChecksum("UDP_1", "SCION")
+
+	pkt1 := pkt0.CloneAndUpdate(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:00:01 DstMAC=f0:0d:ca:fe:be:ef
+		IP4: Src=192.168.0.11 Dst=192.168.0.61 Checksum=0
+		UDP: Src=30001 Dst=30041
+		SCION:
+			HF_2: ConsIngress=131 ConsEgress=0 ExpTime=63
+	`)
+	// XXX Go BR sets ExpTime to default, which is currently 63
+	pkt1.SetDev("veth_int")
+	pkt1.SetChecksum("UDP", "IP4")
+	pkt1.GenerateMac("SCION", "IF_1", "HF_2", "HF_1")
+
+	SendPackets(pkt0)
+
+	return ExpectedPackets("one-hop-path udp parent to internal/bs", defaultTimeout, pkt1)
+}
+
+func ohp_udp_internal_bs_to_parent() int {
+	pkt0 := AllocatePacket()
+	pkt0.ParsePacket(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:01 EthernetType=IPv4
+		IP4: Src=192.168.0.51 Dst=192.168.0.11 NextHdr=UDP Flags=DF
+		UDP: Src=30041 Dst=30001
+		SCION: NextHdr=HBH CurrInfoF=4 CurrHopF=5 SrcType=IPv4 DstType=SVC
+			ADDR: SrcIA=1-ff00:0:1 Src=192.168.0.61 DstIA=1-ff00:0:3 Dst=BS
+			IF_1: ISD=1 Hops=2
+				HF_1: ConsIngress=131 ConsEgress=0
+				HF_2: ConsIngress=0   ConsEgress=0 Mac=000000
+		HBH: NextHdr=HBH Type=OHP
+			HBH.OHP:
+	`)
+	pkt0.SetDev("veth_int")
+	pkt0.SetChecksum("UDP", "IP4")
+	pkt0.GenerateMac("SCION", "IF_1", "HF_1", "HF_2")
+
+	pkt1 := pkt0.CloneAndUpdate(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:00:13 DstMAC=f0:0d:ca:fe:be:ef
+		IP4: Src=192.168.13.2 Dst=192.168.13.3 Checksum=0
+		UDP: Src=50000 Dst=40000
+		SCION: CurrHopF=6
+	`)
+	pkt1.SetDev("veth_131")
+	pkt1.SetChecksum("UDP", "IP4")
+
+	SendPackets(pkt0)
+
+	return ExpectedPackets("one-hop-path up segment internal/bs to parent", defaultTimeout, pkt1)
+}
+
+func ohp_internal_bs_to_parent() int {
+	pkt0 := AllocatePacket()
+	pkt0.ParsePacket(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:01 EthernetType=IPv4
+		IP4: Src=192.168.0.51 Dst=192.168.0.11 NextHdr=UDP Flags=DF
+		UDP: Src=30041 Dst=30001
+		SCION: NextHdr=HBH CurrInfoF=4 CurrHopF=5 SrcType=IPv4 DstType=SVC
+			ADDR: SrcIA=1-ff00:0:1 Src=192.168.0.61 DstIA=1-ff00:0:3 Dst=BS
+			IF_1: ISD=1 Hops=2 Flags=ConsDir
+				HF_1: ConsIngress=0 ConsEgress=131
+				HF_2: ConsIngress=0 ConsEgress=0 Mac=000000
+		HBH: NextHdr=HBH Type=OHP
+			HBH.OHP:
+	`)
+	pkt0.SetDev("veth_int")
+	pkt0.SetChecksum("UDP", "IP4")
+	pkt0.GenerateMac("SCION", "IF_1", "HF_1", "")
+
+	pkt1 := pkt0.CloneAndUpdate(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:00:13 DstMAC=f0:0d:ca:fe:be:ef
+		IP4: Src=192.168.13.2 Dst=192.168.13.3 Checksum=0
+		UDP: Src=50000 Dst=40000
+		SCION: CurrHopF=6
+	`)
+	pkt1.SetDev("veth_131")
+	pkt1.SetChecksum("UDP", "IP4")
+
+	SendPackets(pkt0)
+
+	return ExpectedPackets("one-hop-path internal/bs to parent", defaultTimeout, pkt1)
+}
+
+func parent_scmp_routing_bad_host() int {
+	pkt0 := AllocatePacket()
+	pkt0.ParsePacket(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:13 EthernetType=IPv4
+		IP4: Src=192.168.13.3 Dst=192.168.13.2 NextHdr=UDP Flags=DF
+		UDP: Src=40000 Dst=50000
+		SCION: NextHdr=UDP CurrInfoF=4 CurrHopF=6 SrcType=IPv4 DstType=SVC
+			ADDR: SrcIA=1-ff00:0:3 Src=172.16.3.1 DstIA=1-ff00:0:1 Dst=0009
+			IF_1: ISD=1 Hops=2 Flags=ConsDir
+				HF_1: ConsIngress=0   ConsEgress=311
+				HF_2: ConsIngress=131 ConsEgress=0
+		UDP_1: Src=40111 Dst=40222
+	`)
+	pkt0.SetDev("veth_131")
+	pkt0.SetChecksum("UDP", "IP4")
+	pkt0.SetChecksum("UDP_1", "SCION")
+	pkt0.GenerateMac("SCION", "IF_1", "HF_2", "HF_1")
+
+	pkt1 := AllocatePacket()
+	pkt1.ParsePacket(fmt.Sprintf(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:00:13 DstMAC=f0:0d:ca:fe:be:ef EthernetType=IPv4
+		IP4: Src=192.168.13.2 Dst=192.168.13.3 NextHdr=UDP Flags=DF Checksum=0
+		UDP: Src=50000 Dst=40000 Checksum=0
+		SCION: NextHdr=HBH CurrInfoF=4 CurrHopF=6 SrcType=IPv4 DstType=IPv4
+			ADDR: SrcIA=1-ff00:0:1 Src=192.168.0.11 DstIA=1-ff00:0:3 Dst=172.16.3.1
+			IF_1: ISD=1 Hops=2
+				HF_1: ConsIngress=131 ConsEgress=0
+				HF_2: ConsIngress=0   ConsEgress=311
+		HBH: NextHdr=SCMP Type=SCMP
+			HBH.SCMP: Flags=Error,HBH
+		SCMP: Class=ROUTING Type=BAD_HOST Checksum=0
+			QUOTED: RawPkt=%s
+	`, pkt0.Serialize()))
+	pkt1.SetDev("veth_131")
+	pkt1.SetChecksum("UDP", "IP4")
+	pkt1.SetChecksum("SCMP", "SCION")
+	pkt1.GenerateMac("SCION", "IF_1", "HF_1", "HF_2")
+
+	SendPackets(pkt0)
+
+	return ExpectedPackets("parent scmp routing bad host", defaultTimeout, pkt1)
+}
diff --git a/go/border/braccept/revocation_tests.go b/go/border/braccept/revocation_tests.go
index 8f74d4cff..17ca87aaf 100644
--- a/go/border/braccept/revocation_tests.go
+++ b/go/border/braccept/revocation_tests.go
@@ -275,7 +275,7 @@ func revocation_owned_peer() int {
 	pkt0.ParsePacket(`
 		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:01 EthernetType=IPv4
 		IP4: Src=192.168.0.71 Dst=192.168.0.11 NextHdr=UDP Flags=DF
-		UDP: Src=30031 Dst=30001
+		UDP: Src=30041 Dst=30001
 		SCION: NextHdr=UDP CurrInfoF=4 CurrHopF=6 SrcType=IPv4 DstType=IPv4
 			ADDR: SrcIA=1-ff00:0:1 Src=192.168.0.71 DstIA=1-ff00:0:2 Dst=172.16.2.1
 			IF_1: ISD=1 Hops=3 Flags=Peer,Shortcut
@@ -299,7 +299,7 @@ func revocation_owned_peer() int {
 	pkt1.ParsePacket(fmt.Sprintf(`
 		Ethernet: SrcMAC=f0:0d:ca:fe:00:01 DstMAC=f0:0d:ca:fe:be:ef EthernetType=IPv4
 		IP4: Src=192.168.0.11 Dst=192.168.0.71 NextHdr=UDP Flags=DF Checksum=0
-		UDP: Src=30001 Dst=30031 Checksum=0
+		UDP: Src=30001 Dst=30041 Checksum=0
 		SCION: NextHdr=HBH CurrInfoF=8 CurrHopF=10 SrcType=IPv4 DstType=IPv4
 			ADDR: SrcIA=1-ff00:0:1 Src=192.168.0.11 DstIA=1-ff00:0:1 Dst=192.168.0.71
 			IF_1: ISD=2 Hops=3 Flags=Peer,Shortcut
@@ -351,6 +351,157 @@ func revocation_owned_peer() int {
 // revocation reply:
 //   interface not owned
 //   owned child interface
-//   owned parent interface
-//   owned peer interface
 //   owned core interface
+//   overlapping revocations
+
+func revocation_owned_parent() int {
+
+	shared.UpdateNow()
+
+	ifStateDown := AllocatePacket()
+	ifStateDown.ParsePacket(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:01 EthernetType=IPv4
+		IP4: Src=192.168.0.61 Dst=192.168.0.11 NextHdr=UDP Flags=DF
+		UDP: Src=20006 Dst=30041
+		SCION: NextHdr=UDP SrcType=IPv4 DstType=IPv4
+			ADDR: SrcIA=1-ff00:0:1 Src=192.168.0.61 DstIA=1-ff00:0:1 Dst=192.168.0.101
+		UDP_1: Src=20006 Dst=20001
+		IFStateInfo: IfID=131 Active=false
+			SignedRevInfo: IfID=131 IA=1-ff00:0:1 Link=parent TS=now TTL=10
+	`)
+	ifStateDown.SetDev("veth_int")
+	ifStateDown.SetChecksum("UDP", "IP4")
+	ifStateDown.SetChecksum("UDP_1", "SCION")
+
+	pkt0 := AllocatePacket()
+	pkt0.ParsePacket(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:01 EthernetType=IPv4
+		IP4: Src=192.168.0.71 Dst=192.168.0.11 NextHdr=UDP Flags=DF
+		UDP: Src=30041 Dst=30001
+		SCION: NextHdr=UDP CurrInfoF=4 CurrHopF=5 SrcType=IPv4 DstType=IPv4
+			ADDR: SrcIA=1-ff00:0:1 Src=192.168.0.71 DstIA=1-ff00:0:3 Dst=172.16.3.1
+			IF_1: ISD=1 Hops=2
+				HF_1: ConsIngress=131 ConsEgress=0
+				HF_2: ConsIngress=0   ConsEgress=311
+		UDP_1: Src=40111 Dst=40222
+	`)
+	pkt0.SetDev("veth_int")
+	pkt0.SetChecksum("UDP", "IP4")
+	pkt0.SetChecksum("UDP_1", "SCION")
+	pkt0.GenerateMac("SCION", "IF_1", "HF_1", "HF_2")
+
+	// SCMP revocation reply (reversed SCION header) from the BR to the source of the packet.
+	pkt1 := AllocatePacket()
+	pkt1.ParsePacket(fmt.Sprintf(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:00:01 DstMAC=f0:0d:ca:fe:be:ef EthernetType=IPv4
+		IP4: Src=192.168.0.11 Dst=192.168.0.71 NextHdr=UDP Flags=DF Checksum=0
+		UDP: Src=30001 Dst=30041 Checksum=0
+		SCION: NextHdr=HBH CurrInfoF=4 CurrHopF=6 SrcType=IPv4 DstType=IPv4
+			ADDR: SrcIA=1-ff00:0:1 Src=192.168.0.11 DstIA=1-ff00:0:1 Dst=192.168.0.71
+			IF_1: ISD=1 Hops=2 Flags=ConsDir
+				HF_1: ConsIngress=0   ConsEgress=311
+				HF_2: ConsIngress=131 ConsEgress=0
+		HBH: NextHdr=SCMP Type=SCMP
+			HBH.SCMP: Flags=Error,HBH
+		SCMP: Class=PATH Type=REVOKED_IF Checksum=0
+			InfoRevocation: InfoF=4 HopF=5 IfID=131 Ingress=false
+				SignedRevInfo: IfID=131 IA=1-ff00:0:1 Link=parent TS=now TTL=10
+			QUOTED: RawPkt=%s
+	`, pkt0.Serialize()))
+	pkt1.SetDev("veth_int")
+	pkt1.SetChecksum("SCMP", "SCION")
+	pkt1.GenerateMac("SCION", "IF_1", "HF_2", "HF_1")
+
+	// Send/Expect packets
+	SendPackets(ifStateDown)
+	Sleep("250ms")
+
+	SendPackets(pkt0)
+
+	ret := ExpectedPackets("revoked parent interface", defaultTimeout, pkt1)
+
+	ifStateUp := ifStateDown.CloneAndUpdate(`
+		IFStateInfo: Active=true
+	`)
+	SendPackets(ifStateUp)
+	Sleep("250ms")
+
+	return ret
+}
+
+func revocation_not_owned_child_link() int {
+
+	shared.UpdateNow()
+
+	ifStateDown := AllocatePacket()
+	ifStateDown.ParsePacket(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:01 EthernetType=IPv4
+		IP4: Src=192.168.0.61 Dst=192.168.0.11 NextHdr=UDP Flags=DF
+		UDP: Src=20006 Dst=30041
+		SCION: NextHdr=UDP SrcType=IPv4 DstType=IPv4
+			ADDR: SrcIA=1-ff00:0:1 Src=192.168.0.61 DstIA=1-ff00:0:1 Dst=192.168.0.101
+		UDP_1: Src=20006 Dst=20001
+		IFStateInfo: IfID=181 Active=false
+			SignedRevInfo: IfID=181 IA=1-ff00:0:1 Link=child TS=now TTL=10
+	`)
+	ifStateDown.SetDev("veth_int")
+	ifStateDown.SetChecksum("UDP", "IP4")
+	ifStateDown.SetChecksum("UDP_1", "SCION")
+
+	pkt0 := AllocatePacket()
+	pkt0.ParsePacket(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:13 EthernetType=IPv4
+		IP4: Src=192.168.13.3 Dst=192.168.13.2 NextHdr=UDP Flags=DF
+		UDP: Src=40000 Dst=50000
+		SCION: NextHdr=UDP CurrInfoF=4 CurrHopF=6 SrcType=IPv4 DstType=IPv4
+			ADDR: SrcIA=1-ff00:0:3 Src=172.16.3.1 DstIA=1-ff00:0:8 Dst=172.16.8.1
+			IF_1: ISD=1 Hops=3 Flags=ConsDir
+				HF_1: ConsIngress=0   ConsEgress=311
+				HF_2: ConsIngress=131 ConsEgress=181
+				HF_3: ConsIngress=811 ConsEgress=0
+		UDP_1: Src=40111 Dst=40222
+	`)
+	pkt0.SetDev("veth_131")
+	pkt0.SetChecksum("UDP", "IP4")
+	pkt0.SetChecksum("UDP_1", "SCION")
+	pkt0.GenerateMac("SCION", "IF_1", "HF_2", "HF_1")
+
+	// SCMP revocation reply (reversed SCION header) from the BR to the source of the packet.
+	pkt1 := AllocatePacket()
+	pkt1.ParsePacket(fmt.Sprintf(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:00:13 DstMAC=f0:0d:ca:fe:be:ef EthernetType=IPv4
+		IP4: Src=192.168.13.2 Dst=192.168.13.3 NextHdr=UDP Flags=DF Checksum=0
+		UDP: Src=50000 Dst=40000 Checksum=0
+		SCION: NextHdr=HBH CurrInfoF=4 CurrHopF=7 SrcType=IPv4 DstType=IPv4
+			ADDR: SrcIA=1-ff00:0:1 Src=192.168.0.11 DstIA=1-ff00:0:3 Dst=172.16.3.1
+			IF_1: ISD=1 Hops=3
+				HF_1: ConsIngress=811 ConsEgress=0
+				HF_2: ConsIngress=131 ConsEgress=181
+				HF_3: ConsIngress=0   ConsEgress=311
+		HBH: NextHdr=SCMP Type=SCMP
+			HBH.SCMP: Flags=Error,HBH
+		SCMP: Class=PATH Type=REVOKED_IF Checksum=0
+			InfoRevocation: InfoF=4 HopF=6 IfID=181 Ingress=true
+				SignedRevInfo: IfID=181 IA=1-ff00:0:1 Link=child TS=now TTL=10
+			QUOTED: RawPkt=%s
+	`, pkt0.Serialize()))
+	pkt1.SetDev("veth_131")
+	pkt1.SetChecksum("SCMP", "SCION")
+	pkt1.GenerateMac("SCION", "IF_1", "HF_2", "HF_3")
+
+	// Send/Expect packets
+	SendPackets(ifStateDown)
+	Sleep("250ms")
+
+	SendPackets(pkt0)
+
+	ret := ExpectedPackets("revoked child (not owned) interface", "500ms", pkt1)
+
+	ifStateUp := ifStateDown.CloneAndUpdate(`
+		IFStateInfo: Active=true
+	`)
+	SendPackets(ifStateUp)
+	Sleep("250ms")
+
+	return ret
+}
diff --git a/go/border/braccept/svc_tests.go b/go/border/braccept/svc_tests.go
new file mode 100644
index 000000000..6f87a516e
--- /dev/null
+++ b/go/border/braccept/svc_tests.go
@@ -0,0 +1,115 @@
+// Copyright 2019 ETH Zurich
+//
+// 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 main
+
+func svc_anycast_parent_to_internal_host() int {
+	pkt0 := AllocatePacket()
+	pkt0.ParsePacket(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:13 EthernetType=IPv4
+		IP4: Src=192.168.13.3 Dst=192.168.13.2 NextHdr=UDP Flags=DF
+		UDP: Src=40000 Dst=50000
+		SCION: NextHdr=UDP CurrInfoF=4 CurrHopF=6 SrcType=IPv4 DstType=SVC
+			ADDR: SrcIA=1-ff00:0:3 Src=172.16.3.1 DstIA=1-ff00:0:1 Dst=BS
+			IF_1: ISD=1 Hops=2 Flags=ConsDir
+				HF_1: ConsIngress=0   ConsEgress=311
+				HF_2: ConsIngress=131 ConsEgress=0
+		UDP_1: Src=40111 Dst=40222
+	`)
+	pkt0.SetDev("veth_131")
+	pkt0.SetChecksum("UDP", "IP4")
+	pkt0.SetChecksum("UDP_1", "SCION")
+	pkt0.GenerateMac("SCION", "IF_1", "HF_2", "HF_1")
+
+	pkt1 := pkt0.CloneAndUpdate(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:00:01 DstMAC=f0:0d:ca:fe:be:ef
+		IP4: Src=192.168.0.11 Dst=192.168.0.61 Checksum=0
+		UDP: Src=30001 Dst=30041
+	`)
+	pkt1.SetDev("veth_int")
+	pkt1.SetChecksum("UDP", "IP4")
+
+	SendPackets(pkt0)
+
+	return ExpectedPackets("SVC anycast parent to internal/host", defaultTimeout, pkt1)
+}
+
+func svc_multicast_parent_to_internal_host() int {
+	pkt0 := AllocatePacket()
+	pkt0.ParsePacket(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:13 EthernetType=IPv4
+		IP4: Src=192.168.13.3 Dst=192.168.13.2 NextHdr=UDP Flags=DF
+		UDP: Src=40000 Dst=50000
+		SCION: NextHdr=UDP CurrInfoF=4 CurrHopF=6 SrcType=IPv4 DstType=SVC
+			ADDR: SrcIA=1-ff00:0:3 Src=172.16.3.1 DstIA=1-ff00:0:1 Dst=CS_M
+			IF_1: ISD=1 Hops=2 Flags=ConsDir
+				HF_1: ConsIngress=0   ConsEgress=311
+				HF_2: ConsIngress=131 ConsEgress=0
+		UDP_1: Src=40111 Dst=40222
+	`)
+	pkt0.SetDev("veth_131")
+	pkt0.SetChecksum("UDP", "IP4")
+	pkt0.SetChecksum("UDP_1", "SCION")
+	pkt0.GenerateMac("SCION", "IF_1", "HF_2", "HF_1")
+
+	pkt1 := pkt0.CloneAndUpdate(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:00:01 DstMAC=f0:0d:ca:fe:be:ef
+		IP4: Src=192.168.0.11 Dst=192.168.0.71 Checksum=0
+		UDP: Src=30001 Dst=30041
+	`)
+	pkt1.SetDev("veth_int")
+	pkt1.SetChecksum("UDP", "IP4")
+
+	pkt2 := pkt1.CloneAndUpdate(`
+		IP4: Src=192.168.0.11 Dst=192.168.0.72 Checksum=0
+	`)
+	pkt2.SetDev("veth_int")
+	pkt2.SetChecksum("UDP", "IP4")
+
+	SendPackets(pkt0)
+
+	return ExpectedPackets("SVC multicast parent to internal/host", defaultTimeout, pkt1, pkt2)
+}
+
+func svc_multicast_same_host_parent_to_internal_host() int {
+	pkt0 := AllocatePacket()
+	pkt0.ParsePacket(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:be:ef DstMAC=f0:0d:ca:fe:00:13 EthernetType=IPv4
+		IP4: Src=192.168.13.3 Dst=192.168.13.2 NextHdr=UDP Flags=DF
+		UDP: Src=40000 Dst=50000
+		SCION: NextHdr=UDP CurrInfoF=4 CurrHopF=6 SrcType=IPv4 DstType=SVC
+			ADDR: SrcIA=1-ff00:0:3 Src=172.16.3.1 DstIA=1-ff00:0:1 Dst=PS_M
+			IF_1: ISD=1 Hops=2 Flags=ConsDir
+				HF_1: ConsIngress=0   ConsEgress=311
+				HF_2: ConsIngress=131 ConsEgress=0
+		UDP_1: Src=40111 Dst=40222
+	`)
+	pkt0.SetDev("veth_131")
+	pkt0.SetChecksum("UDP", "IP4")
+	pkt0.SetChecksum("UDP_1", "SCION")
+	pkt0.GenerateMac("SCION", "IF_1", "HF_2", "HF_1")
+
+	pkt1 := pkt0.CloneAndUpdate(`
+		Ethernet: SrcMAC=f0:0d:ca:fe:00:01 DstMAC=f0:0d:ca:fe:be:ef
+		IP4: Src=192.168.0.11 Dst=192.168.0.51 Checksum=0
+		UDP: Src=30001 Dst=30041
+	`)
+	pkt1.SetDev("veth_int")
+	pkt1.SetChecksum("UDP", "IP4")
+
+	SendPackets(pkt0)
+
+	return ExpectedPackets("SVC multicast (same host) parent to internal/host",
+		defaultTimeout, pkt1)
+}