Browse Source

IPv6 support for outside (udp) (#369)

Nathan Brown 4 years ago
parent
commit
7073d204a8
34 changed files with 1708 additions and 714 deletions
  1. 2 2
      Makefile
  2. 3 2
      allow_list.go
  3. 14 5
      allow_list_test.go
  4. 2 2
      cert/Makefile
  5. 224 128
      cert/cert.pb.go
  6. 2 0
      cert/cert.proto
  7. 203 0
      cidr6_radix.go
  8. 134 0
      cidr6_radix_test.go
  9. 1 1
      cidr_radix.go
  10. 39 17
      config.go
  11. 23 0
      config_test.go
  12. 28 14
      control.go
  13. 5 5
      control_test.go
  14. 1 0
      examples/config.yml
  15. 3 1
      go.mod
  16. 62 0
      go.sum
  17. 1 1
      handshake.go
  18. 2 2
      handshake_ix.go
  19. 2 2
      handshake_manager_test.go
  20. 17 12
      hostmap.go
  21. 12 10
      hostmap_test.go
  22. 152 29
      lighthouse.go
  23. 29 16
      lighthouse_test.go
  24. 8 19
      main.go
  25. 617 289
      nebula.pb.go
  26. 7 2
      nebula.proto
  27. 2 2
      outside.go
  28. 2 2
      ssh.go
  29. 62 0
      udp_all.go
  30. 2 1
      udp_darwin.go
  31. 10 62
      udp_generic.go
  32. 25 80
      udp_linux.go
  33. 6 4
      udp_linux_32.go
  34. 6 4
      udp_linux_64.go

+ 2 - 2
Makefile

@@ -113,8 +113,8 @@ bench-cpu-long:
 proto: nebula.pb.go cert/cert.pb.go
 
 nebula.pb.go: nebula.proto .FORCE
-	go build github.com/golang/protobuf/protoc-gen-go
-	PATH="$(PWD):$(PATH)" protoc --go_out=. $<
+	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
 
 cert/cert.pb.go: cert/cert.proto .FORCE

+ 3 - 2
allow_list.go

@@ -2,12 +2,13 @@ package nebula
 
 import (
 	"fmt"
+	"net"
 	"regexp"
 )
 
 type AllowList struct {
 	// The values of this cidrTree are `bool`, signifying allow/deny
-	cidrTree *CIDRTree
+	cidrTree *CIDR6Tree
 
 	// To avoid ambiguity, all rules must be true, or all rules must be false.
 	nameRules []AllowListNameRule
@@ -18,7 +19,7 @@ type AllowListNameRule struct {
 	Allow bool
 }
 
-func (al *AllowList) Allow(ip uint32) bool {
+func (al *AllowList) Allow(ip net.IP) bool {
 	if al == nil {
 		return true
 	}

+ 14 - 5
allow_list_test.go

@@ -9,17 +9,26 @@ import (
 )
 
 func TestAllowList_Allow(t *testing.T) {
-	assert.Equal(t, true, ((*AllowList)(nil)).Allow(ip2int(net.ParseIP("1.1.1.1"))))
+	assert.Equal(t, true, ((*AllowList)(nil)).Allow(net.ParseIP("1.1.1.1")))
 
-	tree := NewCIDRTree()
+	tree := NewCIDR6Tree()
 	tree.AddCIDR(getCIDR("0.0.0.0/0"), true)
 	tree.AddCIDR(getCIDR("10.0.0.0/8"), false)
+	tree.AddCIDR(getCIDR("10.42.42.42/32"), true)
+	tree.AddCIDR(getCIDR("10.42.0.0/16"), true)
 	tree.AddCIDR(getCIDR("10.42.42.0/24"), true)
+	tree.AddCIDR(getCIDR("10.42.42.0/24"), false)
+	tree.AddCIDR(getCIDR("::1/128"), true)
+	tree.AddCIDR(getCIDR("::2/128"), false)
 	al := &AllowList{cidrTree: tree}
 
-	assert.Equal(t, true, al.Allow(ip2int(net.ParseIP("1.1.1.1"))))
-	assert.Equal(t, false, al.Allow(ip2int(net.ParseIP("10.0.0.4"))))
-	assert.Equal(t, true, al.Allow(ip2int(net.ParseIP("10.42.42.42"))))
+	assert.Equal(t, true, al.Allow(net.ParseIP("1.1.1.1")))
+	assert.Equal(t, false, al.Allow(net.ParseIP("10.0.0.4")))
+	assert.Equal(t, true, al.Allow(net.ParseIP("10.42.42.42")))
+	assert.Equal(t, false, al.Allow(net.ParseIP("10.42.42.41")))
+	assert.Equal(t, true, al.Allow(net.ParseIP("10.42.0.1")))
+	assert.Equal(t, true, al.Allow(net.ParseIP("::1")))
+	assert.Equal(t, false, al.Allow(net.ParseIP("::2")))
 }
 
 func TestAllowList_AllowName(t *testing.T) {

+ 2 - 2
cert/Makefile

@@ -2,8 +2,8 @@ GO111MODULE = on
 export GO111MODULE
 
 cert.pb.go: cert.proto .FORCE
-	go build github.com/golang/protobuf/protoc-gen-go
-	PATH="$(PWD):$(PATH)" protoc --go_out=. $<
+	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
 
 .FORCE:

+ 224 - 128
cert/cert.pb.go

@@ -1,202 +1,298 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.14.0
 // source: cert.proto
 
 package cert
 
 import (
-	fmt "fmt"
-	proto "github.com/golang/protobuf/proto"
-	math "math"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
 )
 
-// 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.ProtoPackageIsVersion3 // please upgrade the proto package
+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)
+)
 
 type RawNebulaCertificate struct {
-	Details              *RawNebulaCertificateDetails `protobuf:"bytes,1,opt,name=Details,json=details,proto3" json:"Details,omitempty"`
-	Signature            []byte                       `protobuf:"bytes,2,opt,name=Signature,json=signature,proto3" json:"Signature,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}                     `json:"-"`
-	XXX_unrecognized     []byte                       `json:"-"`
-	XXX_sizecache        int32                        `json:"-"`
-}
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
 
-func (m *RawNebulaCertificate) Reset()         { *m = RawNebulaCertificate{} }
-func (m *RawNebulaCertificate) String() string { return proto.CompactTextString(m) }
-func (*RawNebulaCertificate) ProtoMessage()    {}
-func (*RawNebulaCertificate) Descriptor() ([]byte, []int) {
-	return fileDescriptor_a142e29cbef9b1cf, []int{0}
+	Details   *RawNebulaCertificateDetails `protobuf:"bytes,1,opt,name=Details,proto3" json:"Details,omitempty"`
+	Signature []byte                       `protobuf:"bytes,2,opt,name=Signature,proto3" json:"Signature,omitempty"`
 }
 
-func (m *RawNebulaCertificate) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_RawNebulaCertificate.Unmarshal(m, b)
-}
-func (m *RawNebulaCertificate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_RawNebulaCertificate.Marshal(b, m, deterministic)
-}
-func (m *RawNebulaCertificate) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_RawNebulaCertificate.Merge(m, src)
+func (x *RawNebulaCertificate) Reset() {
+	*x = RawNebulaCertificate{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cert_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
 }
-func (m *RawNebulaCertificate) XXX_Size() int {
-	return xxx_messageInfo_RawNebulaCertificate.Size(m)
+
+func (x *RawNebulaCertificate) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
-func (m *RawNebulaCertificate) XXX_DiscardUnknown() {
-	xxx_messageInfo_RawNebulaCertificate.DiscardUnknown(m)
+
+func (*RawNebulaCertificate) ProtoMessage() {}
+
+func (x *RawNebulaCertificate) ProtoReflect() protoreflect.Message {
+	mi := &file_cert_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)
 }
 
-var xxx_messageInfo_RawNebulaCertificate proto.InternalMessageInfo
+// Deprecated: Use RawNebulaCertificate.ProtoReflect.Descriptor instead.
+func (*RawNebulaCertificate) Descriptor() ([]byte, []int) {
+	return file_cert_proto_rawDescGZIP(), []int{0}
+}
 
-func (m *RawNebulaCertificate) GetDetails() *RawNebulaCertificateDetails {
-	if m != nil {
-		return m.Details
+func (x *RawNebulaCertificate) GetDetails() *RawNebulaCertificateDetails {
+	if x != nil {
+		return x.Details
 	}
 	return nil
 }
 
-func (m *RawNebulaCertificate) GetSignature() []byte {
-	if m != nil {
-		return m.Signature
+func (x *RawNebulaCertificate) GetSignature() []byte {
+	if x != nil {
+		return x.Signature
 	}
 	return nil
 }
 
 type RawNebulaCertificateDetails struct {
-	Name string `protobuf:"bytes,1,opt,name=Name,json=name,proto3" json:"Name,omitempty"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
 	// Ips and Subnets are in big endian 32 bit pairs, 1st the ip, 2nd the mask
-	Ips       []uint32 `protobuf:"varint,2,rep,packed,name=Ips,json=ips,proto3" json:"Ips,omitempty"`
-	Subnets   []uint32 `protobuf:"varint,3,rep,packed,name=Subnets,json=subnets,proto3" json:"Subnets,omitempty"`
-	Groups    []string `protobuf:"bytes,4,rep,name=Groups,json=groups,proto3" json:"Groups,omitempty"`
-	NotBefore int64    `protobuf:"varint,5,opt,name=NotBefore,json=notBefore,proto3" json:"NotBefore,omitempty"`
-	NotAfter  int64    `protobuf:"varint,6,opt,name=NotAfter,json=notAfter,proto3" json:"NotAfter,omitempty"`
-	PublicKey []byte   `protobuf:"bytes,7,opt,name=PublicKey,json=publicKey,proto3" json:"PublicKey,omitempty"`
-	IsCA      bool     `protobuf:"varint,8,opt,name=IsCA,json=isCA,proto3" json:"IsCA,omitempty"`
+	Ips       []uint32 `protobuf:"varint,2,rep,packed,name=Ips,proto3" json:"Ips,omitempty"`
+	Subnets   []uint32 `protobuf:"varint,3,rep,packed,name=Subnets,proto3" json:"Subnets,omitempty"`
+	Groups    []string `protobuf:"bytes,4,rep,name=Groups,proto3" json:"Groups,omitempty"`
+	NotBefore int64    `protobuf:"varint,5,opt,name=NotBefore,proto3" json:"NotBefore,omitempty"`
+	NotAfter  int64    `protobuf:"varint,6,opt,name=NotAfter,proto3" json:"NotAfter,omitempty"`
+	PublicKey []byte   `protobuf:"bytes,7,opt,name=PublicKey,proto3" json:"PublicKey,omitempty"`
+	IsCA      bool     `protobuf:"varint,8,opt,name=IsCA,proto3" json:"IsCA,omitempty"`
 	// sha-256 of the issuer certificate, if this field is blank the cert is self-signed
-	Issuer               []byte   `protobuf:"bytes,9,opt,name=Issuer,json=issuer,proto3" json:"Issuer,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Issuer []byte `protobuf:"bytes,9,opt,name=Issuer,proto3" json:"Issuer,omitempty"`
 }
 
-func (m *RawNebulaCertificateDetails) Reset()         { *m = RawNebulaCertificateDetails{} }
-func (m *RawNebulaCertificateDetails) String() string { return proto.CompactTextString(m) }
-func (*RawNebulaCertificateDetails) ProtoMessage()    {}
-func (*RawNebulaCertificateDetails) Descriptor() ([]byte, []int) {
-	return fileDescriptor_a142e29cbef9b1cf, []int{1}
+func (x *RawNebulaCertificateDetails) Reset() {
+	*x = RawNebulaCertificateDetails{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cert_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
 }
 
-func (m *RawNebulaCertificateDetails) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_RawNebulaCertificateDetails.Unmarshal(m, b)
-}
-func (m *RawNebulaCertificateDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_RawNebulaCertificateDetails.Marshal(b, m, deterministic)
-}
-func (m *RawNebulaCertificateDetails) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_RawNebulaCertificateDetails.Merge(m, src)
+func (x *RawNebulaCertificateDetails) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
-func (m *RawNebulaCertificateDetails) XXX_Size() int {
-	return xxx_messageInfo_RawNebulaCertificateDetails.Size(m)
-}
-func (m *RawNebulaCertificateDetails) XXX_DiscardUnknown() {
-	xxx_messageInfo_RawNebulaCertificateDetails.DiscardUnknown(m)
+
+func (*RawNebulaCertificateDetails) ProtoMessage() {}
+
+func (x *RawNebulaCertificateDetails) ProtoReflect() protoreflect.Message {
+	mi := &file_cert_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)
 }
 
-var xxx_messageInfo_RawNebulaCertificateDetails proto.InternalMessageInfo
+// Deprecated: Use RawNebulaCertificateDetails.ProtoReflect.Descriptor instead.
+func (*RawNebulaCertificateDetails) Descriptor() ([]byte, []int) {
+	return file_cert_proto_rawDescGZIP(), []int{1}
+}
 
-func (m *RawNebulaCertificateDetails) GetName() string {
-	if m != nil {
-		return m.Name
+func (x *RawNebulaCertificateDetails) GetName() string {
+	if x != nil {
+		return x.Name
 	}
 	return ""
 }
 
-func (m *RawNebulaCertificateDetails) GetIps() []uint32 {
-	if m != nil {
-		return m.Ips
+func (x *RawNebulaCertificateDetails) GetIps() []uint32 {
+	if x != nil {
+		return x.Ips
 	}
 	return nil
 }
 
-func (m *RawNebulaCertificateDetails) GetSubnets() []uint32 {
-	if m != nil {
-		return m.Subnets
+func (x *RawNebulaCertificateDetails) GetSubnets() []uint32 {
+	if x != nil {
+		return x.Subnets
 	}
 	return nil
 }
 
-func (m *RawNebulaCertificateDetails) GetGroups() []string {
-	if m != nil {
-		return m.Groups
+func (x *RawNebulaCertificateDetails) GetGroups() []string {
+	if x != nil {
+		return x.Groups
 	}
 	return nil
 }
 
-func (m *RawNebulaCertificateDetails) GetNotBefore() int64 {
-	if m != nil {
-		return m.NotBefore
+func (x *RawNebulaCertificateDetails) GetNotBefore() int64 {
+	if x != nil {
+		return x.NotBefore
 	}
 	return 0
 }
 
-func (m *RawNebulaCertificateDetails) GetNotAfter() int64 {
-	if m != nil {
-		return m.NotAfter
+func (x *RawNebulaCertificateDetails) GetNotAfter() int64 {
+	if x != nil {
+		return x.NotAfter
 	}
 	return 0
 }
 
-func (m *RawNebulaCertificateDetails) GetPublicKey() []byte {
-	if m != nil {
-		return m.PublicKey
+func (x *RawNebulaCertificateDetails) GetPublicKey() []byte {
+	if x != nil {
+		return x.PublicKey
 	}
 	return nil
 }
 
-func (m *RawNebulaCertificateDetails) GetIsCA() bool {
-	if m != nil {
-		return m.IsCA
+func (x *RawNebulaCertificateDetails) GetIsCA() bool {
+	if x != nil {
+		return x.IsCA
 	}
 	return false
 }
 
-func (m *RawNebulaCertificateDetails) GetIssuer() []byte {
-	if m != nil {
-		return m.Issuer
+func (x *RawNebulaCertificateDetails) GetIssuer() []byte {
+	if x != nil {
+		return x.Issuer
 	}
 	return nil
 }
 
-func init() {
-	proto.RegisterType((*RawNebulaCertificate)(nil), "cert.RawNebulaCertificate")
-	proto.RegisterType((*RawNebulaCertificateDetails)(nil), "cert.RawNebulaCertificateDetails")
-}
-
-func init() { proto.RegisterFile("cert.proto", fileDescriptor_a142e29cbef9b1cf) }
-
-var fileDescriptor_a142e29cbef9b1cf = []byte{
-	// 279 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0xcf, 0x4a, 0xf4, 0x30,
-	0x14, 0xc5, 0xc9, 0xa4, 0x5f, 0xdb, 0xe4, 0x53, 0x90, 0x20, 0x12, 0xd4, 0x45, 0x9c, 0x55, 0x56,
-	0xb3, 0xd0, 0xa5, 0xab, 0x71, 0x04, 0x29, 0x42, 0x91, 0xcc, 0x13, 0xa4, 0xf5, 0x76, 0x08, 0x74,
-	0x9a, 0x9a, 0x3f, 0x88, 0x8f, 0xee, 0x4e, 0x9a, 0x4e, 0x77, 0xe2, 0xee, 0x9e, 0x5f, 0xce, 0x49,
-	0x4e, 0x2e, 0xa5, 0x2d, 0xb8, 0xb0, 0x19, 0x9d, 0x0d, 0x96, 0x65, 0xd3, 0xbc, 0xfe, 0xa0, 0x97,
-	0x4a, 0x7f, 0xd6, 0xd0, 0xc4, 0x5e, 0xef, 0xc0, 0x05, 0xd3, 0x99, 0x56, 0x07, 0x60, 0x8f, 0xb4,
-	0x78, 0x86, 0xa0, 0x4d, 0xef, 0x39, 0x12, 0x48, 0xfe, 0xbf, 0xbf, 0xdb, 0xa4, 0xec, 0x6f, 0xe6,
-	0x93, 0x51, 0x15, 0xef, 0xf3, 0xc0, 0x6e, 0x29, 0xd9, 0x9b, 0xc3, 0xa0, 0x43, 0x74, 0xc0, 0x57,
-	0x02, 0xc9, 0x33, 0x45, 0xfc, 0x02, 0xd6, 0xdf, 0x88, 0xde, 0xfc, 0x71, 0x0d, 0x63, 0x34, 0xab,
-	0xf5, 0x11, 0xd2, 0xbb, 0x44, 0x65, 0x83, 0x3e, 0x02, 0xbb, 0xa0, 0xb8, 0x1a, 0x3d, 0x5f, 0x09,
-	0x2c, 0xcf, 0x15, 0x36, 0xa3, 0x67, 0x9c, 0x16, 0xfb, 0xd8, 0x0c, 0x10, 0x3c, 0xc7, 0x89, 0x16,
-	0x7e, 0x96, 0xec, 0x8a, 0xe6, 0x2f, 0xce, 0xc6, 0xd1, 0xf3, 0x4c, 0x60, 0x49, 0x54, 0x7e, 0x48,
-	0x6a, 0x6a, 0x55, 0xdb, 0xf0, 0x04, 0x9d, 0x75, 0xc0, 0xff, 0x09, 0x24, 0xb1, 0x22, 0xc3, 0x02,
-	0xd8, 0x35, 0x2d, 0x6b, 0x1b, 0xb6, 0x5d, 0x00, 0xc7, 0xf3, 0x74, 0x58, 0x0e, 0x27, 0x3d, 0x25,
-	0xdf, 0x62, 0xd3, 0x9b, 0xf6, 0x15, 0xbe, 0x78, 0x31, 0xff, 0x67, 0x5c, 0xc0, 0xd4, 0xb7, 0xf2,
-	0xbb, 0x2d, 0x2f, 0x05, 0x92, 0xa5, 0xca, 0x8c, 0xdf, 0x6d, 0xa7, 0x0e, 0x95, 0xf7, 0x11, 0x1c,
-	0x27, 0xc9, 0x9e, 0x9b, 0xa4, 0x9a, 0x3c, 0xed, 0xfe, 0xe1, 0x27, 0x00, 0x00, 0xff, 0xff, 0x2c,
-	0xe3, 0x08, 0x37, 0x89, 0x01, 0x00, 0x00,
+var File_cert_proto protoreflect.FileDescriptor
+
+var file_cert_proto_rawDesc = []byte{
+	0x0a, 0x0a, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x63, 0x65,
+	0x72, 0x74, 0x22, 0x71, 0x0a, 0x14, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x43,
+	0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x07, 0x44, 0x65,
+	0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x65,
+	0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x43, 0x65, 0x72, 0x74,
+	0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x07,
+	0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x69, 0x67, 0x6e, 0x61,
+	0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x53, 0x69, 0x67, 0x6e,
+	0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xf9, 0x01, 0x0a, 0x1b, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62,
+	0x75, 0x6c, 0x61, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65,
+	0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x49, 0x70, 0x73,
+	0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x03, 0x49, 0x70, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x53,
+	0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x07, 0x53, 0x75,
+	0x62, 0x6e, 0x65, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18,
+	0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x1c, 0x0a,
+	0x09, 0x4e, 0x6f, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03,
+	0x52, 0x09, 0x4e, 0x6f, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x4e,
+	0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x4e,
+	0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x75, 0x62, 0x6c, 0x69,
+	0x63, 0x4b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x50, 0x75, 0x62, 0x6c,
+	0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x49, 0x73, 0x43, 0x41, 0x18, 0x08, 0x20,
+	0x01, 0x28, 0x08, 0x52, 0x04, 0x49, 0x73, 0x43, 0x41, 0x12, 0x16, 0x0a, 0x06, 0x49, 0x73, 0x73,
+	0x75, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x49, 0x73, 0x73, 0x75, 0x65,
+	0x72, 0x42, 0x20, 0x5a, 0x1e, 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, 0x2f, 0x63,
+	0x65, 0x72, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_cert_proto_rawDescOnce sync.Once
+	file_cert_proto_rawDescData = file_cert_proto_rawDesc
+)
+
+func file_cert_proto_rawDescGZIP() []byte {
+	file_cert_proto_rawDescOnce.Do(func() {
+		file_cert_proto_rawDescData = protoimpl.X.CompressGZIP(file_cert_proto_rawDescData)
+	})
+	return file_cert_proto_rawDescData
+}
+
+var file_cert_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_cert_proto_goTypes = []interface{}{
+	(*RawNebulaCertificate)(nil),        // 0: cert.RawNebulaCertificate
+	(*RawNebulaCertificateDetails)(nil), // 1: cert.RawNebulaCertificateDetails
+}
+var file_cert_proto_depIdxs = []int32{
+	1, // 0: cert.RawNebulaCertificate.Details:type_name -> cert.RawNebulaCertificateDetails
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_cert_proto_init() }
+func file_cert_proto_init() {
+	if File_cert_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_cert_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*RawNebulaCertificate); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cert_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*RawNebulaCertificateDetails); 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_cert_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_cert_proto_goTypes,
+		DependencyIndexes: file_cert_proto_depIdxs,
+		MessageInfos:      file_cert_proto_msgTypes,
+	}.Build()
+	File_cert_proto = out.File
+	file_cert_proto_rawDesc = nil
+	file_cert_proto_goTypes = nil
+	file_cert_proto_depIdxs = nil
 }

+ 2 - 0
cert/cert.proto

@@ -1,6 +1,8 @@
 syntax = "proto3";
 package cert;
 
+option go_package = "github.com/slackhq/nebula/cert";
+
 //import "google/protobuf/timestamp.proto";
 
 message RawNebulaCertificate {

+ 203 - 0
cidr6_radix.go

@@ -0,0 +1,203 @@
+package nebula
+
+import (
+	"encoding/binary"
+	"net"
+)
+
+type CIDR6Tree struct {
+	root4 *CIDRNode
+	root6 *CIDRNode
+}
+
+func NewCIDR6Tree() *CIDR6Tree {
+	tree := new(CIDR6Tree)
+	tree.root4 = &CIDRNode{}
+	tree.root6 = &CIDRNode{}
+	return tree
+}
+
+func (tree *CIDR6Tree) AddCIDR(cidr *net.IPNet, val interface{}) {
+	var node, next *CIDRNode
+
+	cidrIP, ipv4 := isIPV4(cidr.IP)
+	if ipv4 {
+		node = tree.root4
+		next = tree.root4
+
+	} else {
+		node = tree.root6
+		next = tree.root6
+	}
+
+	for i := 0; i < len(cidrIP); i += 4 {
+		ip := binary.BigEndian.Uint32(cidrIP[i : i+4])
+		mask := binary.BigEndian.Uint32(cidr.Mask[i : i+4])
+		bit := startbit
+
+		// Find our last ancestor in the tree
+		for bit&mask != 0 {
+			if ip&bit != 0 {
+				next = node.right
+			} else {
+				next = node.left
+			}
+
+			if next == nil {
+				break
+			}
+
+			bit = bit >> 1
+			node = next
+		}
+
+		// Build up the rest of the tree we don't already have
+		for bit&mask != 0 {
+			next = &CIDRNode{}
+			next.parent = node
+
+			if ip&bit != 0 {
+				node.right = next
+			} else {
+				node.left = next
+			}
+
+			bit >>= 1
+			node = next
+		}
+	}
+
+	// Final node marks our cidr, set the value
+	node.value = val
+}
+
+// Finds the first match, which may be the least specific
+func (tree *CIDR6Tree) Contains(ip net.IP) (value interface{}) {
+	var node *CIDRNode
+
+	wholeIP, ipv4 := isIPV4(ip)
+	if ipv4 {
+		node = tree.root4
+	} else {
+		node = tree.root6
+	}
+
+	for i := 0; i < len(wholeIP); i += 4 {
+		ip := ip2int(wholeIP[i : i+4])
+		bit := startbit
+
+		for node != nil {
+			if node.value != nil {
+				return node.value
+			}
+
+			// Check if we have reached the end and the above return did not trigger, move to the next uint32 if available
+			if bit == 0 {
+				break
+			}
+
+			if ip&bit != 0 {
+				node = node.right
+			} else {
+				node = node.left
+			}
+
+			bit >>= 1
+		}
+	}
+
+	// Nothing found
+	return
+}
+
+// Finds the most specific match
+func (tree *CIDR6Tree) MostSpecificContains(ip net.IP) (value interface{}) {
+	var node *CIDRNode
+
+	wholeIP, ipv4 := isIPV4(ip)
+	if ipv4 {
+		node = tree.root4
+	} else {
+		node = tree.root6
+	}
+
+	for i := 0; i < len(wholeIP); i += 4 {
+		ip := ip2int(wholeIP[i : i+4])
+		bit := startbit
+
+		for node != nil {
+			if node.value != nil {
+				value = node.value
+			}
+
+			if bit == 0 {
+				break
+			}
+
+			if ip&bit != 0 {
+				node = node.right
+			} else {
+				node = node.left
+			}
+
+			bit >>= 1
+		}
+	}
+
+	return value
+}
+
+// Finds the most specific match
+func (tree *CIDR6Tree) Match(ip net.IP) (value interface{}) {
+	var node *CIDRNode
+	var bit uint32
+
+	wholeIP, ipv4 := isIPV4(ip)
+	if ipv4 {
+		node = tree.root4
+	} else {
+		node = tree.root6
+	}
+
+	for i := 0; i < len(wholeIP); i += 4 {
+		ip := ip2int(wholeIP[i : i+4])
+		bit = startbit
+
+		for node != nil && bit > 0 {
+			if ip&bit != 0 {
+				node = node.right
+			} else {
+				node = node.left
+			}
+
+			bit >>= 1
+		}
+	}
+
+	if bit == 0 && node != nil {
+		value = node.value
+	}
+
+	return value
+}
+
+func isIPV4(ip net.IP) (net.IP, bool) {
+	if len(ip) == net.IPv4len {
+		return ip, true
+	}
+
+	if len(ip) == net.IPv6len && isZeros(ip[0:10]) && ip[10] == 0xff && ip[11] == 0xff {
+		return ip[12:16], true
+	}
+
+	return ip, false
+}
+
+func isZeros(p net.IP) bool {
+	for i := 0; i < len(p); i++ {
+		if p[i] != 0 {
+			return false
+		}
+	}
+	return true
+}

+ 134 - 0
cidr6_radix_test.go

@@ -0,0 +1,134 @@
+package nebula
+
+import (
+	"net"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCIDR6Tree_Contains(t *testing.T) {
+	tree := NewCIDR6Tree()
+	tree.AddCIDR(getCIDR("1.0.0.0/8"), "1")
+	tree.AddCIDR(getCIDR("2.1.0.0/16"), "2")
+	tree.AddCIDR(getCIDR("3.1.1.0/24"), "3")
+	tree.AddCIDR(getCIDR("4.1.1.0/24"), "4a")
+	tree.AddCIDR(getCIDR("4.1.1.1/32"), "4b")
+	tree.AddCIDR(getCIDR("4.1.2.1/32"), "4c")
+	tree.AddCIDR(getCIDR("254.0.0.0/4"), "5")
+	tree.AddCIDR(getCIDR("2800::FFFF:1/128"), "a")
+	tree.AddCIDR(getCIDR("2800:1:2:3::0/64"), "b")
+
+	tests := []struct {
+		Result interface{}
+		IP     string
+	}{
+		{"1", "1.0.0.0"},
+		{"1", "1.255.255.255"},
+		{"2", "2.1.0.0"},
+		{"2", "2.1.255.255"},
+		{"3", "3.1.1.0"},
+		{"3", "3.1.1.255"},
+		{"4a", "4.1.1.255"},
+		{"4a", "4.1.1.1"},
+		{"5", "240.0.0.0"},
+		{"5", "255.255.255.255"},
+		{nil, "239.0.0.0"},
+		{nil, "4.1.2.2"},
+		{"a", "2800::FFFF:1"},
+		{"b", "2800:1:2:3::1"},
+		{"b", "2800:1:2:3::6"},
+	}
+
+	for _, tt := range tests {
+		assert.Equal(t, tt.Result, tree.Contains(net.ParseIP(tt.IP)))
+	}
+
+	tree = NewCIDR6Tree()
+	tree.AddCIDR(getCIDR("1.1.1.1/0"), "cool")
+	tree.AddCIDR(getCIDR("::/0"), "cool6")
+	assert.Equal(t, "cool", tree.Contains(net.ParseIP("0.0.0.0")))
+	assert.Equal(t, "cool", tree.Contains(net.ParseIP("255.255.255.255")))
+	assert.Equal(t, "cool6", tree.Contains(net.ParseIP("::")))
+	assert.Equal(t, "cool6", tree.Contains(net.ParseIP("1:2:3:4:5:6:7:8")))
+}
+
+func TestCIDR6Tree_MostSpecificContains(t *testing.T) {
+	tree := NewCIDR6Tree()
+	tree.AddCIDR(getCIDR("1.0.0.0/8"), "1")
+	tree.AddCIDR(getCIDR("2.1.0.0/16"), "2")
+	tree.AddCIDR(getCIDR("3.1.1.0/24"), "3")
+	tree.AddCIDR(getCIDR("4.1.1.1/24"), "4a")
+	tree.AddCIDR(getCIDR("4.1.1.1/30"), "4b")
+	tree.AddCIDR(getCIDR("4.1.1.1/32"), "4c")
+	tree.AddCIDR(getCIDR("254.0.0.0/4"), "5")
+	tree.AddCIDR(getCIDR("1:2:0:4:5:0:0:0/64"), "6a")
+	tree.AddCIDR(getCIDR("1:2:0:4:5:0:0:0/80"), "6b")
+	tree.AddCIDR(getCIDR("1:2:0:4:5:0:0:0/96"), "6c")
+
+	tests := []struct {
+		Result interface{}
+		IP     string
+	}{
+		{"1", "1.0.0.0"},
+		{"1", "1.255.255.255"},
+		{"2", "2.1.0.0"},
+		{"2", "2.1.255.255"},
+		{"3", "3.1.1.0"},
+		{"3", "3.1.1.255"},
+		{"4a", "4.1.1.255"},
+		{"4b", "4.1.1.2"},
+		{"4c", "4.1.1.1"},
+		{"5", "240.0.0.0"},
+		{"5", "255.255.255.255"},
+		{"6a", "1:2:0:4:1:1:1:1"},
+		{"6b", "1:2:0:4:5:1:1:1"},
+		{"6c", "1:2:0:4:5:0:0:0"},
+		{nil, "239.0.0.0"},
+		{nil, "4.1.2.2"},
+	}
+
+	for _, tt := range tests {
+		assert.Equal(t, tt.Result, tree.MostSpecificContains(net.ParseIP(tt.IP)))
+	}
+
+	tree = NewCIDR6Tree()
+	tree.AddCIDR(getCIDR("1.1.1.1/0"), "cool")
+	tree.AddCIDR(getCIDR("::/0"), "cool6")
+	assert.Equal(t, "cool", tree.MostSpecificContains(net.ParseIP("0.0.0.0")))
+	assert.Equal(t, "cool", tree.MostSpecificContains(net.ParseIP("255.255.255.255")))
+	assert.Equal(t, "cool6", tree.Contains(net.ParseIP("::")))
+	assert.Equal(t, "cool6", tree.Contains(net.ParseIP("1:2:3:4:5:6:7:8")))
+}
+
+func TestCIDR6Tree_Match(t *testing.T) {
+	tree := NewCIDR6Tree()
+	tree.AddCIDR(getCIDR("4.1.1.0/32"), "1a")
+	tree.AddCIDR(getCIDR("4.1.1.1/32"), "1b")
+	tree.AddCIDR(getCIDR("1:2:3:4:5:6:7:8/128"), "2a")
+	tree.AddCIDR(getCIDR("1:2:3:4:5:6:7:0/128"), "2b")
+
+	tests := []struct {
+		Result interface{}
+		IP     string
+	}{
+		{"1a", "4.1.1.0"},
+		{"1b", "4.1.1.1"},
+		{nil, "4.1.1.2"},
+		{"2a", "1:2:3:4:5:6:7:8"},
+		{"2b", "1:2:3:4:5:6:7:0"},
+		{nil, "1:2:3:4:5:6:7:9"},
+	}
+
+	for _, tt := range tests {
+		assert.Equal(t, tt.Result, tree.Match(net.ParseIP(tt.IP)))
+	}
+
+	tree = NewCIDR6Tree()
+	tree.AddCIDR(getCIDR("1.1.1.1/0"), "cool")
+	tree.AddCIDR(getCIDR("1:2:3:4:5:6:7:8/0"), "cool6")
+	assert.Equal(t, "cool", tree.Contains(net.ParseIP("0.0.0.0")))
+	assert.Equal(t, "cool", tree.Contains(net.ParseIP("255.255.255.255")))
+	assert.Equal(t, "cool6", tree.Contains(net.ParseIP("::")))
+	assert.Equal(t, "cool6", tree.Contains(net.ParseIP("1:2:3:4:5:6:7:8")))
+}

+ 1 - 1
cidr_radix.go

@@ -76,7 +76,7 @@ func (tree *CIDRTree) AddCIDR(cidr *net.IPNet, val interface{}) {
 	node.value = val
 }
 
-// Finds the first match, which way be the least specific
+// Finds the first match, which may be the least specific
 func (tree *CIDRTree) Contains(ip uint32) (value interface{}) {
 	bit := startbit
 	node := tree.root

+ 39 - 17
config.go

@@ -235,13 +235,18 @@ func (c *Config) GetAllowList(k string, allowInterfaces bool) (*AllowList, error
 		return nil, fmt.Errorf("config `%s` has invalid type: %T", k, r)
 	}
 
-	tree := NewCIDRTree()
+	tree := NewCIDR6Tree()
 	var nameRules []AllowListNameRule
 
-	firstValue := true
-	allValuesMatch := true
-	defaultSet := false
-	var allValues bool
+	// Keep track of the rules we have added for both ipv4 and ipv6
+	type allowListRules struct {
+		firstValue     bool
+		allValuesMatch bool
+		defaultSet     bool
+		allValues      bool
+	}
+	rules4 := allowListRules{firstValue: true, allValuesMatch: true, defaultSet: false}
+	rules6 := allowListRules{firstValue: true, allValuesMatch: true, defaultSet: false}
 
 	for rawKey, rawValue := range rawMap {
 		rawCIDR, ok := rawKey.(string)
@@ -276,31 +281,48 @@ func (c *Config) GetAllowList(k string, allowInterfaces bool) (*AllowList, error
 		// TODO: should we error on duplicate CIDRs in the config?
 		tree.AddCIDR(cidr, value)
 
-		if firstValue {
-			allValues = value
-			firstValue = false
+		maskBits, maskSize := cidr.Mask.Size()
+
+		var rules *allowListRules
+		if maskSize == 32 {
+			rules = &rules4
+		} else {
+			rules = &rules6
+		}
+
+		if rules.firstValue {
+			rules.allValues = value
+			rules.firstValue = false
 		} else {
-			if value != allValues {
-				allValuesMatch = false
+			if value != rules.allValues {
+				rules.allValuesMatch = false
 			}
 		}
 
-		// Check if this is 0.0.0.0/0
-		bits, size := cidr.Mask.Size()
-		if bits == 0 && size == 32 {
-			defaultSet = true
+		// Check if this is 0.0.0.0/0 or ::/0
+		if maskBits == 0 {
+			rules.defaultSet = true
 		}
 	}
 
-	if !defaultSet {
-		if allValuesMatch {
+	if !rules4.defaultSet {
+		if rules4.allValuesMatch {
 			_, zeroCIDR, _ := net.ParseCIDR("0.0.0.0/0")
-			tree.AddCIDR(zeroCIDR, !allValues)
+			tree.AddCIDR(zeroCIDR, !rules4.allValues)
 		} else {
 			return nil, fmt.Errorf("config `%s` contains both true and false rules, but no default set for 0.0.0.0/0", k)
 		}
 	}
 
+	if !rules6.defaultSet {
+		if rules6.allValuesMatch {
+			_, zeroCIDR, _ := net.ParseCIDR("::/0")
+			tree.AddCIDR(zeroCIDR, !rules6.allValues)
+		} else {
+			return nil, fmt.Errorf("config `%s` contains both true and false rules, but no default set for ::/0", k)
+		}
+	}
+
 	return &AllowList{cidrTree: tree, nameRules: nameRules}, nil
 }
 

+ 23 - 0
config_test.go

@@ -109,6 +109,16 @@ func TestConfig_GetAllowList(t *testing.T) {
 	r, err = c.GetAllowList("allowlist", false)
 	assert.EqualError(t, err, "config `allowlist` contains both true and false rules, but no default set for 0.0.0.0/0")
 
+	c.Settings["allowlist"] = map[interface{}]interface{}{
+		"0.0.0.0/0":      true,
+		"10.0.0.0/8":     false,
+		"10.42.42.0/24":  true,
+		"fd00::/8":       true,
+		"fd00:fd00::/16": false,
+	}
+	r, err = c.GetAllowList("allowlist", false)
+	assert.EqualError(t, err, "config `allowlist` contains both true and false rules, but no default set for ::/0")
+
 	c.Settings["allowlist"] = map[interface{}]interface{}{
 		"0.0.0.0/0":     true,
 		"10.0.0.0/8":    false,
@@ -119,6 +129,19 @@ func TestConfig_GetAllowList(t *testing.T) {
 		assert.NotNil(t, r)
 	}
 
+	c.Settings["allowlist"] = map[interface{}]interface{}{
+		"0.0.0.0/0":      true,
+		"10.0.0.0/8":     false,
+		"10.42.42.0/24":  true,
+		"::/0":           false,
+		"fd00::/8":       true,
+		"fd00:fd00::/16": false,
+	}
+	r, err = c.GetAllowList("allowlist", false)
+	if assert.NoError(t, err) {
+		assert.NotNil(t, r)
+	}
+
 	// Test interface names
 
 	c.Settings["allowlist"] = map[interface{}]interface{}{

+ 28 - 14
control.go

@@ -23,11 +23,11 @@ type ControlHostInfo struct {
 	VpnIP          net.IP                  `json:"vpnIp"`
 	LocalIndex     uint32                  `json:"localIndex"`
 	RemoteIndex    uint32                  `json:"remoteIndex"`
-	RemoteAddrs    []udpAddr               `json:"remoteAddrs"`
+	RemoteAddrs    []*udpAddr              `json:"remoteAddrs"`
 	CachedPackets  int                     `json:"cachedPackets"`
 	Cert           *cert.NebulaCertificate `json:"cert"`
 	MessageCounter uint64                  `json:"messageCounter"`
-	CurrentRemote  udpAddr                 `json:"currentRemote"`
+	CurrentRemote  *udpAddr                `json:"currentRemote"`
 }
 
 // Start actually runs nebula, this is a nonblocking call. To block use Control.ShutdownBlock()
@@ -38,16 +38,7 @@ func (c *Control) Start() {
 // Stop signals nebula to shutdown, returns after the shutdown is complete
 func (c *Control) Stop() {
 	//TODO: stop tun and udp routines, the lock on hostMap effectively does that though
-	//TODO: this is probably better as a function in ConnectionManager or HostMap directly
-	c.f.hostMap.Lock()
-	for _, h := range c.f.hostMap.Hosts {
-		if h.ConnectionState.ready {
-			c.f.send(closeTunnel, 0, h.ConnectionState, h, h.remote, []byte{}, make([]byte, 12, 12), make([]byte, mtu))
-			c.l.WithField("vpnIp", IntIp(h.hostId)).WithField("udpAddr", h.remote).
-				Debug("Sending close tunnel message")
-		}
-	}
-	c.f.hostMap.Unlock()
+	c.CloseAllTunnels(false)
 	c.l.Info("Goodbye")
 }
 
@@ -149,13 +140,36 @@ func (c *Control) CloseTunnel(vpnIP uint32, localOnly bool) bool {
 	return true
 }
 
+// CloseAllTunnels is just like CloseTunnel except it goes through and shuts them all down, optionally you can avoid shutting down lighthouse tunnels
+// the int returned is a count of tunnels closed
+func (c *Control) CloseAllTunnels(excludeLighthouses bool) (closed int) {
+	//TODO: this is probably better as a function in ConnectionManager or HostMap directly
+	c.f.hostMap.Lock()
+	for _, h := range c.f.hostMap.Hosts {
+		if excludeLighthouses {
+			if _, ok := c.f.lightHouse.lighthouses[h.hostId]; ok {
+				continue
+			}
+		}
+
+		if h.ConnectionState.ready {
+			c.f.send(closeTunnel, 0, h.ConnectionState, h, h.remote, []byte{}, make([]byte, 12, 12), make([]byte, mtu))
+			c.l.WithField("vpnIp", IntIp(h.hostId)).WithField("udpAddr", h.remote).
+				Debug("Sending close tunnel message")
+			closed++
+		}
+	}
+	c.f.hostMap.Unlock()
+	return
+}
+
 func copyHostInfo(h *HostInfo) ControlHostInfo {
 	addrs := h.RemoteUDPAddrs()
 	chi := ControlHostInfo{
 		VpnIP:          int2ip(h.hostId),
 		LocalIndex:     h.localIndexId,
 		RemoteIndex:    h.remoteIndexId,
-		RemoteAddrs:    make([]udpAddr, len(addrs), len(addrs)),
+		RemoteAddrs:    make([]*udpAddr, len(addrs), len(addrs)),
 		CachedPackets:  len(h.packetStore),
 		MessageCounter: atomic.LoadUint64(&h.ConnectionState.atomicMessageCounter),
 	}
@@ -165,7 +179,7 @@ func copyHostInfo(h *HostInfo) ControlHostInfo {
 	}
 
 	if h.remote != nil {
-		chi.CurrentRemote = *h.remote
+		chi.CurrentRemote = h.remote.Copy()
 	}
 
 	for i, addr := range addrs {

+ 5 - 5
control_test.go

@@ -16,15 +16,15 @@ func TestControl_GetHostInfoByVpnIP(t *testing.T) {
 	// Special care must be taken to re-use all objects provided to the hostmap and certificate in the expectedInfo object
 	// To properly ensure we are not exposing core memory to the caller
 	hm := NewHostMap("test", &net.IPNet{}, make([]*net.IPNet, 0))
-	remote1 := NewUDPAddr(100, 4444)
-	remote2 := NewUDPAddr(101, 4444)
+	remote1 := NewUDPAddr(int2ip(100), 4444)
+	remote2 := NewUDPAddr(net.ParseIP("1:2:3:4:5:6:7:8"), 4444)
 	ipNet := net.IPNet{
 		IP:   net.IPv4(1, 2, 3, 4),
 		Mask: net.IPMask{255, 255, 255, 0},
 	}
 
 	ipNet2 := net.IPNet{
-		IP:   net.IPv4(1, 2, 3, 5),
+		IP:   net.ParseIP("1:2:3:4:5:6:7:8"),
 		Mask: net.IPMask{255, 255, 255, 0},
 	}
 
@@ -80,11 +80,11 @@ func TestControl_GetHostInfoByVpnIP(t *testing.T) {
 		VpnIP:          net.IPv4(1, 2, 3, 4).To4(),
 		LocalIndex:     201,
 		RemoteIndex:    200,
-		RemoteAddrs:    []udpAddr{*remote1, *remote2},
+		RemoteAddrs:    []*udpAddr{remote1, remote2},
 		CachedPackets:  0,
 		Cert:           crt.Copy(),
 		MessageCounter: 0,
-		CurrentRemote:  *NewUDPAddr(100, 4444),
+		CurrentRemote:  NewUDPAddr(int2ip(100), 4444),
 	}
 
 	// Make sure we don't have any unexpected fields

+ 1 - 0
examples/config.yml

@@ -74,6 +74,7 @@ lighthouse:
 # Port Nebula will be listening on. The default here is 4242. For a lighthouse node, the port should be defined,
 # however using port 0 will dynamically assign a port and is recommended for roaming nodes.
 listen:
+  # To listen on both any ipv4 and ipv6 use "[::]"
   host: 0.0.0.0
   port: 4242
   # Sets the max number of packets to pull from the kernel for each syscall (under systems that support recvmmsg)

+ 3 - 1
go.mod

@@ -9,7 +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-20180327030543-2492fe189ae6
-	github.com/golang/protobuf v1.3.2
+	github.com/golang/protobuf v1.5.0
 	github.com/imdario/mergo v0.3.8
 	github.com/kardianos/service v1.1.0
 	github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
@@ -29,6 +29,8 @@ require (
 	golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
 	golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
 	golang.org/x/sys v0.0.0-20191210023423-ac6580df4449
+	google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
+	google.golang.org/protobuf v1.26.0
 	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
 	gopkg.in/yaml.v2 v2.2.7
 )

+ 62 - 0
go.sum

@@ -1,3 +1,5 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -11,15 +13,19 @@ github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
 github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432 h1:M5QgkYacWj0Xs8MhpIK/5uwU02icXpEoSo9sM2aRCps=
 github.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432/go.mod h1:xwIwAxMvYnVrGJPe2FKx5prTrnAjGOD8zvDOnxnrrkM=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 h1:u/UEqS66A5ckRmS4yNpjmVH56sVtS/RfclBAYocb4as=
@@ -30,14 +36,31 @@ 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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
 github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
@@ -118,19 +141,30 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 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-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 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/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 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=
@@ -145,8 +179,34 @@ golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+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=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
@@ -158,3 +218,5 @@ gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
 gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 1 - 1
handshake.go

@@ -6,7 +6,7 @@ const (
 )
 
 func HandleIncomingHandshake(f *Interface, addr *udpAddr, packet []byte, h *Header, hostinfo *HostInfo) {
-	if !f.lightHouse.remoteAllowList.Allow(udp2ipInt(addr)) {
+	if !f.lightHouse.remoteAllowList.Allow(addr.IP) {
 		l.WithField("udpAddr", addr).Debug("lighthouse.remote_allow_list denied incoming handshake")
 		return
 	}

+ 2 - 2
handshake_ix.go

@@ -186,7 +186,7 @@ func ixHandshakeStage1(f *Interface, addr *udpAddr, packet []byte, h *Header) {
 	//l.Debugln("got symmetric pairs")
 
 	//hostinfo.ClearRemotes()
-	hostinfo.AddRemote(*addr)
+	hostinfo.AddRemote(addr)
 	hostinfo.ForcePromoteBest(f.hostMap.preferredRanges)
 	hostinfo.CreateRemoteCIDR(remoteCert)
 
@@ -358,7 +358,7 @@ func ixHandshakeStage2(f *Interface, addr *udpAddr, hostinfo *HostInfo, packet [
 	ci.eKey = NewNebulaCipherState(eKey)
 	//l.Debugln("got symmetric pairs")
 
-	hostinfo.SetRemote(*addr)
+	hostinfo.SetRemote(addr)
 	hostinfo.CreateRemoteCIDR(remoteCert)
 
 	f.handshakeManager.Complete(hostinfo, f)

+ 2 - 2
handshake_manager_test.go

@@ -140,8 +140,8 @@ 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")},
+	lh.addrMap = map[uint32][]*udpAddr{
+		ip: {NewUDPAddrFromString("10.1.1.1:4242")},
 	}
 
 	// This should trigger the hostmap to populate the hostinfo

+ 17 - 12
hostmap.go

@@ -303,7 +303,7 @@ func (hm *HostMap) AddRemote(vpnIp uint32, remote *udpAddr) *HostInfo {
 	hm.Lock()
 	i, v := hm.Hosts[vpnIp]
 	if v {
-		i.AddRemote(*remote)
+		i.AddRemote(remote)
 	} else {
 		i = &HostInfo{
 			Remotes:         []*HostInfoDest{NewHostInfoDest(remote)},
@@ -424,10 +424,11 @@ func (hm *HostMap) Punchy(conn *udpConn) {
 		metricsTxPunchy = metrics.NilCounter{}
 	}
 
+	b := []byte{1}
 	for {
 		for _, addr := range hm.PunchList() {
 			metricsTxPunchy.Inc(1)
-			conn.WriteTo([]byte{1}, addr)
+			conn.WriteTo(b, addr)
 		}
 		time.Sleep(time.Second * 30)
 	}
@@ -473,7 +474,7 @@ func (i *HostInfo) TryPromoteBest(preferredRanges []*net.IPNet, ifce *Interface)
 
 	if atomic.AddUint32(&i.promoteCounter, 1)&PromoteEvery == 0 {
 		// return early if we are already on a preferred remote
-		rIP := udp2ip(i.remote)
+		rIP := i.remote.IP
 		for _, l := range preferredRanges {
 			if l.Contains(rIP) {
 				return
@@ -506,7 +507,7 @@ func (i *HostInfo) ForcePromoteBest(preferredRanges []*net.IPNet) {
 func (i *HostInfo) getBestRemote(preferredRanges []*net.IPNet) (best *udpAddr, preferred bool) {
 	if len(i.Remotes) > 0 {
 		for _, r := range i.Remotes {
-			rIP := udp2ip(r.addr)
+			rIP := r.addr.IP
 
 			for _, l := range preferredRanges {
 				if l.Contains(rIP) {
@@ -625,8 +626,7 @@ func (i *HostInfo) GetCert() *cert.NebulaCertificate {
 	return nil
 }
 
-func (i *HostInfo) AddRemote(r udpAddr) *udpAddr {
-	remote := &r
+func (i *HostInfo) AddRemote(remote *udpAddr) *udpAddr {
 	//add := true
 	for _, r := range i.Remotes {
 		if r.addr.Equals(remote) {
@@ -638,12 +638,13 @@ func (i *HostInfo) AddRemote(r udpAddr) *udpAddr {
 	if len(i.Remotes) > MaxRemotes {
 		i.Remotes = i.Remotes[len(i.Remotes)-MaxRemotes:]
 	}
-	i.Remotes = append(i.Remotes, NewHostInfoDest(remote))
-	return remote
+	r := NewHostInfoDest(remote)
+	i.Remotes = append(i.Remotes, r)
+	return r.addr
 	//l.Debugf("Added remote %s for vpn ip", remote)
 }
 
-func (i *HostInfo) SetRemote(remote udpAddr) {
+func (i *HostInfo) SetRemote(remote *udpAddr) {
 	i.remote = i.AddRemote(remote)
 }
 
@@ -701,7 +702,7 @@ func (i *HostInfo) logger() *logrus.Entry {
 
 func NewHostInfoDest(addr *udpAddr) *HostInfoDest {
 	i := &HostInfoDest{
-		addr: addr,
+		addr: addr.Copy(),
 	}
 	return i
 }
@@ -816,8 +817,11 @@ func localIps(allowList *AllowList) *[]net.IP {
 			case *net.IPAddr:
 				ip = v.IP
 			}
-			if ip.To4() != nil && ip.IsLoopback() == false {
-				allow := allowList.Allow(ip2int(ip))
+
+			//TODO: Filtering out link local for now, this is probably the most correct thing
+			//TODO: Would be nice to filter out SLAAC MAC based ips as well
+			if ip.IsLoopback() == false && !ip.IsLinkLocalUnicast() {
+				allow := allowList.Allow(ip)
 				l.WithField("localIp", ip).WithField("allow", allow).Debug("localAllowList.Allow")
 				if !allow {
 					continue
@@ -831,6 +835,7 @@ func localIps(allowList *AllowList) *[]net.IP {
 }
 
 func PrivateIP(ip net.IP) bool {
+	//TODO: Private for ipv6 or just let it ride?
 	private := false
 	_, private24BitBlock, _ := net.ParseCIDR("10.0.0.0/8")
 	_, private20BitBlock, _ := net.ParseCIDR("172.16.0.0/12")

+ 12 - 10
hostmap_test.go

@@ -98,7 +98,7 @@ func TestHostmap(t *testing.T) {
 
 	// Promotion should ensure that the best remote is chosen (y)
 	info.ForcePromoteBest(myNets)
-	assert.True(t, myNet.Contains(udp2ip(info.remote)))
+	assert.True(t, myNet.Contains(info.remote.IP))
 
 }
 
@@ -125,27 +125,29 @@ func TestHostMap_rotateRemote(t *testing.T) {
 	assert.Nil(t, h.remote)
 
 	// 1 remote, no panic
-	h.AddRemote(*NewUDPAddr(ip2int(net.IP{1, 1, 1, 1}), 0))
+	h.AddRemote(NewUDPAddr(net.IP{1, 1, 1, 1}, 0))
 	h.rotateRemote()
-	assert.Equal(t, udp2ipInt(h.remote), ip2int(net.IP{1, 1, 1, 1}))
+	assert.Equal(t, h.remote.IP, net.IP{1, 1, 1, 1})
 
-	h.AddRemote(*NewUDPAddr(ip2int(net.IP{1, 1, 1, 2}), 0))
-	h.AddRemote(*NewUDPAddr(ip2int(net.IP{1, 1, 1, 3}), 0))
-	h.AddRemote(*NewUDPAddr(ip2int(net.IP{1, 1, 1, 4}), 0))
+	h.AddRemote(NewUDPAddr(net.IP{1, 1, 1, 2}, 0))
+	h.AddRemote(NewUDPAddr(net.IP{1, 1, 1, 3}, 0))
+	h.AddRemote(NewUDPAddr(net.IP{1, 1, 1, 4}, 0))
+
+	//TODO: ensure we are copying and not storing the slice!
 
 	// Rotate through those 3
 	h.rotateRemote()
-	assert.Equal(t, udp2ipInt(h.remote), ip2int(net.IP{1, 1, 1, 2}))
+	assert.Equal(t, h.remote.IP, net.IP{1, 1, 1, 2})
 
 	h.rotateRemote()
-	assert.Equal(t, udp2ipInt(h.remote), ip2int(net.IP{1, 1, 1, 3}))
+	assert.Equal(t, h.remote.IP, net.IP{1, 1, 1, 3})
 
 	h.rotateRemote()
-	assert.Equal(t, udp2ipInt(h.remote), ip2int(net.IP{1, 1, 1, 4}))
+	assert.Equal(t, h.remote, &udpAddr{IP: net.IP{1, 1, 1, 4}, Port: 0})
 
 	// Finally, we should start over
 	h.rotateRemote()
-	assert.Equal(t, udp2ipInt(h.remote), ip2int(net.IP{1, 1, 1, 1}))
+	assert.Equal(t, h.remote, &udpAddr{IP: net.IP{1, 1, 1, 1}, Port: 0})
 }
 
 func BenchmarkHostmappromote2(b *testing.B) {

+ 152 - 29
lighthouse.go

@@ -9,6 +9,7 @@ import (
 
 	"github.com/golang/protobuf/proto"
 	"github.com/rcrowley/go-metrics"
+	"github.com/sirupsen/logrus"
 	"github.com/slackhq/nebula/cert"
 )
 
@@ -21,7 +22,7 @@ type LightHouse struct {
 	punchConn    *udpConn
 
 	// Local cache of answers from light houses
-	addrMap map[uint32][]udpAddr
+	addrMap map[uint32][]*udpAddr
 
 	// filters remote addresses allowed for each host
 	// - When we are a lighthouse, this filters what addresses we store and
@@ -41,7 +42,7 @@ type LightHouse struct {
 	staticList  map[uint32]struct{}
 	lighthouses map[uint32]struct{}
 	interval    int
-	nebulaPort  uint32
+	nebulaPort  uint32 // 32 bits because protobuf does not have a uint16
 	punchBack   bool
 	punchDelay  time.Duration
 
@@ -58,7 +59,7 @@ func NewLightHouse(amLighthouse bool, myIp uint32, ips []uint32, interval int, n
 	h := LightHouse{
 		amLighthouse: amLighthouse,
 		myIp:         myIp,
-		addrMap:      make(map[uint32][]udpAddr),
+		addrMap:      make(map[uint32][]*udpAddr),
 		nebulaPort:   nebulaPort,
 		lighthouses:  make(map[uint32]struct{}),
 		staticList:   make(map[uint32]struct{}),
@@ -106,7 +107,7 @@ func (lh *LightHouse) ValidateLHStaticEntries() error {
 	return nil
 }
 
-func (lh *LightHouse) Query(ip uint32, f EncWriter) ([]udpAddr, error) {
+func (lh *LightHouse) Query(ip uint32, f EncWriter) ([]*udpAddr, error) {
 	if !lh.IsLighthouseIP(ip) {
 		lh.QueryServer(ip, f)
 	}
@@ -139,7 +140,7 @@ func (lh *LightHouse) QueryServer(ip uint32, f EncWriter) {
 }
 
 // Query our local lighthouse cached results
-func (lh *LightHouse) QueryCache(ip uint32) []udpAddr {
+func (lh *LightHouse) QueryCache(ip uint32) []*udpAddr {
 	lh.RLock()
 	if v, ok := lh.addrMap[ip]; ok {
 		lh.RUnlock()
@@ -179,7 +180,7 @@ func (lh *LightHouse) AddRemote(vpnIP uint32, toIp *udpAddr, static bool) {
 		}
 	}
 
-	allow := lh.remoteAllowList.Allow(udp2ipInt(toIp))
+	allow := lh.remoteAllowList.Allow(toIp.IP)
 	l.WithField("remoteIp", toIp).WithField("allow", allow).Debug("remoteAllowList.Allow")
 	if !allow {
 		return
@@ -189,7 +190,8 @@ func (lh *LightHouse) AddRemote(vpnIP uint32, toIp *udpAddr, static bool) {
 	if static {
 		lh.staticList[vpnIP] = struct{}{}
 	}
-	lh.addrMap[vpnIP] = append(lh.addrMap[vpnIP], *toIp)
+
+	lh.addrMap[vpnIP] = append(lh.addrMap[vpnIP], toIp.Copy())
 }
 
 func (lh *LightHouse) AddRemoteAndReset(vpnIP uint32, toIp *udpAddr) {
@@ -216,12 +218,41 @@ func NewLhQueryByInt(VpnIp uint32) *NebulaMeta {
 	}
 }
 
-func NewIpAndPort(ip net.IP, port uint32) IpAndPort {
-	return IpAndPort{Ip: ip2int(ip), Port: port}
+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 NewIpAndPortFromUDPAddr(addr *udpAddr) ip4Or6 {
+	return NewIpAndPort(addr.IP, uint32(addr.Port))
 }
 
-func NewIpAndPortFromUDPAddr(addr udpAddr) IpAndPort {
-	return IpAndPort{Ip: udp2ipInt(&addr), Port: uint32(addr.Port)}
+func NewUDPAddrFromLH4(ipp *IpAndPort) *udpAddr {
+	ip := ipp.Ip
+	return NewUDPAddr(
+		net.IPv4(byte(ip&0xff000000>>24), byte(ip&0x00ff0000>>16), byte(ip&0x0000ff00>>8), byte(ip&0x000000ff)),
+		uint16(ipp.Port),
+	)
+}
+
+func NewUDPAddrFromLH6(ipp *Ip6AndPort) *udpAddr {
+	return NewUDPAddr(ipp.Ip, uint16(ipp.Port))
 }
 
 func (lh *LightHouse) LhUpdateWorker(f EncWriter) {
@@ -236,20 +267,27 @@ func (lh *LightHouse) LhUpdateWorker(f EncWriter) {
 }
 
 func (lh *LightHouse) SendUpdate(f EncWriter) {
-	var ipps []*IpAndPort
+	var v4 []*IpAndPort
+	var v6 []*Ip6AndPort
 
 	for _, e := range *localIps(lh.localAllowList) {
 		// Only add IPs that aren't my VPN/tun IP
 		if ip2int(e) != lh.myIp {
 			ipp := NewIpAndPort(e, lh.nebulaPort)
-			ipps = append(ipps, &ipp)
+			if len(ipp.v6.Ip) > 0 {
+				v6 = append(v6, &ipp.v6)
+			} else {
+				v4 = append(v4, &ipp.v4)
+			}
+
 		}
 	}
 	m := &NebulaMeta{
 		Type: NebulaMeta_HostUpdateNotification,
 		Details: &NebulaMetaDetails{
-			VpnIp:      lh.myIp,
-			IpAndPorts: ipps,
+			VpnIp:       lh.myIp,
+			IpAndPorts:  v4,
+			Ip6AndPorts: v6,
 		},
 	}
 
@@ -272,8 +310,8 @@ type LightHouseHandler struct {
 	nb   []byte
 	out  []byte
 	meta *NebulaMeta
-	iap  []IpAndPort
-	iapp []*IpAndPort
+	iap  []ip4Or6
+	iapp []*ip4Or6
 }
 
 func (lh *LightHouse) NewRequestHandler() *LightHouseHandler {
@@ -306,8 +344,8 @@ func (lhh *LightHouseHandler) resetMeta() *NebulaMeta {
 
 func (lhh *LightHouseHandler) resizeIpAndPorts(n int) {
 	if cap(lhh.iap) < n {
-		lhh.iap = make([]IpAndPort, n)
-		lhh.iapp = make([]*IpAndPort, n)
+		lhh.iap = make([]ip4Or6, n)
+		lhh.iapp = make([]*ip4Or6, n)
 
 		for i := range lhh.iap {
 			lhh.iapp[i] = &lhh.iap[i]
@@ -317,7 +355,7 @@ func (lhh *LightHouseHandler) resizeIpAndPorts(n int) {
 	lhh.iapp = lhh.iapp[:n]
 }
 
-func (lhh *LightHouseHandler) setIpAndPortsFromNetIps(ips []udpAddr) []*IpAndPort {
+func (lhh *LightHouseHandler) setIpAndPortsFromNetIps(ips []*udpAddr) []*ip4Or6 {
 	lhh.resizeIpAndPorts(len(ips))
 	for i, e := range ips {
 		lhh.iap[i] = NewIpAndPortFromUDPAddr(e)
@@ -363,7 +401,25 @@ func (lhh *LightHouseHandler) HandleRequest(rAddr *udpAddr, vpnIp uint32, p []by
 			n = lhh.resetMeta()
 			n.Type = NebulaMeta_HostQueryReply
 			n.Details.VpnIp = reqVpnIP
-			n.Details.IpAndPorts = lhh.setIpAndPortsFromNetIps(ips)
+
+			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, err := proto.Marshal(n)
 			if err != nil {
 				l.WithError(err).WithField("vpnIp", IntIp(vpnIp)).Error("Failed to marshal lighthouse host query reply")
@@ -382,7 +438,25 @@ func (lhh *LightHouseHandler) HandleRequest(rAddr *udpAddr, vpnIp uint32, p []by
 				n = lhh.resetMeta()
 				n.Type = NebulaMeta_HostPunchNotification
 				n.Details.VpnIp = vpnIp
-				n.Details.IpAndPorts = lhh.setIpAndPortsFromNetIps(ips)
+
+				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])
@@ -394,11 +468,21 @@ func (lhh *LightHouseHandler) HandleRequest(rAddr *udpAddr, vpnIp uint32, p []by
 		if !lh.IsLighthouseIP(vpnIp) {
 			return
 		}
+
 		for _, a := range n.Details.IpAndPorts {
-			//first := n.Details.IpAndPorts[0]
-			ans := NewUDPAddr(a.Ip, uint16(a.Port))
-			lh.AddRemote(n.Details.VpnIp, ans, false)
+			ans := NewUDPAddrFromLH4(a)
+			if ans != nil {
+				lh.AddRemote(n.Details.VpnIp, ans, false)
+			}
 		}
+
+		for _, a := range n.Details.Ip6AndPorts {
+			ans := NewUDPAddrFromLH6(a)
+			if ans != nil {
+				lh.AddRemote(n.Details.VpnIp, ans, false)
+			}
+		}
+
 		// Non-blocking attempt to trigger, skip if it would block
 		select {
 		case lh.handshakeTrigger <- n.Details.VpnIp:
@@ -411,10 +495,21 @@ func (lhh *LightHouseHandler) HandleRequest(rAddr *udpAddr, vpnIp uint32, p []by
 			l.WithField("vpnIp", IntIp(vpnIp)).WithField("answer", IntIp(n.Details.VpnIp)).Debugln("Host sent invalid update")
 			return
 		}
+
 		for _, a := range n.Details.IpAndPorts {
-			ans := NewUDPAddr(a.Ip, uint16(a.Port))
-			lh.AddRemote(n.Details.VpnIp, ans, false)
+			ans := NewUDPAddrFromLH4(a)
+			if ans != nil {
+				lh.AddRemote(n.Details.VpnIp, ans, false)
+			}
+		}
+
+		for _, a := range n.Details.Ip6AndPorts {
+			ans := NewUDPAddrFromLH6(a)
+			if ans != nil {
+				lh.AddRemote(n.Details.VpnIp, ans, false)
+			}
 		}
+
 	case NebulaMeta_HostMovedNotification:
 	case NebulaMeta_HostPunchNotification:
 		if !lh.IsLighthouseIP(vpnIp) {
@@ -423,15 +518,43 @@ func (lhh *LightHouseHandler) HandleRequest(rAddr *udpAddr, vpnIp uint32, p []by
 
 		empty := []byte{0}
 		for _, a := range n.Details.IpAndPorts {
-			vpnPeer := NewUDPAddr(a.Ip, uint16(a.Port))
+			vpnPeer := NewUDPAddrFromLH4(a)
+			if vpnPeer == nil {
+				continue
+			}
+
 			go func() {
 				time.Sleep(lh.punchDelay)
 				lh.metricHolepunchTx.Inc(1)
 				lh.punchConn.WriteTo(empty, vpnPeer)
 
 			}()
-			l.Debugf("Punching %s on %d for %s", IntIp(a.Ip), a.Port, IntIp(n.Details.VpnIp))
+
+			if 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))
+				l.Debugf("Punching on %d for %s", a.Port, IntIp(n.Details.VpnIp))
+			}
 		}
+
+		for _, a := range n.Details.Ip6AndPorts {
+			vpnPeer := NewUDPAddrFromLH6(a)
+			if vpnPeer == nil {
+				continue
+			}
+
+			go func() {
+				time.Sleep(lh.punchDelay)
+				lh.metricHolepunchTx.Inc(1)
+				lh.punchConn.WriteTo(empty, vpnPeer)
+
+			}()
+
+			if 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))
+				l.Debugf("Punching on %d for %s", a.Port, IntIp(n.Details.VpnIp))
+			}
+		}
+
 		// 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.

+ 29 - 16
lighthouse_test.go

@@ -8,6 +8,17 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+//TODO: Add a test to ensure udpAddr is copied and not reused
+
+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
+	err := proto.Unmarshal(b, &m)
+	assert.NoError(t, err)
+	assert.Equal(t, "10.1.1.1", int2ip(m.GetIp()).String())
+}
+
 func TestNewLhQuery(t *testing.T) {
 	myIp := net.ParseIP("192.1.1.1")
 	myIpint := ip2int(myIp)
@@ -31,24 +42,24 @@ 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.Ip)
-	assert.Equal(t, uint32(12345), meh.Port)
+	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}
+	group := []*udpAddr{blah, blah2}
 	var lh *LightHouse
 	lhh := lh.NewRequestHandler()
 	result := lhh.setIpAndPortsFromNetIps(group)
-	assert.IsType(t, []*IpAndPort{}, result)
+	assert.IsType(t, []*ip4Or6{}, result)
 	assert.Len(t, result, 2)
-	assert.Equal(t, uint32(0x01020203), result[0].Ip)
-	assert.Equal(t, uint32(12345), result[0].Port)
-	assert.Equal(t, uint32(0x09090909), result[1].Ip)
-	assert.Equal(t, uint32(47828), result[1].Port)
+	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))
 
 }
@@ -60,7 +71,7 @@ func Test_lhStaticMapping(t *testing.T) {
 	udpServer, _ := NewListener("0.0.0.0", 0, true)
 
 	meh := NewLightHouse(true, 1, []uint32{ip2int(lh1IP)}, 10, 10003, udpServer, false, 1, false)
-	meh.AddRemote(ip2int(lh1IP), NewUDPAddr(ip2int(lh1IP), uint16(4242)), true)
+	meh.AddRemote(ip2int(lh1IP), NewUDPAddr(lh1IP, uint16(4242)), true)
 	err := meh.ValidateLHStaticEntries()
 	assert.Nil(t, err)
 
@@ -68,7 +79,7 @@ func Test_lhStaticMapping(t *testing.T) {
 	lh2IP := net.ParseIP(lh2)
 
 	meh = NewLightHouse(true, 1, []uint32{ip2int(lh1IP), ip2int(lh2IP)}, 10, 10003, udpServer, false, 1, false)
-	meh.AddRemote(ip2int(lh1IP), NewUDPAddr(ip2int(lh1IP), uint16(4242)), true)
+	meh.AddRemote(ip2int(lh1IP), NewUDPAddr(lh1IP, uint16(4242)), true)
 	err = meh.ValidateLHStaticEntries()
 	assert.EqualError(t, err, "Lighthouse 10.128.0.3 does not have a static_host_map entry")
 }
@@ -83,11 +94,11 @@ 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] = []*udpAddr{hAddr, hAddr2}
 
 	rAddr := NewUDPAddrFromString("1.2.2.3:12345")
 	rAddr2 := NewUDPAddrFromString("1.2.2.3:12346")
-	lh.addrMap[2] = []udpAddr{*rAddr, *rAddr2}
+	lh.addrMap[2] = []*udpAddr{rAddr, rAddr2}
 
 	mw := &mockEncWriter{}
 
@@ -142,15 +153,17 @@ func Test_lhRemoteAllowList(t *testing.T) {
 
 	remote1 := "10.20.0.3"
 	remote1IP := net.ParseIP(remote1)
-	lh.AddRemote(ip2int(remote1IP), NewUDPAddr(ip2int(remote1IP), uint16(4242)), true)
+	lh.AddRemote(ip2int(remote1IP), NewUDPAddr(remote1IP, uint16(4242)), true)
 	assert.Nil(t, lh.addrMap[ip2int(remote1IP)])
 
 	remote2 := "10.128.0.3"
 	remote2IP := net.ParseIP(remote2)
-	remote2UDPAddr := NewUDPAddr(ip2int(remote2IP), uint16(4242))
+	remote2UDPAddr := NewUDPAddr(remote2IP, uint16(4242))
 
 	lh.AddRemote(ip2int(remote2IP), remote2UDPAddr, true)
-	assert.Equal(t, remote2UDPAddr, &lh.addrMap[ip2int(remote2IP)][0])
+	// 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])
 }
 
 //func NewLightHouse(amLighthouse bool, myIp uint32, ips []string, interval int, nebulaPort int, pc *udpConn, punchBack bool) *LightHouse {

+ 8 - 19
main.go

@@ -4,8 +4,6 @@ import (
 	"encoding/binary"
 	"fmt"
 	"net"
-	"strconv"
-	"strings"
 	"time"
 
 	"github.com/sirupsen/logrus"
@@ -301,28 +299,19 @@ func Main(config *Config, configTest bool, buildVersion string, logger *logrus.L
 		vals, ok := v.([]interface{})
 		if ok {
 			for _, v := range vals {
-				parts := strings.Split(fmt.Sprintf("%v", v), ":")
-				addr, err := net.ResolveIPAddr("ip", parts[0])
+				ip, port, err := parseIPAndPort(fmt.Sprintf("%v", v))
 				if err == nil {
-					ip := addr.IP
-					port, err := strconv.Atoi(parts[1])
-					if err != nil {
-						return nil, NewContextualError("Static host address could not be parsed", m{"vpnIp": vpnIp}, err)
-					}
-					lightHouse.AddRemote(ip2int(vpnIp), NewUDPAddr(ip2int(ip), uint16(port)), true)
+					lightHouse.AddRemote(ip2int(vpnIp), NewUDPAddr(ip, port), true)
+				} else {
+					return nil, NewContextualError("Static host address could not be parsed", m{"vpnIp": vpnIp}, err)
 				}
 			}
 		} else {
-			//TODO: make this all a helper
-			parts := strings.Split(fmt.Sprintf("%v", v), ":")
-			addr, err := net.ResolveIPAddr("ip", parts[0])
+			ip, port, err := parseIPAndPort(fmt.Sprintf("%v", v))
 			if err == nil {
-				ip := addr.IP
-				port, err := strconv.Atoi(parts[1])
-				if err != nil {
-					return nil, NewContextualError("Static host address could not be parsed", m{"vpnIp": vpnIp}, err)
-				}
-				lightHouse.AddRemote(ip2int(vpnIp), NewUDPAddr(ip2int(ip), uint16(port)), true)
+				lightHouse.AddRemote(ip2int(vpnIp), NewUDPAddr(ip, port), true)
+			} else {
+				return nil, NewContextualError("Static host address could not be parsed", m{"vpnIp": vpnIp}, err)
 			}
 		}
 	}

+ 617 - 289
nebula.pb.go

@@ -1,24 +1,24 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.14.0
 // source: nebula.proto
 
 package nebula
 
 import (
-	fmt "fmt"
-	proto "github.com/golang/protobuf/proto"
-	math "math"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
 )
 
-// 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.ProtoPackageIsVersion3 // please upgrade the proto package
+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)
+)
 
 type NebulaMeta_MessageType int32
 
@@ -35,38 +35,59 @@ const (
 	NebulaMeta_PathCheckReply         NebulaMeta_MessageType = 9
 )
 
-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,
+// 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
 }
 
 func (x NebulaMeta_MessageType) String() string {
-	return proto.EnumName(NebulaMeta_MessageType_name, int32(x))
+	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)
+}
+
+// Deprecated: Use NebulaMeta_MessageType.Descriptor instead.
 func (NebulaMeta_MessageType) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_2d65afa7693df5ef, []int{0, 0}
+	return file_nebula_proto_rawDescGZIP(), []int{0, 0}
 }
 
 type NebulaPing_MessageType int32
@@ -76,382 +97,689 @@ const (
 	NebulaPing_Reply NebulaPing_MessageType = 1
 )
 
-var NebulaPing_MessageType_name = map[int32]string{
-	0: "Ping",
-	1: "Reply",
-}
+// 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,
+	}
+)
 
-var 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 proto.EnumName(NebulaPing_MessageType_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
-func (NebulaPing_MessageType) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_2d65afa7693df5ef, []int{3, 0}
+func (NebulaPing_MessageType) Descriptor() protoreflect.EnumDescriptor {
+	return file_nebula_proto_enumTypes[1].Descriptor()
 }
 
-type NebulaMeta struct {
-	Type                 NebulaMeta_MessageType `protobuf:"varint,1,opt,name=Type,json=type,proto3,enum=nebula.NebulaMeta_MessageType" json:"Type,omitempty"`
-	Details              *NebulaMetaDetails     `protobuf:"bytes,2,opt,name=Details,json=details,proto3" json:"Details,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}               `json:"-"`
-	XXX_unrecognized     []byte                 `json:"-"`
-	XXX_sizecache        int32                  `json:"-"`
+func (NebulaPing_MessageType) Type() protoreflect.EnumType {
+	return &file_nebula_proto_enumTypes[1]
 }
 
-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 (x NebulaPing_MessageType) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
 }
 
-func (m *NebulaMeta) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_NebulaMeta.Unmarshal(m, b)
+// Deprecated: Use NebulaPing_MessageType.Descriptor instead.
+func (NebulaPing_MessageType) EnumDescriptor() ([]byte, []int) {
+	return file_nebula_proto_rawDescGZIP(), []int{4, 0}
 }
-func (m *NebulaMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_NebulaMeta.Marshal(b, m, deterministic)
+
+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 (m *NebulaMeta) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_NebulaMeta.Merge(m, src)
+
+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) XXX_Size() int {
-	return xxx_messageInfo_NebulaMeta.Size(m)
+
+func (x *NebulaMeta) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
-func (m *NebulaMeta) XXX_DiscardUnknown() {
-	xxx_messageInfo_NebulaMeta.DiscardUnknown(m)
+
+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)
 }
 
-var xxx_messageInfo_NebulaMeta proto.InternalMessageInfo
+// Deprecated: Use NebulaMeta.ProtoReflect.Descriptor instead.
+func (*NebulaMeta) Descriptor() ([]byte, []int) {
+	return file_nebula_proto_rawDescGZIP(), []int{0}
+}
 
-func (m *NebulaMeta) GetType() NebulaMeta_MessageType {
-	if m != nil {
-		return m.Type
+func (x *NebulaMeta) GetType() NebulaMeta_MessageType {
+	if x != nil {
+		return x.Type
 	}
 	return NebulaMeta_None
 }
 
-func (m *NebulaMeta) GetDetails() *NebulaMetaDetails {
-	if m != nil {
-		return m.Details
+func (x *NebulaMeta) GetDetails() *NebulaMetaDetails {
+	if x != nil {
+		return x.Details
 	}
 	return nil
 }
 
 type NebulaMetaDetails struct {
-	VpnIp                uint32       `protobuf:"varint,1,opt,name=VpnIp,json=vpnIp,proto3" json:"VpnIp,omitempty"`
-	IpAndPorts           []*IpAndPort `protobuf:"bytes,2,rep,name=IpAndPorts,json=ipAndPorts,proto3" json:"IpAndPorts,omitempty"`
-	Counter              uint32       `protobuf:"varint,3,opt,name=counter,proto3" json:"counter,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
-	XXX_unrecognized     []byte       `json:"-"`
-	XXX_sizecache        int32        `json:"-"`
+	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"`
+	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 (x *NebulaMetaDetails) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
 
-func (m *NebulaMetaDetails) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_NebulaMetaDetails.Unmarshal(m, b)
-}
-func (m *NebulaMetaDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_NebulaMetaDetails.Marshal(b, m, deterministic)
-}
-func (m *NebulaMetaDetails) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_NebulaMetaDetails.Merge(m, src)
-}
-func (m *NebulaMetaDetails) XXX_Size() int {
-	return xxx_messageInfo_NebulaMetaDetails.Size(m)
-}
-func (m *NebulaMetaDetails) XXX_DiscardUnknown() {
-	xxx_messageInfo_NebulaMetaDetails.DiscardUnknown(m)
+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)
 }
 
-var xxx_messageInfo_NebulaMetaDetails proto.InternalMessageInfo
+// Deprecated: Use NebulaMetaDetails.ProtoReflect.Descriptor instead.
+func (*NebulaMetaDetails) Descriptor() ([]byte, []int) {
+	return file_nebula_proto_rawDescGZIP(), []int{1}
+}
 
-func (m *NebulaMetaDetails) GetVpnIp() uint32 {
-	if m != nil {
-		return m.VpnIp
+func (x *NebulaMetaDetails) GetVpnIp() uint32 {
+	if x != nil {
+		return x.VpnIp
 	}
 	return 0
 }
 
-func (m *NebulaMetaDetails) GetIpAndPorts() []*IpAndPort {
-	if m != nil {
-		return m.IpAndPorts
+func (x *NebulaMetaDetails) GetIpAndPorts() []*IpAndPort {
+	if x != nil {
+		return x.IpAndPorts
 	}
 	return nil
 }
 
-func (m *NebulaMetaDetails) GetCounter() uint32 {
-	if m != nil {
-		return m.Counter
+func (x *NebulaMetaDetails) GetIp6AndPorts() []*Ip6AndPort {
+	if x != nil {
+		return x.Ip6AndPorts
+	}
+	return nil
+}
+
+func (x *NebulaMetaDetails) GetCounter() uint32 {
+	if x != nil {
+		return x.Counter
 	}
 	return 0
 }
 
 type IpAndPort struct {
-	Ip                   uint32   `protobuf:"varint,1,opt,name=Ip,json=ip,proto3" json:"Ip,omitempty"`
-	Port                 uint32   `protobuf:"varint,2,opt,name=Port,json=port,proto3" json:"Port,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	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 (m *IpAndPort) Reset()         { *m = IpAndPort{} }
-func (m *IpAndPort) String() string { return proto.CompactTextString(m) }
-func (*IpAndPort) ProtoMessage()    {}
-func (*IpAndPort) Descriptor() ([]byte, []int) {
-	return fileDescriptor_2d65afa7693df5ef, []int{2}
+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 *IpAndPort) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_IpAndPort.Unmarshal(m, b)
+func (x *IpAndPort) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
-func (m *IpAndPort) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_IpAndPort.Marshal(b, m, deterministic)
+
+func (*IpAndPort) ProtoMessage() {}
+
+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)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
 }
-func (m *IpAndPort) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_IpAndPort.Merge(m, src)
+
+// Deprecated: Use IpAndPort.ProtoReflect.Descriptor instead.
+func (*IpAndPort) Descriptor() ([]byte, []int) {
+	return file_nebula_proto_rawDescGZIP(), []int{2}
 }
-func (m *IpAndPort) XXX_Size() int {
-	return xxx_messageInfo_IpAndPort.Size(m)
+
+func (x *IpAndPort) GetIp() uint32 {
+	if x != nil {
+		return x.Ip
+	}
+	return 0
 }
-func (m *IpAndPort) XXX_DiscardUnknown() {
-	xxx_messageInfo_IpAndPort.DiscardUnknown(m)
+
+func (x *IpAndPort) GetPort() uint32 {
+	if x != nil {
+		return x.Port
+	}
+	return 0
 }
 
-var xxx_messageInfo_IpAndPort proto.InternalMessageInfo
+type Ip6AndPort struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	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 *IpAndPort) GetIp() uint32 {
-	if m != nil {
-		return m.Ip
+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)
 	}
-	return 0
 }
 
-func (m *IpAndPort) GetPort() uint32 {
-	if m != nil {
-		return m.Port
+func (x *Ip6AndPort) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Ip6AndPort) ProtoMessage() {}
+
+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)
+		}
+		return ms
 	}
-	return 0
+	return mi.MessageOf(x)
 }
 
-type NebulaPing struct {
-	Type                 NebulaPing_MessageType `protobuf:"varint,1,opt,name=Type,json=type,proto3,enum=nebula.NebulaPing_MessageType" json:"Type,omitempty"`
-	Time                 uint64                 `protobuf:"varint,2,opt,name=Time,json=time,proto3" json:"Time,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}               `json:"-"`
-	XXX_unrecognized     []byte                 `json:"-"`
-	XXX_sizecache        int32                  `json:"-"`
+// Deprecated: Use Ip6AndPort.ProtoReflect.Descriptor instead.
+func (*Ip6AndPort) Descriptor() ([]byte, []int) {
+	return file_nebula_proto_rawDescGZIP(), []int{3}
 }
 
-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{3}
+func (x *Ip6AndPort) GetIp() []byte {
+	if x != nil {
+		return x.Ip
+	}
+	return nil
 }
 
-func (m *NebulaPing) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_NebulaPing.Unmarshal(m, b)
+func (x *Ip6AndPort) GetPort() uint32 {
+	if x != nil {
+		return x.Port
+	}
+	return 0
 }
-func (m *NebulaPing) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_NebulaPing.Marshal(b, m, deterministic)
+
+type NebulaPing struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	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) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_NebulaPing.Merge(m, src)
+
+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 *NebulaPing) XXX_Size() int {
-	return xxx_messageInfo_NebulaPing.Size(m)
+
+func (x *NebulaPing) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
-func (m *NebulaPing) XXX_DiscardUnknown() {
-	xxx_messageInfo_NebulaPing.DiscardUnknown(m)
+
+func (*NebulaPing) ProtoMessage() {}
+
+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)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
 }
 
-var xxx_messageInfo_NebulaPing proto.InternalMessageInfo
+// Deprecated: Use NebulaPing.ProtoReflect.Descriptor instead.
+func (*NebulaPing) Descriptor() ([]byte, []int) {
+	return file_nebula_proto_rawDescGZIP(), []int{4}
+}
 
-func (m *NebulaPing) GetType() NebulaPing_MessageType {
-	if m != nil {
-		return m.Type
+func (x *NebulaPing) GetType() NebulaPing_MessageType {
+	if x != nil {
+		return x.Type
 	}
 	return NebulaPing_Ping
 }
 
-func (m *NebulaPing) GetTime() uint64 {
-	if m != nil {
-		return m.Time
+func (x *NebulaPing) GetTime() uint64 {
+	if x != nil {
+		return x.Time
 	}
 	return 0
 }
 
 type NebulaHandshake struct {
-	Details              *NebulaHandshakeDetails `protobuf:"bytes,1,opt,name=Details,json=details,proto3" json:"Details,omitempty"`
-	Hmac                 []byte                  `protobuf:"bytes,2,opt,name=Hmac,json=hmac,proto3" json:"Hmac,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}                `json:"-"`
-	XXX_unrecognized     []byte                  `json:"-"`
-	XXX_sizecache        int32                   `json:"-"`
-}
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
 
-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{4}
+	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 *NebulaHandshake) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_NebulaHandshake.Unmarshal(m, b)
-}
-func (m *NebulaHandshake) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_NebulaHandshake.Marshal(b, m, deterministic)
-}
-func (m *NebulaHandshake) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_NebulaHandshake.Merge(m, src)
+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 *NebulaHandshake) XXX_Size() int {
-	return xxx_messageInfo_NebulaHandshake.Size(m)
+
+func (x *NebulaHandshake) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
-func (m *NebulaHandshake) XXX_DiscardUnknown() {
-	xxx_messageInfo_NebulaHandshake.DiscardUnknown(m)
+
+func (*NebulaHandshake) ProtoMessage() {}
+
+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)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
 }
 
-var xxx_messageInfo_NebulaHandshake proto.InternalMessageInfo
+// Deprecated: Use NebulaHandshake.ProtoReflect.Descriptor instead.
+func (*NebulaHandshake) Descriptor() ([]byte, []int) {
+	return file_nebula_proto_rawDescGZIP(), []int{5}
+}
 
-func (m *NebulaHandshake) GetDetails() *NebulaHandshakeDetails {
-	if m != nil {
-		return m.Details
+func (x *NebulaHandshake) GetDetails() *NebulaHandshakeDetails {
+	if x != nil {
+		return x.Details
 	}
 	return nil
 }
 
-func (m *NebulaHandshake) GetHmac() []byte {
-	if m != nil {
-		return m.Hmac
+func (x *NebulaHandshake) GetHmac() []byte {
+	if x != nil {
+		return x.Hmac
 	}
 	return nil
 }
 
 type NebulaHandshakeDetails struct {
-	Cert                 []byte   `protobuf:"bytes,1,opt,name=Cert,json=cert,proto3" json:"Cert,omitempty"`
-	InitiatorIndex       uint32   `protobuf:"varint,2,opt,name=InitiatorIndex,json=initiatorIndex,proto3" json:"InitiatorIndex,omitempty"`
-	ResponderIndex       uint32   `protobuf:"varint,3,opt,name=ResponderIndex,json=responderIndex,proto3" json:"ResponderIndex,omitempty"`
-	Cookie               uint64   `protobuf:"varint,4,opt,name=Cookie,json=cookie,proto3" json:"Cookie,omitempty"`
-	Time                 uint64   `protobuf:"varint,5,opt,name=Time,json=time,proto3" json:"Time,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
-}
-
-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{5}
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	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 *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 *NebulaHandshakeDetails) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_NebulaHandshakeDetails.Unmarshal(m, b)
-}
-func (m *NebulaHandshakeDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_NebulaHandshakeDetails.Marshal(b, m, deterministic)
-}
-func (m *NebulaHandshakeDetails) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_NebulaHandshakeDetails.Merge(m, src)
-}
-func (m *NebulaHandshakeDetails) XXX_Size() int {
-	return xxx_messageInfo_NebulaHandshakeDetails.Size(m)
+func (x *NebulaHandshakeDetails) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
-func (m *NebulaHandshakeDetails) XXX_DiscardUnknown() {
-	xxx_messageInfo_NebulaHandshakeDetails.DiscardUnknown(m)
+
+func (*NebulaHandshakeDetails) ProtoMessage() {}
+
+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
+	}
+	return mi.MessageOf(x)
 }
 
-var xxx_messageInfo_NebulaHandshakeDetails proto.InternalMessageInfo
+// Deprecated: Use NebulaHandshakeDetails.ProtoReflect.Descriptor instead.
+func (*NebulaHandshakeDetails) Descriptor() ([]byte, []int) {
+	return file_nebula_proto_rawDescGZIP(), []int{6}
+}
 
-func (m *NebulaHandshakeDetails) GetCert() []byte {
-	if m != nil {
-		return m.Cert
+func (x *NebulaHandshakeDetails) GetCert() []byte {
+	if x != nil {
+		return x.Cert
 	}
 	return nil
 }
 
-func (m *NebulaHandshakeDetails) GetInitiatorIndex() uint32 {
-	if m != nil {
-		return m.InitiatorIndex
+func (x *NebulaHandshakeDetails) GetInitiatorIndex() uint32 {
+	if x != nil {
+		return x.InitiatorIndex
 	}
 	return 0
 }
 
-func (m *NebulaHandshakeDetails) GetResponderIndex() uint32 {
-	if m != nil {
-		return m.ResponderIndex
+func (x *NebulaHandshakeDetails) GetResponderIndex() uint32 {
+	if x != nil {
+		return x.ResponderIndex
 	}
 	return 0
 }
 
-func (m *NebulaHandshakeDetails) GetCookie() uint64 {
-	if m != nil {
-		return m.Cookie
+func (x *NebulaHandshakeDetails) GetCookie() uint64 {
+	if x != nil {
+		return x.Cookie
 	}
 	return 0
 }
 
-func (m *NebulaHandshakeDetails) GetTime() uint64 {
-	if m != nil {
-		return m.Time
+func (x *NebulaHandshakeDetails) GetTime() uint64 {
+	if x != nil {
+		return x.Time
 	}
 	return 0
 }
 
-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((*IpAndPort)(nil), "nebula.IpAndPort")
-	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{
-	// 491 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0x41, 0x6f, 0xda, 0x4c,
-	0x10, 0x8d, 0x61, 0x81, 0x30, 0x80, 0xe3, 0xcc, 0xf7, 0x15, 0x91, 0x1e, 0xaa, 0xc8, 0x87, 0x8a,
-	0x13, 0x55, 0xc9, 0xa5, 0xd7, 0x8a, 0x1e, 0xe0, 0x00, 0xa2, 0x56, 0xda, 0x1e, 0xab, 0x8d, 0x3d,
-	0x8d, 0x57, 0xe0, 0xdd, 0x95, 0xbd, 0xa0, 0xf0, 0x8f, 0xfa, 0x63, 0x7a, 0xec, 0x0f, 0xaa, 0x76,
-	0x0d, 0xa6, 0x84, 0xa8, 0xb7, 0x7d, 0xf3, 0xde, 0xcc, 0x8e, 0xdf, 0x5b, 0x43, 0x57, 0xd2, 0xc3,
-	0x66, 0xcd, 0x47, 0x3a, 0x57, 0x46, 0x61, 0xb3, 0x44, 0xe1, 0xaf, 0x1a, 0xc0, 0xc2, 0x1d, 0xe7,
-	0x64, 0x38, 0x8e, 0x81, 0xdd, 0xef, 0x34, 0x0d, 0xbc, 0x5b, 0x6f, 0xe8, 0x8f, 0xdf, 0x8c, 0xf6,
-	0x3d, 0x47, 0xc5, 0x68, 0x4e, 0x45, 0xc1, 0x1f, 0xc9, 0xaa, 0x22, 0x66, 0x76, 0x9a, 0xf0, 0x0e,
-	0x5a, 0x9f, 0xc8, 0x70, 0xb1, 0x2e, 0x06, 0xb5, 0x5b, 0x6f, 0xd8, 0x19, 0xdf, 0x9c, 0xb7, 0xed,
-	0x05, 0x51, 0x2b, 0x29, 0x0f, 0xe1, 0x6f, 0x0f, 0x3a, 0x7f, 0x8d, 0xc2, 0x4b, 0x60, 0x0b, 0x25,
-	0x29, 0xb8, 0xc0, 0x1e, 0xb4, 0xa7, 0xaa, 0x30, 0x9f, 0x37, 0x94, 0xef, 0x02, 0x0f, 0x11, 0xfc,
-	0x0a, 0x46, 0xa4, 0xd7, 0xbb, 0xa0, 0x86, 0xaf, 0xa1, 0x6f, 0x6b, 0x5f, 0x74, 0xc2, 0x0d, 0x2d,
-	0x94, 0x11, 0x3f, 0x44, 0xcc, 0x8d, 0x50, 0x32, 0xa8, 0xe3, 0x0d, 0xbc, 0xb2, 0xdc, 0x5c, 0x6d,
-	0x29, 0x39, 0xa1, 0xd8, 0x81, 0x5a, 0x6e, 0x64, 0x9c, 0x9e, 0x50, 0x0d, 0xf4, 0x01, 0x2c, 0xf5,
-	0x2d, 0x55, 0x3c, 0x13, 0x41, 0x13, 0xff, 0x83, 0xab, 0x23, 0x2e, 0xaf, 0x6d, 0xd9, 0xcd, 0x96,
-	0xdc, 0xa4, 0x93, 0x94, 0xe2, 0x55, 0x70, 0x69, 0x37, 0xab, 0x60, 0x29, 0x69, 0x87, 0x5b, 0xb8,
-	0x3e, 0xfb, 0x68, 0xfc, 0x1f, 0x1a, 0x5f, 0xb5, 0x9c, 0x69, 0xe7, 0x6a, 0x2f, 0x6a, 0x6c, 0x2d,
-	0xc0, 0xf7, 0x00, 0x33, 0xfd, 0x51, 0x26, 0x4b, 0x95, 0x1b, 0xeb, 0x5c, 0x7d, 0xd8, 0x19, 0x5f,
-	0x1f, 0x9c, 0xab, 0x98, 0x08, 0x44, 0x25, 0xc2, 0x01, 0xb4, 0x62, 0xb5, 0x91, 0x86, 0xf2, 0x41,
-	0xdd, 0x8d, 0x3a, 0xc0, 0xf0, 0x1d, 0xb4, 0xab, 0x16, 0xf4, 0xa1, 0x56, 0x5d, 0x56, 0x13, 0x1a,
-	0x11, 0x98, 0xad, 0xbb, 0x74, 0x7a, 0x11, 0xd3, 0x2a, 0x37, 0xe1, 0xd3, 0x21, 0xf6, 0xa5, 0x90,
-	0x8f, 0xff, 0x8e, 0xdd, 0x2a, 0x5e, 0x88, 0x1d, 0x81, 0xdd, 0x8b, 0x8c, 0xdc, 0x54, 0x16, 0x31,
-	0x23, 0x32, 0x0a, 0xc3, 0xb3, 0x50, 0x6d, 0x73, 0x70, 0x81, 0x6d, 0x68, 0x94, 0x16, 0x79, 0xe1,
-	0x77, 0xb8, 0x2a, 0xe7, 0x4e, 0xb9, 0x4c, 0x8a, 0x94, 0xaf, 0x08, 0x3f, 0x1c, 0x5f, 0x90, 0xe7,
-	0x5e, 0xd0, 0xb3, 0x0d, 0x2a, 0xe5, 0xf3, 0x67, 0x64, 0x97, 0x98, 0x66, 0x3c, 0x76, 0x4b, 0x74,
-	0x23, 0x96, 0x66, 0x3c, 0x0e, 0x7f, 0x7a, 0xd0, 0x7f, 0xb9, 0xcf, 0xca, 0x27, 0x94, 0x1b, 0x77,
-	0x4b, 0x37, 0x62, 0x31, 0xe5, 0x06, 0xdf, 0x82, 0x3f, 0x93, 0xc2, 0x08, 0x6e, 0x54, 0x3e, 0x93,
-	0x09, 0x3d, 0xed, 0x7d, 0xf2, 0xc5, 0x49, 0xd5, 0xea, 0x22, 0x2a, 0xb4, 0x92, 0x09, 0xed, 0x75,
-	0x65, 0x06, 0x7e, 0x7e, 0x52, 0xc5, 0x3e, 0x34, 0x27, 0x4a, 0xad, 0x04, 0x0d, 0x98, 0x73, 0xa6,
-	0x19, 0x3b, 0x54, 0xf9, 0xd5, 0x38, 0xfa, 0xf5, 0xd0, 0x74, 0x3f, 0xe3, 0xdd, 0x9f, 0x00, 0x00,
-	0x00, 0xff, 0xff, 0x65, 0xc6, 0x25, 0x44, 0x9c, 0x03, 0x00, 0x00,
+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,
+}
+
+var (
+	file_nebula_proto_rawDescOnce sync.Once
+	file_nebula_proto_rawDescData = file_nebula_proto_rawDesc
+)
+
+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
 }

+ 7 - 2
nebula.proto

@@ -1,6 +1,8 @@
 syntax = "proto3";
 package nebula;
 
+option go_package = "github.com/slackhq/nebula";
+
 message NebulaMeta {
   enum MessageType {
     None = 0;
@@ -20,11 +22,10 @@ message NebulaMeta {
   NebulaMetaDetails Details = 2;
 }
 
-
 message NebulaMetaDetails {
-
   uint32 VpnIp = 1;
   repeated IpAndPort IpAndPorts = 2;
+  repeated Ip6AndPort Ip6AndPorts = 4;
   uint32 counter = 3;
 }
 
@@ -33,6 +34,10 @@ message IpAndPort {
   uint32 Port = 2;
 }
 
+message Ip6AndPort {
+  bytes Ip = 1;
+  uint32 Port = 2;
+}
 
 message NebulaPing {
   enum MessageType {

+ 2 - 2
outside.go

@@ -142,7 +142,7 @@ func (f *Interface) closeTunnel(hostInfo *HostInfo) {
 
 func (f *Interface) handleHostRoaming(hostinfo *HostInfo, addr *udpAddr) {
 	if hostDidRoam(hostinfo.remote, addr) {
-		if !f.lightHouse.remoteAllowList.Allow(udp2ipInt(addr)) {
+		if !f.lightHouse.remoteAllowList.Allow(addr.IP) {
 			hostinfo.logger().WithField("newAddr", addr).Debug("lighthouse.remote_allow_list denied roaming")
 			return
 		}
@@ -159,7 +159,7 @@ func (f *Interface) handleHostRoaming(hostinfo *HostInfo, addr *udpAddr) {
 		hostinfo.lastRoam = time.Now()
 		remoteCopy := *hostinfo.remote
 		hostinfo.lastRoamRemote = &remoteCopy
-		hostinfo.SetRemote(*addr)
+		hostinfo.SetRemote(addr)
 		if f.lightHouse.amLighthouse {
 			f.lightHouse.AddRemote(hostinfo.hostId, addr, false)
 		}

+ 2 - 2
ssh.go

@@ -562,7 +562,7 @@ func sshCreateTunnel(ifce *Interface, fs interface{}, a []string, w sshd.StringW
 
 	hostInfo = ifce.handshakeManager.AddVpnIP(vpnIp)
 	if addr != nil {
-		hostInfo.SetRemote(*addr)
+		hostInfo.SetRemote(addr)
 	}
 	ifce.getOrHandshake(vpnIp)
 
@@ -604,7 +604,7 @@ func sshChangeRemote(ifce *Interface, fs interface{}, a []string, w sshd.StringW
 		return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn ip: %v", a[0]))
 	}
 
-	hostInfo.SetRemote(*addr)
+	hostInfo.SetRemote(addr)
 	return w.WriteLine("Changed")
 }
 

+ 62 - 0
udp_all.go

@@ -0,0 +1,62 @@
+package nebula
+
+import (
+	"encoding/json"
+	"fmt"
+	"net"
+	"strconv"
+)
+
+type udpAddr struct {
+	IP   net.IP
+	Port uint16
+}
+
+func NewUDPAddr(ip net.IP, port uint16) *udpAddr {
+	addr := udpAddr{IP: make([]byte, len(ip)), Port: port}
+	copy(addr.IP, ip)
+	return &addr
+}
+
+func NewUDPAddrFromString(s string) *udpAddr {
+	ip, port, err := parseIPAndPort(s)
+	//TODO: handle err
+	_ = err
+	return &udpAddr{IP: ip, Port: port}
+}
+
+func (ua *udpAddr) Equals(t *udpAddr) bool {
+	if t == nil || ua == nil {
+		return t == nil && ua == nil
+	}
+	return ua.IP.Equal(t.IP) && ua.Port == t.Port
+}
+
+func (ua *udpAddr) String() string {
+	return net.JoinHostPort(ua.IP.String(), fmt.Sprintf("%v", ua.Port))
+}
+
+func (ua *udpAddr) MarshalJSON() ([]byte, error) {
+	return json.Marshal(m{"ip": ua.IP, "port": ua.Port})
+}
+
+func (ua *udpAddr) Copy() *udpAddr {
+	nu := udpAddr{
+		Port: ua.Port,
+		IP:   make(net.IP, len(ua.IP)),
+	}
+
+	copy(nu.IP, ua.IP)
+	return &nu
+}
+
+func parseIPAndPort(s string) (net.IP, uint16, error) {
+	rIp, sPort, err := net.SplitHostPort(s)
+	if err != nil {
+		return nil, 0, err
+	}
+
+	iPort, err := strconv.Atoi(sPort)
+	ip := net.ParseIP(rIp)
+	return ip, uint16(iPort), nil
+}

+ 2 - 1
udp_darwin.go

@@ -28,6 +28,7 @@ func NewListenConfig(multi bool) net.ListenConfig {
 					return controlErr
 				}
 			}
+
 			return nil
 		},
 	}
@@ -39,5 +40,5 @@ func (u *udpConn) Rebind() error {
 		return err
 	}
 
-	return syscall.SetsockoptInt(int(file.Fd()), unix.IPPROTO_IP, unix.IP_BOUND_IF, 0)
+	return syscall.SetsockoptInt(int(file.Fd()), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, 0)
 }

+ 10 - 62
udp_generic.go

@@ -7,48 +7,17 @@ package nebula
 
 import (
 	"context"
-	"encoding/binary"
 	"fmt"
 	"net"
-	"strconv"
-	"strings"
 )
 
-type udpAddr struct {
-	net.UDPAddr
-}
-
 type udpConn struct {
 	*net.UDPConn
 }
 
-func NewUDPAddr(ip uint32, port uint16) *udpAddr {
-	return &udpAddr{
-		UDPAddr: net.UDPAddr{
-			IP:   int2ip(ip),
-			Port: int(port),
-		},
-	}
-}
-
-func NewUDPAddrFromString(s string) *udpAddr {
-	p := strings.Split(s, ":")
-	if len(p) < 2 {
-		return nil
-	}
-
-	port, _ := strconv.Atoi(p[1])
-	return &udpAddr{
-		UDPAddr: net.UDPAddr{
-			IP:   net.ParseIP(p[0]),
-			Port: port,
-		},
-	}
-}
-
 func NewListener(ip string, port int, multi bool) (*udpConn, error) {
 	lc := NewListenConfig(multi)
-	pc, err := lc.ListenPacket(context.TODO(), "udp4", fmt.Sprintf("%s:%d", ip, port))
+	pc, err := lc.ListenPacket(context.TODO(), "udp", fmt.Sprintf("%s:%d", ip, port))
 	if err != nil {
 		return nil, err
 	}
@@ -58,26 +27,8 @@ func NewListener(ip string, port int, multi bool) (*udpConn, error) {
 	return nil, fmt.Errorf("Unexpected PacketConn: %T %#v", pc, pc)
 }
 
-func (ua *udpAddr) Equals(t *udpAddr) bool {
-	if t == nil || ua == nil {
-		return t == nil && ua == nil
-	}
-	return ua.IP.Equal(t.IP) && ua.Port == t.Port
-}
-
-func (ua *udpAddr) Copy() udpAddr {
-	nu := udpAddr{net.UDPAddr{
-		Port: ua.Port,
-		Zone: ua.Zone,
-		IP:   make(net.IP, len(ua.IP)),
-	}}
-
-	copy(nu.IP, ua.IP)
-	return nu
-}
-
 func (uc *udpConn) WriteTo(b []byte, addr *udpAddr) error {
-	_, err := uc.UDPConn.WriteToUDP(b, &addr.UDPAddr)
+	_, err := uc.UDPConn.WriteToUDP(b, &net.UDPAddr{IP: addr.IP, Port: int(addr.Port)})
 	return err
 }
 
@@ -86,7 +37,11 @@ func (uc *udpConn) LocalAddr() (*udpAddr, error) {
 
 	switch v := a.(type) {
 	case *net.UDPAddr:
-		return &udpAddr{UDPAddr: *v}, nil
+		addr := &udpAddr{IP: make([]byte, len(v.IP))}
+		copy(addr.IP, v.IP)
+		addr.Port = uint16(v.Port)
+		return addr, nil
+
 	default:
 		return nil, fmt.Errorf("LocalAddr returned: %#v", a)
 	}
@@ -110,7 +65,7 @@ func (u *udpConn) ListenOut(f *Interface, q int) {
 	buffer := make([]byte, mtu)
 	header := &Header{}
 	fwPacket := &FirewallPacket{}
-	udpAddr := &udpAddr{}
+	udpAddr := &udpAddr{IP: make([]byte, 16)}
 	nb := make([]byte, 12, 12)
 
 	lhh := f.lightHouse.NewRequestHandler()
@@ -125,19 +80,12 @@ func (u *udpConn) ListenOut(f *Interface, q int) {
 			continue
 		}
 
-		udpAddr.UDPAddr = *rua
+		udpAddr.IP = rua.IP
+		udpAddr.Port = uint16(rua.Port)
 		f.readOutsidePackets(udpAddr, plaintext[:0], buffer[:n], header, fwPacket, lhh, nb, q, conntrackCache.Get())
 	}
 }
 
-func udp2ip(addr *udpAddr) net.IP {
-	return addr.IP
-}
-
-func udp2ipInt(addr *udpAddr) uint32 {
-	return binary.BigEndian.Uint32(addr.IP.To4())
-}
-
 func hostDidRoam(addr *udpAddr, newaddr *udpAddr) bool {
 	return !addr.Equals(newaddr)
 }

+ 25 - 80
udp_linux.go

@@ -4,11 +4,8 @@ package nebula
 
 import (
 	"encoding/binary"
-	"encoding/json"
 	"fmt"
 	"net"
-	"strconv"
-	"strings"
 	"syscall"
 	"unsafe"
 
@@ -22,38 +19,6 @@ type udpConn struct {
 	sysFd int
 }
 
-type udpAddr struct {
-	IP   uint32
-	Port uint16
-}
-
-func NewUDPAddr(ip uint32, port uint16) *udpAddr {
-	return &udpAddr{IP: ip, Port: port}
-}
-
-func NewUDPAddrFromString(s string) *udpAddr {
-	p := strings.Split(s, ":")
-	if len(p) < 2 {
-		return nil
-	}
-
-	port, _ := strconv.Atoi(p[1])
-	return &udpAddr{
-		IP:   ip2int(net.ParseIP(p[0])),
-		Port: uint16(port),
-	}
-}
-
-type rawSockaddr struct {
-	Family uint16
-	Data   [14]uint8
-}
-
-type rawSockaddrAny struct {
-	Addr rawSockaddr
-	Pad  [96]int8
-}
-
 var x int
 
 // From linux/sock_diag.h
@@ -75,7 +40,7 @@ type _SK_MEMINFO [_SK_MEMINFO_VARS]uint32
 
 func NewListener(ip string, port int, multi bool) (*udpConn, error) {
 	syscall.ForkLock.RLock()
-	fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
+	fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
 	if err == nil {
 		unix.CloseOnExec(fd)
 	}
@@ -86,8 +51,8 @@ func NewListener(ip string, port int, multi bool) (*udpConn, error) {
 		return nil, fmt.Errorf("unable to open socket: %s", err)
 	}
 
-	var lip [4]byte
-	copy(lip[:], net.ParseIP(ip).To4())
+	var lip [16]byte
+	copy(lip[:], net.ParseIP(ip))
 
 	if multi {
 		if err = unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {
@@ -95,7 +60,8 @@ func NewListener(ip string, port int, multi bool) (*udpConn, error) {
 		}
 	}
 
-	if err = unix.Bind(fd, &unix.SockaddrInet4{Addr: lip, Port: port}); err != nil {
+	//TODO: support multiple listening IPs (for limiting ipv6)
+	if err = unix.Bind(fd, &unix.SockaddrInet6{Addr: lip, Port: port}); err != nil {
 		return nil, fmt.Errorf("unable to bind to socket: %s", err)
 	}
 
@@ -111,10 +77,6 @@ func (u *udpConn) Rebind() error {
 	return nil
 }
 
-func (ua *udpAddr) Copy() udpAddr {
-	return *ua
-}
-
 func (u *udpConn) SetRecvBuffer(n int) error {
 	return unix.SetsockoptInt(u.sysFd, unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, n)
 }
@@ -132,7 +94,7 @@ func (u *udpConn) GetSendBuffer() (int, error) {
 }
 
 func (u *udpConn) LocalAddr() (*udpAddr, error) {
-	var rsa rawSockaddrAny
+	var rsa unix.RawSockaddrAny
 	var rLen = unix.SizeofSockaddrAny
 
 	_, _, err := unix.Syscall(
@@ -148,12 +110,24 @@ func (u *udpConn) LocalAddr() (*udpAddr, error) {
 
 	addr := &udpAddr{}
 	if rsa.Addr.Family == unix.AF_INET {
+		pp := (*unix.RawSockaddrInet4)(unsafe.Pointer(&rsa))
+		addr.Port = uint16(rsa.Addr.Data[0])<<8 + uint16(rsa.Addr.Data[1])
+		copy(addr.IP, pp.Addr[:])
+
+	} else if rsa.Addr.Family == unix.AF_INET6 {
+		//TODO: this cast sucks and we can do better
+		pp := (*unix.RawSockaddrInet6)(unsafe.Pointer(&rsa))
 		addr.Port = uint16(rsa.Addr.Data[0])<<8 + uint16(rsa.Addr.Data[1])
-		addr.IP = uint32(rsa.Addr.Data[2])<<24 + uint32(rsa.Addr.Data[3])<<16 + uint32(rsa.Addr.Data[4])<<8 + uint32(rsa.Addr.Data[5])
+		copy(addr.IP, pp.Addr[:])
+
 	} else {
 		addr.Port = 0
-		addr.IP = 0
+		addr.IP = []byte{}
 	}
+
+	//TODO: Just use this instead?
+	//a, b := unix.Getsockname(u.sysFd)
+
 	return addr, nil
 }
 
@@ -185,9 +159,8 @@ func (u *udpConn) ListenOut(f *Interface, q int) {
 
 		//metric.Update(int64(n))
 		for i := 0; i < n; i++ {
-			udpAddr.IP = binary.BigEndian.Uint32(names[i][4:8])
+			udpAddr.IP = names[i][8:24]
 			udpAddr.Port = binary.BigEndian.Uint16(names[i][2:4])
-
 			f.readOutsidePackets(udpAddr, plaintext[:0], buffers[i][:msgs[i].Len], header, fwPacket, lhh, nb, q, conntrackCache.Get())
 		}
 	}
@@ -235,18 +208,13 @@ func (u *udpConn) ReadMulti(msgs []rawMessage) (int, error) {
 }
 
 func (u *udpConn) WriteTo(b []byte, addr *udpAddr) error {
-	var rsa unix.RawSockaddrInet4
 
-	//TODO: sometimes addr is nil!
-	rsa.Family = unix.AF_INET
+	var rsa unix.RawSockaddrInet6
+	rsa.Family = unix.AF_INET6
 	p := (*[2]byte)(unsafe.Pointer(&rsa.Port))
 	p[0] = byte(addr.Port >> 8)
 	p[1] = byte(addr.Port)
-
-	rsa.Addr[0] = byte(addr.IP & 0xff000000 >> 24)
-	rsa.Addr[1] = byte(addr.IP & 0x00ff0000 >> 16)
-	rsa.Addr[2] = byte(addr.IP & 0x0000ff00 >> 8)
-	rsa.Addr[3] = byte(addr.IP & 0x000000ff)
+	copy(rsa.Addr[:], addr.IP)
 
 	for {
 		_, _, err := unix.Syscall6(
@@ -256,7 +224,7 @@ func (u *udpConn) WriteTo(b []byte, addr *udpAddr) error {
 			uintptr(len(b)),
 			uintptr(0),
 			uintptr(unsafe.Pointer(&rsa)),
-			uintptr(unix.SizeofSockaddrInet4),
+			uintptr(unix.SizeofSockaddrInet6),
 		)
 
 		if err != 0 {
@@ -342,29 +310,6 @@ func NewUDPStatsEmitter(udpConns []*udpConn) func() {
 	}
 }
 
-func (ua *udpAddr) Equals(t *udpAddr) bool {
-	if t == nil || ua == nil {
-		return t == nil && ua == nil
-	}
-	return ua.IP == t.IP && ua.Port == t.Port
-}
-
-func (ua *udpAddr) String() string {
-	return fmt.Sprintf("%s:%v", int2ip(ua.IP), ua.Port)
-}
-
-func (ua *udpAddr) MarshalJSON() ([]byte, error) {
-	return json.Marshal(m{"ip": int2ip(ua.IP), "port": ua.Port})
-}
-
-func udp2ip(addr *udpAddr) net.IP {
-	return int2ip(addr.IP)
-}
-
-func udp2ipInt(addr *udpAddr) uint32 {
-	return addr.IP
-}
-
 func hostDidRoam(addr *udpAddr, newaddr *udpAddr) bool {
 	return !addr.Equals(newaddr)
 }

+ 6 - 4
udp_linux_32.go

@@ -4,7 +4,9 @@
 
 package nebula
 
-import "unsafe"
+import (
+	"golang.org/x/sys/unix"
+)
 
 type iovec struct {
 	Base *byte
@@ -33,17 +35,17 @@ func (u *udpConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
 
 	for i := range msgs {
 		buffers[i] = make([]byte, mtu)
-		names[i] = make([]byte, 0x1c) //TODO = sizeofSockaddrInet6
+		names[i] = make([]byte, unix.SizeofSockaddrInet6)
 
 		//TODO: this is still silly, no need for an array
 		vs := []iovec{
-			{Base: (*byte)(unsafe.Pointer(&buffers[i][0])), Len: uint32(len(buffers[i]))},
+			{Base: &buffers[i][0], Len: uint32(len(buffers[i]))},
 		}
 
 		msgs[i].Hdr.Iov = &vs[0]
 		msgs[i].Hdr.Iovlen = uint32(len(vs))
 
-		msgs[i].Hdr.Name = (*byte)(unsafe.Pointer(&names[i][0]))
+		msgs[i].Hdr.Name = &names[i][0]
 		msgs[i].Hdr.Namelen = uint32(len(names[i]))
 	}
 

+ 6 - 4
udp_linux_64.go

@@ -4,7 +4,9 @@
 
 package nebula
 
-import "unsafe"
+import (
+	"golang.org/x/sys/unix"
+)
 
 type iovec struct {
 	Base *byte
@@ -36,17 +38,17 @@ func (u *udpConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
 
 	for i := range msgs {
 		buffers[i] = make([]byte, mtu)
-		names[i] = make([]byte, 0x1c) //TODO = sizeofSockaddrInet6
+		names[i] = make([]byte, unix.SizeofSockaddrInet6)
 
 		//TODO: this is still silly, no need for an array
 		vs := []iovec{
-			{Base: (*byte)(unsafe.Pointer(&buffers[i][0])), Len: uint64(len(buffers[i]))},
+			{Base: &buffers[i][0], Len: uint64(len(buffers[i]))},
 		}
 
 		msgs[i].Hdr.Iov = &vs[0]
 		msgs[i].Hdr.Iovlen = uint64(len(vs))
 
-		msgs[i].Hdr.Name = (*byte)(unsafe.Pointer(&names[i][0]))
+		msgs[i].Hdr.Name = &names[i][0]
 		msgs[i].Hdr.Namelen = uint32(len(names[i]))
 	}