From 830d6d4639add4acaf1f931e4d9a4940b94dbeeb Mon Sep 17 00:00:00 2001 From: Nathan Brown Date: Mon, 29 Mar 2021 14:29:20 -0500 Subject: [PATCH] Start of end to end testing with a good handshake between two nodes (#425) --- Makefile | 5 +- control_tester.go | 92 +++++++++++++++ e2e/handshakes_test.go | 60 ++++++++++ e2e/helpers_test.go | 252 +++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 7 ++ tun_android.go | 2 + tun_darwin.go | 1 + tun_freebsd.go | 2 + tun_ios.go | 1 + tun_linux.go | 1 + tun_linux_test.go | 2 + tun_tester.go | 103 +++++++++++++++++ udp_tester.go | 116 +++++++++++++++++++ 14 files changed, 643 insertions(+), 2 deletions(-) create mode 100644 control_tester.go create mode 100644 e2e/handshakes_test.go create mode 100644 e2e/helpers_test.go create mode 100644 tun_tester.go create mode 100644 udp_tester.go diff --git a/Makefile b/Makefile index a300e6f..3af3429 100644 --- a/Makefile +++ b/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 diff --git a/control_tester.go b/control_tester.go new file mode 100644 index 0000000..01e4d1f --- /dev/null +++ b/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()) +} diff --git a/e2e/handshakes_test.go b/e2e/handshakes_test.go new file mode 100644 index 0000000..87c5d93 --- /dev/null +++ b/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) +} diff --git a/e2e/helpers_test.go b/e2e/helpers_test.go new file mode 100644 index 0000000..b85da66 --- /dev/null +++ b/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") +} diff --git a/go.mod b/go.mod index 93dc132..a0d0297 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index a9247d6..4c20404 100644 --- a/go.sum +++ b/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= diff --git a/tun_android.go b/tun_android.go index b71a627..11e9e35 100644 --- a/tun_android.go +++ b/tun_android.go @@ -1,3 +1,5 @@ +// +build !e2e_testing + package nebula import ( diff --git a/tun_darwin.go b/tun_darwin.go index 0e39481..6e4cb6c 100644 --- a/tun_darwin.go +++ b/tun_darwin.go @@ -1,4 +1,5 @@ // +build !ios +// +build !e2e_testing package nebula diff --git a/tun_freebsd.go b/tun_freebsd.go index 4415401..7bbb2c4 100644 --- a/tun_freebsd.go +++ b/tun_freebsd.go @@ -1,3 +1,5 @@ +// +build !e2e_testing + package nebula import ( diff --git a/tun_ios.go b/tun_ios.go index 2e0e784..6e64d23 100644 --- a/tun_ios.go +++ b/tun_ios.go @@ -1,4 +1,5 @@ // +build ios +// +build !e2e_testing package nebula diff --git a/tun_linux.go b/tun_linux.go index 5dd3e5d..aba5c88 100644 --- a/tun_linux.go +++ b/tun_linux.go @@ -1,4 +1,5 @@ // +build !android +// +build !e2e_testing package nebula diff --git a/tun_linux_test.go b/tun_linux_test.go index 4e70aa0..95eb8d2 100644 --- a/tun_linux_test.go +++ b/tun_linux_test.go @@ -1,3 +1,5 @@ +// +build !e2e_testing + package nebula import "testing" diff --git a/tun_tester.go b/tun_tester.go new file mode 100644 index 0000000..a7bbd4e --- /dev/null +++ b/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") +} diff --git a/udp_tester.go b/udp_tester.go new file mode 100644 index 0000000..b527837 --- /dev/null +++ b/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) +}