瀏覽代碼

Lighthouse performance pass (#418)

Nathan Brown 4 年之前
父節點
當前提交
75f7bda0a4
共有 12 個文件被更改,包括 2334 次插入928 次删除
  1. 3 3
      Makefile
  2. 4 4
      e2e/handshakes_test.go
  3. 1 0
      go.mod
  4. 19 2
      go.sum
  5. 5 2
      handshake_manager_test.go
  6. 1 1
      inside.go
  7. 400 267
      lighthouse.go
  8. 213 64
      lighthouse_test.go
  9. 1683 569
      nebula.pb.go
  10. 2 4
      nebula.proto
  11. 1 1
      outside.go
  12. 2 11
      ssh.go

+ 3 - 3
Makefile

@@ -126,9 +126,9 @@ bench-cpu-long:
 proto: nebula.pb.go cert/cert.pb.go
 
 nebula.pb.go: nebula.proto .FORCE
-	go build google.golang.org/protobuf/cmd/protoc-gen-go
-	PATH="$(CURDIR):$(PATH)" protoc --go_out=. --go_opt=paths=source_relative $<
-	rm protoc-gen-go
+	go build github.com/gogo/protobuf/protoc-gen-gogofaster
+	PATH="$(CURDIR):$(PATH)" protoc --gogofaster_out=. $<
+	rm protoc-gen-gogofaster
 
 cert/cert.pb.go: cert/cert.proto .FORCE
 	$(MAKE) -C cert cert.pb.go

+ 4 - 4
e2e/handshakes_test.go

@@ -66,12 +66,12 @@ func TestWrongResponderHandshake(t *testing.T) {
 	theirControl, theirVpnIp, theirUdpAddr := newSimpleServer(ca, caKey, "them", net.IP{10, 0, 0, 2})
 	evilControl, evilVpnIp, evilUdpAddr := newSimpleServer(ca, caKey, "evil", net.IP{10, 0, 0, 99})
 
-	// Put the evil udp addr in for their vpn Ip, this is a case of being lied to by the lighthouse
-	myControl.InjectLightHouseAddr(theirVpnIp, evilUdpAddr)
-
-	// But also add their real udp addr, which should be tried after evil
+	// Add their real udp addr, which should be tried after evil. Doing this first because learned addresses are prepended
 	myControl.InjectLightHouseAddr(theirVpnIp, theirUdpAddr)
 
+	// Put the evil udp addr in for their vpn Ip, this is a case of being lied to by the lighthouse. This will now be the first attempted ip
+	myControl.InjectLightHouseAddr(theirVpnIp, evilUdpAddr)
+
 	// Build a router so we don't have to reason who gets which packet
 	r := router.NewR(myControl, theirControl, evilControl)
 

+ 1 - 0
go.mod

@@ -9,6 +9,7 @@ require (
 	github.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432
 	github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
 	github.com/flynn/noise v0.0.0-20210331153838-4bdb43be3117
+	github.com/gogo/protobuf v1.3.2
 	github.com/golang/protobuf v1.5.0
 	github.com/google/gopacket v1.1.19
 	github.com/imdario/mergo v0.3.8

+ 19 - 2
go.sum

@@ -28,6 +28,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -47,6 +49,8 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kardianos/service v1.1.0 h1:QV2SiEeWK42P0aEmGcsAgjApw/lRxkwopvT+Gu6t1/0=
 github.com/kardianos/service v1.1.0/go.mod h1:RrJI2xn5vve/r32U5suTbeaSGoMU6GbNPoj36CVYcHc=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -107,26 +111,34 @@ github.com/vishvananda/netlink v1.0.1-0.20190522153524-00009fb8606a h1:Bt1IVPhiC
 github.com/vishvananda/netlink v1.0.1-0.20190522153524-00009fb8606a/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
 github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
 github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -136,6 +148,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
@@ -145,11 +158,15 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=

+ 5 - 2
handshake_manager_test.go

@@ -142,8 +142,11 @@ func Test_NewHandshakeManagerTrigger(t *testing.T) {
 	hi := blah.pendingHostMap.Hosts[ip]
 	assert.Nil(t, hi.remote)
 
-	lh.addrMap = map[uint32][]*udpAddr{
-		ip: {NewUDPAddrFromString("10.1.1.1:4242")},
+	uaddr := NewUDPAddrFromString("10.1.1.1:4242")
+	lh.addrMap = map[uint32]*ip4And6{}
+	lh.addrMap[ip] = &ip4And6{
+		v4: []*Ip4AndPort{NewIp4AndPort(uaddr.IP, uint32(uaddr.Port))},
+		v6: []*Ip6AndPort{},
 	}
 
 	// This should trigger the hostmap to populate the hostinfo

+ 1 - 1
inside.go

@@ -247,7 +247,7 @@ func (f *Interface) sendNoMetrics(t NebulaMessageType, st NebulaMessageSubType,
 	if hostinfo.lastRebindCount != f.rebindCount {
 		//NOTE: there is an update hole if a tunnel isn't used and exactly 256 rebinds occur before the tunnel is
 		// finally used again. This tunnel would eventually be torn down and recreated if this action didn't help.
-		f.lightHouse.Query(hostinfo.hostId, f)
+		f.lightHouse.QueryServer(hostinfo.hostId, f)
 		hostinfo.lastRebindCount = f.rebindCount
 		if f.l.Level >= logrus.DebugLevel {
 			f.l.WithField("vpnIp", hostinfo.hostId).Debug("Lighthouse update triggered for punch due to rebind counter")

+ 400 - 267
lighthouse.go

@@ -1,6 +1,7 @@
 package nebula
 
 import (
+	"bytes"
 	"errors"
 	"fmt"
 	"net"
@@ -10,19 +11,38 @@ import (
 	"github.com/golang/protobuf/proto"
 	"github.com/rcrowley/go-metrics"
 	"github.com/sirupsen/logrus"
-	"github.com/slackhq/nebula/cert"
 )
 
+//TODO: if the pb code for ipv6 used a fixed data type we could save more work
+//TODO: nodes are roaming lighthouses, this is bad. How are they learning?
+//TODO: as a lh client, ignore any address within my nebula network?????
+
 var ErrHostNotKnown = errors.New("host not known")
 
+// The maximum number of ip addresses to store for a given vpnIp per address family
+const maxAddrs = 10
+
+type ip4And6 struct {
+	//TODO: adding a lock here could allow us to release the lock on lh.addrMap quicker
+
+	// v4 and v6 store addresses that have been self reported by the client
+	v4 []*Ip4AndPort
+	v6 []*Ip6AndPort
+
+	// Learned addresses are ones that a client does not know about but a lighthouse learned from as a result of the received packet
+	learnedV4 []*Ip4AndPort
+	learnedV6 []*Ip6AndPort
+}
+
 type LightHouse struct {
+	//TODO: We need a timer wheel to kick out vpnIps that haven't reported in a long time
 	sync.RWMutex //Because we concurrently read and write to our maps
 	amLighthouse bool
 	myIp         uint32
 	punchConn    *udpConn
 
 	// Local cache of answers from light houses
-	addrMap map[uint32][]*udpAddr
+	addrMap map[uint32]*ip4And6
 
 	// filters remote addresses allowed for each host
 	// - When we are a lighthouse, this filters what addresses we store and
@@ -53,14 +73,13 @@ type LightHouse struct {
 
 type EncWriter interface {
 	SendMessageToVpnIp(t NebulaMessageType, st NebulaMessageSubType, vpnIp uint32, p, nb, out []byte)
-	SendMessageToAll(t NebulaMessageType, st NebulaMessageSubType, vpnIp uint32, p, nb, out []byte)
 }
 
 func NewLightHouse(l *logrus.Logger, amLighthouse bool, myIp uint32, ips []uint32, interval int, nebulaPort uint32, pc *udpConn, punchBack bool, punchDelay time.Duration, metricsEnabled bool) *LightHouse {
 	h := LightHouse{
 		amLighthouse: amLighthouse,
 		myIp:         myIp,
-		addrMap:      make(map[uint32][]*udpAddr),
+		addrMap:      make(map[uint32]*ip4And6),
 		nebulaPort:   nebulaPort,
 		lighthouses:  make(map[uint32]struct{}),
 		staticList:   make(map[uint32]struct{}),
@@ -110,13 +129,14 @@ func (lh *LightHouse) ValidateLHStaticEntries() error {
 }
 
 func (lh *LightHouse) Query(ip uint32, f EncWriter) ([]*udpAddr, error) {
+	//TODO: we need to hold the lock through the next func
 	if !lh.IsLighthouseIP(ip) {
 		lh.QueryServer(ip, f)
 	}
 	lh.RLock()
 	if v, ok := lh.addrMap[ip]; ok {
 		lh.RUnlock()
-		return v, nil
+		return TransformLHReplyToUdpAddrs(v), nil
 	}
 	lh.RUnlock()
 	return nil, ErrHostNotKnown
@@ -141,17 +161,29 @@ func (lh *LightHouse) QueryServer(ip uint32, f EncWriter) {
 	}
 }
 
-// Query our local lighthouse cached results
 func (lh *LightHouse) QueryCache(ip uint32) []*udpAddr {
+	//TODO: we need to hold the lock through the next func
 	lh.RLock()
 	if v, ok := lh.addrMap[ip]; ok {
 		lh.RUnlock()
-		return v
+		return TransformLHReplyToUdpAddrs(v)
 	}
 	lh.RUnlock()
 	return nil
 }
 
+//
+func (lh *LightHouse) queryAndPrepMessage(ip uint32, f func(*ip4And6) (int, error)) (bool, int, error) {
+	lh.RLock()
+	if v, ok := lh.addrMap[ip]; ok {
+		n, err := f(v)
+		lh.RUnlock()
+		return true, n, err
+	}
+	lh.RUnlock()
+	return false, 0, nil
+}
+
 func (lh *LightHouse) DeleteVpnIP(vpnIP uint32) {
 	// First we check the static mapping
 	// and do nothing if it is there
@@ -161,11 +193,46 @@ func (lh *LightHouse) DeleteVpnIP(vpnIP uint32) {
 	lh.Lock()
 	//l.Debugln(lh.addrMap)
 	delete(lh.addrMap, vpnIP)
-	lh.l.Debugf("deleting %s from lighthouse.", IntIp(vpnIP))
+
+	if lh.l.Level >= logrus.DebugLevel {
+		lh.l.Debugf("deleting %s from lighthouse.", IntIp(vpnIP))
+	}
+
 	lh.Unlock()
 }
 
-func (lh *LightHouse) AddRemote(vpnIP uint32, toIp *udpAddr, static bool) {
+// AddRemote is correct way for non LightHouse members to add an address. toAddr will be placed in the learned map
+// static means this is a static host entry from the config file, it should only be used on start up
+func (lh *LightHouse) AddRemote(vpnIP uint32, toAddr *udpAddr, static bool) {
+	if ipv4 := toAddr.IP.To4(); ipv4 != nil {
+		lh.addRemoteV4(vpnIP, NewIp4AndPort(ipv4, uint32(toAddr.Port)), static, true)
+	} else {
+		lh.addRemoteV6(vpnIP, NewIp6AndPort(toAddr.IP, uint32(toAddr.Port)), static, true)
+	}
+
+	//TODO: if we do not add due to a config filter we may end up not having any addresses here
+	if static {
+		lh.staticList[vpnIP] = struct{}{}
+	}
+}
+
+// unsafeGetAddrs assumes you have the lh lock
+func (lh *LightHouse) unsafeGetAddrs(vpnIP uint32) *ip4And6 {
+	am, ok := lh.addrMap[vpnIP]
+	if !ok {
+		am = &ip4And6{
+			v4:        make([]*Ip4AndPort, 0),
+			v6:        make([]*Ip6AndPort, 0),
+			learnedV4: make([]*Ip4AndPort, 0),
+			learnedV6: make([]*Ip6AndPort, 0),
+		}
+		lh.addrMap[vpnIP] = am
+	}
+	return am
+}
+
+// addRemoteV4 is a lighthouse internal method that prepends a remote if it is allowed by the allow list and not duplicated
+func (lh *LightHouse) addRemoteV4(vpnIP uint32, to *Ip4AndPort, static bool, learned bool) {
 	// First we check if the sender thinks this is a static entry
 	// and do nothing if it is not, but should be considered static
 	if static == false {
@@ -176,24 +243,108 @@ func (lh *LightHouse) AddRemote(vpnIP uint32, toIp *udpAddr, static bool) {
 
 	lh.Lock()
 	defer lh.Unlock()
-	for _, v := range lh.addrMap[vpnIP] {
-		if v.Equals(toIp) {
+	am := lh.unsafeGetAddrs(vpnIP)
+
+	if learned {
+		if !lh.unlockedShouldAddV4(am.learnedV4, to) {
 			return
 		}
+		am.learnedV4 = prependAndLimitV4(am.learnedV4, to)
+	} else {
+		if !lh.unlockedShouldAddV4(am.v4, to) {
+			return
+		}
+		am.v4 = prependAndLimitV4(am.v4, to)
+	}
+}
+
+func prependAndLimitV4(cache []*Ip4AndPort, to *Ip4AndPort) []*Ip4AndPort {
+	cache = append(cache, nil)
+	copy(cache[1:], cache)
+	cache[0] = to
+	if len(cache) > MaxRemotes {
+		cache = cache[:maxAddrs]
+	}
+	return cache
+}
+
+// unlockedShouldAddV4 checks if to is allowed by our allow list and is not already present in the cache
+func (lh *LightHouse) unlockedShouldAddV4(am []*Ip4AndPort, to *Ip4AndPort) bool {
+	ip := int2ip(to.Ip)
+	allow := lh.remoteAllowList.Allow(ip)
+	if lh.l.Level >= logrus.DebugLevel {
+		lh.l.WithField("remoteIp", ip).WithField("allow", allow).Debug("remoteAllowList.Allow")
 	}
 
-	allow := lh.remoteAllowList.Allow(toIp.IP)
-	lh.l.WithField("remoteIp", toIp).WithField("allow", allow).Debug("remoteAllowList.Allow")
 	if !allow {
-		return
+		return false
 	}
 
-	//l.Debugf("Adding reply of %s as %s\n", IntIp(vpnIP), toIp)
-	if static {
-		lh.staticList[vpnIP] = struct{}{}
+	for _, v := range am {
+		if v.Ip == to.Ip && v.Port == to.Port {
+			return false
+		}
+	}
+
+	return true
+}
+
+// addRemoteV6 is a lighthouse internal method that prepends a remote if it is allowed by the allow list and not duplicated
+func (lh *LightHouse) addRemoteV6(vpnIP uint32, to *Ip6AndPort, static bool, learned bool) {
+	// First we check if the sender thinks this is a static entry
+	// and do nothing if it is not, but should be considered static
+	if static == false {
+		if _, ok := lh.staticList[vpnIP]; ok {
+			return
+		}
 	}
 
-	lh.addrMap[vpnIP] = append(lh.addrMap[vpnIP], toIp.Copy())
+	lh.Lock()
+	defer lh.Unlock()
+	am := lh.unsafeGetAddrs(vpnIP)
+
+	if learned {
+		if !lh.unlockedShouldAddV6(am.learnedV6, to) {
+			return
+		}
+		am.learnedV6 = prependAndLimitV6(am.learnedV6, to)
+	} else {
+		if !lh.unlockedShouldAddV6(am.v6, to) {
+			return
+		}
+		am.v6 = prependAndLimitV6(am.v6, to)
+	}
+}
+
+func prependAndLimitV6(cache []*Ip6AndPort, to *Ip6AndPort) []*Ip6AndPort {
+	cache = append(cache, nil)
+	copy(cache[1:], cache)
+	cache[0] = to
+	if len(cache) > MaxRemotes {
+		cache = cache[:maxAddrs]
+	}
+	return cache
+}
+
+// unlockedShouldAddV6 checks if to is allowed by our allow list and is not already present in the cache
+func (lh *LightHouse) unlockedShouldAddV6(am []*Ip6AndPort, to *Ip6AndPort) bool {
+	ip := net.IP(to.Ip)
+	allow := lh.remoteAllowList.Allow(ip)
+	if lh.l.Level >= logrus.DebugLevel {
+		lh.l.WithField("remoteIp", ip).WithField("allow", allow).Debug("remoteAllowList.Allow")
+	}
+
+	if !allow {
+		return false
+	}
+
+	for _, v := range am {
+		if bytes.Equal(v.Ip, to.Ip) && v.Port == to.Port {
+			return false
+		}
+	}
+
+	return true
 }
 
 func (lh *LightHouse) AddRemoteAndReset(vpnIP uint32, toIp *udpAddr) {
@@ -201,7 +352,6 @@ func (lh *LightHouse) AddRemoteAndReset(vpnIP uint32, toIp *udpAddr) {
 		lh.DeleteVpnIP(vpnIP)
 		lh.AddRemote(vpnIP, toIp, false)
 	}
-
 }
 
 func (lh *LightHouse) IsLighthouseIP(vpnIP uint32) bool {
@@ -220,32 +370,20 @@ func NewLhQueryByInt(VpnIp uint32) *NebulaMeta {
 	}
 }
 
-type ip4Or6 struct {
-	v4 IpAndPort
-	v6 Ip6AndPort
-}
-
-func NewIpAndPort(ip net.IP, port uint32) ip4Or6 {
-	ipp := ip4Or6{}
-
-	if ipv4 := ip.To4(); ipv4 != nil {
-		ipp.v4 = IpAndPort{Port: port}
-		ipp.v4.Ip = ip2int(ip)
-
-	} else {
-		ipp.v6 = Ip6AndPort{Port: port}
-		ipp.v6.Ip = make([]byte, len(ip))
-		copy(ipp.v6.Ip, ip)
-	}
-
-	return ipp
+func NewIp4AndPort(ip net.IP, port uint32) *Ip4AndPort {
+	ipp := Ip4AndPort{Port: port}
+	ipp.Ip = ip2int(ip)
+	return &ipp
 }
 
-func NewIpAndPortFromUDPAddr(addr *udpAddr) ip4Or6 {
-	return NewIpAndPort(addr.IP, uint32(addr.Port))
+func NewIp6AndPort(ip net.IP, port uint32) *Ip6AndPort {
+	ipp := Ip6AndPort{Port: port}
+	ipp.Ip = make([]byte, len(ip))
+	copy(ipp.Ip, ip)
+	return &ipp
 }
 
-func NewUDPAddrFromLH4(ipp *IpAndPort) *udpAddr {
+func NewUDPAddrFromLH4(ipp *Ip4AndPort) *udpAddr {
 	ip := ipp.Ip
 	return NewUDPAddr(
 		net.IPv4(byte(ip&0xff000000>>24), byte(ip&0x00ff0000>>16), byte(ip&0x0000ff00>>8), byte(ip&0x000000ff)),
@@ -269,26 +407,26 @@ func (lh *LightHouse) LhUpdateWorker(f EncWriter) {
 }
 
 func (lh *LightHouse) SendUpdate(f EncWriter) {
-	var v4 []*IpAndPort
+	var v4 []*Ip4AndPort
 	var v6 []*Ip6AndPort
 
 	for _, e := range *localIps(lh.l, lh.localAllowList) {
-		// Only add IPs that aren't my VPN/tun IP
-		if ip2int(e) != lh.myIp {
-			ipp := NewIpAndPort(e, lh.nebulaPort)
-			if len(ipp.v6.Ip) > 0 {
-				v6 = append(v6, &ipp.v6)
-			} else {
-				v4 = append(v4, &ipp.v4)
-			}
+		if ip2int(e) == lh.myIp {
+			continue
+		}
 
+		// Only add IPs that aren't my VPN/tun IP
+		if ip := e.To4(); ip != nil {
+			v4 = append(v4, NewIp4AndPort(e, lh.nebulaPort))
+		} else {
+			v6 = append(v6, NewIp6AndPort(e, lh.nebulaPort))
 		}
 	}
 	m := &NebulaMeta{
 		Type: NebulaMeta_HostUpdateNotification,
 		Details: &NebulaMetaDetails{
 			VpnIp:       lh.myIp,
-			IpAndPorts:  v4,
+			Ip4AndPorts: v4,
 			Ip6AndPorts: v6,
 		},
 	}
@@ -298,7 +436,7 @@ func (lh *LightHouse) SendUpdate(f EncWriter) {
 	out := make([]byte, mtu)
 	for vpnIp := range lh.lighthouses {
 		mm, err := proto.Marshal(m)
-		if err != nil {
+		if err != nil && lh.l.Level >= logrus.DebugLevel {
 			lh.l.Debugf("Invalid marshal to update")
 		}
 		//l.Error("LIGHTHOUSE PACKET SEND", mm)
@@ -311,9 +449,9 @@ type LightHouseHandler struct {
 	lh   *LightHouse
 	nb   []byte
 	out  []byte
+	pb   []byte
 	meta *NebulaMeta
-	iap  []ip4Or6
-	iapp []*ip4Or6
+	l    *logrus.Logger
 }
 
 func (lh *LightHouse) NewRequestHandler() *LightHouseHandler {
@@ -321,288 +459,283 @@ func (lh *LightHouse) NewRequestHandler() *LightHouseHandler {
 		lh:  lh,
 		nb:  make([]byte, 12, 12),
 		out: make([]byte, mtu),
+		l:   lh.l,
+		pb:  make([]byte, mtu),
 
 		meta: &NebulaMeta{
 			Details: &NebulaMetaDetails{},
 		},
 	}
 
-	lhh.resizeIpAndPorts(10)
-
 	return lhh
 }
 
+func (lh *LightHouse) metricRx(t NebulaMeta_MessageType, i int64) {
+	lh.metrics.Rx(NebulaMessageType(t), 0, i)
+}
+
+func (lh *LightHouse) metricTx(t NebulaMeta_MessageType, i int64) {
+	lh.metrics.Tx(NebulaMessageType(t), 0, i)
+}
+
 // This method is similar to Reset(), but it re-uses the pointer structs
 // so that we don't have to re-allocate them
 func (lhh *LightHouseHandler) resetMeta() *NebulaMeta {
 	details := lhh.meta.Details
-
-	details.Reset()
 	lhh.meta.Reset()
+
+	// Keep the array memory around
+	details.Ip4AndPorts = details.Ip4AndPorts[:0]
+	details.Ip6AndPorts = details.Ip6AndPorts[:0]
 	lhh.meta.Details = details
 
 	return lhh.meta
 }
 
-func (lhh *LightHouseHandler) resizeIpAndPorts(n int) {
-	if cap(lhh.iap) < n {
-		lhh.iap = make([]ip4Or6, n)
-		lhh.iapp = make([]*ip4Or6, n)
-
-		for i := range lhh.iap {
-			lhh.iapp[i] = &lhh.iap[i]
-		}
-	}
-	lhh.iap = lhh.iap[:n]
-	lhh.iapp = lhh.iapp[:n]
-}
-
-func (lhh *LightHouseHandler) setIpAndPortsFromNetIps(ips []*udpAddr) []*ip4Or6 {
-	lhh.resizeIpAndPorts(len(ips))
-	for i, e := range ips {
-		lhh.iap[i] = NewIpAndPortFromUDPAddr(e)
-	}
-	return lhh.iapp
-}
-
-func (lhh *LightHouseHandler) HandleRequest(rAddr *udpAddr, vpnIp uint32, p []byte, c *cert.NebulaCertificate, f EncWriter) {
-	lh := lhh.lh
+//TODO: do we need c here?
+func (lhh *LightHouseHandler) HandleRequest(rAddr *udpAddr, vpnIp uint32, p []byte, w EncWriter) {
 	n := lhh.resetMeta()
-	err := proto.UnmarshalMerge(p, n)
+	err := n.Unmarshal(p)
 	if err != nil {
-		lh.l.WithError(err).WithField("vpnIp", IntIp(vpnIp)).WithField("udpAddr", rAddr).
+		lhh.l.WithError(err).WithField("vpnIp", IntIp(vpnIp)).WithField("udpAddr", rAddr).
 			Error("Failed to unmarshal lighthouse packet")
 		//TODO: send recv_error?
 		return
 	}
 
 	if n.Details == nil {
-		lh.l.WithField("vpnIp", IntIp(vpnIp)).WithField("udpAddr", rAddr).
+		lhh.l.WithField("vpnIp", IntIp(vpnIp)).WithField("udpAddr", rAddr).
 			Error("Invalid lighthouse update")
 		//TODO: send recv_error?
 		return
 	}
 
-	lh.metricRx(n.Type, 1)
+	lhh.lh.metricRx(n.Type, 1)
 
 	switch n.Type {
 	case NebulaMeta_HostQuery:
-		// Exit if we don't answer queries
-		if !lh.amLighthouse {
-			lh.l.Debugln("I don't answer queries, but received from: ", rAddr)
-			return
-		}
+		lhh.handleHostQuery(n, vpnIp, rAddr, w)
 
-		//l.Debugln("Got Query")
-		ips, err := lh.Query(n.Details.VpnIp, f)
-		if err != nil {
-			//l.Debugf("Can't answer query %s from %s because error: %s", IntIp(n.Details.VpnIp), rAddr, err)
-			return
-		} else {
-			reqVpnIP := n.Details.VpnIp
-			n = lhh.resetMeta()
-			n.Type = NebulaMeta_HostQueryReply
-			n.Details.VpnIp = reqVpnIP
-
-			v4s := make([]*IpAndPort, 0)
-			v6s := make([]*Ip6AndPort, 0)
-			for _, v := range lhh.setIpAndPortsFromNetIps(ips) {
-				if len(v.v6.Ip) > 0 {
-					v6s = append(v6s, &v.v6)
-				} else {
-					v4s = append(v4s, &v.v4)
-				}
-			}
+	case NebulaMeta_HostQueryReply:
+		lhh.handleHostQueryReply(n, vpnIp)
 
-			if len(v4s) > 0 {
-				n.Details.IpAndPorts = v4s
-			}
+	case NebulaMeta_HostUpdateNotification:
+		lhh.handleHostUpdateNotification(n, vpnIp)
 
-			if len(v6s) > 0 {
-				n.Details.Ip6AndPorts = v6s
-			}
+	case NebulaMeta_HostMovedNotification:
+	case NebulaMeta_HostPunchNotification:
+		lhh.handleHostPunchNotification(n, vpnIp, w)
+	}
+}
 
-			reply, err := proto.Marshal(n)
-			if err != nil {
-				lh.l.WithError(err).WithField("vpnIp", IntIp(vpnIp)).Error("Failed to marshal lighthouse host query reply")
-				return
-			}
-			lh.metricTx(NebulaMeta_HostQueryReply, 1)
-			f.SendMessageToVpnIp(lightHouse, 0, vpnIp, reply, lhh.nb, lhh.out[:0])
-
-			// This signals the other side to punch some zero byte udp packets
-			ips, err = lh.Query(vpnIp, f)
-			if err != nil {
-				lh.l.WithField("vpnIp", IntIp(vpnIp)).Debugln("Can't notify host to punch")
-				return
-			} else {
-				//l.Debugln("Notify host to punch", iap)
-				n = lhh.resetMeta()
-				n.Type = NebulaMeta_HostPunchNotification
-				n.Details.VpnIp = vpnIp
-
-				v4s := make([]*IpAndPort, 0)
-				v6s := make([]*Ip6AndPort, 0)
-				for _, v := range lhh.setIpAndPortsFromNetIps(ips) {
-					if len(v.v6.Ip) > 0 {
-						v6s = append(v6s, &v.v6)
-					} else {
-						v4s = append(v4s, &v.v4)
-					}
-				}
-
-				if len(v4s) > 0 {
-					n.Details.IpAndPorts = v4s
-				}
-
-				if len(v6s) > 0 {
-					n.Details.Ip6AndPorts = v6s
-				}
-
-				reply, _ := proto.Marshal(n)
-				lh.metricTx(NebulaMeta_HostPunchNotification, 1)
-				f.SendMessageToVpnIp(lightHouse, 0, reqVpnIP, reply, lhh.nb, lhh.out[:0])
-			}
-			//fmt.Println(reply, remoteaddr)
+func (lhh *LightHouseHandler) handleHostQuery(n *NebulaMeta, vpnIp uint32, addr *udpAddr, w EncWriter) {
+	// Exit if we don't answer queries
+	if !lhh.lh.amLighthouse {
+		if lhh.l.Level >= logrus.DebugLevel {
+			lhh.l.Debugln("I don't answer queries, but received from: ", addr)
 		}
+		return
+	}
 
-	case NebulaMeta_HostQueryReply:
-		if !lh.IsLighthouseIP(vpnIp) {
-			return
-		}
+	//TODO: we can DRY this further
+	reqVpnIP := n.Details.VpnIp
+	//TODO: Maybe instead of marshalling into n we marshal into a new `r` to not nuke our current request data
+	//TODO: If we use a lock on cache we can avoid holding it on lh.addrMap and keep things moving better
+	found, ln, err := lhh.lh.queryAndPrepMessage(n.Details.VpnIp, func(cache *ip4And6) (int, error) {
+		n = lhh.resetMeta()
+		n.Type = NebulaMeta_HostQueryReply
+		n.Details.VpnIp = reqVpnIP
 
-		for _, a := range n.Details.IpAndPorts {
-			ans := NewUDPAddrFromLH4(a)
-			if ans != nil {
-				lh.AddRemote(n.Details.VpnIp, ans, false)
-			}
-		}
+		lhh.coalesceAnswers(cache, n)
 
-		for _, a := range n.Details.Ip6AndPorts {
-			ans := NewUDPAddrFromLH6(a)
-			if ans != nil {
-				lh.AddRemote(n.Details.VpnIp, ans, false)
-			}
-		}
+		return n.MarshalTo(lhh.pb)
+	})
 
-		// Non-blocking attempt to trigger, skip if it would block
-		select {
-		case lh.handshakeTrigger <- n.Details.VpnIp:
-		default:
-		}
+	if !found {
+		return
+	}
 
-	case NebulaMeta_HostUpdateNotification:
-		//Simple check that the host sent this not someone else
-		if n.Details.VpnIp != vpnIp {
-			lh.l.WithField("vpnIp", IntIp(vpnIp)).WithField("answer", IntIp(n.Details.VpnIp)).Debugln("Host sent invalid update")
-			return
-		}
+	if err != nil {
+		lhh.l.WithError(err).WithField("vpnIp", IntIp(vpnIp)).Error("Failed to marshal lighthouse host query reply")
+		return
+	}
 
-		for _, a := range n.Details.IpAndPorts {
-			ans := NewUDPAddrFromLH4(a)
-			if ans != nil {
-				lh.AddRemote(n.Details.VpnIp, ans, false)
-			}
-		}
+	lhh.lh.metricTx(NebulaMeta_HostQueryReply, 1)
+	w.SendMessageToVpnIp(lightHouse, 0, vpnIp, lhh.pb[:ln], lhh.nb, lhh.out[:0])
 
-		for _, a := range n.Details.Ip6AndPorts {
-			ans := NewUDPAddrFromLH6(a)
-			if ans != nil {
-				lh.AddRemote(n.Details.VpnIp, ans, false)
-			}
+	// This signals the other side to punch some zero byte udp packets
+	found, ln, err = lhh.lh.queryAndPrepMessage(vpnIp, func(cache *ip4And6) (int, error) {
+		n = lhh.resetMeta()
+		n.Type = NebulaMeta_HostPunchNotification
+		n.Details.VpnIp = vpnIp
+
+		lhh.coalesceAnswers(cache, n)
+
+		return n.MarshalTo(lhh.pb)
+	})
+
+	if !found {
+		return
+	}
+
+	if err != nil {
+		lhh.l.WithError(err).WithField("vpnIp", IntIp(vpnIp)).Error("Failed to marshal lighthouse host was queried for")
+		return
+	}
+
+	lhh.lh.metricTx(NebulaMeta_HostPunchNotification, 1)
+	w.SendMessageToVpnIp(lightHouse, 0, reqVpnIP, lhh.pb[:ln], lhh.nb, lhh.out[:0])
+}
+
+func (lhh *LightHouseHandler) coalesceAnswers(cache *ip4And6, n *NebulaMeta) {
+	n.Details.Ip4AndPorts = append(n.Details.Ip4AndPorts, cache.v4...)
+	n.Details.Ip4AndPorts = append(n.Details.Ip4AndPorts, cache.learnedV4...)
+
+	n.Details.Ip6AndPorts = append(n.Details.Ip6AndPorts, cache.v6...)
+	n.Details.Ip6AndPorts = append(n.Details.Ip6AndPorts, cache.learnedV6...)
+}
+
+func (lhh *LightHouseHandler) handleHostQueryReply(n *NebulaMeta, vpnIp uint32) {
+	if !lhh.lh.IsLighthouseIP(vpnIp) {
+		return
+	}
+
+	// We can't just slam the responses in as they may come from multiple lighthouses and we should coalesce the answers
+	for _, to := range n.Details.Ip4AndPorts {
+		lhh.lh.addRemoteV4(n.Details.VpnIp, to, false, false)
+	}
+
+	for _, to := range n.Details.Ip6AndPorts {
+		lhh.lh.addRemoteV6(n.Details.VpnIp, to, false, false)
+	}
+
+	// Non-blocking attempt to trigger, skip if it would block
+	select {
+	case lhh.lh.handshakeTrigger <- n.Details.VpnIp:
+	default:
+	}
+}
+
+func (lhh *LightHouseHandler) handleHostUpdateNotification(n *NebulaMeta, vpnIp uint32) {
+	if !lhh.lh.amLighthouse {
+		if lhh.l.Level >= logrus.DebugLevel {
+			lhh.l.Debugln("I am not a lighthouse, do not take host updates: ", vpnIp)
 		}
+		return
+	}
 
-	case NebulaMeta_HostMovedNotification:
-	case NebulaMeta_HostPunchNotification:
-		if !lh.IsLighthouseIP(vpnIp) {
-			return
+	//Simple check that the host sent this not someone else
+	if n.Details.VpnIp != vpnIp {
+		if lhh.l.Level >= logrus.DebugLevel {
+			lhh.l.WithField("vpnIp", IntIp(vpnIp)).WithField("answer", IntIp(n.Details.VpnIp)).Debugln("Host sent invalid update")
 		}
+		return
+	}
 
-		empty := []byte{0}
-		for _, a := range n.Details.IpAndPorts {
-			vpnPeer := NewUDPAddrFromLH4(a)
-			if vpnPeer == nil {
-				continue
-			}
+	lhh.lh.Lock()
+	defer lhh.lh.Unlock()
+	am := lhh.lh.unsafeGetAddrs(vpnIp)
 
-			go func() {
-				time.Sleep(lh.punchDelay)
-				lh.metricHolepunchTx.Inc(1)
-				lh.punchConn.WriteTo(empty, vpnPeer)
+	//TODO: other note on a lock for am so we can release more quickly and lock our real unit of change which is far less contended
+	//TODO: we are not filtering by local or remote allowed addrs here, is this an ok change to make?
 
-			}()
+	// We don't accumulate addresses being told to us
+	am.v4 = am.v4[:0]
+	am.v6 = am.v6[:0]
 
-			if lh.l.Level >= logrus.DebugLevel {
-				//TODO: lacking the ip we are actually punching on, old: l.Debugf("Punching %s on %d for %s", IntIp(a.Ip), a.Port, IntIp(n.Details.VpnIp))
-				lh.l.Debugf("Punching on %d for %s", a.Port, IntIp(n.Details.VpnIp))
-			}
+	for _, v := range n.Details.Ip4AndPorts {
+		if lhh.lh.unlockedShouldAddV4(am.v4, v) {
+			am.v4 = append(am.v4, v)
 		}
+	}
 
-		for _, a := range n.Details.Ip6AndPorts {
-			vpnPeer := NewUDPAddrFromLH6(a)
-			if vpnPeer == nil {
-				continue
-			}
+	for _, v := range n.Details.Ip6AndPorts {
+		if lhh.lh.unlockedShouldAddV6(am.v6, v) {
+			am.v6 = append(am.v6, v)
+		}
+	}
 
-			go func() {
-				time.Sleep(lh.punchDelay)
-				lh.metricHolepunchTx.Inc(1)
-				lh.punchConn.WriteTo(empty, vpnPeer)
+	// We prefer the first n addresses if we got too big
+	if len(am.v4) > MaxRemotes {
+		am.v4 = am.v4[:MaxRemotes]
+	}
 
-			}()
+	if len(am.v6) > MaxRemotes {
+		am.v6 = am.v6[:MaxRemotes]
+	}
+}
 
-			if lh.l.Level >= logrus.DebugLevel {
-				//TODO: lacking the ip we are actually punching on, old: l.Debugf("Punching %s on %d for %s", IntIp(a.Ip), a.Port, IntIp(n.Details.VpnIp))
-				lh.l.Debugf("Punching on %d for %s", a.Port, IntIp(n.Details.VpnIp))
-			}
+func (lhh *LightHouseHandler) handleHostPunchNotification(n *NebulaMeta, vpnIp uint32, w EncWriter) {
+	if !lhh.lh.IsLighthouseIP(vpnIp) {
+		return
+	}
+
+	empty := []byte{0}
+	punch := func(vpnPeer *udpAddr) {
+		if vpnPeer == nil {
+			return
 		}
 
-		// This sends a nebula test packet to the host trying to contact us. In the case
-		// of a double nat or other difficult scenario, this may help establish
-		// a tunnel.
-		if lh.punchBack {
-			go func() {
-				time.Sleep(time.Second * 5)
-				lh.l.Debugf("Sending a nebula test packet to vpn ip %s", IntIp(n.Details.VpnIp))
-				// TODO we have to allocate a new output buffer here since we are spawning a new goroutine
-				// for each punchBack packet. We should move this into a timerwheel or a single goroutine
-				// managed by a channel.
-				f.SendMessageToVpnIp(test, testRequest, n.Details.VpnIp, []byte(""), make([]byte, 12, 12), make([]byte, mtu))
-			}()
+		go func() {
+			time.Sleep(lhh.lh.punchDelay)
+			lhh.lh.metricHolepunchTx.Inc(1)
+			lhh.lh.punchConn.WriteTo(empty, vpnPeer)
+		}()
+
+		if lhh.l.Level >= logrus.DebugLevel {
+			//TODO: lacking the ip we are actually punching on, old: l.Debugf("Punching %s on %d for %s", IntIp(a.Ip), a.Port, IntIp(n.Details.VpnIp))
+			lhh.l.Debugf("Punching on %d for %s", vpnPeer.Port, IntIp(n.Details.VpnIp))
 		}
 	}
-}
 
-func (lh *LightHouse) metricRx(t NebulaMeta_MessageType, i int64) {
-	lh.metrics.Rx(NebulaMessageType(t), 0, i)
-}
-func (lh *LightHouse) metricTx(t NebulaMeta_MessageType, i int64) {
-	lh.metrics.Tx(NebulaMessageType(t), 0, i)
-}
+	for _, a := range n.Details.Ip4AndPorts {
+		punch(NewUDPAddrFromLH4(a))
+	}
 
-/*
-func (f *Interface) sendPathCheck(ci *ConnectionState, endpoint *net.UDPAddr, counter int) {
-	c := ci.messageCounter
-    b := HeaderEncode(nil, Version, uint8(path_check), 0, ci.remoteIndex, c)
-	ci.messageCounter++
+	for _, a := range n.Details.Ip6AndPorts {
+		punch(NewUDPAddrFromLH6(a))
+	}
 
-	if ci.eKey != nil {
-		msg := ci.eKey.EncryptDanger(b, nil, []byte(strconv.Itoa(counter)), c)
-		//msg := ci.eKey.EncryptDanger(b, nil, []byte(fmt.Sprintf("%d", counter)), c)
-		f.outside.WriteTo(msg, endpoint)
-		l.Debugf("path_check sent, remote index: %d, pathCounter %d", ci.remoteIndex, counter)
+	// This sends a nebula test packet to the host trying to contact us. In the case
+	// of a double nat or other difficult scenario, this may help establish
+	// a tunnel.
+	if lhh.lh.punchBack {
+		go func() {
+			time.Sleep(time.Second * 5)
+			if lhh.l.Level >= logrus.DebugLevel {
+				lhh.l.Debugf("Sending a nebula test packet to vpn ip %s", IntIp(n.Details.VpnIp))
+			}
+			//NOTE: we have to allocate a new output buffer here since we are spawning a new goroutine
+			// for each punchBack packet. We should move this into a timerwheel or a single goroutine
+			// managed by a channel.
+			w.SendMessageToVpnIp(test, testRequest, n.Details.VpnIp, []byte(""), make([]byte, 12, 12), make([]byte, mtu))
+		}()
 	}
 }
 
-func (f *Interface) sendPathCheckReply(ci *ConnectionState, endpoint *net.UDPAddr, counter []byte) {
-	c := ci.messageCounter
-    b := HeaderEncode(nil, Version, uint8(path_check_reply), 0, ci.remoteIndex, c)
-	ci.messageCounter++
+func TransformLHReplyToUdpAddrs(ips *ip4And6) []*udpAddr {
+	addrs := make([]*udpAddr, len(ips.v4)+len(ips.v6)+len(ips.learnedV4)+len(ips.learnedV6))
+	i := 0
 
-	if ci.eKey != nil {
-		msg := ci.eKey.EncryptDanger(b, nil, counter, c)
-		f.outside.WriteTo(msg, endpoint)
-		l.Debugln("path_check sent, remote index: ", ci.remoteIndex)
+	for _, v := range ips.learnedV4 {
+		addrs[i] = NewUDPAddrFromLH4(v)
+		i++
 	}
+
+	for _, v := range ips.v4 {
+		addrs[i] = NewUDPAddrFromLH4(v)
+		i++
+	}
+
+	for _, v := range ips.learnedV6 {
+		addrs[i] = NewUDPAddrFromLH6(v)
+		i++
+	}
+
+	for _, v := range ips.v6 {
+		addrs[i] = NewUDPAddrFromLH6(v)
+		i++
+	}
+
+	return addrs
 }
-*/

+ 213 - 64
lighthouse_test.go

@@ -1,6 +1,7 @@
 package nebula
 
 import (
+	"fmt"
 	"net"
 	"testing"
 
@@ -13,7 +14,7 @@ import (
 func TestOldIPv4Only(t *testing.T) {
 	// This test ensures our new ipv6 enabled LH protobuf IpAndPorts works with the old style to enable backwards compatibility
 	b := []byte{8, 129, 130, 132, 80, 16, 10}
-	var m IpAndPort
+	var m Ip4AndPort
 	err := proto.Unmarshal(b, &m)
 	assert.NoError(t, err)
 	assert.Equal(t, "10.1.1.1", int2ip(m.GetIp()).String())
@@ -40,30 +41,6 @@ func TestNewLhQuery(t *testing.T) {
 
 }
 
-func TestNewipandportfromudpaddr(t *testing.T) {
-	blah := NewUDPAddrFromString("1.2.2.3:12345")
-	meh := NewIpAndPortFromUDPAddr(blah)
-	assert.Equal(t, uint32(16908803), meh.v4.Ip)
-	assert.Equal(t, uint32(12345), meh.v4.Port)
-}
-
-func TestSetipandportsfromudpaddrs(t *testing.T) {
-	blah := NewUDPAddrFromString("1.2.2.3:12345")
-	blah2 := NewUDPAddrFromString("9.9.9.9:47828")
-	group := []*udpAddr{blah, blah2}
-	var lh *LightHouse
-	lhh := lh.NewRequestHandler()
-	result := lhh.setIpAndPortsFromNetIps(group)
-	assert.IsType(t, []*ip4Or6{}, result)
-	assert.Len(t, result, 2)
-	assert.Equal(t, uint32(0x01020203), result[0].v4.Ip)
-	assert.Equal(t, uint32(12345), result[0].v4.Port)
-	assert.Equal(t, uint32(0x09090909), result[1].v4.Ip)
-	assert.Equal(t, uint32(47828), result[1].v4.Port)
-	//t.Error(reflect.TypeOf(hah))
-
-}
-
 func Test_lhStaticMapping(t *testing.T) {
 	l := NewTestLogger()
 	lh1 := "10.128.0.2"
@@ -96,11 +73,17 @@ func BenchmarkLighthouseHandleRequest(b *testing.B) {
 
 	hAddr := NewUDPAddrFromString("4.5.6.7:12345")
 	hAddr2 := NewUDPAddrFromString("4.5.6.7:12346")
-	lh.addrMap[3] = []*udpAddr{hAddr, hAddr2}
+	lh.addrMap[3] = &ip4And6{v4: []*Ip4AndPort{
+		NewIp4AndPort(hAddr.IP, uint32(hAddr.Port)),
+		NewIp4AndPort(hAddr2.IP, uint32(hAddr2.Port))},
+	}
 
 	rAddr := NewUDPAddrFromString("1.2.2.3:12345")
 	rAddr2 := NewUDPAddrFromString("1.2.2.3:12346")
-	lh.addrMap[2] = []*udpAddr{rAddr, rAddr2}
+	lh.addrMap[2] = &ip4And6{v4: []*Ip4AndPort{
+		NewIp4AndPort(rAddr.IP, uint32(rAddr.Port)),
+		NewIp4AndPort(rAddr2.IP, uint32(rAddr2.Port))},
+	}
 
 	mw := &mockEncWriter{}
 
@@ -109,14 +92,14 @@ func BenchmarkLighthouseHandleRequest(b *testing.B) {
 		req := &NebulaMeta{
 			Type: NebulaMeta_HostQuery,
 			Details: &NebulaMetaDetails{
-				VpnIp:      4,
-				IpAndPorts: nil,
+				VpnIp:       4,
+				Ip4AndPorts: nil,
 			},
 		}
 		p, err := proto.Marshal(req)
 		assert.NoError(b, err)
 		for n := 0; n < b.N; n++ {
-			lhh.HandleRequest(rAddr, 2, p, nil, mw)
+			lhh.HandleRequest(rAddr, 2, p, mw)
 		}
 	})
 	b.Run("found", func(b *testing.B) {
@@ -124,19 +107,139 @@ func BenchmarkLighthouseHandleRequest(b *testing.B) {
 		req := &NebulaMeta{
 			Type: NebulaMeta_HostQuery,
 			Details: &NebulaMetaDetails{
-				VpnIp:      3,
-				IpAndPorts: nil,
+				VpnIp:       3,
+				Ip4AndPorts: nil,
 			},
 		}
 		p, err := proto.Marshal(req)
 		assert.NoError(b, err)
 
 		for n := 0; n < b.N; n++ {
-			lhh.HandleRequest(rAddr, 2, p, nil, mw)
+			lhh.HandleRequest(rAddr, 2, p, mw)
 		}
 	})
 }
 
+func TestLighthouse_Memory(t *testing.T) {
+	l := NewTestLogger()
+
+	myUdpAddr0 := &udpAddr{IP: net.ParseIP("10.0.0.2"), Port: 4242}
+	myUdpAddr1 := &udpAddr{IP: net.ParseIP("192.168.0.2"), Port: 4242}
+	myUdpAddr2 := &udpAddr{IP: net.ParseIP("172.16.0.2"), Port: 4242}
+	myUdpAddr3 := &udpAddr{IP: net.ParseIP("100.152.0.2"), Port: 4242}
+	myUdpAddr4 := &udpAddr{IP: net.ParseIP("24.15.0.2"), Port: 4242}
+	myUdpAddr5 := &udpAddr{IP: net.ParseIP("192.168.0.2"), Port: 4243}
+	myUdpAddr6 := &udpAddr{IP: net.ParseIP("192.168.0.2"), Port: 4244}
+	myUdpAddr7 := &udpAddr{IP: net.ParseIP("192.168.0.2"), Port: 4245}
+	myUdpAddr8 := &udpAddr{IP: net.ParseIP("192.168.0.2"), Port: 4246}
+	myUdpAddr9 := &udpAddr{IP: net.ParseIP("192.168.0.2"), Port: 4247}
+	myUdpAddr10 := &udpAddr{IP: net.ParseIP("192.168.0.2"), Port: 4248}
+	myUdpAddr11 := &udpAddr{IP: net.ParseIP("192.168.0.2"), Port: 4249}
+	myVpnIp := ip2int(net.ParseIP("10.128.0.2"))
+
+	theirUdpAddr0 := &udpAddr{IP: net.ParseIP("10.0.0.3"), Port: 4242}
+	theirUdpAddr1 := &udpAddr{IP: net.ParseIP("192.168.0.3"), Port: 4242}
+	theirUdpAddr2 := &udpAddr{IP: net.ParseIP("172.16.0.3"), Port: 4242}
+	theirUdpAddr3 := &udpAddr{IP: net.ParseIP("100.152.0.3"), Port: 4242}
+	theirUdpAddr4 := &udpAddr{IP: net.ParseIP("24.15.0.3"), Port: 4242}
+	theirVpnIp := ip2int(net.ParseIP("10.128.0.3"))
+
+	lhIP := net.ParseIP("10.128.0.1")
+	udpServer, _ := NewListener(l, "0.0.0.0", 0, true)
+	lh := NewLightHouse(l, true, 1, []uint32{ip2int(lhIP)}, 10, 10003, udpServer, false, 1, false)
+	lhh := lh.NewRequestHandler()
+
+	// Test that my first update responds with just that
+	newLHHostUpdate(myUdpAddr0, myVpnIp, []*udpAddr{myUdpAddr1, myUdpAddr2}, lhh)
+	r := newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)
+	assertIp4InArray(t, r.msg.Details.Ip4AndPorts, myUdpAddr1, myUdpAddr2)
+
+	// Ensure we don't accumulate addresses
+	newLHHostUpdate(myUdpAddr0, myVpnIp, []*udpAddr{myUdpAddr3}, lhh)
+	r = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)
+	assertIp4InArray(t, r.msg.Details.Ip4AndPorts, myUdpAddr3)
+
+	// Grow it back to 2
+	newLHHostUpdate(myUdpAddr0, myVpnIp, []*udpAddr{myUdpAddr1, myUdpAddr4}, lhh)
+	r = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)
+	assertIp4InArray(t, r.msg.Details.Ip4AndPorts, myUdpAddr1, myUdpAddr4)
+
+	// Update a different host
+	newLHHostUpdate(theirUdpAddr0, theirVpnIp, []*udpAddr{theirUdpAddr1, theirUdpAddr2, theirUdpAddr3, theirUdpAddr4}, lhh)
+	r = newLHHostRequest(theirUdpAddr0, theirVpnIp, myVpnIp, lhh)
+	assertIp4InArray(t, r.msg.Details.Ip4AndPorts, theirUdpAddr1, theirUdpAddr2, theirUdpAddr3, theirUdpAddr4)
+
+	// Make sure we didn't get changed
+	r = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)
+	assertIp4InArray(t, r.msg.Details.Ip4AndPorts, myUdpAddr1, myUdpAddr4)
+
+	// Finally ensure proper ordering and limiting
+	// Send 12 addrs, get 10 back, one removed on a dupe check the other by limiting
+	newLHHostUpdate(
+		myUdpAddr0,
+		myVpnIp,
+		[]*udpAddr{
+			myUdpAddr1,
+			myUdpAddr2,
+			myUdpAddr3,
+			myUdpAddr4,
+			myUdpAddr5,
+			myUdpAddr5, //Duplicated on purpose
+			myUdpAddr6,
+			myUdpAddr7,
+			myUdpAddr8,
+			myUdpAddr9,
+			myUdpAddr10,
+			myUdpAddr11, // This should get cut
+		}, lhh)
+	r = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)
+	assertIp4InArray(
+		t,
+		r.msg.Details.Ip4AndPorts,
+		myUdpAddr1, myUdpAddr2, myUdpAddr3, myUdpAddr4, myUdpAddr5, myUdpAddr6, myUdpAddr7, myUdpAddr8, myUdpAddr9, myUdpAddr10,
+	)
+}
+
+func newLHHostRequest(fromAddr *udpAddr, myVpnIp, queryVpnIp uint32, lhh *LightHouseHandler) testLhReply {
+	req := &NebulaMeta{
+		Type: NebulaMeta_HostQuery,
+		Details: &NebulaMetaDetails{
+			VpnIp: queryVpnIp,
+		},
+	}
+
+	b, err := req.Marshal()
+	if err != nil {
+		panic(err)
+	}
+
+	w := &testEncWriter{}
+	lhh.HandleRequest(fromAddr, myVpnIp, b, w)
+	return w.lastReply
+}
+
+func newLHHostUpdate(fromAddr *udpAddr, vpnIp uint32, addrs []*udpAddr, lhh *LightHouseHandler) {
+	req := &NebulaMeta{
+		Type: NebulaMeta_HostUpdateNotification,
+		Details: &NebulaMetaDetails{
+			VpnIp:       vpnIp,
+			Ip4AndPorts: make([]*Ip4AndPort, len(addrs)),
+		},
+	}
+
+	for k, v := range addrs {
+		req.Details.Ip4AndPorts[k] = &Ip4AndPort{Ip: ip2int(v.IP), Port: uint32(v.Port)}
+	}
+
+	b, err := req.Marshal()
+	if err != nil {
+		panic(err)
+	}
+
+	w := &testEncWriter{}
+	lhh.HandleRequest(fromAddr, vpnIp, b, w)
+}
+
 func Test_lhRemoteAllowList(t *testing.T) {
 	l := NewTestLogger()
 	c := NewConfig(l)
@@ -154,48 +257,94 @@ func Test_lhRemoteAllowList(t *testing.T) {
 	lh := NewLightHouse(l, true, 1, []uint32{ip2int(lh1IP)}, 10, 10003, udpServer, false, 1, false)
 	lh.SetRemoteAllowList(allowList)
 
-	remote1 := "10.20.0.3"
-	remote1IP := net.ParseIP(remote1)
+	// A disallowed ip should not enter the cache but we should end up with an empty entry in the addrMap
+	remote1IP := net.ParseIP("10.20.0.3")
 	lh.AddRemote(ip2int(remote1IP), NewUDPAddr(remote1IP, uint16(4242)), true)
-	assert.Nil(t, lh.addrMap[ip2int(remote1IP)])
+	assert.NotNil(t, lh.addrMap[ip2int(remote1IP)])
+	assert.Empty(t, lh.addrMap[ip2int(remote1IP)].v4)
+	assert.Empty(t, lh.addrMap[ip2int(remote1IP)].v6)
 
-	remote2 := "10.128.0.3"
-	remote2IP := net.ParseIP(remote2)
+	// Make sure a good ip enters the cache and addrMap
+	remote2IP := net.ParseIP("10.128.0.3")
 	remote2UDPAddr := NewUDPAddr(remote2IP, uint16(4242))
-
 	lh.AddRemote(ip2int(remote2IP), remote2UDPAddr, true)
-	// Make sure the pointers are different but the contents are equal since we are using slices
-	assert.False(t, remote2UDPAddr == lh.addrMap[ip2int(remote2IP)][0])
-	assert.Equal(t, remote2UDPAddr, lh.addrMap[ip2int(remote2IP)][0])
+	assertIp4InArray(t, lh.addrMap[ip2int(remote2IP)].learnedV4, remote2UDPAddr)
+
+	// Another good ip gets into the cache, ordering is inverted
+	remote3IP := net.ParseIP("10.128.0.4")
+	remote3UDPAddr := NewUDPAddr(remote3IP, uint16(4243))
+	lh.AddRemote(ip2int(remote2IP), remote3UDPAddr, true)
+	assertIp4InArray(t, lh.addrMap[ip2int(remote2IP)].learnedV4, remote3UDPAddr, remote2UDPAddr)
+
+	// If we exceed the length limit we should only have the most recent addresses
+	addedAddrs := []*udpAddr{}
+	for i := 0; i < 11; i++ {
+		remoteUDPAddr := NewUDPAddr(net.IP{10, 128, 0, 4}, uint16(4243+i))
+		lh.AddRemote(ip2int(remote2IP), remoteUDPAddr, true)
+		// The first entry here is a duplicate, don't add it to the assert list
+		if i != 0 {
+			addedAddrs = append(addedAddrs, remoteUDPAddr)
+		}
+	}
+
+	// We should only have the last 10 of what we tried to add
+	assert.True(t, len(addedAddrs) >= 10, "We should have tried to add at least 10 addresses")
+	ln := len(addedAddrs)
+	assertIp4InArray(
+		t,
+		lh.addrMap[ip2int(remote2IP)].learnedV4,
+		addedAddrs[ln-1],
+		addedAddrs[ln-2],
+		addedAddrs[ln-3],
+		addedAddrs[ln-4],
+		addedAddrs[ln-5],
+		addedAddrs[ln-6],
+		addedAddrs[ln-7],
+		addedAddrs[ln-8],
+		addedAddrs[ln-9],
+		addedAddrs[ln-10],
+	)
 }
 
-//func NewLightHouse(amLighthouse bool, myIp uint32, ips []string, interval int, nebulaPort int, pc *udpConn, punchBack bool) *LightHouse {
+type testLhReply struct {
+	nebType    NebulaMessageType
+	nebSubType NebulaMessageSubType
+	vpnIp      uint32
+	msg        *NebulaMeta
+}
 
-/*
-func TestLHQuery(t *testing.T) {
-	//n := NewLhQueryByIpString("10.128.0.3")
-	_, myNet, _ := net.ParseCIDR("10.128.0.0/16")
-	m := NewHostMap(myNet)
-	y, _ := net.ResolveUDPAddr("udp", "10.128.0.3:11111")
-	m.Add(ip2int(net.ParseIP("127.0.0.1")), y)
-	//t.Errorf("%s", m)
-	_ = m
+type testEncWriter struct {
+	lastReply testLhReply
+}
 
-	_, n, _ := net.ParseCIDR("127.0.0.1/8")
+func (tw *testEncWriter) SendMessageToVpnIp(t NebulaMessageType, st NebulaMessageSubType, vpnIp uint32, p, _, _ []byte) {
+	tw.lastReply = testLhReply{
+		nebType:    t,
+		nebSubType: st,
+		vpnIp:      vpnIp,
+		msg:        &NebulaMeta{},
+	}
 
-	/*udpServer, err := net.ListenUDP("udp", &net.UDPAddr{Port: 10009})
+	err := proto.Unmarshal(p, tw.lastReply.msg)
 	if err != nil {
-		t.Errorf("%s", err)
+		panic(err)
 	}
+}
 
-	meh := NewLightHouse(n, m, []string{"10.128.0.2"}, false, 10, 10003, 10004)
-	//t.Error(m.Hosts)
-	meh2, err := meh.Query(ip2int(net.ParseIP("10.128.0.3")))
-	t.Error(err)
-	if err != nil {
-		return
+// assertIp4InArray asserts every address in want is at the same position in have and that the lengths match
+func assertIp4InArray(t *testing.T, have []*Ip4AndPort, want ...*udpAddr) {
+	assert.Len(t, have, len(want))
+	for k, w := range want {
+		if !(have[k].Ip == ip2int(w.IP) && have[k].Port == uint32(w.Port)) {
+			assert.Fail(t, fmt.Sprintf("Response did not contain: %v:%v at %v; %v", w.IP, w.Port, k, translateV4toUdpAddr(have)))
+		}
+	}
+}
+
+func translateV4toUdpAddr(ips []*Ip4AndPort) []*udpAddr {
+	addrs := make([]*udpAddr, len(ips))
+	for k, v := range ips {
+		addrs[k] = NewUDPAddrFromLH4(v)
 	}
-	t.Errorf("%s", meh2)
-	t.Errorf("%s", n)
+	return addrs
 }
-*/

+ 1683 - 569
nebula.pb.go

@@ -1,24 +1,26 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// 	protoc-gen-go v1.26.0
-// 	protoc        v3.14.0
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
 // source: nebula.proto
 
 package nebula
 
 import (
-	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
-	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
-	reflect "reflect"
-	sync "sync"
+	fmt "fmt"
+	proto "github.com/gogo/protobuf/proto"
+	io "io"
+	math "math"
+	math_bits "math/bits"
 )
 
-const (
-	// Verify that this generated code is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
-	// Verify that runtime/protoimpl is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 
 type NebulaMeta_MessageType int32
 
@@ -35,59 +37,38 @@ const (
 	NebulaMeta_PathCheckReply         NebulaMeta_MessageType = 9
 )
 
-// Enum value maps for NebulaMeta_MessageType.
-var (
-	NebulaMeta_MessageType_name = map[int32]string{
-		0: "None",
-		1: "HostQuery",
-		2: "HostQueryReply",
-		3: "HostUpdateNotification",
-		4: "HostMovedNotification",
-		5: "HostPunchNotification",
-		6: "HostWhoami",
-		7: "HostWhoamiReply",
-		8: "PathCheck",
-		9: "PathCheckReply",
-	}
-	NebulaMeta_MessageType_value = map[string]int32{
-		"None":                   0,
-		"HostQuery":              1,
-		"HostQueryReply":         2,
-		"HostUpdateNotification": 3,
-		"HostMovedNotification":  4,
-		"HostPunchNotification":  5,
-		"HostWhoami":             6,
-		"HostWhoamiReply":        7,
-		"PathCheck":              8,
-		"PathCheckReply":         9,
-	}
-)
-
-func (x NebulaMeta_MessageType) Enum() *NebulaMeta_MessageType {
-	p := new(NebulaMeta_MessageType)
-	*p = x
-	return p
+var NebulaMeta_MessageType_name = map[int32]string{
+	0: "None",
+	1: "HostQuery",
+	2: "HostQueryReply",
+	3: "HostUpdateNotification",
+	4: "HostMovedNotification",
+	5: "HostPunchNotification",
+	6: "HostWhoami",
+	7: "HostWhoamiReply",
+	8: "PathCheck",
+	9: "PathCheckReply",
+}
+
+var NebulaMeta_MessageType_value = map[string]int32{
+	"None":                   0,
+	"HostQuery":              1,
+	"HostQueryReply":         2,
+	"HostUpdateNotification": 3,
+	"HostMovedNotification":  4,
+	"HostPunchNotification":  5,
+	"HostWhoami":             6,
+	"HostWhoamiReply":        7,
+	"PathCheck":              8,
+	"PathCheckReply":         9,
 }
 
 func (x NebulaMeta_MessageType) String() string {
-	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (NebulaMeta_MessageType) Descriptor() protoreflect.EnumDescriptor {
-	return file_nebula_proto_enumTypes[0].Descriptor()
-}
-
-func (NebulaMeta_MessageType) Type() protoreflect.EnumType {
-	return &file_nebula_proto_enumTypes[0]
-}
-
-func (x NebulaMeta_MessageType) Number() protoreflect.EnumNumber {
-	return protoreflect.EnumNumber(x)
+	return proto.EnumName(NebulaMeta_MessageType_name, int32(x))
 }
 
-// Deprecated: Use NebulaMeta_MessageType.Descriptor instead.
 func (NebulaMeta_MessageType) EnumDescriptor() ([]byte, []int) {
-	return file_nebula_proto_rawDescGZIP(), []int{0, 0}
+	return fileDescriptor_2d65afa7693df5ef, []int{0, 0}
 }
 
 type NebulaPing_MessageType int32
@@ -97,689 +78,1822 @@ const (
 	NebulaPing_Reply NebulaPing_MessageType = 1
 )
 
-// Enum value maps for NebulaPing_MessageType.
-var (
-	NebulaPing_MessageType_name = map[int32]string{
-		0: "Ping",
-		1: "Reply",
-	}
-	NebulaPing_MessageType_value = map[string]int32{
-		"Ping":  0,
-		"Reply": 1,
-	}
-)
-
-func (x NebulaPing_MessageType) Enum() *NebulaPing_MessageType {
-	p := new(NebulaPing_MessageType)
-	*p = x
-	return p
-}
-
-func (x NebulaPing_MessageType) String() string {
-	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (NebulaPing_MessageType) Descriptor() protoreflect.EnumDescriptor {
-	return file_nebula_proto_enumTypes[1].Descriptor()
+var NebulaPing_MessageType_name = map[int32]string{
+	0: "Ping",
+	1: "Reply",
 }
 
-func (NebulaPing_MessageType) Type() protoreflect.EnumType {
-	return &file_nebula_proto_enumTypes[1]
+var NebulaPing_MessageType_value = map[string]int32{
+	"Ping":  0,
+	"Reply": 1,
 }
 
-func (x NebulaPing_MessageType) Number() protoreflect.EnumNumber {
-	return protoreflect.EnumNumber(x)
+func (x NebulaPing_MessageType) String() string {
+	return proto.EnumName(NebulaPing_MessageType_name, int32(x))
 }
 
-// Deprecated: Use NebulaPing_MessageType.Descriptor instead.
 func (NebulaPing_MessageType) EnumDescriptor() ([]byte, []int) {
-	return file_nebula_proto_rawDescGZIP(), []int{4, 0}
+	return fileDescriptor_2d65afa7693df5ef, []int{4, 0}
 }
 
 type NebulaMeta struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
 	Type    NebulaMeta_MessageType `protobuf:"varint,1,opt,name=Type,proto3,enum=nebula.NebulaMeta_MessageType" json:"Type,omitempty"`
 	Details *NebulaMetaDetails     `protobuf:"bytes,2,opt,name=Details,proto3" json:"Details,omitempty"`
 }
 
-func (x *NebulaMeta) Reset() {
-	*x = NebulaMeta{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_nebula_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
+func (m *NebulaMeta) Reset()         { *m = NebulaMeta{} }
+func (m *NebulaMeta) String() string { return proto.CompactTextString(m) }
+func (*NebulaMeta) ProtoMessage()    {}
+func (*NebulaMeta) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2d65afa7693df5ef, []int{0}
+}
+func (m *NebulaMeta) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *NebulaMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_NebulaMeta.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
 	}
 }
-
-func (x *NebulaMeta) String() string {
-	return protoimpl.X.MessageStringOf(x)
+func (m *NebulaMeta) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_NebulaMeta.Merge(m, src)
 }
-
-func (*NebulaMeta) ProtoMessage() {}
-
-func (x *NebulaMeta) ProtoReflect() protoreflect.Message {
-	mi := &file_nebula_proto_msgTypes[0]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
+func (m *NebulaMeta) XXX_Size() int {
+	return m.Size()
 }
-
-// Deprecated: Use NebulaMeta.ProtoReflect.Descriptor instead.
-func (*NebulaMeta) Descriptor() ([]byte, []int) {
-	return file_nebula_proto_rawDescGZIP(), []int{0}
+func (m *NebulaMeta) XXX_DiscardUnknown() {
+	xxx_messageInfo_NebulaMeta.DiscardUnknown(m)
 }
 
-func (x *NebulaMeta) GetType() NebulaMeta_MessageType {
-	if x != nil {
-		return x.Type
+var xxx_messageInfo_NebulaMeta proto.InternalMessageInfo
+
+func (m *NebulaMeta) GetType() NebulaMeta_MessageType {
+	if m != nil {
+		return m.Type
 	}
 	return NebulaMeta_None
 }
 
-func (x *NebulaMeta) GetDetails() *NebulaMetaDetails {
-	if x != nil {
-		return x.Details
+func (m *NebulaMeta) GetDetails() *NebulaMetaDetails {
+	if m != nil {
+		return m.Details
 	}
 	return nil
 }
 
 type NebulaMetaDetails struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
 	VpnIp       uint32        `protobuf:"varint,1,opt,name=VpnIp,proto3" json:"VpnIp,omitempty"`
-	IpAndPorts  []*IpAndPort  `protobuf:"bytes,2,rep,name=IpAndPorts,proto3" json:"IpAndPorts,omitempty"`
+	Ip4AndPorts []*Ip4AndPort `protobuf:"bytes,2,rep,name=Ip4AndPorts,proto3" json:"Ip4AndPorts,omitempty"`
 	Ip6AndPorts []*Ip6AndPort `protobuf:"bytes,4,rep,name=Ip6AndPorts,proto3" json:"Ip6AndPorts,omitempty"`
 	Counter     uint32        `protobuf:"varint,3,opt,name=counter,proto3" json:"counter,omitempty"`
 }
 
-func (x *NebulaMetaDetails) Reset() {
-	*x = NebulaMetaDetails{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_nebula_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
+func (m *NebulaMetaDetails) Reset()         { *m = NebulaMetaDetails{} }
+func (m *NebulaMetaDetails) String() string { return proto.CompactTextString(m) }
+func (*NebulaMetaDetails) ProtoMessage()    {}
+func (*NebulaMetaDetails) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2d65afa7693df5ef, []int{1}
+}
+func (m *NebulaMetaDetails) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *NebulaMetaDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_NebulaMetaDetails.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
 	}
 }
-
-func (x *NebulaMetaDetails) String() string {
-	return protoimpl.X.MessageStringOf(x)
+func (m *NebulaMetaDetails) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_NebulaMetaDetails.Merge(m, src)
 }
-
-func (*NebulaMetaDetails) ProtoMessage() {}
-
-func (x *NebulaMetaDetails) ProtoReflect() protoreflect.Message {
-	mi := &file_nebula_proto_msgTypes[1]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
+func (m *NebulaMetaDetails) XXX_Size() int {
+	return m.Size()
 }
-
-// Deprecated: Use NebulaMetaDetails.ProtoReflect.Descriptor instead.
-func (*NebulaMetaDetails) Descriptor() ([]byte, []int) {
-	return file_nebula_proto_rawDescGZIP(), []int{1}
+func (m *NebulaMetaDetails) XXX_DiscardUnknown() {
+	xxx_messageInfo_NebulaMetaDetails.DiscardUnknown(m)
 }
 
-func (x *NebulaMetaDetails) GetVpnIp() uint32 {
-	if x != nil {
-		return x.VpnIp
+var xxx_messageInfo_NebulaMetaDetails proto.InternalMessageInfo
+
+func (m *NebulaMetaDetails) GetVpnIp() uint32 {
+	if m != nil {
+		return m.VpnIp
 	}
 	return 0
 }
 
-func (x *NebulaMetaDetails) GetIpAndPorts() []*IpAndPort {
-	if x != nil {
-		return x.IpAndPorts
+func (m *NebulaMetaDetails) GetIp4AndPorts() []*Ip4AndPort {
+	if m != nil {
+		return m.Ip4AndPorts
 	}
 	return nil
 }
 
-func (x *NebulaMetaDetails) GetIp6AndPorts() []*Ip6AndPort {
-	if x != nil {
-		return x.Ip6AndPorts
+func (m *NebulaMetaDetails) GetIp6AndPorts() []*Ip6AndPort {
+	if m != nil {
+		return m.Ip6AndPorts
 	}
 	return nil
 }
 
-func (x *NebulaMetaDetails) GetCounter() uint32 {
-	if x != nil {
-		return x.Counter
+func (m *NebulaMetaDetails) GetCounter() uint32 {
+	if m != nil {
+		return m.Counter
 	}
 	return 0
 }
 
-type IpAndPort struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
+type Ip4AndPort struct {
 	Ip   uint32 `protobuf:"varint,1,opt,name=Ip,proto3" json:"Ip,omitempty"`
 	Port uint32 `protobuf:"varint,2,opt,name=Port,proto3" json:"Port,omitempty"`
 }
 
-func (x *IpAndPort) Reset() {
-	*x = IpAndPort{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_nebula_proto_msgTypes[2]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
+func (m *Ip4AndPort) Reset()         { *m = Ip4AndPort{} }
+func (m *Ip4AndPort) String() string { return proto.CompactTextString(m) }
+func (*Ip4AndPort) ProtoMessage()    {}
+func (*Ip4AndPort) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2d65afa7693df5ef, []int{2}
+}
+func (m *Ip4AndPort) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Ip4AndPort) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Ip4AndPort.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *Ip4AndPort) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Ip4AndPort.Merge(m, src)
+}
+func (m *Ip4AndPort) XXX_Size() int {
+	return m.Size()
+}
+func (m *Ip4AndPort) XXX_DiscardUnknown() {
+	xxx_messageInfo_Ip4AndPort.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Ip4AndPort proto.InternalMessageInfo
+
+func (m *Ip4AndPort) GetIp() uint32 {
+	if m != nil {
+		return m.Ip
 	}
+	return 0
 }
 
-func (x *IpAndPort) String() string {
-	return protoimpl.X.MessageStringOf(x)
+func (m *Ip4AndPort) GetPort() uint32 {
+	if m != nil {
+		return m.Port
+	}
+	return 0
 }
 
-func (*IpAndPort) ProtoMessage() {}
+type Ip6AndPort struct {
+	Ip   []byte `protobuf:"bytes,1,opt,name=Ip,proto3" json:"Ip,omitempty"`
+	Port uint32 `protobuf:"varint,2,opt,name=Port,proto3" json:"Port,omitempty"`
+}
 
-func (x *IpAndPort) ProtoReflect() protoreflect.Message {
-	mi := &file_nebula_proto_msgTypes[2]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
+func (m *Ip6AndPort) Reset()         { *m = Ip6AndPort{} }
+func (m *Ip6AndPort) String() string { return proto.CompactTextString(m) }
+func (*Ip6AndPort) ProtoMessage()    {}
+func (*Ip6AndPort) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2d65afa7693df5ef, []int{3}
+}
+func (m *Ip6AndPort) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Ip6AndPort) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Ip6AndPort.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
 		}
-		return ms
+		return b[:n], nil
 	}
-	return mi.MessageOf(x)
 }
+func (m *Ip6AndPort) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Ip6AndPort.Merge(m, src)
+}
+func (m *Ip6AndPort) XXX_Size() int {
+	return m.Size()
+}
+func (m *Ip6AndPort) XXX_DiscardUnknown() {
+	xxx_messageInfo_Ip6AndPort.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Ip6AndPort proto.InternalMessageInfo
 
-// Deprecated: Use IpAndPort.ProtoReflect.Descriptor instead.
-func (*IpAndPort) Descriptor() ([]byte, []int) {
-	return file_nebula_proto_rawDescGZIP(), []int{2}
+func (m *Ip6AndPort) GetIp() []byte {
+	if m != nil {
+		return m.Ip
+	}
+	return nil
 }
 
-func (x *IpAndPort) GetIp() uint32 {
-	if x != nil {
-		return x.Ip
+func (m *Ip6AndPort) GetPort() uint32 {
+	if m != nil {
+		return m.Port
 	}
 	return 0
 }
 
-func (x *IpAndPort) GetPort() uint32 {
-	if x != nil {
-		return x.Port
+type NebulaPing struct {
+	Type NebulaPing_MessageType `protobuf:"varint,1,opt,name=Type,proto3,enum=nebula.NebulaPing_MessageType" json:"Type,omitempty"`
+	Time uint64                 `protobuf:"varint,2,opt,name=Time,proto3" json:"Time,omitempty"`
+}
+
+func (m *NebulaPing) Reset()         { *m = NebulaPing{} }
+func (m *NebulaPing) String() string { return proto.CompactTextString(m) }
+func (*NebulaPing) ProtoMessage()    {}
+func (*NebulaPing) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2d65afa7693df5ef, []int{4}
+}
+func (m *NebulaPing) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *NebulaPing) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_NebulaPing.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *NebulaPing) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_NebulaPing.Merge(m, src)
+}
+func (m *NebulaPing) XXX_Size() int {
+	return m.Size()
+}
+func (m *NebulaPing) XXX_DiscardUnknown() {
+	xxx_messageInfo_NebulaPing.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_NebulaPing proto.InternalMessageInfo
+
+func (m *NebulaPing) GetType() NebulaPing_MessageType {
+	if m != nil {
+		return m.Type
+	}
+	return NebulaPing_Ping
+}
+
+func (m *NebulaPing) GetTime() uint64 {
+	if m != nil {
+		return m.Time
 	}
 	return 0
 }
 
-type Ip6AndPort struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
+type NebulaHandshake struct {
+	Details *NebulaHandshakeDetails `protobuf:"bytes,1,opt,name=Details,proto3" json:"Details,omitempty"`
+	Hmac    []byte                  `protobuf:"bytes,2,opt,name=Hmac,proto3" json:"Hmac,omitempty"`
+}
 
-	Ip   []byte `protobuf:"bytes,1,opt,name=Ip,proto3" json:"Ip,omitempty"`
-	Port uint32 `protobuf:"varint,2,opt,name=Port,proto3" json:"Port,omitempty"`
+func (m *NebulaHandshake) Reset()         { *m = NebulaHandshake{} }
+func (m *NebulaHandshake) String() string { return proto.CompactTextString(m) }
+func (*NebulaHandshake) ProtoMessage()    {}
+func (*NebulaHandshake) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2d65afa7693df5ef, []int{5}
+}
+func (m *NebulaHandshake) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *NebulaHandshake) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_NebulaHandshake.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *NebulaHandshake) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_NebulaHandshake.Merge(m, src)
+}
+func (m *NebulaHandshake) XXX_Size() int {
+	return m.Size()
 }
+func (m *NebulaHandshake) XXX_DiscardUnknown() {
+	xxx_messageInfo_NebulaHandshake.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_NebulaHandshake proto.InternalMessageInfo
 
-func (x *Ip6AndPort) Reset() {
-	*x = Ip6AndPort{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_nebula_proto_msgTypes[3]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
+func (m *NebulaHandshake) GetDetails() *NebulaHandshakeDetails {
+	if m != nil {
+		return m.Details
 	}
+	return nil
 }
 
-func (x *Ip6AndPort) String() string {
-	return protoimpl.X.MessageStringOf(x)
+func (m *NebulaHandshake) GetHmac() []byte {
+	if m != nil {
+		return m.Hmac
+	}
+	return nil
 }
 
-func (*Ip6AndPort) ProtoMessage() {}
+type NebulaHandshakeDetails struct {
+	Cert           []byte `protobuf:"bytes,1,opt,name=Cert,proto3" json:"Cert,omitempty"`
+	InitiatorIndex uint32 `protobuf:"varint,2,opt,name=InitiatorIndex,proto3" json:"InitiatorIndex,omitempty"`
+	ResponderIndex uint32 `protobuf:"varint,3,opt,name=ResponderIndex,proto3" json:"ResponderIndex,omitempty"`
+	Cookie         uint64 `protobuf:"varint,4,opt,name=Cookie,proto3" json:"Cookie,omitempty"`
+	Time           uint64 `protobuf:"varint,5,opt,name=Time,proto3" json:"Time,omitempty"`
+}
 
-func (x *Ip6AndPort) ProtoReflect() protoreflect.Message {
-	mi := &file_nebula_proto_msgTypes[3]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
+func (m *NebulaHandshakeDetails) Reset()         { *m = NebulaHandshakeDetails{} }
+func (m *NebulaHandshakeDetails) String() string { return proto.CompactTextString(m) }
+func (*NebulaHandshakeDetails) ProtoMessage()    {}
+func (*NebulaHandshakeDetails) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2d65afa7693df5ef, []int{6}
+}
+func (m *NebulaHandshakeDetails) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *NebulaHandshakeDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_NebulaHandshakeDetails.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
 		}
-		return ms
+		return b[:n], nil
 	}
-	return mi.MessageOf(x)
 }
-
-// Deprecated: Use Ip6AndPort.ProtoReflect.Descriptor instead.
-func (*Ip6AndPort) Descriptor() ([]byte, []int) {
-	return file_nebula_proto_rawDescGZIP(), []int{3}
+func (m *NebulaHandshakeDetails) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_NebulaHandshakeDetails.Merge(m, src)
 }
+func (m *NebulaHandshakeDetails) XXX_Size() int {
+	return m.Size()
+}
+func (m *NebulaHandshakeDetails) XXX_DiscardUnknown() {
+	xxx_messageInfo_NebulaHandshakeDetails.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_NebulaHandshakeDetails proto.InternalMessageInfo
 
-func (x *Ip6AndPort) GetIp() []byte {
-	if x != nil {
-		return x.Ip
+func (m *NebulaHandshakeDetails) GetCert() []byte {
+	if m != nil {
+		return m.Cert
 	}
 	return nil
 }
 
-func (x *Ip6AndPort) GetPort() uint32 {
-	if x != nil {
-		return x.Port
+func (m *NebulaHandshakeDetails) GetInitiatorIndex() uint32 {
+	if m != nil {
+		return m.InitiatorIndex
 	}
 	return 0
 }
 
-type NebulaPing struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
+func (m *NebulaHandshakeDetails) GetResponderIndex() uint32 {
+	if m != nil {
+		return m.ResponderIndex
+	}
+	return 0
+}
 
-	Type NebulaPing_MessageType `protobuf:"varint,1,opt,name=Type,proto3,enum=nebula.NebulaPing_MessageType" json:"Type,omitempty"`
-	Time uint64                 `protobuf:"varint,2,opt,name=Time,proto3" json:"Time,omitempty"`
+func (m *NebulaHandshakeDetails) GetCookie() uint64 {
+	if m != nil {
+		return m.Cookie
+	}
+	return 0
 }
 
-func (x *NebulaPing) Reset() {
-	*x = NebulaPing{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_nebula_proto_msgTypes[4]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
+func (m *NebulaHandshakeDetails) GetTime() uint64 {
+	if m != nil {
+		return m.Time
 	}
+	return 0
 }
 
-func (x *NebulaPing) String() string {
-	return protoimpl.X.MessageStringOf(x)
+func init() {
+	proto.RegisterEnum("nebula.NebulaMeta_MessageType", NebulaMeta_MessageType_name, NebulaMeta_MessageType_value)
+	proto.RegisterEnum("nebula.NebulaPing_MessageType", NebulaPing_MessageType_name, NebulaPing_MessageType_value)
+	proto.RegisterType((*NebulaMeta)(nil), "nebula.NebulaMeta")
+	proto.RegisterType((*NebulaMetaDetails)(nil), "nebula.NebulaMetaDetails")
+	proto.RegisterType((*Ip4AndPort)(nil), "nebula.Ip4AndPort")
+	proto.RegisterType((*Ip6AndPort)(nil), "nebula.Ip6AndPort")
+	proto.RegisterType((*NebulaPing)(nil), "nebula.NebulaPing")
+	proto.RegisterType((*NebulaHandshake)(nil), "nebula.NebulaHandshake")
+	proto.RegisterType((*NebulaHandshakeDetails)(nil), "nebula.NebulaHandshakeDetails")
+}
+
+func init() { proto.RegisterFile("nebula.proto", fileDescriptor_2d65afa7693df5ef) }
+
+var fileDescriptor_2d65afa7693df5ef = []byte{
+	// 527 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xbb, 0x8e, 0xda, 0x40,
+	0x14, 0x65, 0xc0, 0xc0, 0x72, 0x79, 0xac, 0x73, 0x93, 0x20, 0x6f, 0x0a, 0x6b, 0xe5, 0x22, 0xa2,
+	0x42, 0x11, 0xbb, 0x42, 0x69, 0x13, 0x52, 0x40, 0x01, 0x22, 0xd6, 0x26, 0x29, 0xa3, 0x59, 0x3c,
+	0x59, 0x2c, 0x60, 0xc6, 0xb2, 0x87, 0x68, 0xf9, 0x8b, 0x7c, 0x46, 0xba, 0xfc, 0x46, 0x8a, 0x14,
+	0x5b, 0xa4, 0x48, 0x19, 0xc1, 0x8f, 0x44, 0x33, 0x36, 0x36, 0x6b, 0x36, 0xdb, 0xdd, 0xc7, 0x39,
+	0x97, 0xc3, 0x99, 0x23, 0x43, 0x83, 0xb3, 0xeb, 0xf5, 0x92, 0x76, 0x83, 0x50, 0x48, 0x81, 0x95,
+	0xb8, 0x73, 0x7e, 0x15, 0x01, 0x26, 0xba, 0x1c, 0x33, 0x49, 0xb1, 0x07, 0xc6, 0xd5, 0x26, 0x60,
+	0x16, 0x39, 0x27, 0x9d, 0x56, 0xcf, 0xee, 0x26, 0x9c, 0x0c, 0xd1, 0x1d, 0xb3, 0x28, 0xa2, 0x37,
+	0x4c, 0xa1, 0x5c, 0x8d, 0xc5, 0x0b, 0xa8, 0xbe, 0x63, 0x92, 0xfa, 0xcb, 0xc8, 0x2a, 0x9e, 0x93,
+	0x4e, 0xbd, 0x77, 0x76, 0x4c, 0x4b, 0x00, 0xee, 0x1e, 0xe9, 0xfc, 0x26, 0x50, 0x3f, 0x38, 0x85,
+	0x27, 0x60, 0x4c, 0x04, 0x67, 0x66, 0x01, 0x9b, 0x50, 0x1b, 0x8a, 0x48, 0xbe, 0x5f, 0xb3, 0x70,
+	0x63, 0x12, 0x44, 0x68, 0xa5, 0xad, 0xcb, 0x82, 0xe5, 0xc6, 0x2c, 0xe2, 0x0b, 0x68, 0xab, 0xd9,
+	0x87, 0xc0, 0xa3, 0x92, 0x4d, 0x84, 0xf4, 0xbf, 0xf8, 0x33, 0x2a, 0x7d, 0xc1, 0xcd, 0x12, 0x9e,
+	0xc1, 0x73, 0xb5, 0x1b, 0x8b, 0xaf, 0xcc, 0xbb, 0xb7, 0x32, 0xf6, 0xab, 0xe9, 0x9a, 0xcf, 0xe6,
+	0xf7, 0x56, 0x65, 0x6c, 0x01, 0xa8, 0xd5, 0xa7, 0xb9, 0xa0, 0x2b, 0xdf, 0xac, 0xe0, 0x53, 0x38,
+	0xcd, 0xfa, 0xf8, 0x67, 0xab, 0x4a, 0xd9, 0x94, 0xca, 0xf9, 0x60, 0xce, 0x66, 0x0b, 0xf3, 0x44,
+	0x29, 0x4b, 0xdb, 0x18, 0x52, 0x73, 0x7e, 0x10, 0x78, 0x72, 0xf4, 0xaf, 0xf1, 0x19, 0x94, 0x3f,
+	0x06, 0x7c, 0x14, 0x68, 0x5b, 0x9b, 0x6e, 0xdc, 0xe0, 0x25, 0xd4, 0x47, 0xc1, 0xe5, 0x1b, 0xee,
+	0x4d, 0x45, 0x28, 0x95, 0x77, 0xa5, 0x4e, 0xbd, 0x87, 0x7b, 0xef, 0xb2, 0x95, 0x7b, 0x08, 0x8b,
+	0x59, 0xfd, 0x94, 0x65, 0xe4, 0x59, 0xfd, 0x03, 0x56, 0x0a, 0x43, 0x0b, 0xaa, 0x33, 0xb1, 0xe6,
+	0x92, 0x85, 0x56, 0x49, 0x6b, 0xd8, 0xb7, 0xce, 0x2b, 0x80, 0xec, 0x3c, 0xb6, 0xa0, 0x98, 0xca,
+	0x2c, 0x8e, 0x02, 0x44, 0x30, 0xd4, 0x5c, 0x3f, 0x6c, 0xd3, 0xd5, 0x75, 0xcc, 0xe8, 0x1f, 0x33,
+	0x1a, 0xff, 0x65, 0xdc, 0xee, 0x33, 0x36, 0xf5, 0xf9, 0xcd, 0xe3, 0x19, 0x53, 0x88, 0x07, 0x32,
+	0x86, 0x60, 0x5c, 0xf9, 0x2b, 0xa6, 0xaf, 0x1a, 0xae, 0xae, 0x1d, 0xe7, 0x28, 0x41, 0x8a, 0x6c,
+	0x16, 0xb0, 0x06, 0xe5, 0xf8, 0x3d, 0x88, 0xf3, 0x19, 0x4e, 0xe3, 0xbb, 0x43, 0xca, 0xbd, 0x68,
+	0x4e, 0x17, 0x0c, 0x5f, 0x67, 0x71, 0x25, 0x3a, 0xae, 0x39, 0x05, 0x29, 0x32, 0x9f, 0x59, 0x25,
+	0x62, 0xb8, 0xa2, 0x33, 0x2d, 0xa2, 0xe1, 0xea, 0xda, 0xf9, 0x4e, 0xa0, 0xfd, 0x30, 0x4f, 0xc1,
+	0x07, 0x2c, 0x94, 0x89, 0x37, 0xba, 0xc6, 0x97, 0xd0, 0x1a, 0x71, 0x5f, 0xfa, 0x54, 0x8a, 0x70,
+	0xc4, 0x3d, 0x76, 0x9b, 0xf8, 0x94, 0x9b, 0x2a, 0x9c, 0xcb, 0xa2, 0x40, 0x70, 0x8f, 0x25, 0xb8,
+	0xf8, 0xd9, 0x72, 0x53, 0x6c, 0x43, 0x65, 0x20, 0xc4, 0xc2, 0x67, 0x96, 0xa1, 0x9d, 0x49, 0xba,
+	0xd4, 0xaf, 0x72, 0xe6, 0xd7, 0x5b, 0xeb, 0xe7, 0xd6, 0x26, 0x77, 0x5b, 0x9b, 0xfc, 0xdd, 0xda,
+	0xe4, 0xdb, 0xce, 0x2e, 0xdc, 0xed, 0xec, 0xc2, 0x9f, 0x9d, 0x5d, 0xb8, 0xae, 0xe8, 0x6f, 0xc2,
+	0xc5, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x36, 0xec, 0xba, 0x62, 0x23, 0x04, 0x00, 0x00,
+}
+
+func (m *NebulaMeta) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
 }
 
-func (*NebulaPing) ProtoMessage() {}
+func (m *NebulaMeta) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
 
-func (x *NebulaPing) ProtoReflect() protoreflect.Message {
-	mi := &file_nebula_proto_msgTypes[4]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
+func (m *NebulaMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.Details != nil {
+		{
+			size, err := m.Details.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintNebula(dAtA, i, uint64(size))
 		}
-		return ms
+		i--
+		dAtA[i] = 0x12
 	}
-	return mi.MessageOf(x)
+	if m.Type != 0 {
+		i = encodeVarintNebula(dAtA, i, uint64(m.Type))
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
 }
 
-// Deprecated: Use NebulaPing.ProtoReflect.Descriptor instead.
-func (*NebulaPing) Descriptor() ([]byte, []int) {
-	return file_nebula_proto_rawDescGZIP(), []int{4}
+func (m *NebulaMetaDetails) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *NebulaMetaDetails) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *NebulaMetaDetails) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Ip6AndPorts) > 0 {
+		for iNdEx := len(m.Ip6AndPorts) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Ip6AndPorts[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintNebula(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x22
+		}
+	}
+	if m.Counter != 0 {
+		i = encodeVarintNebula(dAtA, i, uint64(m.Counter))
+		i--
+		dAtA[i] = 0x18
+	}
+	if len(m.Ip4AndPorts) > 0 {
+		for iNdEx := len(m.Ip4AndPorts) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Ip4AndPorts[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintNebula(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	if m.VpnIp != 0 {
+		i = encodeVarintNebula(dAtA, i, uint64(m.VpnIp))
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
 }
 
-func (x *NebulaPing) GetType() NebulaPing_MessageType {
-	if x != nil {
-		return x.Type
+func (m *Ip4AndPort) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
 	}
-	return NebulaPing_Ping
+	return dAtA[:n], nil
 }
 
-func (x *NebulaPing) GetTime() uint64 {
-	if x != nil {
-		return x.Time
+func (m *Ip4AndPort) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Ip4AndPort) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.Port != 0 {
+		i = encodeVarintNebula(dAtA, i, uint64(m.Port))
+		i--
+		dAtA[i] = 0x10
 	}
-	return 0
+	if m.Ip != 0 {
+		i = encodeVarintNebula(dAtA, i, uint64(m.Ip))
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
 }
 
-type NebulaHandshake struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
+func (m *Ip6AndPort) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
 
-	Details *NebulaHandshakeDetails `protobuf:"bytes,1,opt,name=Details,proto3" json:"Details,omitempty"`
-	Hmac    []byte                  `protobuf:"bytes,2,opt,name=Hmac,proto3" json:"Hmac,omitempty"`
+func (m *Ip6AndPort) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Ip6AndPort) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.Port != 0 {
+		i = encodeVarintNebula(dAtA, i, uint64(m.Port))
+		i--
+		dAtA[i] = 0x10
+	}
+	if len(m.Ip) > 0 {
+		i -= len(m.Ip)
+		copy(dAtA[i:], m.Ip)
+		i = encodeVarintNebula(dAtA, i, uint64(len(m.Ip)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *NebulaPing) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *NebulaPing) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
 }
 
-func (x *NebulaHandshake) Reset() {
-	*x = NebulaHandshake{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_nebula_proto_msgTypes[5]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
+func (m *NebulaPing) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.Time != 0 {
+		i = encodeVarintNebula(dAtA, i, uint64(m.Time))
+		i--
+		dAtA[i] = 0x10
 	}
+	if m.Type != 0 {
+		i = encodeVarintNebula(dAtA, i, uint64(m.Type))
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
 }
 
-func (x *NebulaHandshake) String() string {
-	return protoimpl.X.MessageStringOf(x)
+func (m *NebulaHandshake) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
 }
 
-func (*NebulaHandshake) ProtoMessage() {}
+func (m *NebulaHandshake) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
 
-func (x *NebulaHandshake) ProtoReflect() protoreflect.Message {
-	mi := &file_nebula_proto_msgTypes[5]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
+func (m *NebulaHandshake) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Hmac) > 0 {
+		i -= len(m.Hmac)
+		copy(dAtA[i:], m.Hmac)
+		i = encodeVarintNebula(dAtA, i, uint64(len(m.Hmac)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if m.Details != nil {
+		{
+			size, err := m.Details.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintNebula(dAtA, i, uint64(size))
 		}
-		return ms
+		i--
+		dAtA[i] = 0xa
 	}
-	return mi.MessageOf(x)
+	return len(dAtA) - i, nil
 }
 
-// Deprecated: Use NebulaHandshake.ProtoReflect.Descriptor instead.
-func (*NebulaHandshake) Descriptor() ([]byte, []int) {
-	return file_nebula_proto_rawDescGZIP(), []int{5}
+func (m *NebulaHandshakeDetails) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *NebulaHandshakeDetails) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
 }
 
-func (x *NebulaHandshake) GetDetails() *NebulaHandshakeDetails {
-	if x != nil {
-		return x.Details
+func (m *NebulaHandshakeDetails) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.Time != 0 {
+		i = encodeVarintNebula(dAtA, i, uint64(m.Time))
+		i--
+		dAtA[i] = 0x28
 	}
-	return nil
+	if m.Cookie != 0 {
+		i = encodeVarintNebula(dAtA, i, uint64(m.Cookie))
+		i--
+		dAtA[i] = 0x20
+	}
+	if m.ResponderIndex != 0 {
+		i = encodeVarintNebula(dAtA, i, uint64(m.ResponderIndex))
+		i--
+		dAtA[i] = 0x18
+	}
+	if m.InitiatorIndex != 0 {
+		i = encodeVarintNebula(dAtA, i, uint64(m.InitiatorIndex))
+		i--
+		dAtA[i] = 0x10
+	}
+	if len(m.Cert) > 0 {
+		i -= len(m.Cert)
+		copy(dAtA[i:], m.Cert)
+		i = encodeVarintNebula(dAtA, i, uint64(len(m.Cert)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
 }
 
-func (x *NebulaHandshake) GetHmac() []byte {
-	if x != nil {
-		return x.Hmac
+func encodeVarintNebula(dAtA []byte, offset int, v uint64) int {
+	offset -= sovNebula(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
 	}
-	return nil
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *NebulaMeta) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Type != 0 {
+		n += 1 + sovNebula(uint64(m.Type))
+	}
+	if m.Details != nil {
+		l = m.Details.Size()
+		n += 1 + l + sovNebula(uint64(l))
+	}
+	return n
 }
 
-type NebulaHandshakeDetails struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
+func (m *NebulaMetaDetails) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.VpnIp != 0 {
+		n += 1 + sovNebula(uint64(m.VpnIp))
+	}
+	if len(m.Ip4AndPorts) > 0 {
+		for _, e := range m.Ip4AndPorts {
+			l = e.Size()
+			n += 1 + l + sovNebula(uint64(l))
+		}
+	}
+	if m.Counter != 0 {
+		n += 1 + sovNebula(uint64(m.Counter))
+	}
+	if len(m.Ip6AndPorts) > 0 {
+		for _, e := range m.Ip6AndPorts {
+			l = e.Size()
+			n += 1 + l + sovNebula(uint64(l))
+		}
+	}
+	return n
+}
 
-	Cert           []byte `protobuf:"bytes,1,opt,name=Cert,proto3" json:"Cert,omitempty"`
-	InitiatorIndex uint32 `protobuf:"varint,2,opt,name=InitiatorIndex,proto3" json:"InitiatorIndex,omitempty"`
-	ResponderIndex uint32 `protobuf:"varint,3,opt,name=ResponderIndex,proto3" json:"ResponderIndex,omitempty"`
-	Cookie         uint64 `protobuf:"varint,4,opt,name=Cookie,proto3" json:"Cookie,omitempty"`
-	Time           uint64 `protobuf:"varint,5,opt,name=Time,proto3" json:"Time,omitempty"`
+func (m *Ip4AndPort) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Ip != 0 {
+		n += 1 + sovNebula(uint64(m.Ip))
+	}
+	if m.Port != 0 {
+		n += 1 + sovNebula(uint64(m.Port))
+	}
+	return n
 }
 
-func (x *NebulaHandshakeDetails) Reset() {
-	*x = NebulaHandshakeDetails{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_nebula_proto_msgTypes[6]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
+func (m *Ip6AndPort) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Ip)
+	if l > 0 {
+		n += 1 + l + sovNebula(uint64(l))
 	}
+	if m.Port != 0 {
+		n += 1 + sovNebula(uint64(m.Port))
+	}
+	return n
 }
 
-func (x *NebulaHandshakeDetails) String() string {
-	return protoimpl.X.MessageStringOf(x)
+func (m *NebulaPing) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Type != 0 {
+		n += 1 + sovNebula(uint64(m.Type))
+	}
+	if m.Time != 0 {
+		n += 1 + sovNebula(uint64(m.Time))
+	}
+	return n
 }
 
-func (*NebulaHandshakeDetails) ProtoMessage() {}
+func (m *NebulaHandshake) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Details != nil {
+		l = m.Details.Size()
+		n += 1 + l + sovNebula(uint64(l))
+	}
+	l = len(m.Hmac)
+	if l > 0 {
+		n += 1 + l + sovNebula(uint64(l))
+	}
+	return n
+}
 
-func (x *NebulaHandshakeDetails) ProtoReflect() protoreflect.Message {
-	mi := &file_nebula_proto_msgTypes[6]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
+func (m *NebulaHandshakeDetails) Size() (n int) {
+	if m == nil {
+		return 0
 	}
-	return mi.MessageOf(x)
+	var l int
+	_ = l
+	l = len(m.Cert)
+	if l > 0 {
+		n += 1 + l + sovNebula(uint64(l))
+	}
+	if m.InitiatorIndex != 0 {
+		n += 1 + sovNebula(uint64(m.InitiatorIndex))
+	}
+	if m.ResponderIndex != 0 {
+		n += 1 + sovNebula(uint64(m.ResponderIndex))
+	}
+	if m.Cookie != 0 {
+		n += 1 + sovNebula(uint64(m.Cookie))
+	}
+	if m.Time != 0 {
+		n += 1 + sovNebula(uint64(m.Time))
+	}
+	return n
 }
 
-// Deprecated: Use NebulaHandshakeDetails.ProtoReflect.Descriptor instead.
-func (*NebulaHandshakeDetails) Descriptor() ([]byte, []int) {
-	return file_nebula_proto_rawDescGZIP(), []int{6}
+func sovNebula(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
 }
+func sozNebula(x uint64) (n int) {
+	return sovNebula(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *NebulaMeta) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowNebula
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: NebulaMeta: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: NebulaMeta: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
+			}
+			m.Type = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Type |= NebulaMeta_MessageType(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Details", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthNebula
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Details == nil {
+				m.Details = &NebulaMetaDetails{}
+			}
+			if err := m.Details.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipNebula(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
 
-func (x *NebulaHandshakeDetails) GetCert() []byte {
-	if x != nil {
-		return x.Cert
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
 	}
 	return nil
 }
+func (m *NebulaMetaDetails) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowNebula
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: NebulaMetaDetails: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: NebulaMetaDetails: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field VpnIp", wireType)
+			}
+			m.VpnIp = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.VpnIp |= uint32(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Ip4AndPorts", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthNebula
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Ip4AndPorts = append(m.Ip4AndPorts, &Ip4AndPort{})
+			if err := m.Ip4AndPorts[len(m.Ip4AndPorts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 3:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Counter", wireType)
+			}
+			m.Counter = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Counter |= uint32(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Ip6AndPorts", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthNebula
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Ip6AndPorts = append(m.Ip6AndPorts, &Ip6AndPort{})
+			if err := m.Ip6AndPorts[len(m.Ip6AndPorts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipNebula(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
 
-func (x *NebulaHandshakeDetails) GetInitiatorIndex() uint32 {
-	if x != nil {
-		return x.InitiatorIndex
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
 	}
-	return 0
+	return nil
 }
+func (m *Ip4AndPort) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowNebula
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: Ip4AndPort: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Ip4AndPort: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Ip", wireType)
+			}
+			m.Ip = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Ip |= uint32(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 2:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType)
+			}
+			m.Port = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Port |= uint32(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipNebula(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
 
-func (x *NebulaHandshakeDetails) GetResponderIndex() uint32 {
-	if x != nil {
-		return x.ResponderIndex
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
 	}
-	return 0
+	return nil
+}
+func (m *Ip6AndPort) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowNebula
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: Ip6AndPort: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Ip6AndPort: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Ip", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthNebula
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Ip = append(m.Ip[:0], dAtA[iNdEx:postIndex]...)
+			if m.Ip == nil {
+				m.Ip = []byte{}
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType)
+			}
+			m.Port = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Port |= uint32(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipNebula(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
 }
+func (m *NebulaPing) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowNebula
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: NebulaPing: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: NebulaPing: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
+			}
+			m.Type = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Type |= NebulaPing_MessageType(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 2:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType)
+			}
+			m.Time = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Time |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipNebula(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
 
-func (x *NebulaHandshakeDetails) GetCookie() uint64 {
-	if x != nil {
-		return x.Cookie
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
 	}
-	return 0
+	return nil
 }
+func (m *NebulaHandshake) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowNebula
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: NebulaHandshake: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: NebulaHandshake: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Details", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthNebula
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Details == nil {
+				m.Details = &NebulaHandshakeDetails{}
+			}
+			if err := m.Details.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Hmac", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthNebula
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Hmac = append(m.Hmac[:0], dAtA[iNdEx:postIndex]...)
+			if m.Hmac == nil {
+				m.Hmac = []byte{}
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipNebula(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
 
-func (x *NebulaHandshakeDetails) GetTime() uint64 {
-	if x != nil {
-		return x.Time
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
 	}
-	return 0
+	return nil
 }
+func (m *NebulaHandshakeDetails) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowNebula
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: NebulaHandshakeDetails: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: NebulaHandshakeDetails: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Cert", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthNebula
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Cert = append(m.Cert[:0], dAtA[iNdEx:postIndex]...)
+			if m.Cert == nil {
+				m.Cert = []byte{}
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field InitiatorIndex", wireType)
+			}
+			m.InitiatorIndex = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.InitiatorIndex |= uint32(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 3:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ResponderIndex", wireType)
+			}
+			m.ResponderIndex = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.ResponderIndex |= uint32(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 4:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Cookie", wireType)
+			}
+			m.Cookie = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Cookie |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 5:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType)
+			}
+			m.Time = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Time |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipNebula(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthNebula
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
 
-var File_nebula_proto protoreflect.FileDescriptor
-
-var file_nebula_proto_rawDesc = []byte{
-	0x0a, 0x0c, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
-	0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x22, 0xcc, 0x02, 0x0a, 0x0a, 0x4e, 0x65, 0x62, 0x75, 0x6c,
-	0x61, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x32, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2e, 0x4e, 0x65, 0x62,
-	0x75, 0x6c, 0x61, 0x4d, 0x65, 0x74, 0x61, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54,
-	0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x44, 0x65, 0x74,
-	0x61, 0x69, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6e, 0x65, 0x62,
-	0x75, 0x6c, 0x61, 0x2e, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x65,
-	0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x07, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0xd4,
-	0x01, 0x0a, 0x0b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08,
-	0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x6f, 0x73, 0x74,
-	0x51, 0x75, 0x65, 0x72, 0x79, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x48, 0x6f, 0x73, 0x74, 0x51,
-	0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x48,
-	0x6f, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x48, 0x6f, 0x73, 0x74, 0x4d,
-	0x6f, 0x76, 0x65, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x75, 0x6e, 0x63, 0x68, 0x4e,
-	0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x05, 0x12, 0x0e, 0x0a,
-	0x0a, 0x48, 0x6f, 0x73, 0x74, 0x57, 0x68, 0x6f, 0x61, 0x6d, 0x69, 0x10, 0x06, 0x12, 0x13, 0x0a,
-	0x0f, 0x48, 0x6f, 0x73, 0x74, 0x57, 0x68, 0x6f, 0x61, 0x6d, 0x69, 0x52, 0x65, 0x70, 0x6c, 0x79,
-	0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x10,
-	0x08, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65,
-	0x70, 0x6c, 0x79, 0x10, 0x09, 0x22, 0xac, 0x01, 0x0a, 0x11, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61,
-	0x4d, 0x65, 0x74, 0x61, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x56,
-	0x70, 0x6e, 0x49, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x56, 0x70, 0x6e, 0x49,
-	0x70, 0x12, 0x31, 0x0a, 0x0a, 0x49, 0x70, 0x41, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x73, 0x18,
-	0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2e, 0x49,
-	0x70, 0x41, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x0a, 0x49, 0x70, 0x41, 0x6e, 0x64, 0x50,
-	0x6f, 0x72, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x0b, 0x49, 0x70, 0x36, 0x41, 0x6e, 0x64, 0x50, 0x6f,
-	0x72, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6e, 0x65, 0x62, 0x75,
-	0x6c, 0x61, 0x2e, 0x49, 0x70, 0x36, 0x41, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x49,
-	0x70, 0x36, 0x41, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f,
-	0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x63, 0x6f, 0x75,
-	0x6e, 0x74, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x09, 0x49, 0x70, 0x41, 0x6e, 0x64, 0x50, 0x6f, 0x72,
-	0x74, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x49,
-	0x70, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52,
-	0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x30, 0x0a, 0x0a, 0x49, 0x70, 0x36, 0x41, 0x6e, 0x64, 0x50,
-	0x6f, 0x72, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
-	0x02, 0x49, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x0d, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x78, 0x0a, 0x0a, 0x4e, 0x65, 0x62, 0x75, 0x6c,
-	0x61, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x32, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2e, 0x4e, 0x65, 0x62,
-	0x75, 0x6c, 0x61, 0x50, 0x69, 0x6e, 0x67, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54,
-	0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x69, 0x6d,
-	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x22, 0x0a,
-	0x0b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04,
-	0x50, 0x69, 0x6e, 0x67, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x10,
-	0x01, 0x22, 0x5f, 0x0a, 0x0f, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x48, 0x61, 0x6e, 0x64, 0x73,
-	0x68, 0x61, 0x6b, 0x65, 0x12, 0x38, 0x0a, 0x07, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2e, 0x4e,
-	0x65, 0x62, 0x75, 0x6c, 0x61, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x44, 0x65,
-	0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x07, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x12,
-	0x0a, 0x04, 0x48, 0x6d, 0x61, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x48, 0x6d,
-	0x61, 0x63, 0x22, 0xa8, 0x01, 0x0a, 0x16, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x48, 0x61, 0x6e,
-	0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x12, 0x0a,
-	0x04, 0x43, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x43, 0x65, 0x72,
-	0x74, 0x12, 0x26, 0x0a, 0x0e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e,
-	0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x49, 0x6e, 0x69, 0x74, 0x69,
-	0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x26, 0x0a, 0x0e, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x0d, 0x52, 0x0e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65,
-	0x78, 0x12, 0x16, 0x0a, 0x06, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
-	0x04, 0x52, 0x06, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x69, 0x6d,
-	0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x1b, 0x5a,
-	0x19, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6c, 0x61, 0x63,
-	0x6b, 0x68, 0x71, 0x2f, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x33,
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipNebula(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowNebula
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowNebula
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthNebula
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupNebula
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthNebula
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
 }
 
 var (
-	file_nebula_proto_rawDescOnce sync.Once
-	file_nebula_proto_rawDescData = file_nebula_proto_rawDesc
+	ErrInvalidLengthNebula        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowNebula          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupNebula = fmt.Errorf("proto: unexpected end of group")
 )
-
-func file_nebula_proto_rawDescGZIP() []byte {
-	file_nebula_proto_rawDescOnce.Do(func() {
-		file_nebula_proto_rawDescData = protoimpl.X.CompressGZIP(file_nebula_proto_rawDescData)
-	})
-	return file_nebula_proto_rawDescData
-}
-
-var file_nebula_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
-var file_nebula_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
-var file_nebula_proto_goTypes = []interface{}{
-	(NebulaMeta_MessageType)(0),    // 0: nebula.NebulaMeta.MessageType
-	(NebulaPing_MessageType)(0),    // 1: nebula.NebulaPing.MessageType
-	(*NebulaMeta)(nil),             // 2: nebula.NebulaMeta
-	(*NebulaMetaDetails)(nil),      // 3: nebula.NebulaMetaDetails
-	(*IpAndPort)(nil),              // 4: nebula.IpAndPort
-	(*Ip6AndPort)(nil),             // 5: nebula.Ip6AndPort
-	(*NebulaPing)(nil),             // 6: nebula.NebulaPing
-	(*NebulaHandshake)(nil),        // 7: nebula.NebulaHandshake
-	(*NebulaHandshakeDetails)(nil), // 8: nebula.NebulaHandshakeDetails
-}
-var file_nebula_proto_depIdxs = []int32{
-	0, // 0: nebula.NebulaMeta.Type:type_name -> nebula.NebulaMeta.MessageType
-	3, // 1: nebula.NebulaMeta.Details:type_name -> nebula.NebulaMetaDetails
-	4, // 2: nebula.NebulaMetaDetails.IpAndPorts:type_name -> nebula.IpAndPort
-	5, // 3: nebula.NebulaMetaDetails.Ip6AndPorts:type_name -> nebula.Ip6AndPort
-	1, // 4: nebula.NebulaPing.Type:type_name -> nebula.NebulaPing.MessageType
-	8, // 5: nebula.NebulaHandshake.Details:type_name -> nebula.NebulaHandshakeDetails
-	6, // [6:6] is the sub-list for method output_type
-	6, // [6:6] is the sub-list for method input_type
-	6, // [6:6] is the sub-list for extension type_name
-	6, // [6:6] is the sub-list for extension extendee
-	0, // [0:6] is the sub-list for field type_name
-}
-
-func init() { file_nebula_proto_init() }
-func file_nebula_proto_init() {
-	if File_nebula_proto != nil {
-		return
-	}
-	if !protoimpl.UnsafeEnabled {
-		file_nebula_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*NebulaMeta); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_nebula_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*NebulaMetaDetails); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_nebula_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*IpAndPort); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_nebula_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Ip6AndPort); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_nebula_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*NebulaPing); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_nebula_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*NebulaHandshake); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_nebula_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*NebulaHandshakeDetails); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-	}
-	type x struct{}
-	out := protoimpl.TypeBuilder{
-		File: protoimpl.DescBuilder{
-			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_nebula_proto_rawDesc,
-			NumEnums:      2,
-			NumMessages:   7,
-			NumExtensions: 0,
-			NumServices:   0,
-		},
-		GoTypes:           file_nebula_proto_goTypes,
-		DependencyIndexes: file_nebula_proto_depIdxs,
-		EnumInfos:         file_nebula_proto_enumTypes,
-		MessageInfos:      file_nebula_proto_msgTypes,
-	}.Build()
-	File_nebula_proto = out.File
-	file_nebula_proto_rawDesc = nil
-	file_nebula_proto_goTypes = nil
-	file_nebula_proto_depIdxs = nil
-}

+ 2 - 4
nebula.proto

@@ -1,8 +1,6 @@
 syntax = "proto3";
 package nebula;
 
-option go_package = "github.com/slackhq/nebula";
-
 message NebulaMeta {
   enum MessageType {
     None = 0;
@@ -24,12 +22,12 @@ message NebulaMeta {
 
 message NebulaMetaDetails {
   uint32 VpnIp = 1;
-  repeated IpAndPort IpAndPorts = 2;
+  repeated Ip4AndPort Ip4AndPorts = 2;
   repeated Ip6AndPort Ip6AndPorts = 4;
   uint32 counter = 3;
 }
 
-message IpAndPort {
+message Ip4AndPort {
   uint32 Ip = 1;
   uint32 Port = 2;
 }

+ 1 - 1
outside.go

@@ -66,7 +66,7 @@ func (f *Interface) readOutsidePackets(addr *udpAddr, out []byte, packet []byte,
 			return
 		}
 
-		lhh.HandleRequest(addr, hostinfo.hostId, d, hostinfo.GetCert(), f)
+		lhh.HandleRequest(addr, hostinfo.hostId, d, f)
 
 		// Fallthrough to the bottom to record incoming traffic
 

+ 2 - 11
ssh.go

@@ -402,14 +402,9 @@ func sshListLighthouseMap(lightHouse *LightHouse, a interface{}, w sshd.StringWr
 		x := 0
 		var h m
 		for vpnIp, v := range lightHouse.addrMap {
-			ips := make([]string, len(v))
-			for i, ip := range v {
-				ips[i] = ip.String()
-			}
-
 			h = m{
 				"vpnIp": int2ip(vpnIp),
-				"addrs": ips,
+				"addrs": TransformLHReplyToUdpAddrs(v),
 			}
 
 			d[x] = h
@@ -423,11 +418,7 @@ func sshListLighthouseMap(lightHouse *LightHouse, a interface{}, w sshd.StringWr
 		}
 	} else {
 		for vpnIp, v := range lightHouse.addrMap {
-			ips := make([]string, len(v))
-			for i, ip := range v {
-				ips[i] = ip.String()
-			}
-			err := w.WriteLine(fmt.Sprintf("%s: %s", int2ip(vpnIp), ips))
+			err := w.WriteLine(fmt.Sprintf("%s: %s", int2ip(vpnIp), TransformLHReplyToUdpAddrs(v)))
 			if err != nil {
 				return err
 			}