Explorar o código

Start of end to end testing with a good handshake between two nodes (#425)

Nathan Brown %!s(int64=4) %!d(string=hai) anos
pai
achega
830d6d4639
Modificáronse 14 ficheiros con 643 adicións e 2 borrados
  1. 3 2
      Makefile
  2. 92 0
      control_tester.go
  3. 60 0
      e2e/handshakes_test.go
  4. 252 0
      e2e/helpers_test.go
  5. 1 0
      go.mod
  6. 7 0
      go.sum
  7. 2 0
      tun_android.go
  8. 1 0
      tun_darwin.go
  9. 2 0
      tun_freebsd.go
  10. 1 0
      tun_ios.go
  11. 1 0
      tun_linux.go
  12. 2 0
      tun_linux_test.go
  13. 103 0
      tun_tester.go
  14. 116 0
      udp_tester.go

+ 3 - 2
Makefile

@@ -32,7 +32,8 @@ ALL = $(ALL_LINUX) \
 	freebsd-amd64 \
 	windows-amd64
 
-
+e2e:
+	go test -v -tags=e2e_testing ./e2e
 
 all: $(ALL:%=build/%/nebula) $(ALL:%=build/%/nebula-cert)
 
@@ -137,5 +138,5 @@ smoke-docker-race: BUILD_ARGS = -race
 smoke-docker-race: smoke-docker
 
 .FORCE:
-.PHONY: test test-cov-html bench bench-cpu bench-cpu-long bin proto release service smoke-docker smoke-docker-race
+.PHONY: e2e test test-cov-html bench bench-cpu bench-cpu-long bin proto release service smoke-docker smoke-docker-race
 .DEFAULT_GOAL := bin

+ 92 - 0
control_tester.go

@@ -0,0 +1,92 @@
+// +build e2e_testing
+
+package nebula
+
+import (
+	"net"
+
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+)
+
+// WaitForTypeByIndex will pipe all messages from this control device into the pipeTo control device
+// returning after a message matching the criteria has been piped
+func (c *Control) WaitForType(msgType NebulaMessageType, subType NebulaMessageSubType, pipeTo *Control) {
+	h := &Header{}
+	for {
+		p := c.f.outside.Get(true)
+		if err := h.Parse(p.Data); err != nil {
+			panic(err)
+		}
+		pipeTo.InjectUDPPacket(p)
+		if h.Type == msgType && h.Subtype == subType {
+			return
+		}
+	}
+}
+
+// WaitForTypeByIndex is similar to WaitForType except it adds an index check
+// Useful if you have many nodes communicating and want to wait to find a specific nodes packet
+func (c *Control) WaitForTypeByIndex(toIndex uint32, msgType NebulaMessageType, subType NebulaMessageSubType, pipeTo *Control) {
+	h := &Header{}
+	for {
+		p := c.f.outside.Get(true)
+		if err := h.Parse(p.Data); err != nil {
+			panic(err)
+		}
+		pipeTo.InjectUDPPacket(p)
+		if h.RemoteIndex == toIndex && h.Type == msgType && h.Subtype == subType {
+			return
+		}
+	}
+}
+
+// InjectLightHouseAddr will push toAddr into the local lighthouse cache for the vpnIp
+// This is necessary if you did not configure static hosts or are not running a lighthouse
+func (c *Control) InjectLightHouseAddr(vpnIp net.IP, toAddr *net.UDPAddr) {
+	c.f.lightHouse.AddRemote(ip2int(vpnIp), &udpAddr{IP: toAddr.IP, Port: uint16(toAddr.Port)}, false)
+}
+
+// GetFromTun will pull a packet off the tun side of nebula
+func (c *Control) GetFromTun(block bool) []byte {
+	return c.f.inside.(*Tun).Get(block)
+}
+
+// GetFromUDP will pull a udp packet off the udp side of nebula
+func (c *Control) GetFromUDP(block bool) *UdpPacket {
+	return c.f.outside.Get(block)
+}
+
+// InjectUDPPacket will inject a packet into the udp side of nebula
+func (c *Control) InjectUDPPacket(p *UdpPacket) {
+	c.f.outside.Send(p)
+}
+
+// InjectTunUDPPacket puts a udp packet on the tun interface. Using UDP here because it's a simpler protocol
+func (c *Control) InjectTunUDPPacket(toIp net.IP, toPort uint16, fromPort uint16, data []byte) {
+	ip := layers.IPv4{
+		Version:  4,
+		TTL:      64,
+		Protocol: layers.IPProtocolUDP,
+		SrcIP:    c.f.inside.CidrNet().IP,
+		DstIP:    toIp,
+	}
+
+	udp := layers.UDP{
+		SrcPort: layers.UDPPort(fromPort),
+		DstPort: layers.UDPPort(toPort),
+	}
+	udp.SetNetworkLayerForChecksum(&ip)
+
+	buffer := gopacket.NewSerializeBuffer()
+	opt := gopacket.SerializeOptions{
+		ComputeChecksums: true,
+		FixLengths:       true,
+	}
+	err := gopacket.SerializeLayers(buffer, opt, &ip, &udp, gopacket.Payload(data))
+	if err != nil {
+		panic(err)
+	}
+
+	c.f.inside.(*Tun).Send(buffer.Bytes())
+}

+ 60 - 0
e2e/handshakes_test.go

@@ -0,0 +1,60 @@
+// +build e2e_testing
+
+package e2e
+
+import (
+	"net"
+	"testing"
+	"time"
+)
+
+func TestGoodHandshake(t *testing.T) {
+	ca, _, caKey, _ := newTestCaCert(time.Now(), time.Now().Add(10*time.Minute), []*net.IPNet{}, []*net.IPNet{}, []string{})
+	defMask := net.IPMask{0, 0, 0, 0}
+
+	myUdpAddr := &net.UDPAddr{IP: net.IP{10, 0, 0, 1}, Port: 4242}
+	myVpnIpNet := &net.IPNet{IP: net.IP{10, 128, 0, 1}, Mask: defMask}
+	myControl := newSimpleServer(ca, caKey, "me", myUdpAddr, myVpnIpNet)
+
+	theirUdpAddr := &net.UDPAddr{IP: net.IP{10, 0, 0, 2}, Port: 4242}
+	theirVpnIpNet := &net.IPNet{IP: net.IP{10, 128, 0, 2}, Mask: defMask}
+	theirControl := newSimpleServer(ca, caKey, "them", theirUdpAddr, theirVpnIpNet)
+
+	// Put their info in our lighthouse
+	myControl.InjectLightHouseAddr(theirVpnIpNet.IP, theirUdpAddr)
+
+	// Start the servers
+	myControl.Start()
+	theirControl.Start()
+
+	// Send a udp packet through to begin standing up the tunnel, this should come out the other side
+	myControl.InjectTunUDPPacket(theirVpnIpNet.IP, 80, 80, []byte("Hi from me"))
+
+	// Have them consume my stage 0 packet. They have a tunnel now
+	theirControl.InjectUDPPacket(myControl.GetFromUDP(true))
+
+	// Have me consume their stage 1 packet. I have a tunnel now
+	myControl.InjectUDPPacket(theirControl.GetFromUDP(true))
+
+	// Wait until we see my cached packet come through
+	myControl.WaitForType(1, 0, theirControl)
+
+	// Make sure our host infos are correct
+	assertHostInfoPair(t, myUdpAddr, theirUdpAddr, myVpnIpNet.IP, theirVpnIpNet.IP, myControl, theirControl)
+
+	// Get that cached packet and make sure it looks right
+	myCachedPacket := theirControl.GetFromTun(true)
+	assertUdpPacket(t, []byte("Hi from me"), myCachedPacket, myVpnIpNet.IP, theirVpnIpNet.IP, 80, 80)
+
+	// Send a packet from them to me
+	theirControl.InjectTunUDPPacket(myVpnIpNet.IP, 80, 80, []byte("Hi from them"))
+	myControl.InjectUDPPacket(theirControl.GetFromUDP(true))
+	theirPacket := myControl.GetFromTun(true)
+	assertUdpPacket(t, []byte("Hi from them"), theirPacket, theirVpnIpNet.IP, myVpnIpNet.IP, 80, 80)
+
+	// And once more from me to them
+	myControl.InjectTunUDPPacket(theirVpnIpNet.IP, 80, 80, []byte("Hello again from me"))
+	theirControl.InjectUDPPacket(myControl.GetFromUDP(true))
+	myPacket := theirControl.GetFromTun(true)
+	assertUdpPacket(t, []byte("Hello again from me"), myPacket, myVpnIpNet.IP, theirVpnIpNet.IP, 80, 80)
+}

+ 252 - 0
e2e/helpers_test.go

@@ -0,0 +1,252 @@
+// +build e2e_testing
+
+package e2e
+
+import (
+	"crypto/rand"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"net"
+	"testing"
+	"time"
+
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/sirupsen/logrus"
+	"github.com/slackhq/nebula"
+	"github.com/slackhq/nebula/cert"
+	"github.com/stretchr/testify/assert"
+	"golang.org/x/crypto/curve25519"
+	"golang.org/x/crypto/ed25519"
+	"gopkg.in/yaml.v2"
+)
+
+type m map[string]interface{}
+
+// newSimpleServer creates a nebula instance with many assumptions
+func newSimpleServer(caCrt *cert.NebulaCertificate, caKey []byte, name string, listenAddr *net.UDPAddr, vpnIp *net.IPNet) *nebula.Control {
+	l := logrus.New()
+	_, _, myPrivKey, myPEM := newTestCert(caCrt, caKey, name, time.Now(), time.Now().Add(5*time.Minute), vpnIp, nil, []string{})
+
+	caB, err := caCrt.MarshalToPEM()
+	if err != nil {
+		panic(err)
+	}
+
+	mc := m{
+		"pki": m{
+			"ca":   string(caB),
+			"cert": string(myPEM),
+			"key":  string(myPrivKey),
+		},
+		//"tun": m{"disabled": true},
+		"firewall": m{
+			"outbound": []m{{
+				"proto": "any",
+				"port":  "any",
+				"host":  "any",
+			}},
+			"inbound": []m{{
+				"proto": "any",
+				"port":  "any",
+				"host":  "any",
+			}},
+		},
+		"listen": m{
+			"host": listenAddr.IP.String(),
+			"port": listenAddr.Port,
+		},
+		"logging": m{
+			"timestamp_format": fmt.Sprintf("%v 15:04:05.000000", name),
+			"level":            "info",
+		},
+	}
+	cb, err := yaml.Marshal(mc)
+	if err != nil {
+		panic(err)
+	}
+
+	config := nebula.NewConfig(l)
+	config.LoadString(string(cb))
+
+	control, err := nebula.Main(config, false, "e2e-test", l, nil)
+
+	if err != nil {
+		panic(err)
+	}
+
+	return control
+}
+
+// newTestCaCert will generate a CA cert
+func newTestCaCert(before, after time.Time, ips, subnets []*net.IPNet, groups []string) (*cert.NebulaCertificate, []byte, []byte, []byte) {
+	pub, priv, err := ed25519.GenerateKey(rand.Reader)
+	if before.IsZero() {
+		before = time.Now().Add(time.Second * -60).Round(time.Second)
+	}
+	if after.IsZero() {
+		after = time.Now().Add(time.Second * 60).Round(time.Second)
+	}
+
+	nc := &cert.NebulaCertificate{
+		Details: cert.NebulaCertificateDetails{
+			Name:           "test ca",
+			NotBefore:      time.Unix(before.Unix(), 0),
+			NotAfter:       time.Unix(after.Unix(), 0),
+			PublicKey:      pub,
+			IsCA:           true,
+			InvertedGroups: make(map[string]struct{}),
+		},
+	}
+
+	if len(ips) > 0 {
+		nc.Details.Ips = ips
+	}
+
+	if len(subnets) > 0 {
+		nc.Details.Subnets = subnets
+	}
+
+	if len(groups) > 0 {
+		nc.Details.Groups = groups
+	}
+
+	err = nc.Sign(priv)
+	if err != nil {
+		panic(err)
+	}
+
+	pem, err := nc.MarshalToPEM()
+	if err != nil {
+		panic(err)
+	}
+
+	return nc, pub, priv, pem
+}
+
+// newTestCert will generate a signed certificate with the provided details.
+// Expiry times are defaulted if you do not pass them in
+func newTestCert(ca *cert.NebulaCertificate, key []byte, name string, before, after time.Time, ip *net.IPNet, subnets []*net.IPNet, groups []string) (*cert.NebulaCertificate, []byte, []byte, []byte) {
+	issuer, err := ca.Sha256Sum()
+	if err != nil {
+		panic(err)
+	}
+
+	if before.IsZero() {
+		before = time.Now().Add(time.Second * -60).Round(time.Second)
+	}
+
+	if after.IsZero() {
+		after = time.Now().Add(time.Second * 60).Round(time.Second)
+	}
+
+	pub, rawPriv := x25519Keypair()
+
+	nc := &cert.NebulaCertificate{
+		Details: cert.NebulaCertificateDetails{
+			Name:           name,
+			Ips:            []*net.IPNet{ip},
+			Subnets:        subnets,
+			Groups:         groups,
+			NotBefore:      time.Unix(before.Unix(), 0),
+			NotAfter:       time.Unix(after.Unix(), 0),
+			PublicKey:      pub,
+			IsCA:           false,
+			Issuer:         issuer,
+			InvertedGroups: make(map[string]struct{}),
+		},
+	}
+
+	err = nc.Sign(key)
+	if err != nil {
+		panic(err)
+	}
+
+	pem, err := nc.MarshalToPEM()
+	if err != nil {
+		panic(err)
+	}
+
+	return nc, pub, cert.MarshalX25519PrivateKey(rawPriv), pem
+}
+
+func x25519Keypair() ([]byte, []byte) {
+	var pubkey, privkey [32]byte
+	if _, err := io.ReadFull(rand.Reader, privkey[:]); err != nil {
+		panic(err)
+	}
+	curve25519.ScalarBaseMult(&pubkey, &privkey)
+	return pubkey[:], privkey[:]
+}
+
+func ip2int(ip []byte) uint32 {
+	if len(ip) == 16 {
+		return binary.BigEndian.Uint32(ip[12:16])
+	}
+	return binary.BigEndian.Uint32(ip)
+}
+
+func int2ip(nn uint32) net.IP {
+	ip := make(net.IP, 4)
+	binary.BigEndian.PutUint32(ip, nn)
+	return ip
+}
+
+func assertHostInfoPair(t *testing.T, addrA, addrB *net.UDPAddr, vpnIpA, vpnIpB net.IP, controlA, controlB *nebula.Control) {
+	// Get both host infos
+	hBinA := controlA.GetHostInfoByVpnIP(ip2int(vpnIpB), false)
+	assert.NotNil(t, hBinA, "Host B was not found by vpnIP in controlA")
+
+	hAinB := controlB.GetHostInfoByVpnIP(ip2int(vpnIpA), false)
+	assert.NotNil(t, hAinB, "Host A was not found by vpnIP in controlB")
+
+	// Check that both vpn and real addr are correct
+	assert.Equal(t, vpnIpB, hBinA.VpnIP, "HostA VpnIp is wrong in controlB")
+	assert.Equal(t, vpnIpA, hAinB.VpnIP, "HostB VpnIp is wrong in controlA")
+
+	assert.Equal(t, addrB.IP.To16(), hBinA.CurrentRemote.IP.To16(), "HostA remote ip is wrong in controlB")
+	assert.Equal(t, addrA.IP.To16(), hAinB.CurrentRemote.IP.To16(), "HostB remote ip is wrong in controlA")
+
+	assert.Equal(t, uint16(addrA.Port), hBinA.CurrentRemote.Port, "HostA remote ip is wrong in controlB")
+	assert.Equal(t, uint16(addrB.Port), hAinB.CurrentRemote.Port, "HostB remote ip is wrong in controlA")
+
+	// Check that our indexes match
+	assert.Equal(t, hBinA.LocalIndex, hAinB.RemoteIndex, "Host B local index does not match host A remote index")
+	assert.Equal(t, hBinA.RemoteIndex, hAinB.LocalIndex, "Host B remote index does not match host A local index")
+
+	//TODO: Would be nice to assert this memory
+	//checkIndexes := func(name string, hm *HostMap, hi *HostInfo) {
+	//	hBbyIndex := hmA.Indexes[hBinA.localIndexId]
+	//	assert.NotNil(t, hBbyIndex, "Could not host info by local index in %s", name)
+	//	assert.Equal(t, &hBbyIndex, &hBinA, "%s Indexes map did not point to the right host info", name)
+	//
+	//	//TODO: remote indexes are susceptible to collision
+	//	hBbyRemoteIndex := hmA.RemoteIndexes[hBinA.remoteIndexId]
+	//	assert.NotNil(t, hBbyIndex, "Could not host info by remote index in %s", name)
+	//	assert.Equal(t, &hBbyRemoteIndex, &hBinA, "%s RemoteIndexes did not point to the right host info", name)
+	//}
+	//
+	//// Check hostmap indexes too
+	//checkIndexes("hmA", hmA, hBinA)
+	//checkIndexes("hmB", hmB, hAinB)
+}
+
+func assertUdpPacket(t *testing.T, expected, b []byte, fromIp, toIp net.IP, fromPort, toPort uint16) {
+	packet := gopacket.NewPacket(b, layers.LayerTypeIPv4, gopacket.Lazy)
+	v4 := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
+	assert.NotNil(t, v4, "No ipv4 data found")
+
+	assert.Equal(t, fromIp, v4.SrcIP, "Source ip was incorrect")
+	assert.Equal(t, toIp, v4.DstIP, "Dest ip was incorrect")
+
+	udp := packet.Layer(layers.LayerTypeUDP).(*layers.UDP)
+	assert.NotNil(t, udp, "No udp data found")
+
+	assert.Equal(t, fromPort, uint16(udp.SrcPort), "Source port was incorrect")
+	assert.Equal(t, toPort, uint16(udp.DstPort), "Dest port was incorrect")
+
+	data := packet.ApplicationLayer()
+	assert.NotNil(t, data)
+	assert.Equal(t, expected, data.Payload(), "Data was incorrect")
+}

+ 1 - 0
go.mod

@@ -10,6 +10,7 @@ require (
 	github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
 	github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6
 	github.com/golang/protobuf v1.5.0
+	github.com/google/gopacket v1.1.19 // indirect
 	github.com/imdario/mergo v0.3.8
 	github.com/kardianos/service v1.1.0
 	github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect

+ 7 - 0
go.sum

@@ -62,6 +62,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
+github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
 github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
 github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -139,12 +141,15 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -184,7 +189,9 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=

+ 2 - 0
tun_android.go

@@ -1,3 +1,5 @@
+// +build !e2e_testing
+
 package nebula
 
 import (

+ 1 - 0
tun_darwin.go

@@ -1,4 +1,5 @@
 // +build !ios
+// +build !e2e_testing
 
 package nebula
 

+ 2 - 0
tun_freebsd.go

@@ -1,3 +1,5 @@
+// +build !e2e_testing
+
 package nebula
 
 import (

+ 1 - 0
tun_ios.go

@@ -1,4 +1,5 @@
 // +build ios
+// +build !e2e_testing
 
 package nebula
 

+ 1 - 0
tun_linux.go

@@ -1,4 +1,5 @@
 // +build !android
+// +build !e2e_testing
 
 package nebula
 

+ 2 - 0
tun_linux_test.go

@@ -1,3 +1,5 @@
+// +build !e2e_testing
+
 package nebula
 
 import "testing"

+ 103 - 0
tun_tester.go

@@ -0,0 +1,103 @@
+// +build e2e_testing
+
+package nebula
+
+import (
+	"fmt"
+	"io"
+	"net"
+
+	"github.com/sirupsen/logrus"
+)
+
+type Tun struct {
+	Device       string
+	Cidr         *net.IPNet
+	MTU          int
+	UnsafeRoutes []route
+	l            *logrus.Logger
+
+	rxPackets chan []byte // Packets to receive into nebula
+	txPackets chan []byte // Packets transmitted outside by nebula
+}
+
+func newTun(l *logrus.Logger, deviceName string, cidr *net.IPNet, defaultMTU int, _ []route, unsafeRoutes []route, _ int, _ bool) (ifce *Tun, err error) {
+	return &Tun{
+		Device:       deviceName,
+		Cidr:         cidr,
+		MTU:          defaultMTU,
+		UnsafeRoutes: unsafeRoutes,
+		l:            l,
+		rxPackets:    make(chan []byte, 100),
+		txPackets:    make(chan []byte, 100),
+	}, nil
+}
+
+func newTunFromFd(_ *logrus.Logger, _ int, _ *net.IPNet, _ int, _ []route, _ []route, _ int) (ifce *Tun, err error) {
+	return nil, fmt.Errorf("newTunFromFd not supported")
+}
+
+// Send will place a byte array onto the receive queue for nebula to consume
+// These are unencrypted ip layer frames destined for another nebula node.
+// packets should exit the udp side, capture them with udpConn.Get
+func (c *Tun) Send(packet []byte) {
+	c.rxPackets <- packet
+}
+
+// Get will pull an unencrypted ip layer frame from the transmit queue
+// nebula meant to send this message to some application on the local system
+// packets were ingested from the udp side, you can send them with udpConn.Send
+func (c *Tun) Get(block bool) []byte {
+	if block {
+		return <-c.txPackets
+	}
+
+	select {
+	case p := <-c.txPackets:
+		return p
+	default:
+		return nil
+	}
+}
+
+//********************************************************************************************************************//
+// Below this is boilerplate implementation to make nebula actually work
+//********************************************************************************************************************//
+
+func (c *Tun) Activate() error {
+	return nil
+}
+
+func (c *Tun) CidrNet() *net.IPNet {
+	return c.Cidr
+}
+
+func (c *Tun) DeviceName() string {
+	return c.Device
+}
+
+func (c *Tun) Write(b []byte) (n int, err error) {
+	return len(b), c.WriteRaw(b)
+}
+
+func (c *Tun) Close() error {
+	close(c.rxPackets)
+	return nil
+}
+
+func (c *Tun) WriteRaw(b []byte) error {
+	packet := make([]byte, len(b), len(b))
+	copy(packet, b)
+	c.txPackets <- packet
+	return nil
+}
+
+func (c *Tun) Read(b []byte) (int, error) {
+	p := <-c.rxPackets
+	copy(b, p)
+	return len(p), nil
+}
+
+func (c *Tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
+	return nil, fmt.Errorf("TODO: multiqueue not implemented")
+}

+ 116 - 0
udp_tester.go

@@ -0,0 +1,116 @@
+// +build e2e_testing
+
+package nebula
+
+import (
+	"net"
+
+	"github.com/sirupsen/logrus"
+)
+
+type UdpPacket struct {
+	ToIp     net.IP
+	ToPort   uint16
+	FromIp   net.IP
+	FromPort uint16
+	Data     []byte
+}
+
+type udpConn struct {
+	addr *udpAddr
+
+	rxPackets chan *UdpPacket // Packets to receive into nebula
+	txPackets chan *UdpPacket // Packets transmitted outside by nebula
+
+	l *logrus.Logger
+}
+
+func NewListener(l *logrus.Logger, ip string, port int, _ bool) (*udpConn, error) {
+	return &udpConn{
+		addr:      &udpAddr{net.ParseIP(ip), uint16(port)},
+		rxPackets: make(chan *UdpPacket, 1),
+		txPackets: make(chan *UdpPacket, 1),
+		l:         l,
+	}, nil
+}
+
+// Send will place a UdpPacket onto the receive queue for nebula to consume
+// this is an encrypted packet or a handshake message in most cases
+// packets were transmitted from another nebula node, you can send them with Tun.Send
+func (u *udpConn) Send(packet *UdpPacket) {
+	u.rxPackets <- packet
+}
+
+// Get will pull a UdpPacket from the transmit queue
+// nebula meant to send this message on the network, it will be encrypted
+// packets were ingested from the tun side (in most cases), you can send them with Tun.Send
+func (u *udpConn) Get(block bool) *UdpPacket {
+	if block {
+		return <-u.txPackets
+	}
+
+	select {
+	case p := <-u.txPackets:
+		return p
+	default:
+		return nil
+	}
+}
+
+//********************************************************************************************************************//
+// Below this is boilerplate implementation to make nebula actually work
+//********************************************************************************************************************//
+
+func (u *udpConn) WriteTo(b []byte, addr *udpAddr) error {
+	p := &UdpPacket{
+		Data:     make([]byte, len(b), len(b)),
+		FromIp:   make([]byte, 16),
+		FromPort: u.addr.Port,
+		ToIp:     make([]byte, 16),
+		ToPort:   addr.Port,
+	}
+
+	copy(p.Data, b)
+	copy(p.ToIp, addr.IP)
+	copy(p.FromIp, u.addr.IP)
+
+	u.txPackets <- p
+	return nil
+}
+
+func (u *udpConn) ListenOut(f *Interface, q int) {
+	plaintext := make([]byte, mtu)
+	header := &Header{}
+	fwPacket := &FirewallPacket{}
+	ua := &udpAddr{IP: make([]byte, 16)}
+	nb := make([]byte, 12, 12)
+
+	lhh := f.lightHouse.NewRequestHandler()
+	conntrackCache := NewConntrackCacheTicker(f.conntrackCacheTimeout)
+
+	for {
+		p := <-u.rxPackets
+		ua.Port = p.FromPort
+		copy(ua.IP, p.FromIp.To16())
+		f.readOutsidePackets(ua, plaintext[:0], p.Data, header, fwPacket, lhh, nb, q, conntrackCache.Get(u.l))
+	}
+}
+
+func (u *udpConn) reloadConfig(*Config) {}
+
+func NewUDPStatsEmitter(_ []*udpConn) func() {
+	// No UDP stats for non-linux
+	return func() {}
+}
+
+func (u *udpConn) LocalAddr() (*udpAddr, error) {
+	return u.addr, nil
+}
+
+func (u *udpConn) Rebind() error {
+	return nil
+}
+
+func hostDidRoam(addr *udpAddr, newaddr *udpAddr) bool {
+	return !addr.Equals(newaddr)
+}