mirror of
https://github.com/netbirdio/netbird.git
synced 2024-12-12 09:50:47 +01:00
Add support for IPv6 networks (on Linux clients) (#1459)
* Feat add basic support for IPv6 networks Newly generated networks automatically generate an IPv6 prefix of size 64 within the ULA address range, devices obtain a randomly generated address within this prefix. Currently, this is Linux only and does not yet support all features (routes currently cause an error). * Fix firewall configuration for IPv6 networks * Fix routing configuration for IPv6 networks * Feat provide info on IPv6 support for specific client to mgmt server * Feat allow configuration of IPv6 support through API, improve stability * Feat add IPv6 support to new firewall implementation * Fix peer list item response not containing IPv6 address * Fix nftables breaking on IPv6 address change * Fix build issues for non-linux systems * Fix intermittent disconnections when IPv6 is enabled * Fix test issues and make some minor revisions * Fix some more testing issues * Fix more CI issues due to IPv6 * Fix more testing issues * Add inheritance of IPv6 enablement status from groups * Fix IPv6 events not having associated messages * Address first review comments regarding IPv6 support * Fix IPv6 table being created even when IPv6 is disabled Also improved stability of IPv6 route and firewall handling on client side * Fix IPv6 routes not being removed * Fix DNS IPv6 issues, limit IPv6 nameservers to IPv6 peers * Improve code for IPv6 DNS server selection, add AAAA custom records * Ensure IPv6 routes can only exist for IPv6 routing peers * Fix IPv6 network generation randomness * Fix a bunch of compilation issues and test failures * Replace method calls that are unavailable in Go 1.21 * Fix nil dereference in cleanUpDefaultForwardRules6 * Fix nil pointer dereference when persisting IPv6 network in sqlite * Clean up of client-side code changes for IPv6 * Fix nil dereference in rule mangling and compilation issues * Add a bunch of client-side test cases for IPv6 * Fix IPv6 tests running on unsupported environments * Fix import cycle in tests * Add missing method SupportsIPv6() for windows * Require IPv6 default route for IPv6 tests * Fix panics in routemanager tests on non-linux * Fix some more route manager tests concerning IPv6 * Add some final client-side tests * Add IPv6 tests for management code, small fixes * Fix linting issues * Fix small test suite issues * Fix linter issues and builds on macOS and Windows again * fix builds for iOS because of IPv6 breakage
This commit is contained in:
parent
4da29451d0
commit
8b0398c0db
@ -30,3 +30,9 @@ func NewFirewall(context context.Context, iface IFaceMapper) (firewall.Manager,
|
|||||||
}
|
}
|
||||||
return fm, nil
|
return fm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if the current firewall implementation supports IPv6.
|
||||||
|
// Currently false for anything non-linux.
|
||||||
|
func SupportsIPv6() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -70,6 +70,8 @@ func NewFirewall(context context.Context, iface IFaceMapper) (firewall.Manager,
|
|||||||
return nil, errUsp
|
return nil, errUsp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note for devs: When adding IPv6 support to userspace bind, the implementation of AllowNetbird() has to be
|
||||||
|
// adjusted accordingly.
|
||||||
if err := fm.AllowNetbird(); err != nil {
|
if err := fm.AllowNetbird(); err != nil {
|
||||||
log.Errorf("failed to allow netbird interface traffic: %v", err)
|
log.Errorf("failed to allow netbird interface traffic: %v", err)
|
||||||
}
|
}
|
||||||
@ -83,6 +85,12 @@ func NewFirewall(context context.Context, iface IFaceMapper) (firewall.Manager,
|
|||||||
return fm, nil
|
return fm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if the current firewall implementation supports IPv6.
|
||||||
|
// Currently true if the firewall is nftables.
|
||||||
|
func SupportsIPv6() bool {
|
||||||
|
return check() == NFTABLES
|
||||||
|
}
|
||||||
|
|
||||||
// check returns the firewall type based on common lib checks. It returns UNKNOWN if no firewall is found.
|
// check returns the firewall type based on common lib checks. It returns UNKNOWN if no firewall is found.
|
||||||
func check() FWType {
|
func check() FWType {
|
||||||
useIPTABLES := false
|
useIPTABLES := false
|
||||||
|
@ -6,6 +6,7 @@ import "github.com/netbirdio/netbird/iface"
|
|||||||
type IFaceMapper interface {
|
type IFaceMapper interface {
|
||||||
Name() string
|
Name() string
|
||||||
Address() iface.WGAddress
|
Address() iface.WGAddress
|
||||||
|
Address6() *iface.WGAddress
|
||||||
IsUserspaceBind() bool
|
IsUserspaceBind() bool
|
||||||
SetFilter(iface.PacketFilter) error
|
SetFilter(iface.PacketFilter) error
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,14 @@ type Manager struct {
|
|||||||
router *routerManager
|
router *routerManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) ResetV6Firewall() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) V6Active() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// iFaceMapper defines subset methods of interface required for manager
|
// iFaceMapper defines subset methods of interface required for manager
|
||||||
type iFaceMapper interface {
|
type iFaceMapper interface {
|
||||||
Name() string
|
Name() string
|
||||||
|
@ -73,6 +73,9 @@ func TestIptablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
|
|
||||||
for _, testCase := range test.InsertRuleTestCases {
|
for _, testCase := range test.InsertRuleTestCases {
|
||||||
t.Run(testCase.Name, func(t *testing.T) {
|
t.Run(testCase.Name, func(t *testing.T) {
|
||||||
|
if testCase.IsV6 {
|
||||||
|
t.Skip("Environment does not support IPv6, skipping IPv6 test...")
|
||||||
|
}
|
||||||
iptablesClient, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
iptablesClient, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
require.NoError(t, err, "failed to init iptables client")
|
require.NoError(t, err, "failed to init iptables client")
|
||||||
|
|
||||||
@ -154,6 +157,9 @@ func TestIptablesManager_RemoveRoutingRules(t *testing.T) {
|
|||||||
|
|
||||||
for _, testCase := range test.RemoveRuleTestCases {
|
for _, testCase := range test.RemoveRuleTestCases {
|
||||||
t.Run(testCase.Name, func(t *testing.T) {
|
t.Run(testCase.Name, func(t *testing.T) {
|
||||||
|
if testCase.IsV6 {
|
||||||
|
t.Skip("Environment does not support IPv6, skipping IPv6 test...")
|
||||||
|
}
|
||||||
iptablesClient, _ := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
iptablesClient, _ := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
|
|
||||||
manager, err := newRouterManager(context.TODO(), iptablesClient)
|
manager, err := newRouterManager(context.TODO(), iptablesClient)
|
||||||
|
@ -76,6 +76,13 @@ type Manager interface {
|
|||||||
// RemoveRoutingRules removes a routing firewall rule
|
// RemoveRoutingRules removes a routing firewall rule
|
||||||
RemoveRoutingRules(pair RouterPair) error
|
RemoveRoutingRules(pair RouterPair) error
|
||||||
|
|
||||||
|
// ResetV6Firewall makes changes to the firewall to adapt to the IP address changes.
|
||||||
|
// It is expected that after calling this method ApplyFiltering will be called to re-add the firewall rules.
|
||||||
|
ResetV6Firewall() error
|
||||||
|
|
||||||
|
// V6Active returns whether IPv6 rules should/may be created by upper layers.
|
||||||
|
V6Active() bool
|
||||||
|
|
||||||
// Reset firewall to the default state
|
// Reset firewall to the default state
|
||||||
Reset() error
|
Reset() error
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -36,17 +36,25 @@ func Create(context context.Context, wgIface iFaceMapper) (*Manager, error) {
|
|||||||
wgIface: wgIface,
|
wgIface: wgIface,
|
||||||
}
|
}
|
||||||
|
|
||||||
workTable, err := m.createWorkTable()
|
workTable, err := m.createWorkTable(nftables.TableFamilyIPv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.router, err = newRouter(context, workTable)
|
var workTable6 *nftables.Table
|
||||||
|
if wgIface.Address6() != nil {
|
||||||
|
workTable6, err = m.createWorkTable(nftables.TableFamilyIPv6)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.router, err = newRouter(context, workTable, workTable6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.aclManager, err = newAclManager(workTable, wgIface, m.router.RouteingFwChainName())
|
m.aclManager, err = newAclManager(workTable, workTable6, wgIface, m.router.RouteingFwChainName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -54,6 +62,54 @@ func Create(context context.Context, wgIface iFaceMapper) (*Manager, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resets the IPv6 Firewall Table to adapt to changes in IP addresses
|
||||||
|
func (m *Manager) ResetV6Firewall() error {
|
||||||
|
|
||||||
|
// First, prepare reset by deleting all currently active rules.
|
||||||
|
workTable6, err := m.aclManager.PrepareV6Reset()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Depending on whether we now have an IPv6 address, we now either have to create/empty an IPv6 table, or delete it.
|
||||||
|
if m.wgIface.Address6() != nil {
|
||||||
|
if workTable6 != nil {
|
||||||
|
m.rConn.FlushTable(workTable6)
|
||||||
|
} else {
|
||||||
|
workTable6, err = m.createWorkTable(nftables.TableFamilyIPv6)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.rConn.DelTable(workTable6)
|
||||||
|
workTable6 = nil
|
||||||
|
}
|
||||||
|
err = m.rConn.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore routing rules.
|
||||||
|
err = m.router.RestoreAfterV6Reset(workTable6)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore basic firewall chains (needs to happen after routes because chains from router must exist).
|
||||||
|
// Does not restore rules (will be done later during the update, when UpdateFiltering will be called at some point)
|
||||||
|
err = m.aclManager.ReinitAfterV6Reset(workTable6)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.rConn.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) V6Active() bool {
|
||||||
|
return m.aclManager.v6Active
|
||||||
|
}
|
||||||
|
|
||||||
// AddFiltering rule to the firewall
|
// AddFiltering rule to the firewall
|
||||||
//
|
//
|
||||||
// If comment argument is empty firewall manager should set
|
// If comment argument is empty firewall manager should set
|
||||||
@ -72,7 +128,7 @@ func (m *Manager) AddFiltering(
|
|||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
rawIP := ip.To4()
|
rawIP := ip.To4()
|
||||||
if rawIP == nil {
|
if rawIP == nil && m.wgIface.Address6() == nil {
|
||||||
return nil, fmt.Errorf("unsupported IP version: %s", ip.String())
|
return nil, fmt.Errorf("unsupported IP version: %s", ip.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +170,8 @@ func (m *Manager) AllowNetbird() error {
|
|||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
// Note for devs: When adding IPv6 support to uspfilter, the implementation of createDefaultAllowRules()
|
||||||
|
// must be adjusted to include IPv6 rules.
|
||||||
err := m.aclManager.createDefaultAllowRules()
|
err := m.aclManager.createDefaultAllowRules()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create default allow rules: %v", err)
|
return fmt.Errorf("failed to create default allow rules: %v", err)
|
||||||
@ -211,8 +269,8 @@ func (m *Manager) Flush() error {
|
|||||||
return m.aclManager.Flush()
|
return m.aclManager.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) createWorkTable() (*nftables.Table, error) {
|
func (m *Manager) createWorkTable(tableFamily nftables.TableFamily) (*nftables.Table, error) {
|
||||||
tables, err := m.rConn.ListTablesOfFamily(nftables.TableFamilyIPv4)
|
tables, err := m.rConn.ListTablesOfFamily(tableFamily)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("list of tables: %w", err)
|
return nil, fmt.Errorf("list of tables: %w", err)
|
||||||
}
|
}
|
||||||
@ -223,7 +281,7 @@ func (m *Manager) createWorkTable() (*nftables.Table, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4})
|
table := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: tableFamily})
|
||||||
err = m.rConn.Flush()
|
err = m.rConn.Flush()
|
||||||
return table, err
|
return table, err
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,9 @@ import (
|
|||||||
|
|
||||||
// iFaceMapper defines subset methods of interface required for manager
|
// iFaceMapper defines subset methods of interface required for manager
|
||||||
type iFaceMock struct {
|
type iFaceMock struct {
|
||||||
NameFunc func() string
|
NameFunc func() string
|
||||||
AddressFunc func() iface.WGAddress
|
AddressFunc func() iface.WGAddress
|
||||||
|
Address6Func func() *iface.WGAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iFaceMock) Name() string {
|
func (i *iFaceMock) Name() string {
|
||||||
@ -37,6 +38,13 @@ func (i *iFaceMock) Address() iface.WGAddress {
|
|||||||
panic("AddressFunc is not set")
|
panic("AddressFunc is not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *iFaceMock) Address6() *iface.WGAddress {
|
||||||
|
if i.Address6Func != nil {
|
||||||
|
return i.Address6Func()
|
||||||
|
}
|
||||||
|
panic("AddressFunc is not set")
|
||||||
|
}
|
||||||
|
|
||||||
func (i *iFaceMock) IsUserspaceBind() bool { return false }
|
func (i *iFaceMock) IsUserspaceBind() bool { return false }
|
||||||
|
|
||||||
func TestNftablesManager(t *testing.T) {
|
func TestNftablesManager(t *testing.T) {
|
||||||
@ -53,6 +61,7 @@ func TestNftablesManager(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Address6Func: func() *iface.WGAddress { return nil },
|
||||||
}
|
}
|
||||||
|
|
||||||
// just check on the local interface
|
// just check on the local interface
|
||||||
@ -99,11 +108,9 @@ func TestNftablesManager(t *testing.T) {
|
|||||||
Register: 1,
|
Register: 1,
|
||||||
Data: ifname("lo"),
|
Data: ifname("lo"),
|
||||||
},
|
},
|
||||||
&expr.Payload{
|
&expr.Meta{
|
||||||
DestRegister: 1,
|
Key: expr.MetaKeyL4PROTO,
|
||||||
Base: expr.PayloadBaseNetworkHeader,
|
Register: 1,
|
||||||
Offset: uint32(9),
|
|
||||||
Len: uint32(1),
|
|
||||||
},
|
},
|
||||||
&expr.Cmp{
|
&expr.Cmp{
|
||||||
Register: 1,
|
Register: 1,
|
||||||
@ -152,6 +159,370 @@ func TestNftablesManager(t *testing.T) {
|
|||||||
require.NoError(t, err, "failed to reset")
|
require.NoError(t, err, "failed to reset")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNftablesManager6Disabled(t *testing.T) {
|
||||||
|
mock := &iFaceMock{
|
||||||
|
NameFunc: func() string {
|
||||||
|
return "lo"
|
||||||
|
},
|
||||||
|
AddressFunc: func() iface.WGAddress {
|
||||||
|
return iface.WGAddress{
|
||||||
|
IP: net.ParseIP("100.96.0.1"),
|
||||||
|
Network: &net.IPNet{
|
||||||
|
IP: net.ParseIP("100.96.0.0"),
|
||||||
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Address6Func: func() *iface.WGAddress { return nil },
|
||||||
|
}
|
||||||
|
|
||||||
|
// just check on the local interface
|
||||||
|
manager, err := Create(context.Background(), mock)
|
||||||
|
require.NoError(t, err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = manager.Reset()
|
||||||
|
require.NoError(t, err, "failed to reset")
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}()
|
||||||
|
|
||||||
|
ip := net.ParseIP("2001:db8::fedc:ba09:8765:4321")
|
||||||
|
|
||||||
|
testClient := &nftables.Conn{}
|
||||||
|
|
||||||
|
_, err = manager.AddFiltering(
|
||||||
|
ip,
|
||||||
|
fw.ProtocolTCP,
|
||||||
|
nil,
|
||||||
|
&fw.Port{Values: []int{53}},
|
||||||
|
fw.RuleDirectionIN,
|
||||||
|
fw.ActionDrop,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
require.Error(t, err, "IPv6 rule should not be added when IPv6 is disabled")
|
||||||
|
|
||||||
|
err = manager.Flush()
|
||||||
|
require.NoError(t, err, "failed to flush")
|
||||||
|
|
||||||
|
rules, err := testClient.GetRules(manager.aclManager.workTable, manager.aclManager.chainInputRules)
|
||||||
|
require.NoError(t, err, "failed to get rules")
|
||||||
|
|
||||||
|
require.Len(t, rules, 0, "expected no rules")
|
||||||
|
|
||||||
|
err = manager.Reset()
|
||||||
|
require.NoError(t, err, "failed to reset")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNftablesManager6(t *testing.T) {
|
||||||
|
|
||||||
|
if !iface.SupportsIPv6() {
|
||||||
|
t.Skip("Environment does not support IPv6, skipping IPv6 test...")
|
||||||
|
}
|
||||||
|
mock := &iFaceMock{
|
||||||
|
NameFunc: func() string {
|
||||||
|
return "lo"
|
||||||
|
},
|
||||||
|
AddressFunc: func() iface.WGAddress {
|
||||||
|
return iface.WGAddress{
|
||||||
|
IP: net.ParseIP("100.96.0.1"),
|
||||||
|
Network: &net.IPNet{
|
||||||
|
IP: net.ParseIP("100.96.0.0"),
|
||||||
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Address6Func: func() *iface.WGAddress {
|
||||||
|
return &iface.WGAddress{
|
||||||
|
IP: net.ParseIP("2001:db8::0123:4567:890a:bcde"),
|
||||||
|
Network: &net.IPNet{
|
||||||
|
IP: net.ParseIP("2001:db8::"),
|
||||||
|
Mask: net.CIDRMask(64, 128),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// just check on the local interface
|
||||||
|
manager, err := Create(context.Background(), mock)
|
||||||
|
require.NoError(t, err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = manager.Reset()
|
||||||
|
require.NoError(t, err, "failed to reset")
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}()
|
||||||
|
|
||||||
|
require.True(t, manager.V6Active(), "IPv6 is not active even though it should be.")
|
||||||
|
|
||||||
|
ip := net.ParseIP("2001:db8::fedc:ba09:8765:4321")
|
||||||
|
|
||||||
|
testClient := &nftables.Conn{}
|
||||||
|
|
||||||
|
rule, err := manager.AddFiltering(
|
||||||
|
ip,
|
||||||
|
fw.ProtocolTCP,
|
||||||
|
nil,
|
||||||
|
&fw.Port{Values: []int{53}},
|
||||||
|
fw.RuleDirectionIN,
|
||||||
|
fw.ActionDrop,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
require.NoError(t, err, "failed to add rule")
|
||||||
|
|
||||||
|
err = manager.Flush()
|
||||||
|
require.NoError(t, err, "failed to flush")
|
||||||
|
|
||||||
|
rules, err := testClient.GetRules(manager.aclManager.workTable6, manager.aclManager.chainInputRules6)
|
||||||
|
require.NoError(t, err, "failed to get rules")
|
||||||
|
|
||||||
|
require.Len(t, rules, 1, "expected 1 rules")
|
||||||
|
|
||||||
|
ipToAdd, _ := netip.AddrFromSlice(ip)
|
||||||
|
add := ipToAdd.Unmap()
|
||||||
|
expectedExprs := []expr.Any{
|
||||||
|
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
||||||
|
&expr.Cmp{
|
||||||
|
Op: expr.CmpOpEq,
|
||||||
|
Register: 1,
|
||||||
|
Data: ifname("lo"),
|
||||||
|
},
|
||||||
|
&expr.Meta{
|
||||||
|
Key: expr.MetaKeyL4PROTO,
|
||||||
|
Register: 1,
|
||||||
|
},
|
||||||
|
&expr.Cmp{
|
||||||
|
Register: 1,
|
||||||
|
Op: expr.CmpOpEq,
|
||||||
|
Data: []byte{unix.IPPROTO_TCP},
|
||||||
|
},
|
||||||
|
&expr.Payload{
|
||||||
|
DestRegister: 1,
|
||||||
|
Base: expr.PayloadBaseNetworkHeader,
|
||||||
|
Offset: 8,
|
||||||
|
Len: 16,
|
||||||
|
},
|
||||||
|
&expr.Cmp{
|
||||||
|
Op: expr.CmpOpEq,
|
||||||
|
Register: 1,
|
||||||
|
Data: add.AsSlice(),
|
||||||
|
},
|
||||||
|
&expr.Payload{
|
||||||
|
DestRegister: 1,
|
||||||
|
Base: expr.PayloadBaseTransportHeader,
|
||||||
|
Offset: 2,
|
||||||
|
Len: 2,
|
||||||
|
},
|
||||||
|
&expr.Cmp{
|
||||||
|
Op: expr.CmpOpEq,
|
||||||
|
Register: 1,
|
||||||
|
Data: []byte{0, 53},
|
||||||
|
},
|
||||||
|
&expr.Verdict{Kind: expr.VerdictDrop},
|
||||||
|
}
|
||||||
|
require.ElementsMatch(t, rules[0].Exprs, expectedExprs, "expected the same expressions")
|
||||||
|
|
||||||
|
for _, r := range rule {
|
||||||
|
err = manager.DeleteRule(r)
|
||||||
|
require.NoError(t, err, "failed to delete rule")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = manager.Flush()
|
||||||
|
require.NoError(t, err, "failed to flush")
|
||||||
|
|
||||||
|
rules, err = testClient.GetRules(manager.aclManager.workTable6, manager.aclManager.chainInputRules6)
|
||||||
|
require.NoError(t, err, "failed to get rules")
|
||||||
|
require.Len(t, rules, 0, "expected 0 rules after deletion")
|
||||||
|
|
||||||
|
err = manager.Reset()
|
||||||
|
require.NoError(t, err, "failed to reset")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNftablesManagerAddressReset6(t *testing.T) {
|
||||||
|
|
||||||
|
if !iface.SupportsIPv6() {
|
||||||
|
t.Skip("Environment does not support IPv6, skipping IPv6 test...")
|
||||||
|
}
|
||||||
|
mock := &iFaceMock{
|
||||||
|
NameFunc: func() string {
|
||||||
|
return "lo"
|
||||||
|
},
|
||||||
|
AddressFunc: func() iface.WGAddress {
|
||||||
|
return iface.WGAddress{
|
||||||
|
IP: net.ParseIP("100.96.0.1"),
|
||||||
|
Network: &net.IPNet{
|
||||||
|
IP: net.ParseIP("100.96.0.0"),
|
||||||
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Address6Func: func() *iface.WGAddress {
|
||||||
|
return &iface.WGAddress{
|
||||||
|
IP: net.ParseIP("2001:db8::0123:4567:890a:bcde"),
|
||||||
|
Network: &net.IPNet{
|
||||||
|
IP: net.ParseIP("2001:db8::"),
|
||||||
|
Mask: net.CIDRMask(64, 128),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// just check on the local interface
|
||||||
|
manager, err := Create(context.Background(), mock)
|
||||||
|
require.NoError(t, err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = manager.Reset()
|
||||||
|
require.NoError(t, err, "failed to reset")
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}()
|
||||||
|
|
||||||
|
require.True(t, manager.V6Active(), "IPv6 is not active even though it should be.")
|
||||||
|
|
||||||
|
ip := net.ParseIP("2001:db8::fedc:ba09:8765:4321")
|
||||||
|
|
||||||
|
testClient := &nftables.Conn{}
|
||||||
|
|
||||||
|
_, err = manager.AddFiltering(
|
||||||
|
ip,
|
||||||
|
fw.ProtocolTCP,
|
||||||
|
nil,
|
||||||
|
&fw.Port{Values: []int{53}},
|
||||||
|
fw.RuleDirectionIN,
|
||||||
|
fw.ActionDrop,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
require.NoError(t, err, "failed to add rule")
|
||||||
|
|
||||||
|
err = manager.Flush()
|
||||||
|
require.NoError(t, err, "failed to flush")
|
||||||
|
|
||||||
|
rules, err := testClient.GetRules(manager.aclManager.workTable6, manager.aclManager.chainInputRules6)
|
||||||
|
require.NoError(t, err, "failed to get rules")
|
||||||
|
|
||||||
|
require.Len(t, rules, 1, "expected 1 rules")
|
||||||
|
|
||||||
|
mock.Address6Func = func() *iface.WGAddress {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = manager.ResetV6Firewall()
|
||||||
|
require.NoError(t, err, "failed to reset IPv6 firewall")
|
||||||
|
|
||||||
|
err = manager.Flush()
|
||||||
|
require.NoError(t, err, "failed to flush")
|
||||||
|
|
||||||
|
require.False(t, manager.V6Active(), "IPv6 is active even though it shouldn't be.")
|
||||||
|
|
||||||
|
tables, err := testClient.ListTablesOfFamily(nftables.TableFamilyIPv6)
|
||||||
|
require.NoError(t, err, "failed to list IPv6 tables")
|
||||||
|
|
||||||
|
for _, table := range tables {
|
||||||
|
if table.Name == tableName {
|
||||||
|
t.Errorf("When IPv6 is disabled, the netbird table should not exist.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mock.Address6Func = func() *iface.WGAddress {
|
||||||
|
return &iface.WGAddress{
|
||||||
|
IP: net.ParseIP("2001:db8::0123:4567:890a:bcdf"),
|
||||||
|
Network: &net.IPNet{
|
||||||
|
IP: net.ParseIP("2001:db8::"),
|
||||||
|
Mask: net.CIDRMask(64, 128),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = manager.ResetV6Firewall()
|
||||||
|
require.NoError(t, err, "failed to reset IPv6 firewall")
|
||||||
|
|
||||||
|
require.True(t, manager.V6Active(), "IPv6 is not active even though it should be.")
|
||||||
|
|
||||||
|
rule, err := manager.AddFiltering(
|
||||||
|
ip,
|
||||||
|
fw.ProtocolTCP,
|
||||||
|
nil,
|
||||||
|
&fw.Port{Values: []int{53}},
|
||||||
|
fw.RuleDirectionIN,
|
||||||
|
fw.ActionDrop,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
require.NoError(t, err, "failed to add rule")
|
||||||
|
|
||||||
|
err = manager.Flush()
|
||||||
|
require.NoError(t, err, "failed to flush")
|
||||||
|
|
||||||
|
rules, err = testClient.GetRules(manager.aclManager.workTable6, manager.aclManager.chainInputRules6)
|
||||||
|
require.NoError(t, err, "failed to get rules")
|
||||||
|
|
||||||
|
require.Len(t, rules, 1, "expected 1 rule")
|
||||||
|
|
||||||
|
ipToAdd, _ := netip.AddrFromSlice(ip)
|
||||||
|
add := ipToAdd.Unmap()
|
||||||
|
expectedExprs := []expr.Any{
|
||||||
|
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
||||||
|
&expr.Cmp{
|
||||||
|
Op: expr.CmpOpEq,
|
||||||
|
Register: 1,
|
||||||
|
Data: ifname("lo"),
|
||||||
|
},
|
||||||
|
&expr.Meta{
|
||||||
|
Key: expr.MetaKeyL4PROTO,
|
||||||
|
Register: 1,
|
||||||
|
},
|
||||||
|
&expr.Cmp{
|
||||||
|
Register: 1,
|
||||||
|
Op: expr.CmpOpEq,
|
||||||
|
Data: []byte{unix.IPPROTO_TCP},
|
||||||
|
},
|
||||||
|
&expr.Payload{
|
||||||
|
DestRegister: 1,
|
||||||
|
Base: expr.PayloadBaseNetworkHeader,
|
||||||
|
Offset: 8,
|
||||||
|
Len: 16,
|
||||||
|
},
|
||||||
|
&expr.Cmp{
|
||||||
|
Op: expr.CmpOpEq,
|
||||||
|
Register: 1,
|
||||||
|
Data: add.AsSlice(),
|
||||||
|
},
|
||||||
|
&expr.Payload{
|
||||||
|
DestRegister: 1,
|
||||||
|
Base: expr.PayloadBaseTransportHeader,
|
||||||
|
Offset: 2,
|
||||||
|
Len: 2,
|
||||||
|
},
|
||||||
|
&expr.Cmp{
|
||||||
|
Op: expr.CmpOpEq,
|
||||||
|
Register: 1,
|
||||||
|
Data: []byte{0, 53},
|
||||||
|
},
|
||||||
|
&expr.Verdict{Kind: expr.VerdictDrop},
|
||||||
|
}
|
||||||
|
require.ElementsMatch(t, rules[0].Exprs, expectedExprs, "expected the same expressions")
|
||||||
|
|
||||||
|
for _, r := range rule {
|
||||||
|
err = manager.DeleteRule(r)
|
||||||
|
require.NoError(t, err, "failed to delete rule")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = manager.Flush()
|
||||||
|
require.NoError(t, err, "failed to flush")
|
||||||
|
|
||||||
|
rules, err = testClient.GetRules(manager.aclManager.workTable6, manager.aclManager.chainInputRules6)
|
||||||
|
require.NoError(t, err, "failed to get rules")
|
||||||
|
require.Len(t, rules, 0, "expected 0 rules after deletion")
|
||||||
|
|
||||||
|
err = manager.Reset()
|
||||||
|
require.NoError(t, err, "failed to reset")
|
||||||
|
}
|
||||||
|
|
||||||
func TestNFtablesCreatePerformance(t *testing.T) {
|
func TestNFtablesCreatePerformance(t *testing.T) {
|
||||||
mock := &iFaceMock{
|
mock := &iFaceMock{
|
||||||
NameFunc: func() string {
|
NameFunc: func() string {
|
||||||
@ -166,6 +537,16 @@ func TestNFtablesCreatePerformance(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Address6Func: func() *iface.WGAddress {
|
||||||
|
v6addr, v6net, _ := net.ParseCIDR("fd00:1234:dead:beef::1/64")
|
||||||
|
return &iface.WGAddress{
|
||||||
|
IP: v6addr,
|
||||||
|
Network: &net.IPNet{
|
||||||
|
IP: v6net.IP,
|
||||||
|
Mask: v6net.Mask,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
|
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
|
||||||
|
@ -26,7 +26,8 @@ const (
|
|||||||
|
|
||||||
// some presets for building nftable rules
|
// some presets for building nftable rules
|
||||||
var (
|
var (
|
||||||
zeroXor = binaryutil.NativeEndian.PutUint32(0)
|
zeroXor = binaryutil.NativeEndian.PutUint32(0)
|
||||||
|
zeroXor6 = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
|
||||||
|
|
||||||
exprCounterAccept = []expr.Any{
|
exprCounterAccept = []expr.Any{
|
||||||
&expr.Counter{},
|
&expr.Counter{},
|
||||||
@ -39,48 +40,69 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type router struct {
|
type router struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
stop context.CancelFunc
|
stop context.CancelFunc
|
||||||
conn *nftables.Conn
|
conn *nftables.Conn
|
||||||
workTable *nftables.Table
|
workTable *nftables.Table
|
||||||
filterTable *nftables.Table
|
workTable6 *nftables.Table
|
||||||
chains map[string]*nftables.Chain
|
filterTable *nftables.Table
|
||||||
|
filterTable6 *nftables.Table
|
||||||
|
chains map[string]*nftables.Chain
|
||||||
|
chains6 map[string]*nftables.Chain
|
||||||
// rules is useful to avoid duplicates and to get missing attributes that we don't have when adding new rules
|
// rules is useful to avoid duplicates and to get missing attributes that we don't have when adding new rules
|
||||||
rules map[string]*nftables.Rule
|
rules map[string]*nftables.Rule
|
||||||
isDefaultFwdRulesEnabled bool
|
rules6 map[string]*nftables.Rule
|
||||||
|
isDefaultFwdRulesEnabled bool
|
||||||
|
isDefaultFwdRulesEnabled6 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRouter(parentCtx context.Context, workTable *nftables.Table) (*router, error) {
|
func newRouter(parentCtx context.Context, workTable *nftables.Table, workTable6 *nftables.Table) (*router, error) {
|
||||||
ctx, cancel := context.WithCancel(parentCtx)
|
ctx, cancel := context.WithCancel(parentCtx)
|
||||||
|
|
||||||
r := &router{
|
r := &router{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
stop: cancel,
|
stop: cancel,
|
||||||
conn: &nftables.Conn{},
|
conn: &nftables.Conn{},
|
||||||
workTable: workTable,
|
workTable: workTable,
|
||||||
chains: make(map[string]*nftables.Chain),
|
workTable6: workTable6,
|
||||||
rules: make(map[string]*nftables.Rule),
|
chains: make(map[string]*nftables.Chain),
|
||||||
|
chains6: make(map[string]*nftables.Chain),
|
||||||
|
rules: make(map[string]*nftables.Rule),
|
||||||
|
rules6: make(map[string]*nftables.Rule),
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
r.filterTable, err = r.loadFilterTable()
|
r.filterTable, r.filterTable6, err = r.loadFilterTables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errFilterTableNotFound) {
|
if errors.Is(err, errFilterTableNotFound) {
|
||||||
log.Warnf("table 'filter' not found for forward rules")
|
log.Warnf("table 'filter' not found for forward rules for one of the supported address families-")
|
||||||
} else {
|
} else {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.cleanUpDefaultForwardRules()
|
err = r.cleanUpDefaultForwardRules(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to clean up rules from FORWARD chain: %s", err)
|
log.Errorf("failed to clean up rules from FORWARD chain: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.createContainers()
|
err = r.cleanUpDefaultForwardRules(true)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to clean up rules from IPv6 FORWARD chain: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.createContainers(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to create containers for route: %s", err)
|
log.Errorf("failed to create containers for route: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.workTable6 != nil {
|
||||||
|
err = r.createContainers(true)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to create v6 containers for route: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,43 +112,99 @@ func (r *router) RouteingFwChainName() string {
|
|||||||
|
|
||||||
// ResetForwardRules cleans existing nftables default forward rules from the system
|
// ResetForwardRules cleans existing nftables default forward rules from the system
|
||||||
func (r *router) ResetForwardRules() {
|
func (r *router) ResetForwardRules() {
|
||||||
err := r.cleanUpDefaultForwardRules()
|
err := r.cleanUpDefaultForwardRules(false)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to reset forward rules: %s", err)
|
||||||
|
}
|
||||||
|
err = r.cleanUpDefaultForwardRules(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to reset forward rules: %s", err)
|
log.Errorf("failed to reset forward rules: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *router) loadFilterTable() (*nftables.Table, error) {
|
func (r *router) RestoreAfterV6Reset(newWorktable6 *nftables.Table) error {
|
||||||
|
r.workTable6 = newWorktable6
|
||||||
|
if newWorktable6 != nil {
|
||||||
|
|
||||||
|
err := r.cleanUpDefaultForwardRules(true)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to clean up rules from IPv6 FORWARD chain: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.createContainers(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, rule := range r.rules6 {
|
||||||
|
rule = &nftables.Rule{
|
||||||
|
Table: r.workTable6,
|
||||||
|
Chain: r.chains6[rule.Chain.Name],
|
||||||
|
Exprs: rule.Exprs,
|
||||||
|
UserData: rule.UserData,
|
||||||
|
}
|
||||||
|
r.rules6[name] = r.conn.AddRule(rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.conn.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *router) loadFilterTables() (*nftables.Table, *nftables.Table, error) {
|
||||||
tables, err := r.conn.ListTablesOfFamily(nftables.TableFamilyIPv4)
|
tables, err := r.conn.ListTablesOfFamily(nftables.TableFamilyIPv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("nftables: unable to list tables: %v", err)
|
return nil, nil, fmt.Errorf("nftables: unable to list tables: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var table4 *nftables.Table = nil
|
||||||
for _, table := range tables {
|
for _, table := range tables {
|
||||||
if table.Name == "filter" {
|
if table.Name == "filter" {
|
||||||
return table, nil
|
table4 = table
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errFilterTableNotFound
|
var table6 *nftables.Table = nil
|
||||||
|
tables, err = r.conn.ListTablesOfFamily(nftables.TableFamilyIPv6)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("nftables: unable to list tables: %v", err)
|
||||||
|
}
|
||||||
|
for _, table := range tables {
|
||||||
|
if table.Name == "filter" {
|
||||||
|
table6 = table
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nil
|
||||||
|
if table4 == nil || table6 == nil {
|
||||||
|
err = errFilterTableNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return table4, table6, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *router) createContainers() error {
|
func (r *router) createContainers(forV6 bool) error {
|
||||||
|
workTable := r.workTable
|
||||||
|
chainStorage := r.chains
|
||||||
|
if forV6 {
|
||||||
|
workTable = r.workTable6
|
||||||
|
chainStorage = r.chains6
|
||||||
|
}
|
||||||
|
|
||||||
r.chains[chainNameRouteingFw] = r.conn.AddChain(&nftables.Chain{
|
chainStorage[chainNameRouteingFw] = r.conn.AddChain(&nftables.Chain{
|
||||||
Name: chainNameRouteingFw,
|
Name: chainNameRouteingFw,
|
||||||
Table: r.workTable,
|
Table: workTable,
|
||||||
})
|
})
|
||||||
|
|
||||||
r.chains[chainNameRoutingNat] = r.conn.AddChain(&nftables.Chain{
|
chainStorage[chainNameRoutingNat] = r.conn.AddChain(&nftables.Chain{
|
||||||
Name: chainNameRoutingNat,
|
Name: chainNameRoutingNat,
|
||||||
Table: r.workTable,
|
Table: workTable,
|
||||||
Hooknum: nftables.ChainHookPostrouting,
|
Hooknum: nftables.ChainHookPostrouting,
|
||||||
Priority: nftables.ChainPriorityNATSource - 1,
|
Priority: nftables.ChainPriorityNATSource - 1,
|
||||||
Type: nftables.ChainTypeNAT,
|
Type: nftables.ChainTypeNAT,
|
||||||
})
|
})
|
||||||
|
|
||||||
err := r.refreshRulesMap()
|
err := r.refreshRulesMap(forV6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to clean up rules from FORWARD chain: %s", err)
|
log.Errorf("failed to clean up rules from FORWARD chain: %s", err)
|
||||||
}
|
}
|
||||||
@ -140,7 +218,13 @@ func (r *router) createContainers() error {
|
|||||||
|
|
||||||
// InsertRoutingRules inserts a nftable rule pair to the forwarding chain and if enabled, to the nat chain
|
// InsertRoutingRules inserts a nftable rule pair to the forwarding chain and if enabled, to the nat chain
|
||||||
func (r *router) InsertRoutingRules(pair manager.RouterPair) error {
|
func (r *router) InsertRoutingRules(pair manager.RouterPair) error {
|
||||||
err := r.refreshRulesMap()
|
parsedIp, _, _ := net.ParseCIDR(pair.Source)
|
||||||
|
|
||||||
|
if parsedIp.To4() == nil && r.workTable6 == nil {
|
||||||
|
return fmt.Errorf("nftables: attempted to add IPv6 routing rule even though IPv6 is not enabled for this host")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.refreshRulesMap(parsedIp.To4() == nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -165,7 +249,11 @@ func (r *router) InsertRoutingRules(pair manager.RouterPair) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.filterTable != nil && !r.isDefaultFwdRulesEnabled {
|
filterTable := r.filterTable
|
||||||
|
if parsedIp.To4() == nil {
|
||||||
|
filterTable = r.filterTable6
|
||||||
|
}
|
||||||
|
if filterTable != nil && !r.isDefaultFwdRulesEnabled {
|
||||||
log.Debugf("add default accept forward rule")
|
log.Debugf("add default accept forward rule")
|
||||||
r.acceptForwardRule(pair.Source)
|
r.acceptForwardRule(pair.Source)
|
||||||
}
|
}
|
||||||
@ -191,7 +279,13 @@ func (r *router) insertRoutingRule(format, chainName string, pair manager.Router
|
|||||||
|
|
||||||
ruleKey := manager.GenKey(format, pair.ID)
|
ruleKey := manager.GenKey(format, pair.ID)
|
||||||
|
|
||||||
_, exists := r.rules[ruleKey]
|
parsedIp, _, _ := net.ParseCIDR(pair.Source)
|
||||||
|
rules := r.rules
|
||||||
|
if parsedIp.To4() == nil {
|
||||||
|
rules = r.rules6
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := rules[ruleKey]
|
||||||
if exists {
|
if exists {
|
||||||
err := r.removeRoutingRule(format, pair)
|
err := r.removeRoutingRule(format, pair)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -199,18 +293,35 @@ func (r *router) insertRoutingRule(format, chainName string, pair manager.Router
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r.rules[ruleKey] = r.conn.InsertRule(&nftables.Rule{
|
table, chain := r.workTable, r.chains[chainName]
|
||||||
Table: r.workTable,
|
if parsedIp.To4() == nil {
|
||||||
Chain: r.chains[chainName],
|
table, chain = r.workTable6, r.chains6[chainName]
|
||||||
|
}
|
||||||
|
|
||||||
|
newRule := r.conn.InsertRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
Exprs: expression,
|
Exprs: expression,
|
||||||
UserData: []byte(ruleKey),
|
UserData: []byte(ruleKey),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if parsedIp.To4() == nil {
|
||||||
|
r.rules[ruleKey] = newRule
|
||||||
|
} else {
|
||||||
|
r.rules6[ruleKey] = newRule
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *router) acceptForwardRule(sourceNetwork string) {
|
func (r *router) acceptForwardRule(sourceNetwork string) {
|
||||||
src := generateCIDRMatcherExpressions(true, sourceNetwork)
|
src := generateCIDRMatcherExpressions(true, sourceNetwork)
|
||||||
dst := generateCIDRMatcherExpressions(false, "0.0.0.0/0")
|
dst := generateCIDRMatcherExpressions(false, "0.0.0.0/0")
|
||||||
|
table := r.filterTable
|
||||||
|
parsedIp, _, _ := net.ParseCIDR(sourceNetwork)
|
||||||
|
if parsedIp.To4() == nil {
|
||||||
|
dst = generateCIDRMatcherExpressions(false, "::/0")
|
||||||
|
table = r.filterTable6
|
||||||
|
}
|
||||||
|
|
||||||
var exprs []expr.Any
|
var exprs []expr.Any
|
||||||
exprs = append(src, append(dst, &expr.Verdict{ // nolint:gocritic
|
exprs = append(src, append(dst, &expr.Verdict{ // nolint:gocritic
|
||||||
@ -218,10 +329,10 @@ func (r *router) acceptForwardRule(sourceNetwork string) {
|
|||||||
})...)
|
})...)
|
||||||
|
|
||||||
rule := &nftables.Rule{
|
rule := &nftables.Rule{
|
||||||
Table: r.filterTable,
|
Table: table,
|
||||||
Chain: &nftables.Chain{
|
Chain: &nftables.Chain{
|
||||||
Name: "FORWARD",
|
Name: "FORWARD",
|
||||||
Table: r.filterTable,
|
Table: table,
|
||||||
Type: nftables.ChainTypeFilter,
|
Type: nftables.ChainTypeFilter,
|
||||||
Hooknum: nftables.ChainHookForward,
|
Hooknum: nftables.ChainHookForward,
|
||||||
Priority: nftables.ChainPriorityFilter,
|
Priority: nftables.ChainPriorityFilter,
|
||||||
@ -233,6 +344,9 @@ func (r *router) acceptForwardRule(sourceNetwork string) {
|
|||||||
r.conn.AddRule(rule)
|
r.conn.AddRule(rule)
|
||||||
|
|
||||||
src = generateCIDRMatcherExpressions(true, "0.0.0.0/0")
|
src = generateCIDRMatcherExpressions(true, "0.0.0.0/0")
|
||||||
|
if parsedIp.To4() == nil {
|
||||||
|
src = generateCIDRMatcherExpressions(true, "::/0")
|
||||||
|
}
|
||||||
dst = generateCIDRMatcherExpressions(false, sourceNetwork)
|
dst = generateCIDRMatcherExpressions(false, sourceNetwork)
|
||||||
|
|
||||||
exprs = append(src, append(dst, &expr.Verdict{ //nolint:gocritic
|
exprs = append(src, append(dst, &expr.Verdict{ //nolint:gocritic
|
||||||
@ -240,10 +354,10 @@ func (r *router) acceptForwardRule(sourceNetwork string) {
|
|||||||
})...)
|
})...)
|
||||||
|
|
||||||
rule = &nftables.Rule{
|
rule = &nftables.Rule{
|
||||||
Table: r.filterTable,
|
Table: table,
|
||||||
Chain: &nftables.Chain{
|
Chain: &nftables.Chain{
|
||||||
Name: "FORWARD",
|
Name: "FORWARD",
|
||||||
Table: r.filterTable,
|
Table: table,
|
||||||
Type: nftables.ChainTypeFilter,
|
Type: nftables.ChainTypeFilter,
|
||||||
Hooknum: nftables.ChainHookForward,
|
Hooknum: nftables.ChainHookForward,
|
||||||
Priority: nftables.ChainPriorityFilter,
|
Priority: nftables.ChainPriorityFilter,
|
||||||
@ -252,12 +366,21 @@ func (r *router) acceptForwardRule(sourceNetwork string) {
|
|||||||
UserData: []byte(userDataAcceptForwardRuleDst),
|
UserData: []byte(userDataAcceptForwardRuleDst),
|
||||||
}
|
}
|
||||||
r.conn.AddRule(rule)
|
r.conn.AddRule(rule)
|
||||||
r.isDefaultFwdRulesEnabled = true
|
if parsedIp.To4() == nil {
|
||||||
|
r.isDefaultFwdRulesEnabled6 = true
|
||||||
|
} else {
|
||||||
|
r.isDefaultFwdRulesEnabled = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveRoutingRules removes a nftable rule pair from forwarding and nat chains
|
// RemoveRoutingRules removes a nftable rule pair from forwarding and nat chains
|
||||||
func (r *router) RemoveRoutingRules(pair manager.RouterPair) error {
|
func (r *router) RemoveRoutingRules(pair manager.RouterPair) error {
|
||||||
err := r.refreshRulesMap()
|
parsedIp, _, _ := net.ParseCIDR(pair.Source)
|
||||||
|
if parsedIp.To4() == nil && r.workTable6 == nil {
|
||||||
|
return fmt.Errorf("nftables: attempted to remove IPv6 routing rule even though IPv6 is not enabled for this host")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.refreshRulesMap(parsedIp.To4() == nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -282,8 +405,12 @@ func (r *router) RemoveRoutingRules(pair manager.RouterPair) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.rules) == 0 {
|
rulesList := r.rules
|
||||||
err := r.cleanUpDefaultForwardRules()
|
if parsedIp.To4() == nil {
|
||||||
|
rulesList = r.rules6
|
||||||
|
}
|
||||||
|
if len(rulesList) == 0 {
|
||||||
|
err := r.cleanUpDefaultForwardRules(parsedIp.To4() == nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to clean up rules from FORWARD chain: %s", err)
|
log.Errorf("failed to clean up rules from FORWARD chain: %s", err)
|
||||||
}
|
}
|
||||||
@ -301,7 +428,13 @@ func (r *router) RemoveRoutingRules(pair manager.RouterPair) error {
|
|||||||
func (r *router) removeRoutingRule(format string, pair manager.RouterPair) error {
|
func (r *router) removeRoutingRule(format string, pair manager.RouterPair) error {
|
||||||
ruleKey := manager.GenKey(format, pair.ID)
|
ruleKey := manager.GenKey(format, pair.ID)
|
||||||
|
|
||||||
rule, found := r.rules[ruleKey]
|
parsedIp, _, _ := net.ParseCIDR(pair.Source)
|
||||||
|
rules := r.rules
|
||||||
|
if parsedIp.To4() == nil {
|
||||||
|
rules = r.rules6
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, found := rules[ruleKey]
|
||||||
if found {
|
if found {
|
||||||
ruleType := "forwarding"
|
ruleType := "forwarding"
|
||||||
if rule.Chain.Type == nftables.ChainTypeNAT {
|
if rule.Chain.Type == nftables.ChainTypeNAT {
|
||||||
@ -315,49 +448,68 @@ func (r *router) removeRoutingRule(format string, pair manager.RouterPair) error
|
|||||||
|
|
||||||
log.Debugf("nftables: removing %s rule for %s", ruleType, pair.Destination)
|
log.Debugf("nftables: removing %s rule for %s", ruleType, pair.Destination)
|
||||||
|
|
||||||
delete(r.rules, ruleKey)
|
delete(rules, ruleKey)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// refreshRulesMap refreshes the rule map with the latest rules. this is useful to avoid
|
// refreshRulesMap refreshes the rule map with the latest rules. this is useful to avoid
|
||||||
// duplicates and to get missing attributes that we don't have when adding new rules
|
// duplicates and to get missing attributes that we don't have when adding new rules
|
||||||
func (r *router) refreshRulesMap() error {
|
func (r *router) refreshRulesMap(forV6 bool) error {
|
||||||
for _, chain := range r.chains {
|
chainList := r.chains
|
||||||
|
if forV6 {
|
||||||
|
chainList = r.chains6
|
||||||
|
}
|
||||||
|
for _, chain := range chainList {
|
||||||
rules, err := r.conn.GetRules(chain.Table, chain)
|
rules, err := r.conn.GetRules(chain.Table, chain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("nftables: unable to list rules: %v", err)
|
return fmt.Errorf("nftables: unable to list rules: %v", err)
|
||||||
}
|
}
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
if len(rule.UserData) > 0 {
|
if len(rule.UserData) > 0 {
|
||||||
r.rules[string(rule.UserData)] = rule
|
if forV6 {
|
||||||
|
r.rules6[string(rule.UserData)] = rule
|
||||||
|
} else {
|
||||||
|
r.rules[string(rule.UserData)] = rule
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *router) cleanUpDefaultForwardRules() error {
|
func (r *router) cleanUpDefaultForwardRules(forV6 bool) error {
|
||||||
if r.filterTable == nil {
|
tableFamily := nftables.TableFamilyIPv4
|
||||||
r.isDefaultFwdRulesEnabled = false
|
filterTable := r.filterTable
|
||||||
|
if forV6 {
|
||||||
|
tableFamily = nftables.TableFamilyIPv6
|
||||||
|
filterTable = r.filterTable6
|
||||||
|
}
|
||||||
|
|
||||||
|
if filterTable == nil {
|
||||||
|
if forV6 {
|
||||||
|
r.isDefaultFwdRulesEnabled6 = false
|
||||||
|
} else {
|
||||||
|
r.isDefaultFwdRulesEnabled = false
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
chains, err := r.conn.ListChainsOfTableFamily(nftables.TableFamilyIPv4)
|
chains, err := r.conn.ListChainsOfTableFamily(tableFamily)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var rules []*nftables.Rule
|
var rules []*nftables.Rule
|
||||||
for _, chain := range chains {
|
for _, chain := range chains {
|
||||||
if chain.Table.Name != r.filterTable.Name {
|
if chain.Table.Name != filterTable.Name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if chain.Name != "FORWARD" {
|
if chain.Name != "FORWARD" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rules, err = r.conn.GetRules(r.filterTable, chain)
|
rules, err = r.conn.GetRules(filterTable, chain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -371,7 +523,12 @@ func (r *router) cleanUpDefaultForwardRules() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.isDefaultFwdRulesEnabled = false
|
|
||||||
|
if forV6 {
|
||||||
|
r.isDefaultFwdRulesEnabled6 = false
|
||||||
|
} else {
|
||||||
|
r.isDefaultFwdRulesEnabled = false
|
||||||
|
}
|
||||||
return r.conn.Flush()
|
return r.conn.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,6 +544,18 @@ func generateCIDRMatcherExpressions(source bool, cidr string) []expr.Any {
|
|||||||
} else {
|
} else {
|
||||||
offSet = 16 // dst offset
|
offSet = 16 // dst offset
|
||||||
}
|
}
|
||||||
|
addrLen := uint32(4)
|
||||||
|
zeroXor := zeroXor
|
||||||
|
|
||||||
|
if ip.To4() == nil {
|
||||||
|
if source {
|
||||||
|
offSet = 8 // src offset
|
||||||
|
} else {
|
||||||
|
offSet = 24 // dst offset
|
||||||
|
}
|
||||||
|
addrLen = 16
|
||||||
|
zeroXor = zeroXor6
|
||||||
|
}
|
||||||
|
|
||||||
return []expr.Any{
|
return []expr.Any{
|
||||||
// fetch src add
|
// fetch src add
|
||||||
@ -394,13 +563,13 @@ func generateCIDRMatcherExpressions(source bool, cidr string) []expr.Any {
|
|||||||
DestRegister: 1,
|
DestRegister: 1,
|
||||||
Base: expr.PayloadBaseNetworkHeader,
|
Base: expr.PayloadBaseNetworkHeader,
|
||||||
Offset: offSet,
|
Offset: offSet,
|
||||||
Len: 4,
|
Len: addrLen,
|
||||||
},
|
},
|
||||||
// net mask
|
// net mask
|
||||||
&expr.Bitwise{
|
&expr.Bitwise{
|
||||||
DestRegister: 1,
|
DestRegister: 1,
|
||||||
SourceRegister: 1,
|
SourceRegister: 1,
|
||||||
Len: 4,
|
Len: addrLen,
|
||||||
Mask: network.Mask,
|
Mask: network.Mask,
|
||||||
Xor: zeroXor,
|
Xor: zeroXor,
|
||||||
},
|
},
|
||||||
|
@ -4,6 +4,7 @@ package nftables
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/go-iptables/iptables"
|
"github.com/coreos/go-iptables/iptables"
|
||||||
@ -29,16 +30,19 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
t.Skip("nftables not supported on this OS")
|
t.Skip("nftables not supported on this OS")
|
||||||
}
|
}
|
||||||
|
|
||||||
table, err := createWorkTable()
|
table, table6, err := createWorkTables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer deleteWorkTable()
|
defer deleteWorkTables()
|
||||||
|
|
||||||
for _, testCase := range test.InsertRuleTestCases {
|
for _, testCase := range test.InsertRuleTestCases {
|
||||||
t.Run(testCase.Name, func(t *testing.T) {
|
t.Run(testCase.Name, func(t *testing.T) {
|
||||||
manager, err := newRouter(context.TODO(), table)
|
if testCase.IsV6 && table6 == nil {
|
||||||
|
t.Skip("Environment does not support IPv6, skipping IPv6 test...")
|
||||||
|
}
|
||||||
|
manager, err := newRouter(context.TODO(), table, table6)
|
||||||
require.NoError(t, err, "failed to create router")
|
require.NoError(t, err, "failed to create router")
|
||||||
|
|
||||||
nftablesTestingClient := &nftables.Conn{}
|
nftablesTestingClient := &nftables.Conn{}
|
||||||
@ -58,8 +62,13 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
testingExpression := append(sourceExp, destExp...) //nolint:gocritic
|
testingExpression := append(sourceExp, destExp...) //nolint:gocritic
|
||||||
fwdRuleKey := firewall.GenKey(firewall.ForwardingFormat, testCase.InputPair.ID)
|
fwdRuleKey := firewall.GenKey(firewall.ForwardingFormat, testCase.InputPair.ID)
|
||||||
|
|
||||||
|
chains := manager.chains
|
||||||
|
if testCase.IsV6 {
|
||||||
|
chains = manager.chains6
|
||||||
|
}
|
||||||
|
|
||||||
found := 0
|
found := 0
|
||||||
for _, chain := range manager.chains {
|
for _, chain := range chains {
|
||||||
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
||||||
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
@ -75,7 +84,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
if testCase.InputPair.Masquerade {
|
if testCase.InputPair.Masquerade {
|
||||||
natRuleKey := firewall.GenKey(firewall.NatFormat, testCase.InputPair.ID)
|
natRuleKey := firewall.GenKey(firewall.NatFormat, testCase.InputPair.ID)
|
||||||
found := 0
|
found := 0
|
||||||
for _, chain := range manager.chains {
|
for _, chain := range chains {
|
||||||
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
||||||
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
@ -94,7 +103,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
inFwdRuleKey := firewall.GenKey(firewall.InForwardingFormat, testCase.InputPair.ID)
|
inFwdRuleKey := firewall.GenKey(firewall.InForwardingFormat, testCase.InputPair.ID)
|
||||||
|
|
||||||
found = 0
|
found = 0
|
||||||
for _, chain := range manager.chains {
|
for _, chain := range chains {
|
||||||
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
||||||
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
@ -110,7 +119,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
if testCase.InputPair.Masquerade {
|
if testCase.InputPair.Masquerade {
|
||||||
inNatRuleKey := firewall.GenKey(firewall.InNatFormat, testCase.InputPair.ID)
|
inNatRuleKey := firewall.GenKey(firewall.InNatFormat, testCase.InputPair.ID)
|
||||||
found := 0
|
found := 0
|
||||||
for _, chain := range manager.chains {
|
for _, chain := range chains {
|
||||||
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
||||||
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
@ -131,16 +140,19 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
|
|||||||
t.Skip("nftables not supported on this OS")
|
t.Skip("nftables not supported on this OS")
|
||||||
}
|
}
|
||||||
|
|
||||||
table, err := createWorkTable()
|
table, table6, err := createWorkTables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer deleteWorkTable()
|
defer deleteWorkTables()
|
||||||
|
|
||||||
for _, testCase := range test.RemoveRuleTestCases {
|
for _, testCase := range test.RemoveRuleTestCases {
|
||||||
t.Run(testCase.Name, func(t *testing.T) {
|
t.Run(testCase.Name, func(t *testing.T) {
|
||||||
manager, err := newRouter(context.TODO(), table)
|
if testCase.IsV6 && table6 == nil {
|
||||||
|
t.Skip("Environment does not support IPv6, skipping IPv6 test...")
|
||||||
|
}
|
||||||
|
manager, err := newRouter(context.TODO(), table, table6)
|
||||||
require.NoError(t, err, "failed to create router")
|
require.NoError(t, err, "failed to create router")
|
||||||
|
|
||||||
nftablesTestingClient := &nftables.Conn{}
|
nftablesTestingClient := &nftables.Conn{}
|
||||||
@ -150,11 +162,18 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
|
|||||||
sourceExp := generateCIDRMatcherExpressions(true, testCase.InputPair.Source)
|
sourceExp := generateCIDRMatcherExpressions(true, testCase.InputPair.Source)
|
||||||
destExp := generateCIDRMatcherExpressions(false, testCase.InputPair.Destination)
|
destExp := generateCIDRMatcherExpressions(false, testCase.InputPair.Destination)
|
||||||
|
|
||||||
|
chains := manager.chains
|
||||||
|
workTable := table
|
||||||
|
if testCase.IsV6 {
|
||||||
|
chains = manager.chains6
|
||||||
|
workTable = table6
|
||||||
|
}
|
||||||
|
|
||||||
forwardExp := append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic
|
forwardExp := append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic
|
||||||
forwardRuleKey := firewall.GenKey(firewall.ForwardingFormat, testCase.InputPair.ID)
|
forwardRuleKey := firewall.GenKey(firewall.ForwardingFormat, testCase.InputPair.ID)
|
||||||
insertedForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
|
insertedForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
Table: manager.workTable,
|
Table: workTable,
|
||||||
Chain: manager.chains[chainNameRouteingFw],
|
Chain: chains[chainNameRouteingFw],
|
||||||
Exprs: forwardExp,
|
Exprs: forwardExp,
|
||||||
UserData: []byte(forwardRuleKey),
|
UserData: []byte(forwardRuleKey),
|
||||||
})
|
})
|
||||||
@ -163,8 +182,8 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
|
|||||||
natRuleKey := firewall.GenKey(firewall.NatFormat, testCase.InputPair.ID)
|
natRuleKey := firewall.GenKey(firewall.NatFormat, testCase.InputPair.ID)
|
||||||
|
|
||||||
insertedNat := nftablesTestingClient.InsertRule(&nftables.Rule{
|
insertedNat := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
Table: manager.workTable,
|
Table: workTable,
|
||||||
Chain: manager.chains[chainNameRoutingNat],
|
Chain: chains[chainNameRoutingNat],
|
||||||
Exprs: natExp,
|
Exprs: natExp,
|
||||||
UserData: []byte(natRuleKey),
|
UserData: []byte(natRuleKey),
|
||||||
})
|
})
|
||||||
@ -175,8 +194,8 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
|
|||||||
forwardExp = append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic
|
forwardExp = append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic
|
||||||
inForwardRuleKey := firewall.GenKey(firewall.InForwardingFormat, testCase.InputPair.ID)
|
inForwardRuleKey := firewall.GenKey(firewall.InForwardingFormat, testCase.InputPair.ID)
|
||||||
insertedInForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
|
insertedInForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
Table: manager.workTable,
|
Table: workTable,
|
||||||
Chain: manager.chains[chainNameRouteingFw],
|
Chain: chains[chainNameRouteingFw],
|
||||||
Exprs: forwardExp,
|
Exprs: forwardExp,
|
||||||
UserData: []byte(inForwardRuleKey),
|
UserData: []byte(inForwardRuleKey),
|
||||||
})
|
})
|
||||||
@ -185,8 +204,8 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
|
|||||||
inNatRuleKey := firewall.GenKey(firewall.InNatFormat, testCase.InputPair.ID)
|
inNatRuleKey := firewall.GenKey(firewall.InNatFormat, testCase.InputPair.ID)
|
||||||
|
|
||||||
insertedInNat := nftablesTestingClient.InsertRule(&nftables.Rule{
|
insertedInNat := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
Table: manager.workTable,
|
Table: workTable,
|
||||||
Chain: manager.chains[chainNameRoutingNat],
|
Chain: chains[chainNameRoutingNat],
|
||||||
Exprs: natExp,
|
Exprs: natExp,
|
||||||
UserData: []byte(inNatRuleKey),
|
UserData: []byte(inNatRuleKey),
|
||||||
})
|
})
|
||||||
@ -199,7 +218,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
|
|||||||
err = manager.RemoveRoutingRules(testCase.InputPair)
|
err = manager.RemoveRoutingRules(testCase.InputPair)
|
||||||
require.NoError(t, err, "shouldn't return error")
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
for _, chain := range manager.chains {
|
for _, chain := range chains {
|
||||||
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
||||||
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
@ -238,30 +257,39 @@ func isIptablesClientAvailable(client *iptables.IPTables) bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWorkTable() (*nftables.Table, error) {
|
func createWorkTables() (*nftables.Table, *nftables.Table, error) {
|
||||||
sConn, err := nftables.New(nftables.AsLasting())
|
sConn, err := nftables.New(nftables.AsLasting())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tables, err := sConn.ListTablesOfFamily(nftables.TableFamilyIPv4)
|
tables, err := sConn.ListTablesOfFamily(nftables.TableFamilyIPv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range tables {
|
tables6, err := sConn.ListTablesOfFamily(nftables.TableFamilyIPv6)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range append(tables, tables6...) {
|
||||||
if t.Name == tableName {
|
if t.Name == tableName {
|
||||||
sConn.DelTable(t)
|
sConn.DelTable(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table := sConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4})
|
table := sConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4})
|
||||||
|
var table6 *nftables.Table
|
||||||
|
if iface.SupportsIPv6() {
|
||||||
|
table6 = sConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv6})
|
||||||
|
}
|
||||||
err = sConn.Flush()
|
err = sConn.Flush()
|
||||||
|
|
||||||
return table, err
|
return table, table6, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteWorkTable() {
|
func deleteWorkTables() {
|
||||||
sConn, err := nftables.New(nftables.AsLasting())
|
sConn, err := nftables.New(nftables.AsLasting())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -272,6 +300,12 @@ func deleteWorkTable() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tables6, err := sConn.ListTablesOfFamily(nftables.TableFamilyIPv6)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tables = append(tables, tables6...)
|
||||||
|
|
||||||
for _, t := range tables {
|
for _, t := range tables {
|
||||||
if t.Name == tableName {
|
if t.Name == tableName {
|
||||||
sConn.DelTable(t)
|
sConn.DelTable(t)
|
||||||
|
@ -8,6 +8,7 @@ var (
|
|||||||
InsertRuleTestCases = []struct {
|
InsertRuleTestCases = []struct {
|
||||||
Name string
|
Name string
|
||||||
InputPair firewall.RouterPair
|
InputPair firewall.RouterPair
|
||||||
|
IsV6 bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Name: "Insert Forwarding IPV4 Rule",
|
Name: "Insert Forwarding IPV4 Rule",
|
||||||
@ -27,12 +28,32 @@ var (
|
|||||||
Masquerade: true,
|
Masquerade: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "Insert Forwarding IPV6 Rule",
|
||||||
|
InputPair: firewall.RouterPair{
|
||||||
|
ID: "zxa",
|
||||||
|
Source: "2001:db8:0123:4567::1/128",
|
||||||
|
Destination: "2001:db8:0123:abcd::/64",
|
||||||
|
Masquerade: false,
|
||||||
|
},
|
||||||
|
IsV6: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Insert Forwarding And Nat IPV6 Rules",
|
||||||
|
InputPair: firewall.RouterPair{
|
||||||
|
ID: "zxa",
|
||||||
|
Source: "2001:db8:0123:4567::1/128",
|
||||||
|
Destination: "2001:db8:0123:abcd::/64",
|
||||||
|
Masquerade: true,
|
||||||
|
},
|
||||||
|
IsV6: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveRuleTestCases = []struct {
|
RemoveRuleTestCases = []struct {
|
||||||
Name string
|
Name string
|
||||||
InputPair firewall.RouterPair
|
InputPair firewall.RouterPair
|
||||||
IpVersion string
|
IsV6 bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Name: "Remove Forwarding And Nat IPV4 Rules",
|
Name: "Remove Forwarding And Nat IPV4 Rules",
|
||||||
@ -43,5 +64,15 @@ var (
|
|||||||
Masquerade: true,
|
Masquerade: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "Remove Forwarding And Nat IPV6 Rules",
|
||||||
|
InputPair: firewall.RouterPair{
|
||||||
|
ID: "zxa",
|
||||||
|
Source: "2001:db8:0123:4567::1/128",
|
||||||
|
Destination: "2001:db8:0123:abcd::/64",
|
||||||
|
Masquerade: true,
|
||||||
|
},
|
||||||
|
IsV6: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -24,6 +24,7 @@ var (
|
|||||||
type IFaceMapper interface {
|
type IFaceMapper interface {
|
||||||
SetFilter(iface.PacketFilter) error
|
SetFilter(iface.PacketFilter) error
|
||||||
Address() iface.WGAddress
|
Address() iface.WGAddress
|
||||||
|
Address6() *iface.WGAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// RuleSet is a set of rules grouped by a string key
|
// RuleSet is a set of rules grouped by a string key
|
||||||
@ -69,6 +70,14 @@ func CreateWithNativeFirewall(iface IFaceMapper, nativeFirewall firewall.Manager
|
|||||||
return mgr, nil
|
return mgr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) ResetV6Firewall() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) V6Active() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func create(iface IFaceMapper) (*Manager, error) {
|
func create(iface IFaceMapper) (*Manager, error) {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
decoders: sync.Pool{
|
decoders: sync.Pool{
|
||||||
|
@ -33,6 +33,10 @@ func (i *IFaceMock) Address() iface.WGAddress {
|
|||||||
return i.AddressFunc()
|
return i.AddressFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *IFaceMock) Address6() *iface.WGAddress {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestManagerCreate(t *testing.T) {
|
func TestManagerCreate(t *testing.T) {
|
||||||
ifaceMock := &IFaceMock{
|
ifaceMock := &IFaceMock{
|
||||||
SetFilterFunc: func(iface.PacketFilter) error { return nil },
|
SetFilterFunc: func(iface.PacketFilter) error { return nil },
|
||||||
|
@ -16,9 +16,10 @@ import (
|
|||||||
mgmProto "github.com/netbirdio/netbird/management/proto"
|
mgmProto "github.com/netbirdio/netbird/management/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manager is a ACL rules manager
|
// Manager is an ACL rules manager
|
||||||
type Manager interface {
|
type Manager interface {
|
||||||
ApplyFiltering(networkMap *mgmProto.NetworkMap)
|
ApplyFiltering(networkMap *mgmProto.NetworkMap)
|
||||||
|
ResetV6Acl() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultManager uses firewall manager to handle
|
// DefaultManager uses firewall manager to handle
|
||||||
@ -26,16 +27,36 @@ type DefaultManager struct {
|
|||||||
firewall firewall.Manager
|
firewall firewall.Manager
|
||||||
ipsetCounter int
|
ipsetCounter int
|
||||||
rulesPairs map[string][]firewall.Rule
|
rulesPairs map[string][]firewall.Rule
|
||||||
|
rulesPairs6 map[string][]firewall.Rule
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultManager(fm firewall.Manager) *DefaultManager {
|
func NewDefaultManager(fm firewall.Manager) *DefaultManager {
|
||||||
return &DefaultManager{
|
return &DefaultManager{
|
||||||
firewall: fm,
|
firewall: fm,
|
||||||
rulesPairs: make(map[string][]firewall.Rule),
|
rulesPairs: make(map[string][]firewall.Rule),
|
||||||
|
rulesPairs6: make(map[string][]firewall.Rule),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DefaultManager) ResetV6Acl() error {
|
||||||
|
for _, rules := range d.rulesPairs6 {
|
||||||
|
for _, r := range rules {
|
||||||
|
err := d.firewall.DeleteRule(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := d.firewall.ResetV6Firewall()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.rulesPairs6 = make(map[string][]firewall.Rule)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ApplyFiltering firewall rules to the local firewall manager processed by ACL policy.
|
// ApplyFiltering firewall rules to the local firewall manager processed by ACL policy.
|
||||||
//
|
//
|
||||||
// If allowByDefault is true it appends allow ALL traffic rules to input and output chains.
|
// If allowByDefault is true it appends allow ALL traffic rules to input and output chains.
|
||||||
@ -83,6 +104,7 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) {
|
|||||||
if enableSSH {
|
if enableSSH {
|
||||||
rules = append(rules, &mgmProto.FirewallRule{
|
rules = append(rules, &mgmProto.FirewallRule{
|
||||||
PeerIP: "0.0.0.0",
|
PeerIP: "0.0.0.0",
|
||||||
|
PeerIP6: "::",
|
||||||
Direction: mgmProto.FirewallRule_IN,
|
Direction: mgmProto.FirewallRule_IN,
|
||||||
Action: mgmProto.FirewallRule_ACCEPT,
|
Action: mgmProto.FirewallRule_ACCEPT,
|
||||||
Protocol: mgmProto.FirewallRule_TCP,
|
Protocol: mgmProto.FirewallRule_TCP,
|
||||||
@ -97,12 +119,14 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) {
|
|||||||
rules = append(rules,
|
rules = append(rules,
|
||||||
&mgmProto.FirewallRule{
|
&mgmProto.FirewallRule{
|
||||||
PeerIP: "0.0.0.0",
|
PeerIP: "0.0.0.0",
|
||||||
|
PeerIP6: "::",
|
||||||
Direction: mgmProto.FirewallRule_IN,
|
Direction: mgmProto.FirewallRule_IN,
|
||||||
Action: mgmProto.FirewallRule_ACCEPT,
|
Action: mgmProto.FirewallRule_ACCEPT,
|
||||||
Protocol: mgmProto.FirewallRule_ALL,
|
Protocol: mgmProto.FirewallRule_ALL,
|
||||||
},
|
},
|
||||||
&mgmProto.FirewallRule{
|
&mgmProto.FirewallRule{
|
||||||
PeerIP: "0.0.0.0",
|
PeerIP: "0.0.0.0",
|
||||||
|
PeerIP6: "::",
|
||||||
Direction: mgmProto.FirewallRule_OUT,
|
Direction: mgmProto.FirewallRule_OUT,
|
||||||
Action: mgmProto.FirewallRule_ACCEPT,
|
Action: mgmProto.FirewallRule_ACCEPT,
|
||||||
Protocol: mgmProto.FirewallRule_ALL,
|
Protocol: mgmProto.FirewallRule_ALL,
|
||||||
@ -111,6 +135,7 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newRulePairs := make(map[string][]firewall.Rule)
|
newRulePairs := make(map[string][]firewall.Rule)
|
||||||
|
newRulePairs6 := make(map[string][]firewall.Rule)
|
||||||
ipsetByRuleSelectors := make(map[string]string)
|
ipsetByRuleSelectors := make(map[string]string)
|
||||||
|
|
||||||
for _, r := range rules {
|
for _, r := range rules {
|
||||||
@ -123,7 +148,7 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) {
|
|||||||
ipsetName = fmt.Sprintf("nb%07d", d.ipsetCounter)
|
ipsetName = fmt.Sprintf("nb%07d", d.ipsetCounter)
|
||||||
ipsetByRuleSelectors[selector] = ipsetName
|
ipsetByRuleSelectors[selector] = ipsetName
|
||||||
}
|
}
|
||||||
pairID, rulePair, err := d.protoRuleToFirewallRule(r, ipsetName)
|
pairID, rulePair, rulePair6, err := d.protoRuleToFirewallRule(r, ipsetName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to apply firewall rule: %+v, %v", r, err)
|
log.Errorf("failed to apply firewall rule: %+v, %v", r, err)
|
||||||
d.rollBack(newRulePairs)
|
d.rollBack(newRulePairs)
|
||||||
@ -132,6 +157,8 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) {
|
|||||||
if len(rules) > 0 {
|
if len(rules) > 0 {
|
||||||
d.rulesPairs[pairID] = rulePair
|
d.rulesPairs[pairID] = rulePair
|
||||||
newRulePairs[pairID] = rulePair
|
newRulePairs[pairID] = rulePair
|
||||||
|
d.rulesPairs6[pairID] = rulePair6
|
||||||
|
newRulePairs6[pairID] = rulePair6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,59 +173,104 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) {
|
|||||||
delete(d.rulesPairs, pairID)
|
delete(d.rulesPairs, pairID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for pairID, rules := range d.rulesPairs6 {
|
||||||
|
if _, ok := newRulePairs6[pairID]; !ok {
|
||||||
|
for _, rule := range rules {
|
||||||
|
if err := d.firewall.DeleteRule(rule); err != nil {
|
||||||
|
log.Errorf("failed to delete firewall rule: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(d.rulesPairs6, pairID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d.rulesPairs = newRulePairs
|
d.rulesPairs = newRulePairs
|
||||||
|
d.rulesPairs6 = newRulePairs6
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultManager) protoRuleToFirewallRule(
|
func (d *DefaultManager) protoRuleToFirewallRule(
|
||||||
r *mgmProto.FirewallRule,
|
r *mgmProto.FirewallRule,
|
||||||
ipsetName string,
|
ipsetName string,
|
||||||
) (string, []firewall.Rule, error) {
|
) (string, []firewall.Rule, []firewall.Rule, error) {
|
||||||
ip := net.ParseIP(r.PeerIP)
|
ip := net.ParseIP(r.PeerIP)
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return "", nil, fmt.Errorf("invalid IP address, skipping firewall rule")
|
return "", nil, nil, fmt.Errorf("invalid IP address, skipping firewall rule")
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip6 *net.IP = nil
|
||||||
|
if d.firewall.V6Active() && r.PeerIP6 != "" {
|
||||||
|
ip6tmp := net.ParseIP(r.PeerIP6)
|
||||||
|
if ip6tmp == nil {
|
||||||
|
return "", nil, nil, fmt.Errorf("invalid IP address, skipping firewall rule")
|
||||||
|
}
|
||||||
|
ip6 = &ip6tmp
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol, err := convertToFirewallProtocol(r.Protocol)
|
protocol, err := convertToFirewallProtocol(r.Protocol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, fmt.Errorf("skipping firewall rule: %s", err)
|
return "", nil, nil, fmt.Errorf("skipping firewall rule: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
action, err := convertFirewallAction(r.Action)
|
action, err := convertFirewallAction(r.Action)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, fmt.Errorf("skipping firewall rule: %s", err)
|
return "", nil, nil, fmt.Errorf("skipping firewall rule: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var port *firewall.Port
|
var port *firewall.Port
|
||||||
if r.Port != "" {
|
if r.Port != "" {
|
||||||
value, err := strconv.Atoi(r.Port)
|
value, err := strconv.Atoi(r.Port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, fmt.Errorf("invalid port, skipping firewall rule")
|
return "", nil, nil, fmt.Errorf("invalid port, skipping firewall rule")
|
||||||
}
|
}
|
||||||
port = &firewall.Port{
|
port = &firewall.Port{
|
||||||
Values: []int{value},
|
Values: []int{value},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleID := d.getRuleID(ip, protocol, int(r.Direction), port, action, "")
|
var rules []firewall.Rule
|
||||||
|
var rules6 []firewall.Rule
|
||||||
|
|
||||||
|
ruleID := d.getRuleID(ip, ip6, protocol, int(r.Direction), port, action, "")
|
||||||
if rulesPair, ok := d.rulesPairs[ruleID]; ok {
|
if rulesPair, ok := d.rulesPairs[ruleID]; ok {
|
||||||
return ruleID, rulesPair, nil
|
rules = rulesPair
|
||||||
|
}
|
||||||
|
if rulesPair6, ok := d.rulesPairs6[ruleID]; d.firewall.V6Active() && ok && ip6 != nil {
|
||||||
|
rules6 = rulesPair6
|
||||||
}
|
}
|
||||||
|
|
||||||
var rules []firewall.Rule
|
if rules == nil {
|
||||||
switch r.Direction {
|
switch r.Direction {
|
||||||
case mgmProto.FirewallRule_IN:
|
case mgmProto.FirewallRule_IN:
|
||||||
rules, err = d.addInRules(ip, protocol, port, action, ipsetName, "")
|
rules, err = d.addInRules(ip, protocol, port, action, ipsetName, "")
|
||||||
case mgmProto.FirewallRule_OUT:
|
case mgmProto.FirewallRule_OUT:
|
||||||
rules, err = d.addOutRules(ip, protocol, port, action, ipsetName, "")
|
rules, err = d.addOutRules(ip, protocol, port, action, ipsetName, "")
|
||||||
default:
|
default:
|
||||||
return "", nil, fmt.Errorf("invalid direction, skipping firewall rule")
|
return "", nil, nil, fmt.Errorf("invalid direction, skipping firewall rule")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ruleID, rules, nil
|
if d.firewall.V6Active() && ip6 != nil && rules6 == nil {
|
||||||
|
switch r.Direction {
|
||||||
|
case mgmProto.FirewallRule_IN:
|
||||||
|
rules6, err = d.addInRules(*ip6, protocol, port, action, ipsetName, "")
|
||||||
|
case mgmProto.FirewallRule_OUT:
|
||||||
|
rules6, err = d.addOutRules(*ip6, protocol, port, action, ipsetName, "")
|
||||||
|
default:
|
||||||
|
return "", nil, nil, fmt.Errorf("invalid direction, skipping firewall rule")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && err.Error() != "failed to add firewall rule: attempted to configure filtering for IPv6 address even though IPv6 is not active" {
|
||||||
|
return "", rules, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ruleID, rules, rules6, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultManager) addInRules(
|
func (d *DefaultManager) addInRules(
|
||||||
@ -226,8 +298,9 @@ func (d *DefaultManager) addInRules(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to add firewall rule: %v", err)
|
return nil, fmt.Errorf("failed to add firewall rule: %v", err)
|
||||||
}
|
}
|
||||||
|
rules = append(rules, rule...)
|
||||||
|
|
||||||
return append(rules, rule...), nil
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultManager) addOutRules(
|
func (d *DefaultManager) addOutRules(
|
||||||
@ -255,20 +328,26 @@ func (d *DefaultManager) addOutRules(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to add firewall rule: %v", err)
|
return nil, fmt.Errorf("failed to add firewall rule: %v", err)
|
||||||
}
|
}
|
||||||
|
rules = append(rules, rule...)
|
||||||
|
|
||||||
return append(rules, rule...), nil
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRuleID() returns unique ID for the rule based on its parameters.
|
// getRuleID() returns unique ID for the rule based on its parameters.
|
||||||
func (d *DefaultManager) getRuleID(
|
func (d *DefaultManager) getRuleID(
|
||||||
ip net.IP,
|
ip net.IP,
|
||||||
|
ip6 *net.IP,
|
||||||
proto firewall.Protocol,
|
proto firewall.Protocol,
|
||||||
direction int,
|
direction int,
|
||||||
port *firewall.Port,
|
port *firewall.Port,
|
||||||
action firewall.Action,
|
action firewall.Action,
|
||||||
comment string,
|
comment string,
|
||||||
) string {
|
) string {
|
||||||
idStr := ip.String() + string(proto) + strconv.Itoa(direction) + strconv.Itoa(int(action)) + comment
|
ip6Str := ""
|
||||||
|
if ip6 != nil {
|
||||||
|
ip6Str = ip6.String()
|
||||||
|
}
|
||||||
|
idStr := ip.String() + ip6Str + string(proto) + strconv.Itoa(direction) + strconv.Itoa(int(action)) + comment
|
||||||
if port != nil {
|
if port != nil {
|
||||||
idStr += port.String()
|
idStr += port.String()
|
||||||
}
|
}
|
||||||
@ -321,6 +400,8 @@ func (d *DefaultManager) squashAcceptRules(
|
|||||||
// it means that rules for that protocol was already optimized on the
|
// it means that rules for that protocol was already optimized on the
|
||||||
// management side
|
// management side
|
||||||
if r.PeerIP == "0.0.0.0" {
|
if r.PeerIP == "0.0.0.0" {
|
||||||
|
// I don't _think_ that IPv6 is relevant here, as any optimization that has r.PeerIP6 == "::" should also
|
||||||
|
// implicitly have r.PeerIP == "0.0.0.0".
|
||||||
squashedRules = append(squashedRules, r)
|
squashedRules = append(squashedRules, r)
|
||||||
squashedProtocols[r.Protocol] = struct{}{}
|
squashedProtocols[r.Protocol] = struct{}{}
|
||||||
return
|
return
|
||||||
@ -364,6 +445,7 @@ func (d *DefaultManager) squashAcceptRules(
|
|||||||
// add special rule 0.0.0.0 which allows all IP's in our firewall implementations
|
// add special rule 0.0.0.0 which allows all IP's in our firewall implementations
|
||||||
squashedRules = append(squashedRules, &mgmProto.FirewallRule{
|
squashedRules = append(squashedRules, &mgmProto.FirewallRule{
|
||||||
PeerIP: "0.0.0.0",
|
PeerIP: "0.0.0.0",
|
||||||
|
PeerIP6: "::",
|
||||||
Direction: direction,
|
Direction: direction,
|
||||||
Action: mgmProto.FirewallRule_ACCEPT,
|
Action: mgmProto.FirewallRule_ACCEPT,
|
||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
|
@ -19,6 +19,7 @@ func TestDefaultManager(t *testing.T) {
|
|||||||
FirewallRules: []*mgmProto.FirewallRule{
|
FirewallRules: []*mgmProto.FirewallRule{
|
||||||
{
|
{
|
||||||
PeerIP: "10.93.0.1",
|
PeerIP: "10.93.0.1",
|
||||||
|
PeerIP6: "2001:db8::fedc:ba09:8765:0001",
|
||||||
Direction: mgmProto.FirewallRule_OUT,
|
Direction: mgmProto.FirewallRule_OUT,
|
||||||
Action: mgmProto.FirewallRule_ACCEPT,
|
Action: mgmProto.FirewallRule_ACCEPT,
|
||||||
Protocol: mgmProto.FirewallRule_TCP,
|
Protocol: mgmProto.FirewallRule_TCP,
|
||||||
@ -26,6 +27,7 @@ func TestDefaultManager(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
PeerIP: "10.93.0.2",
|
PeerIP: "10.93.0.2",
|
||||||
|
PeerIP6: "2001:db8::fedc:ba09:8765:0002",
|
||||||
Direction: mgmProto.FirewallRule_OUT,
|
Direction: mgmProto.FirewallRule_OUT,
|
||||||
Action: mgmProto.FirewallRule_DROP,
|
Action: mgmProto.FirewallRule_DROP,
|
||||||
Protocol: mgmProto.FirewallRule_UDP,
|
Protocol: mgmProto.FirewallRule_UDP,
|
||||||
@ -50,6 +52,14 @@ func TestDefaultManager(t *testing.T) {
|
|||||||
IP: ip,
|
IP: ip,
|
||||||
Network: network,
|
Network: network,
|
||||||
}).AnyTimes()
|
}).AnyTimes()
|
||||||
|
ip6, network6, err := net.ParseCIDR("2001:db8::fedc:ba09:8765:4321/64")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse IP address: %v", err)
|
||||||
|
}
|
||||||
|
ifaceMock.EXPECT().Address6().Return(&iface.WGAddress{
|
||||||
|
IP: ip6,
|
||||||
|
Network: network6,
|
||||||
|
}).AnyTimes()
|
||||||
|
|
||||||
// we receive one rule from the management so for testing purposes ignore it
|
// we receive one rule from the management so for testing purposes ignore it
|
||||||
fw, err := firewall.NewFirewall(context.Background(), ifaceMock)
|
fw, err := firewall.NewFirewall(context.Background(), ifaceMock)
|
||||||
@ -83,6 +93,7 @@ func TestDefaultManager(t *testing.T) {
|
|||||||
networkMap.FirewallRules,
|
networkMap.FirewallRules,
|
||||||
&mgmProto.FirewallRule{
|
&mgmProto.FirewallRule{
|
||||||
PeerIP: "10.93.0.3",
|
PeerIP: "10.93.0.3",
|
||||||
|
PeerIP6: "2001:db8::fedc:ba09:8765:0003",
|
||||||
Direction: mgmProto.FirewallRule_IN,
|
Direction: mgmProto.FirewallRule_IN,
|
||||||
Action: mgmProto.FirewallRule_DROP,
|
Action: mgmProto.FirewallRule_DROP,
|
||||||
Protocol: mgmProto.FirewallRule_ICMP,
|
Protocol: mgmProto.FirewallRule_ICMP,
|
||||||
@ -343,6 +354,7 @@ func TestDefaultManagerEnableSSHRules(t *testing.T) {
|
|||||||
IP: ip,
|
IP: ip,
|
||||||
Network: network,
|
Network: network,
|
||||||
}).AnyTimes()
|
}).AnyTimes()
|
||||||
|
ifaceMock.EXPECT().Address6().Return(nil).AnyTimes()
|
||||||
|
|
||||||
// we receive one rule from the management so for testing purposes ignore it
|
// we receive one rule from the management so for testing purposes ignore it
|
||||||
fw, err := firewall.NewFirewall(context.Background(), ifaceMock)
|
fw, err := firewall.NewFirewall(context.Background(), ifaceMock)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/netbirdio/netbird/client/internal/acl (interfaces: IFaceMapper)
|
// Source: ./client/firewall/iface.go
|
||||||
|
|
||||||
// Package mocks is a generated GoMock package.
|
// Package mocks is a generated GoMock package.
|
||||||
package mocks
|
package mocks
|
||||||
@ -48,6 +48,20 @@ func (mr *MockIFaceMapperMockRecorder) Address() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address", reflect.TypeOf((*MockIFaceMapper)(nil).Address))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address", reflect.TypeOf((*MockIFaceMapper)(nil).Address))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Address6 mocks base method.
|
||||||
|
func (m *MockIFaceMapper) Address6() *iface.WGAddress {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Address6")
|
||||||
|
ret0, _ := ret[0].(*iface.WGAddress)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address6 indicates an expected call of Address6.
|
||||||
|
func (mr *MockIFaceMapperMockRecorder) Address6() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address6", reflect.TypeOf((*MockIFaceMapper)(nil).Address6))
|
||||||
|
}
|
||||||
|
|
||||||
// IsUserspaceBind mocks base method.
|
// IsUserspaceBind mocks base method.
|
||||||
func (m *MockIFaceMapper) IsUserspaceBind() bool {
|
func (m *MockIFaceMapper) IsUserspaceBind() bool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -310,6 +310,7 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe
|
|||||||
engineConf := &EngineConfig{
|
engineConf := &EngineConfig{
|
||||||
WgIfaceName: config.WgIface,
|
WgIfaceName: config.WgIface,
|
||||||
WgAddr: peerConfig.Address,
|
WgAddr: peerConfig.Address,
|
||||||
|
WgAddr6: peerConfig.Address6,
|
||||||
IFaceBlackList: config.IFaceBlackList,
|
IFaceBlackList: config.IFaceBlackList,
|
||||||
DisableIPv6Discovery: config.DisableIPv6Discovery,
|
DisableIPv6Discovery: config.DisableIPv6Discovery,
|
||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
|
@ -485,7 +485,11 @@ func (s *DefaultServer) updateLocalResolver(update map[string]nbdns.SimpleRecord
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getNSHostPort(ns nbdns.NameServer) string {
|
func getNSHostPort(ns nbdns.NameServer) string {
|
||||||
return fmt.Sprintf("%s:%d", ns.IP.String(), ns.Port)
|
if ns.IP.Is4() {
|
||||||
|
return fmt.Sprintf("%s:%d", ns.IP.String(), ns.Port)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("[%s]:%d", ns.IP.String(), ns.Port)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// upstreamCallbacks returns two functions, the first one is used to deactivate
|
// upstreamCallbacks returns two functions, the first one is used to deactivate
|
||||||
|
@ -38,6 +38,13 @@ func (w *mocWGIface) Address() iface.WGAddress {
|
|||||||
Network: network,
|
Network: network,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func (w *mocWGIface) Address6() *iface.WGAddress {
|
||||||
|
ip, network, _ := net.ParseCIDR("fd00:1234:dead:beef::/64")
|
||||||
|
return &iface.WGAddress{
|
||||||
|
IP: ip,
|
||||||
|
Network: network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (w *mocWGIface) GetFilter() iface.PacketFilter {
|
func (w *mocWGIface) GetFilter() iface.PacketFilter {
|
||||||
return w.filter
|
return w.filter
|
||||||
@ -261,7 +268,7 @@ func TestUpdateDNSServer(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
wgIface, err := iface.NewWGIFace(fmt.Sprintf("utun230%d", n), fmt.Sprintf("100.66.100.%d/32", n+1), 33100, privKey.String(), iface.DefaultMTU, newNet, nil)
|
wgIface, err := iface.NewWGIFace(fmt.Sprintf("utun230%d", n), fmt.Sprintf("100.66.100.%d/32", n+1), fmt.Sprintf("fd00:1234:dead:beef::%d/128", n+1), 33100, privKey.String(), iface.DefaultMTU, newNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -339,7 +346,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
privKey, _ := wgtypes.GeneratePrivateKey()
|
privKey, _ := wgtypes.GeneratePrivateKey()
|
||||||
wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.1/32", 33100, privKey.String(), iface.DefaultMTU, newNet, nil)
|
wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.1/32", "", 33100, privKey.String(), iface.DefaultMTU, newNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("build interface wireguard: %v", err)
|
t.Errorf("build interface wireguard: %v", err)
|
||||||
return
|
return
|
||||||
@ -595,7 +602,7 @@ func TestDNSServerUpstreamDeactivateCallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) {
|
func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) {
|
||||||
wgIFace, err := createWgInterfaceWithBind(t)
|
wgIFace, err := createWgInterfaceWithBind(t, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to initialize wg interface")
|
t.Fatal("failed to initialize wg interface")
|
||||||
}
|
}
|
||||||
@ -621,7 +628,7 @@ func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDNSPermanent_updateUpstream(t *testing.T) {
|
func TestDNSPermanent_updateUpstream(t *testing.T) {
|
||||||
wgIFace, err := createWgInterfaceWithBind(t)
|
wgIFace, err := createWgInterfaceWithBind(t, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to initialize wg interface")
|
t.Fatal("failed to initialize wg interface")
|
||||||
}
|
}
|
||||||
@ -713,7 +720,7 @@ func TestDNSPermanent_updateUpstream(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDNSPermanent_matchOnly(t *testing.T) {
|
func TestDNSPermanent_matchOnly(t *testing.T) {
|
||||||
wgIFace, err := createWgInterfaceWithBind(t)
|
wgIFace, err := createWgInterfaceWithBind(t, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to initialize wg interface")
|
t.Fatal("failed to initialize wg interface")
|
||||||
}
|
}
|
||||||
@ -784,7 +791,7 @@ func TestDNSPermanent_matchOnly(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) {
|
func createWgInterfaceWithBind(t *testing.T, enableV6 bool) (*iface.WGIface, error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ov := os.Getenv("NB_WG_KERNEL_DISABLED")
|
ov := os.Getenv("NB_WG_KERNEL_DISABLED")
|
||||||
defer t.Setenv("NB_WG_KERNEL_DISABLED", ov)
|
defer t.Setenv("NB_WG_KERNEL_DISABLED", ov)
|
||||||
@ -797,7 +804,11 @@ func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
privKey, _ := wgtypes.GeneratePrivateKey()
|
privKey, _ := wgtypes.GeneratePrivateKey()
|
||||||
wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.2/24", 33100, privKey.String(), iface.DefaultMTU, newNet, nil)
|
v6Addr := ""
|
||||||
|
if enableV6 {
|
||||||
|
v6Addr = "fd00:1234:dead:beef::1/128"
|
||||||
|
}
|
||||||
|
wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.2/24", v6Addr, 33100, privKey.String(), iface.DefaultMTU, newNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("build interface wireguard: %v", err)
|
t.Fatalf("build interface wireguard: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -58,7 +58,8 @@ type EngineConfig struct {
|
|||||||
WgIfaceName string
|
WgIfaceName string
|
||||||
|
|
||||||
// WgAddr is a Wireguard local address (Netbird Network IP)
|
// WgAddr is a Wireguard local address (Netbird Network IP)
|
||||||
WgAddr string
|
WgAddr string
|
||||||
|
WgAddr6 string
|
||||||
|
|
||||||
// WgPrivateKey is a Wireguard private key of our peer (it MUST never leave the machine)
|
// WgPrivateKey is a Wireguard private key of our peer (it MUST never leave the machine)
|
||||||
WgPrivateKey wgtypes.Key
|
WgPrivateKey wgtypes.Key
|
||||||
@ -603,6 +604,32 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
|
|||||||
log.Infof("updated peer address from %s to %s", oldAddr, conf.Address)
|
log.Infof("updated peer address from %s to %s", oldAddr, conf.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.wgInterface.Address6() == nil && conf.Address6 != "" ||
|
||||||
|
e.wgInterface.Address6() != nil && e.wgInterface.Address6().String() != conf.Address6 {
|
||||||
|
oldAddr := "none"
|
||||||
|
if e.wgInterface.Address6() != nil {
|
||||||
|
oldAddr = e.wgInterface.Address6().String()
|
||||||
|
}
|
||||||
|
newAddr := "none"
|
||||||
|
if conf.Address6 != "" {
|
||||||
|
newAddr = conf.Address6
|
||||||
|
}
|
||||||
|
log.Debugf("updating peer IPv6 address from %s to %s", oldAddr, newAddr)
|
||||||
|
err := e.wgInterface.UpdateAddr6(conf.Address6)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.config.WgAddr6 = conf.Address6
|
||||||
|
|
||||||
|
err = e.acl.ResetV6Acl()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.routeManager.ResetV6Routes()
|
||||||
|
log.Infof("updated peer IPv6 address from %s to %s", oldAddr, conf.Address6)
|
||||||
|
}
|
||||||
|
|
||||||
if conf.GetSshConfig() != nil {
|
if conf.GetSshConfig() != nil {
|
||||||
err := e.updateSSH(conf.GetSshConfig())
|
err := e.updateSSH(conf.GetSshConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -612,6 +639,7 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
|
|||||||
|
|
||||||
e.statusRecorder.UpdateLocalPeerState(peer.LocalPeerState{
|
e.statusRecorder.UpdateLocalPeerState(peer.LocalPeerState{
|
||||||
IP: e.config.WgAddr,
|
IP: e.config.WgAddr,
|
||||||
|
IP6: e.config.WgAddr6,
|
||||||
PubKey: e.config.WgPrivateKey.PublicKey().String(),
|
PubKey: e.config.WgPrivateKey.PublicKey().String(),
|
||||||
KernelInterface: iface.WireGuardModuleIsLoaded(),
|
KernelInterface: iface.WireGuardModuleIsLoaded(),
|
||||||
FQDN: conf.GetFqdn(),
|
FQDN: conf.GetFqdn(),
|
||||||
@ -1238,7 +1266,7 @@ func (e *Engine) newWgIface() (*iface.WGIface, error) {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
return iface.NewWGIFace(e.config.WgIfaceName, e.config.WgAddr, e.config.WgPort, e.config.WgPrivateKey.String(), iface.DefaultMTU, transportNet, mArgs)
|
return iface.NewWGIFace(e.config.WgIfaceName, e.config.WgAddr, e.config.WgAddr6, e.config.WgPort, e.config.WgPrivateKey.String(), iface.DefaultMTU, transportNet, mArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) wgInterfaceCreate() (err error) {
|
func (e *Engine) wgInterfaceCreate() (err error) {
|
||||||
|
@ -216,7 +216,7 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", engine.config.WgPort, key.String(), iface.DefaultMTU, newNet, nil)
|
engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", "", engine.config.WgPort, key.String(), iface.DefaultMTU, newNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -565,6 +565,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
|||||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||||
WgIfaceName: wgIfaceName,
|
WgIfaceName: wgIfaceName,
|
||||||
WgAddr: wgAddr,
|
WgAddr: wgAddr,
|
||||||
|
WgAddr6: "",
|
||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
|
||||||
@ -573,7 +574,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, engine.config.WgPort, key.String(), iface.DefaultMTU, newNet, nil)
|
engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, engine.config.WgAddr6, engine.config.WgPort, key.String(), iface.DefaultMTU, newNet, nil)
|
||||||
assert.NoError(t, err, "shouldn't return error")
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
input := struct {
|
input := struct {
|
||||||
inputSerial uint64
|
inputSerial uint64
|
||||||
@ -735,6 +736,7 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
|
|||||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||||
WgIfaceName: wgIfaceName,
|
WgIfaceName: wgIfaceName,
|
||||||
WgAddr: wgAddr,
|
WgAddr: wgAddr,
|
||||||
|
WgAddr6: "",
|
||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
|
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
|
||||||
@ -744,7 +746,7 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, 33100, key.String(), iface.DefaultMTU, newNet, nil)
|
engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, engine.config.WgAddr6, 33100, key.String(), iface.DefaultMTU, newNet, nil)
|
||||||
assert.NoError(t, err, "shouldn't return error")
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
mockRouteManager := &routemanager.MockManager{
|
mockRouteManager := &routemanager.MockManager{
|
||||||
|
@ -484,7 +484,7 @@ func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int, rem
|
|||||||
log.Warnf("unable to save peer's state, got error: %v", err)
|
log.Warnf("unable to save peer's state, got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ipNet, err := net.ParseCIDR(conn.config.WgConfig.AllowedIps)
|
_, ipNet, err := net.ParseCIDR(strings.Split(conn.config.WgConfig.AllowedIps, ",")[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,7 @@ func (s *State) GetRoutes() map[string]struct{} {
|
|||||||
// LocalPeerState contains the latest state of the local peer
|
// LocalPeerState contains the latest state of the local peer
|
||||||
type LocalPeerState struct {
|
type LocalPeerState struct {
|
||||||
IP string
|
IP string
|
||||||
|
IP6 string
|
||||||
PubKey string
|
PubKey string
|
||||||
KernelInterface bool
|
KernelInterface bool
|
||||||
FQDN string
|
FQDN string
|
||||||
|
@ -38,6 +38,7 @@ type clientNetwork struct {
|
|||||||
peerStateUpdate chan struct{}
|
peerStateUpdate chan struct{}
|
||||||
routePeersNotifiers map[string]chan struct{}
|
routePeersNotifiers map[string]chan struct{}
|
||||||
chosenRoute *route.Route
|
chosenRoute *route.Route
|
||||||
|
chosenIP *net.IP
|
||||||
network netip.Prefix
|
network netip.Prefix
|
||||||
updateSerial uint64
|
updateSerial uint64
|
||||||
}
|
}
|
||||||
@ -221,6 +222,7 @@ func (c *clientNetwork) removeRouteFromWireguardPeer(peerKey string) error {
|
|||||||
|
|
||||||
func (c *clientNetwork) removeRouteFromPeerAndSystem() error {
|
func (c *clientNetwork) removeRouteFromPeerAndSystem() error {
|
||||||
if c.chosenRoute != nil {
|
if c.chosenRoute != nil {
|
||||||
|
// TODO IPv6 (pass wgInterface)
|
||||||
if err := removeVPNRoute(c.network, c.getAsInterface()); err != nil {
|
if err := removeVPNRoute(c.network, c.getAsInterface()); err != nil {
|
||||||
return fmt.Errorf("remove route %s from system, err: %v", c.network, err)
|
return fmt.Errorf("remove route %s from system, err: %v", c.network, err)
|
||||||
}
|
}
|
||||||
@ -261,10 +263,20 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error {
|
|||||||
return fmt.Errorf("remove route from peer: %v", err)
|
return fmt.Errorf("remove route from peer: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// TODO recheck IPv6
|
||||||
|
gwAddr := c.wgInterface.Address().IP
|
||||||
|
c.chosenIP = &gwAddr
|
||||||
|
if c.network.Addr().Is6() {
|
||||||
|
if c.wgInterface.Address6() == nil {
|
||||||
|
return fmt.Errorf("Could not assign IPv6 route %s for peer %s because no IPv6 address is assigned",
|
||||||
|
c.network.String(), c.wgInterface.Address().IP.String())
|
||||||
|
}
|
||||||
|
c.chosenIP = &c.wgInterface.Address6().IP
|
||||||
|
}
|
||||||
// otherwise add the route to the system
|
// otherwise add the route to the system
|
||||||
if err := addVPNRoute(c.network, c.getAsInterface()); err != nil {
|
if err := addVPNRoute(c.network, c.getAsInterface()); err != nil {
|
||||||
return fmt.Errorf("route %s couldn't be added for peer %s, err: %v",
|
return fmt.Errorf("route %s couldn't be added for peer %s, err: %v",
|
||||||
c.network.String(), c.wgInterface.Address().IP.String(), err)
|
c.network.String(), c.chosenIP.String(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ type Manager interface {
|
|||||||
GetRouteSelector() *routeselector.RouteSelector
|
GetRouteSelector() *routeselector.RouteSelector
|
||||||
SetRouteChangeListener(listener listener.NetworkChangeListener)
|
SetRouteChangeListener(listener listener.NetworkChangeListener)
|
||||||
InitialRouteRange() []string
|
InitialRouteRange() []string
|
||||||
|
ResetV6Routes()
|
||||||
EnableServerRouter(firewall firewall.Manager) error
|
EnableServerRouter(firewall firewall.Manager) error
|
||||||
Stop()
|
Stop()
|
||||||
}
|
}
|
||||||
@ -148,6 +149,18 @@ func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Ro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResetV6Routes deletes all IPv6 routes (necessary if IPv6 address changes).
|
||||||
|
// It is expected that UpdateRoute is called afterwards to recreate the routing table.
|
||||||
|
func (m *DefaultManager) ResetV6Routes() {
|
||||||
|
for id, client := range m.clientNetworks {
|
||||||
|
if client.network.Addr().Is6() {
|
||||||
|
log.Debugf("stopping client network watcher due to IPv6 address change, %s", id)
|
||||||
|
client.stop()
|
||||||
|
delete(m.clientNetworks, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetRouteChangeListener set RouteListener for route change notifier
|
// SetRouteChangeListener set RouteListener for route change notifier
|
||||||
func (m *DefaultManager) SetRouteChangeListener(listener listener.NetworkChangeListener) {
|
func (m *DefaultManager) SetRouteChangeListener(listener listener.NetworkChangeListener) {
|
||||||
m.notifier.setListener(listener)
|
m.notifier.setListener(listener)
|
||||||
|
@ -36,6 +36,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
serverRoutesExpected int
|
serverRoutesExpected int
|
||||||
clientNetworkWatchersExpected int
|
clientNetworkWatchersExpected int
|
||||||
clientNetworkWatchersExpectedAllowed int
|
clientNetworkWatchersExpectedAllowed int
|
||||||
|
isV6 bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Should create 2 client networks",
|
name: "Should create 2 client networks",
|
||||||
@ -65,6 +66,35 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
inputSerial: 1,
|
inputSerial: 1,
|
||||||
clientNetworkWatchersExpected: 2,
|
clientNetworkWatchersExpected: 2,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Should create 2 client networks (IPv6)",
|
||||||
|
inputInitRoutes: []*route.Route{},
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8::7890:abcd/128"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
clientNetworkWatchersExpected: 2,
|
||||||
|
isV6: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Should Create 2 Server Routes",
|
name: "Should Create 2 Server Routes",
|
||||||
inputRoutes: []*route.Route{
|
inputRoutes: []*route.Route{
|
||||||
@ -93,6 +123,34 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
serverRoutesExpected: 2,
|
serverRoutesExpected: 2,
|
||||||
clientNetworkWatchersExpected: 0,
|
clientNetworkWatchersExpected: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Should Create 2 Server Routes (IPv6)",
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: localPeerKey,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: localPeerKey,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8::7890:abcd/128"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
serverRoutesExpected: 2,
|
||||||
|
clientNetworkWatchersExpected: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Should Create 1 Route For Client And Server",
|
name: "Should Create 1 Route For Client And Server",
|
||||||
inputRoutes: []*route.Route{
|
inputRoutes: []*route.Route{
|
||||||
@ -121,6 +179,84 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
serverRoutesExpected: 1,
|
serverRoutesExpected: 1,
|
||||||
clientNetworkWatchersExpected: 1,
|
clientNetworkWatchersExpected: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Should Create 1 Route For Client And Server (IPv6)",
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: localPeerKey,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8::7890:abcd/128"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
serverRoutesExpected: 1,
|
||||||
|
clientNetworkWatchersExpected: 1,
|
||||||
|
isV6: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Create 1 Route For Client And Server for each IP version",
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: localPeerKey,
|
||||||
|
Network: netip.MustParsePrefix("100.64.30.250/30"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("8.8.9.9/32"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: localPeerKey,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8::7890:abcd/128"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
serverRoutesExpected: 2,
|
||||||
|
clientNetworkWatchersExpected: 2,
|
||||||
|
isV6: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Should Create 1 Route For Client and Skip Server Route On Empty Server Router",
|
name: "Should Create 1 Route For Client and Skip Server Route On Empty Server Router",
|
||||||
inputRoutes: []*route.Route{
|
inputRoutes: []*route.Route{
|
||||||
@ -150,6 +286,36 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
serverRoutesExpected: 0,
|
serverRoutesExpected: 0,
|
||||||
clientNetworkWatchersExpected: 1,
|
clientNetworkWatchersExpected: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Should Create 1 Route For Client and Skip Server Route On Empty Server Router (IPv6)",
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: localPeerKey,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8::7890:abcd/128"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
removeSrvRouter: true,
|
||||||
|
serverRoutesExpected: 0,
|
||||||
|
clientNetworkWatchersExpected: 1,
|
||||||
|
isV6: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Should Create 1 HA Route and 1 Standalone",
|
name: "Should Create 1 HA Route and 1 Standalone",
|
||||||
inputRoutes: []*route.Route{
|
inputRoutes: []*route.Route{
|
||||||
@ -187,6 +353,44 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
inputSerial: 1,
|
inputSerial: 1,
|
||||||
clientNetworkWatchersExpected: 2,
|
clientNetworkWatchersExpected: 2,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Should Create 1 HA Route and 1 Standalone (IPv6)",
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey2,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "c",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8::7890:abcd/128"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
clientNetworkWatchersExpected: 2,
|
||||||
|
isV6: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "No Small Client Route Should Be Added",
|
name: "No Small Client Route Should Be Added",
|
||||||
inputRoutes: []*route.Route{
|
inputRoutes: []*route.Route{
|
||||||
@ -205,6 +409,25 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
clientNetworkWatchersExpected: 0,
|
clientNetworkWatchersExpected: 0,
|
||||||
clientNetworkWatchersExpectedAllowed: 1,
|
clientNetworkWatchersExpectedAllowed: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "No Small Client Route Should Be Added (IPv6)",
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("::/0"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
clientNetworkWatchersExpected: 0,
|
||||||
|
clientNetworkWatchersExpectedAllowed: 1,
|
||||||
|
isV6: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Remove 1 Client Route",
|
name: "Remove 1 Client Route",
|
||||||
inputInitRoutes: []*route.Route{
|
inputInitRoutes: []*route.Route{
|
||||||
@ -244,6 +467,46 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
inputSerial: 1,
|
inputSerial: 1,
|
||||||
clientNetworkWatchersExpected: 1,
|
clientNetworkWatchersExpected: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Remove 1 Client Route (IPv6)",
|
||||||
|
inputInitRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8::abcd:7890/128"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
clientNetworkWatchersExpected: 1,
|
||||||
|
isV6: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Update Route to HA",
|
name: "Update Route to HA",
|
||||||
inputInitRoutes: []*route.Route{
|
inputInitRoutes: []*route.Route{
|
||||||
@ -293,6 +556,56 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
inputSerial: 1,
|
inputSerial: 1,
|
||||||
clientNetworkWatchersExpected: 1,
|
clientNetworkWatchersExpected: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Update Route to HA (IPv6)",
|
||||||
|
inputInitRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8::abcd:7890/128"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey2,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
clientNetworkWatchersExpected: 1,
|
||||||
|
isV6: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Remove Client Routes",
|
name: "Remove Client Routes",
|
||||||
inputInitRoutes: []*route.Route{
|
inputInitRoutes: []*route.Route{
|
||||||
@ -321,6 +634,35 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
inputSerial: 1,
|
inputSerial: 1,
|
||||||
clientNetworkWatchersExpected: 0,
|
clientNetworkWatchersExpected: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Remove Client Routes (IPv6)",
|
||||||
|
inputInitRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8::abcd:7890/128"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputRoutes: []*route.Route{},
|
||||||
|
inputSerial: 1,
|
||||||
|
clientNetworkWatchersExpected: 0,
|
||||||
|
isV6: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Remove All Routes",
|
name: "Remove All Routes",
|
||||||
inputInitRoutes: []*route.Route{
|
inputInitRoutes: []*route.Route{
|
||||||
@ -350,6 +692,36 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
serverRoutesExpected: 0,
|
serverRoutesExpected: 0,
|
||||||
clientNetworkWatchersExpected: 0,
|
clientNetworkWatchersExpected: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Remove All Routes (IPv6)",
|
||||||
|
inputInitRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: localPeerKey,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8::abcd:7890/128"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputRoutes: []*route.Route{},
|
||||||
|
inputSerial: 1,
|
||||||
|
serverRoutesExpected: 0,
|
||||||
|
clientNetworkWatchersExpected: 0,
|
||||||
|
isV6: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "HA server should not register routes from the same HA group",
|
name: "HA server should not register routes from the same HA group",
|
||||||
inputRoutes: []*route.Route{
|
inputRoutes: []*route.Route{
|
||||||
@ -398,16 +770,74 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
serverRoutesExpected: 2,
|
serverRoutesExpected: 2,
|
||||||
clientNetworkWatchersExpected: 1,
|
clientNetworkWatchersExpected: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "HA server should not register routes from the same HA group (IPv6)",
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "l1",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: localPeerKey,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "l2",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: localPeerKey,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8::abcd:7890/128"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "r1",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "r2",
|
||||||
|
NetID: "routeC",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("2001:db8::abcd:789f/128"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
serverRoutesExpected: 2,
|
||||||
|
clientNetworkWatchersExpected: 1,
|
||||||
|
isV6: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for n, testCase := range testCases {
|
for n, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
v6Addr := ""
|
||||||
|
//goland:noinspection GoBoolExpressions
|
||||||
|
if !iface.SupportsIPv6() && testCase.isV6 {
|
||||||
|
t.Skip("Platform does not support IPv6, skipping IPv6 test...")
|
||||||
|
} else if testCase.isV6 {
|
||||||
|
v6Addr = "2001:db8::4242:4711/128"
|
||||||
|
}
|
||||||
|
|
||||||
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
|
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
|
||||||
newNet, err := stdnet.NewNet()
|
newNet, err := stdnet.NewNet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun43%d", n), "100.65.65.2/24", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil)
|
wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun43%d", n), "100.65.65.2/24", v6Addr, 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil)
|
||||||
require.NoError(t, err, "should create testing WGIface interface")
|
require.NoError(t, err, "should create testing WGIface interface")
|
||||||
defer wgInterface.Close()
|
defer wgInterface.Close()
|
||||||
|
|
||||||
|
@ -64,6 +64,10 @@ func (m *MockManager) EnableServerRouter(firewall firewall.Manager) error {
|
|||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockManager) ResetV6Routes() {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
// Stop mock implementation of Stop from Manager interface
|
// Stop mock implementation of Stop from Manager interface
|
||||||
func (m *MockManager) Stop() {
|
func (m *MockManager) Stop() {
|
||||||
if m.StopFunc != nil {
|
if m.StopFunc != nil {
|
||||||
|
@ -70,7 +70,7 @@ func (m *defaultServerRouter) updateRoutes(routesMap map[route.ID]*route.Route)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(m.routes) > 0 {
|
if len(m.routes) > 0 {
|
||||||
err := enableIPForwarding()
|
err := enableIPForwarding(m.wgInterface.Address6() != nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -79,7 +79,7 @@ func (m *defaultServerRouter) updateRoutes(routesMap map[route.ID]*route.Route)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultServerRouter) removeFromServerNetwork(route *route.Route) error {
|
func (m *defaultServerRouter) removeFromServerNetwork(rt *route.Route) error {
|
||||||
select {
|
select {
|
||||||
case <-m.ctx.Done():
|
case <-m.ctx.Done():
|
||||||
log.Infof("Not removing from server network because context is done")
|
log.Infof("Not removing from server network because context is done")
|
||||||
@ -87,28 +87,32 @@ func (m *defaultServerRouter) removeFromServerNetwork(route *route.Route) error
|
|||||||
default:
|
default:
|
||||||
m.mux.Lock()
|
m.mux.Lock()
|
||||||
defer m.mux.Unlock()
|
defer m.mux.Unlock()
|
||||||
|
routingAddress := m.wgInterface.Address().Masked().String()
|
||||||
routerPair, err := routeToRouterPair(m.wgInterface.Address().Masked().String(), route)
|
if rt.NetworkType == route.IPv6Network {
|
||||||
|
if m.wgInterface.Address6() == nil {
|
||||||
|
return fmt.Errorf("attempted to add route for IPv6 even though device has no v6 address")
|
||||||
|
}
|
||||||
|
routingAddress = m.wgInterface.Address6().Masked().String()
|
||||||
|
}
|
||||||
|
routerPair, err := routeToRouterPair(routingAddress, rt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse prefix: %w", err)
|
return fmt.Errorf("parse prefix: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.firewall.RemoveRoutingRules(routerPair)
|
err = m.firewall.RemoveRoutingRules(routerPair)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("remove routing rules: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(m.routes, route.ID)
|
delete(m.routes, rt.ID)
|
||||||
|
|
||||||
state := m.statusRecorder.GetLocalPeerState()
|
state := m.statusRecorder.GetLocalPeerState()
|
||||||
delete(state.Routes, route.Network.String())
|
delete(state.Routes, rt.Network.String())
|
||||||
m.statusRecorder.UpdateLocalPeerState(state)
|
m.statusRecorder.UpdateLocalPeerState(state)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultServerRouter) addToServerNetwork(route *route.Route) error {
|
func (m *defaultServerRouter) addToServerNetwork(rt *route.Route) error {
|
||||||
select {
|
select {
|
||||||
case <-m.ctx.Done():
|
case <-m.ctx.Done():
|
||||||
log.Infof("Not adding to server network because context is done")
|
log.Infof("Not adding to server network because context is done")
|
||||||
@ -116,8 +120,15 @@ func (m *defaultServerRouter) addToServerNetwork(route *route.Route) error {
|
|||||||
default:
|
default:
|
||||||
m.mux.Lock()
|
m.mux.Lock()
|
||||||
defer m.mux.Unlock()
|
defer m.mux.Unlock()
|
||||||
|
routingAddress := m.wgInterface.Address().Masked().String()
|
||||||
|
if rt.NetworkType == route.IPv6Network {
|
||||||
|
if m.wgInterface.Address6() == nil {
|
||||||
|
return fmt.Errorf("attempted to add route for IPv6 even though device has no v6 address")
|
||||||
|
}
|
||||||
|
routingAddress = m.wgInterface.Address6().Masked().String()
|
||||||
|
}
|
||||||
|
|
||||||
routerPair, err := routeToRouterPair(m.wgInterface.Address().Masked().String(), route)
|
routerPair, err := routeToRouterPair(routingAddress, rt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse prefix: %w", err)
|
return fmt.Errorf("parse prefix: %w", err)
|
||||||
}
|
}
|
||||||
@ -127,13 +138,13 @@ func (m *defaultServerRouter) addToServerNetwork(route *route.Route) error {
|
|||||||
return fmt.Errorf("insert routing rules: %w", err)
|
return fmt.Errorf("insert routing rules: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.routes[route.ID] = route
|
m.routes[rt.ID] = rt
|
||||||
|
|
||||||
state := m.statusRecorder.GetLocalPeerState()
|
state := m.statusRecorder.GetLocalPeerState()
|
||||||
if state.Routes == nil {
|
if state.Routes == nil {
|
||||||
state.Routes = map[string]struct{}{}
|
state.Routes = map[string]struct{}{}
|
||||||
}
|
}
|
||||||
state.Routes[route.Network.String()] = struct{}{}
|
state.Routes[rt.Network.String()] = struct{}{}
|
||||||
m.statusRecorder.UpdateLocalPeerState(state)
|
m.statusRecorder.UpdateLocalPeerState(state)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -144,10 +155,17 @@ func (m *defaultServerRouter) cleanUp() {
|
|||||||
m.mux.Lock()
|
m.mux.Lock()
|
||||||
defer m.mux.Unlock()
|
defer m.mux.Unlock()
|
||||||
for _, r := range m.routes {
|
for _, r := range m.routes {
|
||||||
routerPair, err := routeToRouterPair(m.wgInterface.Address().Masked().String(), r)
|
routingAddress := m.wgInterface.Address().Masked().String()
|
||||||
|
if r.NetworkType == route.IPv6Network {
|
||||||
|
if m.wgInterface.Address6() == nil {
|
||||||
|
log.Errorf("attempted to remove route for IPv6 even though device has no v6 address")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
routingAddress = m.wgInterface.Address6().Masked().String()
|
||||||
|
}
|
||||||
|
routerPair, err := routeToRouterPair(routingAddress, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to convert route to router pair: %v", err)
|
log.Errorf("parse prefix: %v", err)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.firewall.RemoveRoutingRules(routerPair)
|
err = m.firewall.RemoveRoutingRules(routerPair)
|
||||||
|
@ -122,6 +122,16 @@ func ipToAddr(ip net.IP, intf *net.Interface) (netip.Addr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func existsInRouteTable(prefix netip.Prefix) (bool, error) {
|
func existsInRouteTable(prefix netip.Prefix) (bool, error) {
|
||||||
|
|
||||||
|
linkLocalPrefix, err := netip.ParsePrefix("fe80::/10")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if prefix.Addr().Is6() && linkLocalPrefix.Contains(prefix.Addr()) {
|
||||||
|
// The link local prefix is not explicitly part of the routing table, but should be considered as such.
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
routes, err := getRoutesFromTable()
|
routes, err := getRoutesFromTable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("get routes from table: %w", err)
|
return false, fmt.Errorf("get routes from table: %w", err)
|
||||||
|
@ -19,7 +19,7 @@ func cleanupRouting() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func enableIPForwarding() error {
|
func enableIPForwarding(includeV6 bool) error {
|
||||||
log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS)
|
log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -184,12 +184,6 @@ func addVPNRoute(prefix netip.Prefix, intf *net.Interface) error {
|
|||||||
|
|
||||||
// No need to check if routes exist as main table takes precedence over the VPN table via Rule 1
|
// No need to check if routes exist as main table takes precedence over the VPN table via Rule 1
|
||||||
|
|
||||||
// TODO remove this once we have ipv6 support
|
|
||||||
if prefix == defaultv4 {
|
|
||||||
if err := addUnreachableRoute(defaultv6, NetbirdVPNTableID); err != nil {
|
|
||||||
return fmt.Errorf("add blackhole: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := addRoute(prefix, netip.Addr{}, intf, NetbirdVPNTableID); err != nil {
|
if err := addRoute(prefix, netip.Addr{}, intf, NetbirdVPNTableID); err != nil {
|
||||||
return fmt.Errorf("add route: %w", err)
|
return fmt.Errorf("add route: %w", err)
|
||||||
}
|
}
|
||||||
@ -201,12 +195,6 @@ func removeVPNRoute(prefix netip.Prefix, intf *net.Interface) error {
|
|||||||
return genericRemoveVPNRoute(prefix, intf)
|
return genericRemoveVPNRoute(prefix, intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove this once we have ipv6 support
|
|
||||||
if prefix == defaultv4 {
|
|
||||||
if err := removeUnreachableRoute(defaultv6, NetbirdVPNTableID); err != nil {
|
|
||||||
return fmt.Errorf("remove unreachable route: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := removeRoute(prefix, netip.Addr{}, intf, NetbirdVPNTableID); err != nil {
|
if err := removeRoute(prefix, netip.Addr{}, intf, NetbirdVPNTableID); err != nil {
|
||||||
return fmt.Errorf("remove route: %w", err)
|
return fmt.Errorf("remove route: %w", err)
|
||||||
}
|
}
|
||||||
@ -282,6 +270,9 @@ func addRoute(prefix netip.Prefix, addr netip.Addr, intf *net.Interface, tableID
|
|||||||
// addUnreachableRoute adds an unreachable route for the specified IP family and routing table.
|
// addUnreachableRoute adds an unreachable route for the specified IP family and routing table.
|
||||||
// ipFamily should be netlink.FAMILY_V4 for IPv4 or netlink.FAMILY_V6 for IPv6.
|
// ipFamily should be netlink.FAMILY_V4 for IPv4 or netlink.FAMILY_V6 for IPv6.
|
||||||
// tableID specifies the routing table to which the unreachable route will be added.
|
// tableID specifies the routing table to which the unreachable route will be added.
|
||||||
|
// TODO should this be kept in for future use? If so, the linter needs to be told that this unreachable function should
|
||||||
|
//
|
||||||
|
// be kept
|
||||||
func addUnreachableRoute(prefix netip.Prefix, tableID int) error {
|
func addUnreachableRoute(prefix netip.Prefix, tableID int) error {
|
||||||
_, ipNet, err := net.ParseCIDR(prefix.String())
|
_, ipNet, err := net.ParseCIDR(prefix.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -302,6 +293,9 @@ func addUnreachableRoute(prefix netip.Prefix, tableID int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO should this be kept in for future use? If so, the linter needs to be told that this unreachable function should
|
||||||
|
//
|
||||||
|
// be kept
|
||||||
func removeUnreachableRoute(prefix netip.Prefix, tableID int) error {
|
func removeUnreachableRoute(prefix netip.Prefix, tableID int) error {
|
||||||
_, ipNet, err := net.ParseCIDR(prefix.String())
|
_, ipNet, err := net.ParseCIDR(prefix.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -376,8 +370,14 @@ func flushRoutes(tableID, family int) error {
|
|||||||
return result.ErrorOrNil()
|
return result.ErrorOrNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
func enableIPForwarding() error {
|
func enableIPForwarding(includeV6 bool) error {
|
||||||
_, err := setSysctl(ipv4ForwardingPath, 1, false)
|
_, err := setSysctl(ipv4ForwardingPath, 1, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if includeV6 {
|
||||||
|
_, err = setSysctl(ipv4ForwardingPath, 1, false)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func enableIPForwarding() error {
|
func enableIPForwarding(includeV6 bool) error {
|
||||||
log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS)
|
log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/google/gopacket/routing"
|
||||||
|
"github.com/netbirdio/netbird/client/firewall"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@ -46,18 +48,39 @@ func TestAddRemoveRoutes(t *testing.T) {
|
|||||||
shouldRouteToWireguard: false,
|
shouldRouteToWireguard: false,
|
||||||
shouldBeRemoved: false,
|
shouldBeRemoved: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Should Add And Remove Route 2001:db8:1234:5678::/64",
|
||||||
|
prefix: netip.MustParsePrefix("2001:db8:1234:5678::/64"),
|
||||||
|
shouldRouteToWireguard: true,
|
||||||
|
shouldBeRemoved: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Not Add Or Remove Route ::1/128",
|
||||||
|
prefix: netip.MustParsePrefix("::1/128"),
|
||||||
|
shouldRouteToWireguard: false,
|
||||||
|
shouldBeRemoved: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for n, testCase := range testCases {
|
for n, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
t.Setenv("NB_DISABLE_ROUTE_CACHE", "true")
|
t.Setenv("NB_DISABLE_ROUTE_CACHE", "true")
|
||||||
|
|
||||||
|
v6Addr := ""
|
||||||
|
hasV6DefaultRoute, err := EnvironmentHasIPv6DefaultRoute()
|
||||||
|
//goland:noinspection GoBoolExpressions
|
||||||
|
if (!iface.SupportsIPv6() || !firewall.SupportsIPv6() || !hasV6DefaultRoute || err != nil) && testCase.prefix.Addr().Is6() {
|
||||||
|
t.Skip("Platform does not support IPv6, skipping IPv6 test...")
|
||||||
|
} else if testCase.prefix.Addr().Is6() {
|
||||||
|
v6Addr = "2001:db8::4242:4711/128"
|
||||||
|
}
|
||||||
|
|
||||||
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
|
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
|
||||||
newNet, err := stdnet.NewNet()
|
newNet, err := stdnet.NewNet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil)
|
wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", v6Addr, 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil)
|
||||||
require.NoError(t, err, "should create testing WGIface interface")
|
require.NoError(t, err, "should create testing WGIface interface")
|
||||||
defer wgInterface.Close()
|
defer wgInterface.Close()
|
||||||
|
|
||||||
@ -92,6 +115,10 @@ func TestAddRemoveRoutes(t *testing.T) {
|
|||||||
|
|
||||||
internetGateway, _, err := GetNextHop(netip.MustParseAddr("0.0.0.0"))
|
internetGateway, _, err := GetNextHop(netip.MustParseAddr("0.0.0.0"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
if testCase.prefix.Addr().Is6() {
|
||||||
|
internetGateway, _, err = GetNextHop(netip.MustParseAddr("::/0"))
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
if testCase.shouldBeRemoved {
|
if testCase.shouldBeRemoved {
|
||||||
require.Equal(t, internetGateway, prefixGateway, "route should be pointing to default internet gateway")
|
require.Equal(t, internetGateway, prefixGateway, "route should be pointing to default internet gateway")
|
||||||
@ -151,6 +178,18 @@ func TestAddExistAndRemoveRoute(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("shouldn't return error when fetching the gateway: ", err)
|
t.Fatal("shouldn't return error when fetching the gateway: ", err)
|
||||||
}
|
}
|
||||||
|
var defaultGateway6 *netip.Addr
|
||||||
|
hasV6DefaultRoute, err := EnvironmentHasIPv6DefaultRoute()
|
||||||
|
//goland:noinspection GoBoolExpressions
|
||||||
|
if iface.SupportsIPv6() && firewall.SupportsIPv6() && hasV6DefaultRoute && err == nil {
|
||||||
|
gw6, _, err := GetNextHop(netip.MustParseAddr("::"))
|
||||||
|
gw6 = gw6.WithZone("")
|
||||||
|
defaultGateway6 = &gw6
|
||||||
|
t.Log("defaultGateway6: ", defaultGateway6)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("shouldn't return error when fetching the IPv6 gateway: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
prefix netip.Prefix
|
prefix netip.Prefix
|
||||||
@ -185,6 +224,43 @@ func TestAddExistAndRemoveRoute(t *testing.T) {
|
|||||||
preExistingPrefix: netip.MustParsePrefix("100.100.0.0/16"),
|
preExistingPrefix: netip.MustParsePrefix("100.100.0.0/16"),
|
||||||
shouldAddRoute: false,
|
shouldAddRoute: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Should Add And Remove random Route (IPv6)",
|
||||||
|
prefix: netip.MustParsePrefix("2001:db8::abcd/128"),
|
||||||
|
shouldAddRoute: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Add Route if bigger network exists (IPv6)",
|
||||||
|
prefix: netip.MustParsePrefix("2001:db8:b14d:abcd:1234::/96"),
|
||||||
|
preExistingPrefix: netip.MustParsePrefix("2001:db8:b14d:abcd::/64"),
|
||||||
|
shouldAddRoute: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Add Route if smaller network exists (IPv6)",
|
||||||
|
prefix: netip.MustParsePrefix("2001:db8:b14d::/48"),
|
||||||
|
preExistingPrefix: netip.MustParsePrefix("2001:db8:b14d:abcd::/64"),
|
||||||
|
shouldAddRoute: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Not Add Route if same network exists (IPv6)",
|
||||||
|
prefix: netip.MustParsePrefix("2001:db8:b14d:abcd::/64"),
|
||||||
|
preExistingPrefix: netip.MustParsePrefix("2001:db8:b14d:abcd::/64"),
|
||||||
|
shouldAddRoute: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if defaultGateway6 != nil {
|
||||||
|
testCases = append(testCases, []struct {
|
||||||
|
name string
|
||||||
|
prefix netip.Prefix
|
||||||
|
preExistingPrefix netip.Prefix
|
||||||
|
shouldAddRoute bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Should Not Add Route if overlaps with default gateway (IPv6)",
|
||||||
|
prefix: netip.MustParsePrefix(defaultGateway6.String() + "/127"),
|
||||||
|
shouldAddRoute: false,
|
||||||
|
},
|
||||||
|
}...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for n, testCase := range testCases {
|
for n, testCase := range testCases {
|
||||||
@ -198,12 +274,19 @@ func TestAddExistAndRemoveRoute(t *testing.T) {
|
|||||||
t.Setenv("NB_USE_LEGACY_ROUTING", "true")
|
t.Setenv("NB_USE_LEGACY_ROUTING", "true")
|
||||||
t.Setenv("NB_DISABLE_ROUTE_CACHE", "true")
|
t.Setenv("NB_DISABLE_ROUTE_CACHE", "true")
|
||||||
|
|
||||||
|
v6Addr := ""
|
||||||
|
if testCase.prefix.Addr().Is6() && defaultGateway6 == nil {
|
||||||
|
t.Skip("Platform does not support IPv6, skipping IPv6 test...")
|
||||||
|
} else if testCase.prefix.Addr().Is6() {
|
||||||
|
v6Addr = "2001:db8::4242:4711/128"
|
||||||
|
}
|
||||||
|
|
||||||
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
|
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
|
||||||
newNet, err := stdnet.NewNet()
|
newNet, err := stdnet.NewNet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil)
|
wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", v6Addr, 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil)
|
||||||
require.NoError(t, err, "should create testing WGIface interface")
|
require.NoError(t, err, "should create testing WGIface interface")
|
||||||
defer wgInterface.Close()
|
defer wgInterface.Close()
|
||||||
|
|
||||||
@ -249,6 +332,11 @@ func TestAddExistAndRemoveRoute(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIsSubRange(t *testing.T) {
|
func TestIsSubRange(t *testing.T) {
|
||||||
|
// Note: This test may fail for IPv6 in some environments, where there actually exists another route that the
|
||||||
|
// determined prefix is a sub-range of.
|
||||||
|
hasV6DefaultRoute, err := EnvironmentHasIPv6DefaultRoute()
|
||||||
|
shouldIncludeV6Routes := iface.SupportsIPv6() && firewall.SupportsIPv6() && hasV6DefaultRoute && err == nil
|
||||||
|
|
||||||
addresses, err := net.InterfaceAddrs()
|
addresses, err := net.InterfaceAddrs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("shouldn't return error when fetching interface addresses: ", err)
|
t.Fatal("shouldn't return error when fetching interface addresses: ", err)
|
||||||
@ -258,7 +346,7 @@ func TestIsSubRange(t *testing.T) {
|
|||||||
var nonSubRangeAddressPrefixes []netip.Prefix
|
var nonSubRangeAddressPrefixes []netip.Prefix
|
||||||
for _, address := range addresses {
|
for _, address := range addresses {
|
||||||
p := netip.MustParsePrefix(address.String())
|
p := netip.MustParsePrefix(address.String())
|
||||||
if !p.Addr().IsLoopback() && p.Addr().Is4() && p.Bits() < 32 {
|
if !p.Addr().IsLoopback() && (p.Addr().Is4() && p.Bits() < 32) || (p.Addr().Is6() && shouldIncludeV6Routes && p.Bits() < 128) {
|
||||||
p2 := netip.PrefixFrom(p.Masked().Addr(), p.Bits()+1)
|
p2 := netip.PrefixFrom(p.Masked().Addr(), p.Bits()+1)
|
||||||
subRangeAddressPrefixes = append(subRangeAddressPrefixes, p2)
|
subRangeAddressPrefixes = append(subRangeAddressPrefixes, p2)
|
||||||
nonSubRangeAddressPrefixes = append(nonSubRangeAddressPrefixes, p.Masked())
|
nonSubRangeAddressPrefixes = append(nonSubRangeAddressPrefixes, p.Masked())
|
||||||
@ -286,16 +374,37 @@ func TestIsSubRange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EnvironmentHasIPv6DefaultRoute() (bool, error) {
|
||||||
|
//goland:noinspection GoBoolExpressions
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
// TODO when implementing IPv6 for other operating systems, this should be replaced with code that determines
|
||||||
|
// whether a default route for IPv6 exists (routing.Router panics on non-linux).
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
router, err := routing.New()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
routeIface, _, _, err := router.Route(netip.MustParsePrefix("::/0").Addr().AsSlice())
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return routeIface != nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestExistsInRouteTable(t *testing.T) {
|
func TestExistsInRouteTable(t *testing.T) {
|
||||||
addresses, err := net.InterfaceAddrs()
|
addresses, err := net.InterfaceAddrs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("shouldn't return error when fetching interface addresses: ", err)
|
t.Fatal("shouldn't return error when fetching interface addresses: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasV6DefaultRoute, err := EnvironmentHasIPv6DefaultRoute()
|
||||||
|
shouldIncludeV6Routes := iface.SupportsIPv6() && firewall.SupportsIPv6() && hasV6DefaultRoute && err == nil
|
||||||
|
|
||||||
var addressPrefixes []netip.Prefix
|
var addressPrefixes []netip.Prefix
|
||||||
for _, address := range addresses {
|
for _, address := range addresses {
|
||||||
p := netip.MustParsePrefix(address.String())
|
p := netip.MustParsePrefix(address.String())
|
||||||
if p.Addr().Is6() {
|
if p.Addr().Is6() && !shouldIncludeV6Routes {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Windows sometimes has hidden interface link local addrs that don't turn up on any interface
|
// Windows sometimes has hidden interface link local addrs that don't turn up on any interface
|
||||||
@ -321,7 +430,7 @@ func TestExistsInRouteTable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWGInterface(t *testing.T, interfaceName, ipAddressCIDR string, listenPort int) *iface.WGIface {
|
func createWGInterface(t *testing.T, interfaceName, ipAddressCIDR string, ipAddress6CIDR string, listenPort int) *iface.WGIface {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
peerPrivateKey, err := wgtypes.GeneratePrivateKey()
|
peerPrivateKey, err := wgtypes.GeneratePrivateKey()
|
||||||
@ -330,7 +439,7 @@ func createWGInterface(t *testing.T, interfaceName, ipAddressCIDR string, listen
|
|||||||
newNet, err := stdnet.NewNet()
|
newNet, err := stdnet.NewNet()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
wgInterface, err := iface.NewWGIFace(interfaceName, ipAddressCIDR, listenPort, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil)
|
wgInterface, err := iface.NewWGIFace(interfaceName, ipAddressCIDR, ipAddress6CIDR, listenPort, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil)
|
||||||
require.NoError(t, err, "should create testing WireGuard interface")
|
require.NoError(t, err, "should create testing WireGuard interface")
|
||||||
|
|
||||||
err = wgInterface.Create()
|
err = wgInterface.Create()
|
||||||
@ -348,12 +457,21 @@ func setupTestEnv(t *testing.T) {
|
|||||||
|
|
||||||
setupDummyInterfacesAndRoutes(t)
|
setupDummyInterfacesAndRoutes(t)
|
||||||
|
|
||||||
wgIface := createWGInterface(t, expectedVPNint, "100.64.0.1/24", 51820)
|
v6Addr := ""
|
||||||
|
hasV6DefaultRoute, err := EnvironmentHasIPv6DefaultRoute()
|
||||||
|
//goland:noinspection GoBoolExpressions
|
||||||
|
if !iface.SupportsIPv6() || !firewall.SupportsIPv6() || !hasV6DefaultRoute || err != nil {
|
||||||
|
t.Skip("Platform does not support IPv6, skipping IPv6 test...")
|
||||||
|
} else {
|
||||||
|
v6Addr = "2001:db8::4242:4711/128"
|
||||||
|
}
|
||||||
|
|
||||||
|
wgIface := createWGInterface(t, expectedVPNint, "100.64.0.1/24", v6Addr, 51820)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
assert.NoError(t, wgIface.Close())
|
assert.NoError(t, wgIface.Close())
|
||||||
})
|
})
|
||||||
|
|
||||||
_, _, err := setupRouting(nil, wgIface)
|
_, _, err = setupRouting(nil, wgIface)
|
||||||
require.NoError(t, err, "setupRouting should not return err")
|
require.NoError(t, err, "setupRouting should not return err")
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
assert.NoError(t, cleanupRouting())
|
assert.NoError(t, cleanupRouting())
|
||||||
@ -412,9 +530,15 @@ func assertWGOutInterface(t *testing.T, prefix netip.Prefix, wgIface *iface.WGIf
|
|||||||
|
|
||||||
prefixGateway, _, err := GetNextHop(prefix.Addr())
|
prefixGateway, _, err := GetNextHop(prefix.Addr())
|
||||||
require.NoError(t, err, "GetNextHop should not return err")
|
require.NoError(t, err, "GetNextHop should not return err")
|
||||||
|
|
||||||
|
nexthop := wgIface.Address().IP.String()
|
||||||
|
if prefix.Addr().Is6() {
|
||||||
|
nexthop = wgIface.Address6().IP.String()
|
||||||
|
}
|
||||||
|
|
||||||
if invert {
|
if invert {
|
||||||
assert.NotEqual(t, wgIface.Address().IP.String(), prefixGateway.String(), "route should not point to wireguard interface IP")
|
assert.NotEqual(t, nexthop, prefixGateway.String(), "route should not point to wireguard interface IP")
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, wgIface.Address().IP.String(), prefixGateway.String(), "route should point to wireguard interface IP")
|
assert.Equal(t, nexthop, prefixGateway.String(), "route should point to wireguard interface IP")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ type Info struct {
|
|||||||
SystemProductName string
|
SystemProductName string
|
||||||
SystemManufacturer string
|
SystemManufacturer string
|
||||||
Environment Environment
|
Environment Environment
|
||||||
|
Ipv6Supported bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context
|
// extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context
|
||||||
|
@ -39,6 +39,7 @@ func GetInfo(ctx context.Context) *Info {
|
|||||||
WiretrusteeVersion: version.NetbirdVersion(),
|
WiretrusteeVersion: version.NetbirdVersion(),
|
||||||
UIVersion: extractUIVersion(ctx),
|
UIVersion: extractUIVersion(ctx),
|
||||||
KernelVersion: kernelVersion,
|
KernelVersion: kernelVersion,
|
||||||
|
Ipv6Supported: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
return gio
|
return gio
|
||||||
|
@ -61,6 +61,7 @@ func GetInfo(ctx context.Context) *Info {
|
|||||||
SystemProductName: prodName,
|
SystemProductName: prodName,
|
||||||
SystemManufacturer: manufacturer,
|
SystemManufacturer: manufacturer,
|
||||||
Environment: env,
|
Environment: env,
|
||||||
|
Ipv6Supported: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
systemHostname, _ := os.Hostname()
|
systemHostname, _ := os.Hostname()
|
||||||
|
@ -31,7 +31,7 @@ func GetInfo(ctx context.Context) *Info {
|
|||||||
Platform: detect_platform.Detect(ctx),
|
Platform: detect_platform.Detect(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
gio := &Info{Kernel: osInfo[0], Platform: runtime.GOARCH, OS: osInfo[2], GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), KernelVersion: osInfo[1], Environment: env}
|
gio := &Info{Kernel: osInfo[0], Platform: runtime.GOARCH, OS: osInfo[2], GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), KernelVersion: osInfo[1], Environment: env, Ipv6Supported: false}
|
||||||
|
|
||||||
systemHostname, _ := os.Hostname()
|
systemHostname, _ := os.Hostname()
|
||||||
gio.Hostname = extractDeviceName(ctx, systemHostname)
|
gio.Hostname = extractDeviceName(ctx, systemHostname)
|
||||||
|
@ -6,6 +6,8 @@ package system
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/netbirdio/netbird/client/firewall"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -84,6 +86,7 @@ func GetInfo(ctx context.Context) *Info {
|
|||||||
SystemProductName: prodName,
|
SystemProductName: prodName,
|
||||||
SystemManufacturer: manufacturer,
|
SystemManufacturer: manufacturer,
|
||||||
Environment: env,
|
Environment: env,
|
||||||
|
Ipv6Supported: _checkIPv6Support(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return gio
|
return gio
|
||||||
@ -122,3 +125,8 @@ func sysInfo() (serialNumber string, productName string, manufacturer string) {
|
|||||||
si.GetSysInfo()
|
si.GetSysInfo()
|
||||||
return si.Chassis.Serial, si.Product.Name, si.Product.Vendor
|
return si.Chassis.Serial, si.Product.Name, si.Product.Vendor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _checkIPv6Support() bool {
|
||||||
|
return firewall.SupportsIPv6() &&
|
||||||
|
iface.SupportsIPv6()
|
||||||
|
}
|
||||||
|
@ -75,6 +75,7 @@ func GetInfo(ctx context.Context) *Info {
|
|||||||
SystemProductName: prodName,
|
SystemProductName: prodName,
|
||||||
SystemManufacturer: manufacturer,
|
SystemManufacturer: manufacturer,
|
||||||
Environment: env,
|
Environment: env,
|
||||||
|
Ipv6Supported: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
systemHostname, _ := os.Hostname()
|
systemHostname, _ := os.Hostname()
|
||||||
|
@ -48,6 +48,11 @@ func (w *WGIface) Address() WGAddress {
|
|||||||
return w.tun.WgAddress()
|
return w.tun.WgAddress()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Address6 returns the IPv6 interface address
|
||||||
|
func (w *WGIface) Address6() *WGAddress {
|
||||||
|
return w.tun.WgAddress6()
|
||||||
|
}
|
||||||
|
|
||||||
// Up configures a Wireguard interface
|
// Up configures a Wireguard interface
|
||||||
// The interface must exist before calling this method (e.g. call interface.Create() before)
|
// The interface must exist before calling this method (e.g. call interface.Create() before)
|
||||||
func (w *WGIface) Up() (*bind.UniversalUDPMuxDefault, error) {
|
func (w *WGIface) Up() (*bind.UniversalUDPMuxDefault, error) {
|
||||||
@ -70,6 +75,23 @@ func (w *WGIface) UpdateAddr(newAddr string) error {
|
|||||||
return w.tun.UpdateAddr(addr)
|
return w.tun.UpdateAddr(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateAddr6 updates the IPv6 address of the interface
|
||||||
|
func (w *WGIface) UpdateAddr6(newAddr6 string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
var addr *WGAddress
|
||||||
|
if newAddr6 != "" {
|
||||||
|
parsedAddr, err := parseWGAddress(newAddr6)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
addr = &parsedAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.tun.UpdateAddr6(addr)
|
||||||
|
}
|
||||||
|
|
||||||
// UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist
|
// UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist
|
||||||
// Endpoint is optional
|
// Endpoint is optional
|
||||||
func (w *WGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
func (w *WGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
||||||
|
@ -3,11 +3,16 @@ package iface
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/pion/transport/v3"
|
"github.com/pion/transport/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewWGIFace Creates a new WireGuard interface instance
|
// NewWGIFace Creates a new WireGuard interface instance
|
||||||
func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *MobileIFaceArguments) (*WGIface, error) {
|
func NewWGIFace(iFaceName string, address string, address6 string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *MobileIFaceArguments) (*WGIface, error) {
|
||||||
|
if address6 != "" {
|
||||||
|
log.Errorf("Attempted to configure IPv6 address %s on unsupported operating system", address6)
|
||||||
|
}
|
||||||
wgAddress, err := parseWGAddress(address)
|
wgAddress, err := parseWGAddress(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -38,3 +43,7 @@ func (w *WGIface) CreateOnAndroid(routes []string, dns string, searchDomains []s
|
|||||||
func (w *WGIface) Create() error {
|
func (w *WGIface) Create() error {
|
||||||
return fmt.Errorf("this function has not implemented on this platform")
|
return fmt.Errorf("this function has not implemented on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SupportsIPv6() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -6,13 +6,18 @@ package iface
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/pion/transport/v3"
|
"github.com/pion/transport/v3"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/iface/netstack"
|
"github.com/netbirdio/netbird/iface/netstack"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewWGIFace Creates a new WireGuard interface instance
|
// NewWGIFace Creates a new WireGuard interface instance
|
||||||
func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *MobileIFaceArguments) (*WGIface, error) {
|
func NewWGIFace(iFaceName string, address string, address6 string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *MobileIFaceArguments) (*WGIface, error) {
|
||||||
|
if address6 != "" {
|
||||||
|
log.Errorf("Attempted to configure IPv6 address %s on unsupported operating system", address6)
|
||||||
|
}
|
||||||
wgAddress, err := parseWGAddress(address)
|
wgAddress, err := parseWGAddress(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -36,3 +41,7 @@ func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string,
|
|||||||
func (w *WGIface) CreateOnAndroid([]string, string, []string) error {
|
func (w *WGIface) CreateOnAndroid([]string, string, []string) error {
|
||||||
return fmt.Errorf("this function has not implemented on this platform")
|
return fmt.Errorf("this function has not implemented on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SupportsIPv6() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -5,12 +5,17 @@ package iface
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/pion/transport/v3"
|
"github.com/pion/transport/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewWGIFace Creates a new WireGuard interface instance
|
// NewWGIFace Creates a new WireGuard interface instance
|
||||||
func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *MobileIFaceArguments) (*WGIface, error) {
|
func NewWGIFace(iFaceName string, address string, address6 string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *MobileIFaceArguments) (*WGIface, error) {
|
||||||
|
if address6 != "" {
|
||||||
|
log.Errorf("Attempted to configure IPv6 address %s on unsupported operating system", address6)
|
||||||
|
}
|
||||||
|
|
||||||
wgAddress, err := parseWGAddress(address)
|
wgAddress, err := parseWGAddress(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -27,3 +32,7 @@ func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string,
|
|||||||
func (w *WGIface) CreateOnAndroid([]string, string, []string) error {
|
func (w *WGIface) CreateOnAndroid([]string, string, []string) error {
|
||||||
return fmt.Errorf("this function has not implemented on this platform")
|
return fmt.Errorf("this function has not implemented on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SupportsIPv6() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -5,14 +5,14 @@ package iface
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/pion/transport/v3"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/iface/netstack"
|
"github.com/netbirdio/netbird/iface/netstack"
|
||||||
|
"github.com/pion/transport/v3"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/net/nettest"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewWGIFace Creates a new WireGuard interface instance
|
// NewWGIFace Creates a new WireGuard interface instance
|
||||||
func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *MobileIFaceArguments) (*WGIface, error) {
|
func NewWGIFace(iFaceName string, address string, address6 string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *MobileIFaceArguments) (*WGIface, error) {
|
||||||
wgAddress, err := parseWGAddress(address)
|
wgAddress, err := parseWGAddress(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -20,6 +20,10 @@ func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string,
|
|||||||
|
|
||||||
wgIFace := &WGIface{}
|
wgIFace := &WGIface{}
|
||||||
|
|
||||||
|
if netstack.IsEnabled() || !WireGuardModuleIsLoaded() && address6 != "" {
|
||||||
|
log.Errorf("Attempted to configure IPv6 address %s on unsupported device implementation (netstack or tun).", address6)
|
||||||
|
}
|
||||||
|
|
||||||
// move the kernel/usp/netstack preference evaluation to upper layer
|
// move the kernel/usp/netstack preference evaluation to upper layer
|
||||||
if netstack.IsEnabled() {
|
if netstack.IsEnabled() {
|
||||||
wgIFace.tun = newTunNetstackDevice(iFaceName, wgAddress, wgPort, wgPrivKey, mtu, transportNet, netstack.ListenAddr())
|
wgIFace.tun = newTunNetstackDevice(iFaceName, wgAddress, wgPort, wgPrivKey, mtu, transportNet, netstack.ListenAddr())
|
||||||
@ -28,7 +32,16 @@ func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if WireGuardModuleIsLoaded() {
|
if WireGuardModuleIsLoaded() {
|
||||||
wgIFace.tun = newTunDevice(iFaceName, wgAddress, wgPort, wgPrivKey, mtu, transportNet)
|
|
||||||
|
var wgAddress6 *WGAddress = nil
|
||||||
|
if address6 != "" {
|
||||||
|
tmpWgAddress6, err := parseWGAddress(address6)
|
||||||
|
wgAddress6 = &tmpWgAddress6
|
||||||
|
if err != nil {
|
||||||
|
return wgIFace, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wgIFace.tun = newTunDevice(iFaceName, wgAddress, wgAddress6, wgPort, wgPrivKey, mtu, transportNet)
|
||||||
wgIFace.userspaceBind = false
|
wgIFace.userspaceBind = false
|
||||||
return wgIFace, nil
|
return wgIFace, nil
|
||||||
}
|
}
|
||||||
@ -45,3 +58,7 @@ func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string,
|
|||||||
func (w *WGIface) CreateOnAndroid([]string, string, []string) error {
|
func (w *WGIface) CreateOnAndroid([]string, string, []string) error {
|
||||||
return fmt.Errorf("this function has not implemented on this platform")
|
return fmt.Errorf("this function has not implemented on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SupportsIPv6() bool {
|
||||||
|
return nettest.SupportsIPv6() && WireGuardModuleIsLoaded() && !netstack.IsEnabled()
|
||||||
|
}
|
||||||
|
@ -41,7 +41,7 @@ func TestWGIface_UpdateAddr(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
iface, err := NewWGIFace(ifaceName, addr, wgPort, key, DefaultMTU, newNet, nil)
|
iface, err := NewWGIFace(ifaceName, addr, "", wgPort, key, DefaultMTU, newNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -95,6 +95,64 @@ func TestWGIface_UpdateAddr(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWGIface_UpdateAddr6(t *testing.T) {
|
||||||
|
if !SupportsIPv6() {
|
||||||
|
t.Skip("Environment does not support IPv6, skipping IPv6 test...")
|
||||||
|
}
|
||||||
|
|
||||||
|
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+4)
|
||||||
|
|
||||||
|
addr := "100.64.0.1/8"
|
||||||
|
addr6 := "2001:db8:1234:abcd::42/64"
|
||||||
|
wgPort := 33100
|
||||||
|
newNet, err := stdnet.NewNet()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iface, err := NewWGIFace(ifaceName, addr, addr6, wgPort, key, DefaultMTU, newNet, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = iface.Create()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = iface.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err = iface.Up()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, err := getIfaceAddrs(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, addr, addrs[0].String())
|
||||||
|
|
||||||
|
//update WireGuard address
|
||||||
|
addr = "100.64.0.2/8"
|
||||||
|
err = iface.UpdateAddr(addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, err = getIfaceAddrs(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, addr6, addrs[1].String())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func getIfaceAddrs(ifaceName string) ([]net.Addr, error) {
|
func getIfaceAddrs(ifaceName string) ([]net.Addr, error) {
|
||||||
ief, err := net.InterfaceByName(ifaceName)
|
ief, err := net.InterfaceByName(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -114,7 +172,45 @@ func Test_CreateInterface(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
iface, err := NewWGIFace(ifaceName, wgIP, 33100, key, DefaultMTU, newNet, nil)
|
iface, err := NewWGIFace(ifaceName, wgIP, "", 33100, key, DefaultMTU, newNet, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = iface.Create()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = iface.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
wg, err := wgctrl.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = wg.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_CreateInterface6(t *testing.T) {
|
||||||
|
if !SupportsIPv6() {
|
||||||
|
t.Skip("Environment does not support IPv6, skipping IPv6 test...")
|
||||||
|
}
|
||||||
|
|
||||||
|
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+1)
|
||||||
|
wgIP := "10.99.99.1/32"
|
||||||
|
wgIP6 := "2001:db8:1234:abcd::43/64"
|
||||||
|
newNet, err := stdnet.NewNet()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
iface, err := NewWGIFace(ifaceName, wgIP, wgIP6, 33100, key, DefaultMTU, newNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -149,7 +245,46 @@ func Test_Close(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
iface, err := NewWGIFace(ifaceName, wgIP, wgPort, key, DefaultMTU, newNet, nil)
|
iface, err := NewWGIFace(ifaceName, wgIP, "", wgPort, key, DefaultMTU, newNet, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = iface.Create()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wg, err := wgctrl.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = wg.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = iface.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Close6(t *testing.T) {
|
||||||
|
if !SupportsIPv6() {
|
||||||
|
t.Skip("Environment does not support IPv6, skipping IPv6 test...")
|
||||||
|
}
|
||||||
|
|
||||||
|
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+2)
|
||||||
|
wgIP := "10.99.99.2/32"
|
||||||
|
wgIP6 := "2001:db8:1234:abcd::44/64"
|
||||||
|
wgPort := 33100
|
||||||
|
newNet, err := stdnet.NewNet()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iface, err := NewWGIFace(ifaceName, wgIP, wgIP6, wgPort, key, DefaultMTU, newNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -182,7 +317,7 @@ func Test_ConfigureInterface(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
iface, err := NewWGIFace(ifaceName, wgIP, wgPort, key, DefaultMTU, newNet, nil)
|
iface, err := NewWGIFace(ifaceName, wgIP, "", wgPort, key, DefaultMTU, newNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -230,7 +365,7 @@ func Test_UpdatePeer(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
iface, err := NewWGIFace(ifaceName, wgIP, 33100, key, DefaultMTU, newNet, nil)
|
iface, err := NewWGIFace(ifaceName, wgIP, "", 33100, key, DefaultMTU, newNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -283,6 +418,73 @@ func Test_UpdatePeer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_UpdatePeer6(t *testing.T) {
|
||||||
|
if !SupportsIPv6() {
|
||||||
|
t.Skip("Environment does not support IPv6, skipping IPv6 test...")
|
||||||
|
}
|
||||||
|
|
||||||
|
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+4)
|
||||||
|
wgIP := "10.99.99.9/30"
|
||||||
|
wgIP6 := "2001:db8:1234:abcd::45/64"
|
||||||
|
newNet, err := stdnet.NewNet()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iface, err := NewWGIFace(ifaceName, wgIP, wgIP6, 33100, key, DefaultMTU, newNet, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = iface.Create()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = iface.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err = iface.Up()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
keepAlive := 15 * time.Second
|
||||||
|
allowedIP := "10.99.99.10/32"
|
||||||
|
allowedIP6 := "2001:db8:1234:abcd::46/128"
|
||||||
|
endpoint, err := net.ResolveUDPAddr("udp", "127.0.0.1:9900")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = iface.UpdatePeer(peerPubKey, allowedIP+","+allowedIP6, keepAlive, endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
peer, err := getPeer(ifaceName, peerPubKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if peer.PersistentKeepaliveInterval != keepAlive {
|
||||||
|
t.Fatal("configured peer with mismatched keepalive interval value")
|
||||||
|
}
|
||||||
|
|
||||||
|
if peer.Endpoint.String() != endpoint.String() {
|
||||||
|
t.Fatal("configured peer with mismatched endpoint")
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundAllowedIP bool
|
||||||
|
for _, aip := range peer.AllowedIPs {
|
||||||
|
if aip.String() == allowedIP6 {
|
||||||
|
foundAllowedIP = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundAllowedIP {
|
||||||
|
t.Fatal("configured peer with mismatched Allowed IPs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_RemovePeer(t *testing.T) {
|
func Test_RemovePeer(t *testing.T) {
|
||||||
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+4)
|
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+4)
|
||||||
wgIP := "10.99.99.13/30"
|
wgIP := "10.99.99.13/30"
|
||||||
@ -291,7 +493,7 @@ func Test_RemovePeer(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
iface, err := NewWGIFace(ifaceName, wgIP, 33100, key, DefaultMTU, newNet, nil)
|
iface, err := NewWGIFace(ifaceName, wgIP, "", 33100, key, DefaultMTU, newNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -345,7 +547,7 @@ func Test_ConnectPeers(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
iface1, err := NewWGIFace(peer1ifaceName, peer1wgIP, peer1wgPort, peer1Key.String(), DefaultMTU, newNet, nil)
|
iface1, err := NewWGIFace(peer1ifaceName, peer1wgIP, "", peer1wgPort, peer1Key.String(), DefaultMTU, newNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -368,7 +570,113 @@ func Test_ConnectPeers(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
iface2, err := NewWGIFace(peer2ifaceName, peer2wgIP, peer2wgPort, peer2Key.String(), DefaultMTU, newNet, nil)
|
iface2, err := NewWGIFace(peer2ifaceName, peer2wgIP, "", peer2wgPort, peer2Key.String(), DefaultMTU, newNet, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = iface2.Create()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = iface2.Up()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peer2endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", peer2wgPort))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = iface1.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = iface2.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = iface1.UpdatePeer(peer2Key.PublicKey().String(), peer2wgIP, keepAlive, peer2endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = iface2.UpdatePeer(peer1Key.PublicKey().String(), peer1wgIP, keepAlive, peer1endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// todo: investigate why in some tests execution we need 30s
|
||||||
|
timeout := 30 * time.Second
|
||||||
|
timeoutChannel := time.After(timeout)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-timeoutChannel:
|
||||||
|
t.Fatalf("waiting for peer handshake timeout after %s", timeout.String())
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, gpErr := getPeer(peer1ifaceName, peer2Key.PublicKey().String())
|
||||||
|
if gpErr != nil {
|
||||||
|
t.Fatal(gpErr)
|
||||||
|
}
|
||||||
|
if !peer.LastHandshakeTime.IsZero() {
|
||||||
|
t.Log("peers successfully handshake")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ConnectPeers6(t *testing.T) {
|
||||||
|
if !SupportsIPv6() {
|
||||||
|
t.Skip("Environment does not support IPv6, skipping IPv6 test...")
|
||||||
|
}
|
||||||
|
|
||||||
|
peer1ifaceName := fmt.Sprintf("utun%d", WgIntNumber+400)
|
||||||
|
peer1wgIP := "10.99.99.17/30"
|
||||||
|
peer1wgIP6 := "2001:db8:1234:abcd::47/64"
|
||||||
|
peer1Key, _ := wgtypes.GeneratePrivateKey()
|
||||||
|
peer1wgPort := 33100
|
||||||
|
|
||||||
|
peer2ifaceName := "utun500"
|
||||||
|
peer2wgIP := "10.99.99.18/30"
|
||||||
|
peer2wgIP6 := "2001:db8:1234:abcd::48/64"
|
||||||
|
peer2Key, _ := wgtypes.GeneratePrivateKey()
|
||||||
|
peer2wgPort := 33200
|
||||||
|
|
||||||
|
keepAlive := 1 * time.Second
|
||||||
|
newNet, err := stdnet.NewNet()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iface1, err := NewWGIFace(peer1ifaceName, peer1wgIP, peer1wgIP6, peer1wgPort, peer1Key.String(), DefaultMTU, newNet, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = iface1.Create()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = iface1.Up()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peer1endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", peer1wgPort))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newNet, err = stdnet.NewNet()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
iface2, err := NewWGIFace(peer2ifaceName, peer2wgIP, peer2wgIP6, peer2wgPort, peer2Key.String(), DefaultMTU, newNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,18 @@ package iface
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/pion/transport/v3"
|
"github.com/pion/transport/v3"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/iface/netstack"
|
"github.com/netbirdio/netbird/iface/netstack"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewWGIFace Creates a new WireGuard interface instance
|
// NewWGIFace Creates a new WireGuard interface instance
|
||||||
func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *MobileIFaceArguments) (*WGIface, error) {
|
func NewWGIFace(iFaceName string, address string, address6 string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *MobileIFaceArguments) (*WGIface, error) {
|
||||||
|
if address6 != "" {
|
||||||
|
log.Errorf("Attempted to configure IPv6 address %s on unsupported operating system", address6)
|
||||||
|
}
|
||||||
wgAddress, err := parseWGAddress(address)
|
wgAddress, err := parseWGAddress(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -37,3 +42,7 @@ func (w *WGIface) CreateOnAndroid([]string, string, []string) error {
|
|||||||
func (w *WGIface) GetInterfaceGUIDString() (string, error) {
|
func (w *WGIface) GetInterfaceGUIDString() (string, error) {
|
||||||
return w.tun.(*tunDevice).getInterfaceGUIDString()
|
return w.tun.(*tunDevice).getInterfaceGUIDString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SupportsIPv6() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -12,6 +12,8 @@ type wgTunDevice interface {
|
|||||||
Up() (*bind.UniversalUDPMuxDefault, error)
|
Up() (*bind.UniversalUDPMuxDefault, error)
|
||||||
UpdateAddr(address WGAddress) error
|
UpdateAddr(address WGAddress) error
|
||||||
WgAddress() WGAddress
|
WgAddress() WGAddress
|
||||||
|
UpdateAddr6(addr6 *WGAddress) error
|
||||||
|
WgAddress6() *WGAddress
|
||||||
DeviceName() string
|
DeviceName() string
|
||||||
Close() error
|
Close() error
|
||||||
Wrapper() *DeviceWrapper // todo eliminate this function
|
Wrapper() *DeviceWrapper // todo eliminate this function
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package iface
|
package iface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pion/transport/v3"
|
"github.com/pion/transport/v3"
|
||||||
@ -98,6 +99,13 @@ func (t *wgTunDevice) UpdateAddr(addr WGAddress) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *wgTunDevice) UpdateAddr6(addr6 *WGAddress) error {
|
||||||
|
if addr6 == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("IPv6 is not supported on this operating system")
|
||||||
|
}
|
||||||
|
|
||||||
func (t *wgTunDevice) Close() error {
|
func (t *wgTunDevice) Close() error {
|
||||||
if t.configurer != nil {
|
if t.configurer != nil {
|
||||||
t.configurer.close()
|
t.configurer.close()
|
||||||
@ -127,6 +135,10 @@ func (t *wgTunDevice) WgAddress() WGAddress {
|
|||||||
return t.address
|
return t.address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *wgTunDevice) WgAddress6() *WGAddress {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *wgTunDevice) Wrapper() *DeviceWrapper {
|
func (t *wgTunDevice) Wrapper() *DeviceWrapper {
|
||||||
return t.wrapper
|
return t.wrapper
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
package iface
|
package iface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/pion/transport/v3"
|
"github.com/pion/transport/v3"
|
||||||
@ -88,6 +89,13 @@ func (t *tunDevice) UpdateAddr(address WGAddress) error {
|
|||||||
return t.assignAddr()
|
return t.assignAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) UpdateAddr6(address6 *WGAddress) error {
|
||||||
|
if address6 == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("IPv6 is not supported on this operating system")
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tunDevice) Close() error {
|
func (t *tunDevice) Close() error {
|
||||||
if t.configurer != nil {
|
if t.configurer != nil {
|
||||||
t.configurer.close()
|
t.configurer.close()
|
||||||
@ -108,6 +116,10 @@ func (t *tunDevice) WgAddress() WGAddress {
|
|||||||
return t.address
|
return t.address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) WgAddress6() *WGAddress {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tunDevice) DeviceName() string {
|
func (t *tunDevice) DeviceName() string {
|
||||||
return t.name
|
return t.name
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package iface
|
package iface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/pion/transport/v3"
|
"github.com/pion/transport/v3"
|
||||||
@ -123,11 +124,22 @@ func (t *tunDevice) WgAddress() WGAddress {
|
|||||||
return t.address
|
return t.address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) WgAddress6() *WGAddress {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tunDevice) UpdateAddr(addr WGAddress) error {
|
func (t *tunDevice) UpdateAddr(addr WGAddress) error {
|
||||||
// todo implement
|
// todo implement
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) UpdateAddr6(address6 *WGAddress) error {
|
||||||
|
if address6 == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("IPv6 is not supported on this operating system")
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tunDevice) Wrapper() *DeviceWrapper {
|
func (t *tunDevice) Wrapper() *DeviceWrapper {
|
||||||
return t.wrapper
|
return t.wrapper
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
type tunKernelDevice struct {
|
type tunKernelDevice struct {
|
||||||
name string
|
name string
|
||||||
address WGAddress
|
address WGAddress
|
||||||
|
address6 *WGAddress
|
||||||
wgPort int
|
wgPort int
|
||||||
key string
|
key string
|
||||||
mtu int
|
mtu int
|
||||||
@ -31,13 +32,14 @@ type tunKernelDevice struct {
|
|||||||
udpMux *bind.UniversalUDPMuxDefault
|
udpMux *bind.UniversalUDPMuxDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTunDevice(name string, address WGAddress, wgPort int, key string, mtu int, transportNet transport.Net) wgTunDevice {
|
func newTunDevice(name string, address WGAddress, address6 *WGAddress, wgPort int, key string, mtu int, transportNet transport.Net) wgTunDevice {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
return &tunKernelDevice{
|
return &tunKernelDevice{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
ctxCancel: cancel,
|
ctxCancel: cancel,
|
||||||
name: name,
|
name: name,
|
||||||
address: address,
|
address: address,
|
||||||
|
address6: address6,
|
||||||
wgPort: wgPort,
|
wgPort: wgPort,
|
||||||
key: key,
|
key: key,
|
||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
@ -136,6 +138,11 @@ func (t *tunKernelDevice) UpdateAddr(address WGAddress) error {
|
|||||||
return t.assignAddr()
|
return t.assignAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tunKernelDevice) UpdateAddr6(address6 *WGAddress) error {
|
||||||
|
t.address6 = address6
|
||||||
|
return t.assignAddr()
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tunKernelDevice) Close() error {
|
func (t *tunKernelDevice) Close() error {
|
||||||
if t.link == nil {
|
if t.link == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -168,6 +175,10 @@ func (t *tunKernelDevice) WgAddress() WGAddress {
|
|||||||
return t.address
|
return t.address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tunKernelDevice) WgAddress6() *WGAddress {
|
||||||
|
return t.address6
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tunKernelDevice) DeviceName() string {
|
func (t *tunKernelDevice) DeviceName() string {
|
||||||
return t.name
|
return t.name
|
||||||
}
|
}
|
||||||
@ -203,6 +214,19 @@ func (t *tunKernelDevice) assignAddr() error {
|
|||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure the optional additional IPv6 address if available.
|
||||||
|
if t.address6 != nil {
|
||||||
|
log.Debugf("adding IPv6 address %s to interface: %s", t.address6.String(), t.name)
|
||||||
|
addr6, _ := netlink.ParseAddr(t.address6.String())
|
||||||
|
err = netlink.AddrAdd(link, addr6)
|
||||||
|
if os.IsExist(err) {
|
||||||
|
log.Infof("interface %s already has the address: %s", t.name, t.address.String())
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// On linux, the link must be brought up
|
// On linux, the link must be brought up
|
||||||
err = netlink.LinkSetUp(link)
|
err = netlink.LinkSetUp(link)
|
||||||
return err
|
return err
|
||||||
|
@ -91,6 +91,13 @@ func (t *tunNetstackDevice) UpdateAddr(WGAddress) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tunNetstackDevice) UpdateAddr6(address6 *WGAddress) error {
|
||||||
|
if address6 == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("IPv6 is not supported on this operating system")
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tunNetstackDevice) Close() error {
|
func (t *tunNetstackDevice) Close() error {
|
||||||
if t.configurer != nil {
|
if t.configurer != nil {
|
||||||
t.configurer.close()
|
t.configurer.close()
|
||||||
@ -110,6 +117,10 @@ func (t *tunNetstackDevice) WgAddress() WGAddress {
|
|||||||
return t.address
|
return t.address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tunNetstackDevice) WgAddress6() *WGAddress {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tunNetstackDevice) DeviceName() string {
|
func (t *tunNetstackDevice) DeviceName() string {
|
||||||
return t.name
|
return t.name
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,13 @@ func (t *tunUSPDevice) UpdateAddr(address WGAddress) error {
|
|||||||
return t.assignAddr()
|
return t.assignAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tunUSPDevice) UpdateAddr6(address6 *WGAddress) error {
|
||||||
|
if address6 == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("IPv6 is not supported on this operating system")
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tunUSPDevice) Close() error {
|
func (t *tunUSPDevice) Close() error {
|
||||||
if t.configurer != nil {
|
if t.configurer != nil {
|
||||||
t.configurer.close()
|
t.configurer.close()
|
||||||
@ -117,6 +124,10 @@ func (t *tunUSPDevice) WgAddress() WGAddress {
|
|||||||
return t.address
|
return t.address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tunUSPDevice) WgAddress6() *WGAddress {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tunUSPDevice) DeviceName() string {
|
func (t *tunUSPDevice) DeviceName() string {
|
||||||
return t.name
|
return t.name
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,13 @@ func (t *tunDevice) UpdateAddr(address WGAddress) error {
|
|||||||
return t.assignAddr()
|
return t.assignAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) UpdateAddr6(address6 *WGAddress) error {
|
||||||
|
if address6 == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("IPv6 is not supported on this operating system")
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tunDevice) Close() error {
|
func (t *tunDevice) Close() error {
|
||||||
if t.configurer != nil {
|
if t.configurer != nil {
|
||||||
t.configurer.close()
|
t.configurer.close()
|
||||||
@ -126,6 +133,10 @@ func (t *tunDevice) WgAddress() WGAddress {
|
|||||||
return t.address
|
return t.address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) WgAddress6() *WGAddress {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tunDevice) DeviceName() string {
|
func (t *tunDevice) DeviceName() string {
|
||||||
return t.name
|
return t.name
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ package iface
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@ -46,9 +47,13 @@ func (c *wgKernelConfigurer) configureInterface(privateKey string, port int) err
|
|||||||
|
|
||||||
func (c *wgKernelConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
func (c *wgKernelConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
||||||
// parse allowed ips
|
// parse allowed ips
|
||||||
_, ipNet, err := net.ParseCIDR(allowedIps)
|
var allowedIpNets []net.IPNet
|
||||||
if err != nil {
|
for _, allowedIp := range strings.Split(allowedIps, ",") {
|
||||||
return err
|
_, ipNet, err := net.ParseCIDR(allowedIp)
|
||||||
|
allowedIpNets = append(allowedIpNets, *ipNet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||||
@ -58,7 +63,7 @@ func (c *wgKernelConfigurer) updatePeer(peerKey string, allowedIps string, keepA
|
|||||||
peer := wgtypes.PeerConfig{
|
peer := wgtypes.PeerConfig{
|
||||||
PublicKey: peerKeyParsed,
|
PublicKey: peerKeyParsed,
|
||||||
ReplaceAllowedIPs: true,
|
ReplaceAllowedIPs: true,
|
||||||
AllowedIPs: []net.IPNet{*ipNet},
|
AllowedIPs: allowedIpNets,
|
||||||
PersistentKeepaliveInterval: &keepAlive,
|
PersistentKeepaliveInterval: &keepAlive,
|
||||||
Endpoint: endpoint,
|
Endpoint: endpoint,
|
||||||
PresharedKey: preSharedKey,
|
PresharedKey: preSharedKey,
|
||||||
|
@ -379,6 +379,7 @@ func Test_SystemMetaDataFromClient(t *testing.T) {
|
|||||||
SysProductName: info.SystemProductName,
|
SysProductName: info.SystemProductName,
|
||||||
SysManufacturer: info.SystemManufacturer,
|
SysManufacturer: info.SystemManufacturer,
|
||||||
Environment: &mgmtProto.Environment{Cloud: info.Environment.Cloud, Platform: info.Environment.Platform},
|
Environment: &mgmtProto.Environment{Cloud: info.Environment.Cloud, Platform: info.Environment.Platform},
|
||||||
|
Ipv6Supported: info.Ipv6Supported,
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, ValidKey, actualValidKey)
|
assert.Equal(t, ValidKey, actualValidKey)
|
||||||
|
@ -483,5 +483,6 @@ func infoToMetaData(info *system.Info) *proto.PeerSystemMeta {
|
|||||||
Cloud: info.Environment.Cloud,
|
Cloud: info.Environment.Cloud,
|
||||||
Platform: info.Environment.Platform,
|
Platform: info.Environment.Platform,
|
||||||
},
|
},
|
||||||
|
Ipv6Supported: info.Ipv6Supported,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v4.24.3
|
// protoc v4.25.2
|
||||||
// source: management.proto
|
// source: management.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@ -668,6 +668,7 @@ type PeerSystemMeta struct {
|
|||||||
SysProductName string `protobuf:"bytes,13,opt,name=sysProductName,proto3" json:"sysProductName,omitempty"`
|
SysProductName string `protobuf:"bytes,13,opt,name=sysProductName,proto3" json:"sysProductName,omitempty"`
|
||||||
SysManufacturer string `protobuf:"bytes,14,opt,name=sysManufacturer,proto3" json:"sysManufacturer,omitempty"`
|
SysManufacturer string `protobuf:"bytes,14,opt,name=sysManufacturer,proto3" json:"sysManufacturer,omitempty"`
|
||||||
Environment *Environment `protobuf:"bytes,15,opt,name=environment,proto3" json:"environment,omitempty"`
|
Environment *Environment `protobuf:"bytes,15,opt,name=environment,proto3" json:"environment,omitempty"`
|
||||||
|
Ipv6Supported bool `protobuf:"varint,16,opt,name=ipv6Supported,proto3" json:"ipv6Supported,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PeerSystemMeta) Reset() {
|
func (x *PeerSystemMeta) Reset() {
|
||||||
@ -807,6 +808,13 @@ func (x *PeerSystemMeta) GetEnvironment() *Environment {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *PeerSystemMeta) GetIpv6Supported() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ipv6Supported
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type LoginResponse struct {
|
type LoginResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@ -1172,6 +1180,8 @@ type PeerConfig struct {
|
|||||||
SshConfig *SSHConfig `protobuf:"bytes,3,opt,name=sshConfig,proto3" json:"sshConfig,omitempty"`
|
SshConfig *SSHConfig `protobuf:"bytes,3,opt,name=sshConfig,proto3" json:"sshConfig,omitempty"`
|
||||||
// Peer fully qualified domain name
|
// Peer fully qualified domain name
|
||||||
Fqdn string `protobuf:"bytes,4,opt,name=fqdn,proto3" json:"fqdn,omitempty"`
|
Fqdn string `protobuf:"bytes,4,opt,name=fqdn,proto3" json:"fqdn,omitempty"`
|
||||||
|
// Peer's virtual IPv6 address within the Wiretrustee VPN (a Wireguard address config)
|
||||||
|
Address6 string `protobuf:"bytes,5,opt,name=address6,proto3" json:"address6,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PeerConfig) Reset() {
|
func (x *PeerConfig) Reset() {
|
||||||
@ -1234,6 +1244,13 @@ func (x *PeerConfig) GetFqdn() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *PeerConfig) GetAddress6() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Address6
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections
|
// NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections
|
||||||
type NetworkMap struct {
|
type NetworkMap struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
@ -2254,6 +2271,7 @@ type FirewallRule struct {
|
|||||||
Action FirewallRuleAction `protobuf:"varint,3,opt,name=Action,proto3,enum=management.FirewallRuleAction" json:"Action,omitempty"`
|
Action FirewallRuleAction `protobuf:"varint,3,opt,name=Action,proto3,enum=management.FirewallRuleAction" json:"Action,omitempty"`
|
||||||
Protocol FirewallRuleProtocol `protobuf:"varint,4,opt,name=Protocol,proto3,enum=management.FirewallRuleProtocol" json:"Protocol,omitempty"`
|
Protocol FirewallRuleProtocol `protobuf:"varint,4,opt,name=Protocol,proto3,enum=management.FirewallRuleProtocol" json:"Protocol,omitempty"`
|
||||||
Port string `protobuf:"bytes,5,opt,name=Port,proto3" json:"Port,omitempty"`
|
Port string `protobuf:"bytes,5,opt,name=Port,proto3" json:"Port,omitempty"`
|
||||||
|
PeerIP6 string `protobuf:"bytes,6,opt,name=PeerIP6,proto3" json:"PeerIP6,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FirewallRule) Reset() {
|
func (x *FirewallRule) Reset() {
|
||||||
@ -2323,6 +2341,13 @@ func (x *FirewallRule) GetPort() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *FirewallRule) GetPeerIP6() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.PeerIP6
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type NetworkAddress struct {
|
type NetworkAddress struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@ -2430,7 +2455,7 @@ var file_management_proto_rawDesc = []byte{
|
|||||||
0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c,
|
0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c,
|
||||||
0x6f, 0x75, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x6f, 0x75, 0x64,
|
0x6f, 0x75, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x6f, 0x75, 0x64,
|
||||||
0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x02, 0x20, 0x01,
|
0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0xa9, 0x04, 0x0a,
|
0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0xcf, 0x04, 0x0a,
|
||||||
0x0e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x12,
|
0x0e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x12,
|
||||||
0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x67,
|
0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x67,
|
||||||
@ -2465,260 +2490,266 @@ var file_management_proto_rawDesc = []byte{
|
|||||||
0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01,
|
0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01,
|
||||||
0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||||
0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0b, 0x65, 0x6e, 0x76,
|
0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0b, 0x65, 0x6e, 0x76,
|
||||||
0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x94, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67,
|
0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x70, 0x76, 0x36,
|
||||||
0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x11, 0x77, 0x69,
|
0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18,
|
0x0d, 0x69, 0x70, 0x76, 0x36, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x22, 0x94,
|
||||||
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
0x6e, 0x74, 0x2e, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f,
|
0x12, 0x4b, 0x0a, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43,
|
||||||
0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61,
|
||||||
0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43,
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75,
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61,
|
0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x77, 0x69, 0x72, 0x65,
|
||||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e,
|
0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a,
|
||||||
0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22,
|
0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
0x79, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70,
|
0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50,
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43,
|
||||||
0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x79, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b,
|
||||||
0x73, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
|
||||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
|
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x38, 0x0a, 0x09,
|
||||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74,
|
0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
|
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||||
0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d,
|
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70,
|
||||||
0x70, 0x74, 0x79, 0x22, 0xa8, 0x01, 0x0a, 0x11, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73,
|
0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||||
0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x0a, 0x05, 0x73, 0x74, 0x75,
|
0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
|
||||||
0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xa8, 0x01, 0x0a, 0x11, 0x57, 0x69,
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
||||||
0x52, 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73,
|
0x2c, 0x0a, 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16,
|
||||||
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73,
|
|
||||||
0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x12, 0x2e,
|
|
||||||
0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
|
|
||||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74,
|
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74,
|
||||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x22, 0x98,
|
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73, 0x12, 0x35, 0x0a,
|
||||||
0x01, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a,
|
0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6d,
|
||||||
0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12,
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63,
|
||||||
0x3b, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28,
|
0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x74,
|
||||||
0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48,
|
0x75, 0x72, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x18, 0x03,
|
||||||
0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
|
||||||
0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x3b, 0x0a, 0x08,
|
|
||||||
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10,
|
|
||||||
0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54,
|
|
||||||
0x54, 0x50, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x03, 0x12,
|
|
||||||
0x08, 0x0a, 0x04, 0x44, 0x54, 0x4c, 0x53, 0x10, 0x04, 0x22, 0x7d, 0x0a, 0x13, 0x50, 0x72, 0x6f,
|
|
||||||
0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
|
||||||
0x12, 0x36, 0x0a, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01,
|
|
||||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x68, 0x6f,
|
0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x73, 0x69,
|
||||||
0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72,
|
0x67, 0x6e, 0x61, 0x6c, 0x22, 0x98, 0x01, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08,
|
0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
|
0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x3b, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
|
||||||
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x81, 0x01, 0x0a, 0x0a, 0x50, 0x65, 0x65,
|
0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
|
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
|
||||||
0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
|
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
||||||
0x73, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
|
0x6f, 0x6c, 0x22, 0x3b, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07,
|
||||||
0x64, 0x6e, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01,
|
||||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54,
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73,
|
0x54, 0x50, 0x53, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x54, 0x4c, 0x53, 0x10, 0x04, 0x22,
|
||||||
0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e,
|
0x7d, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74,
|
||||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0xe2, 0x03, 0x0a,
|
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f,
|
||||||
0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53,
|
0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e,
|
||||||
0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72,
|
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
||||||
0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
0x69, 0x67, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12,
|
||||||
0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73,
|
||||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
|
0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03,
|
||||||
0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0b, 0x72,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x9d,
|
||||||
0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
|
0x01, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a,
|
||||||
0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65,
|
0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||||
0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b,
|
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x6e, 0x73, 0x18, 0x02,
|
||||||
0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x6e, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68,
|
||||||
0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74,
|
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d,
|
||||||
0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50,
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e,
|
||||||
0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x06, 0x52,
|
0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12,
|
||||||
0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61,
|
0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71,
|
||||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06,
|
0x64, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x36, 0x18, 0x05,
|
||||||
0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x36, 0x22, 0xe2,
|
||||||
0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
0x03, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x12, 0x16, 0x0a,
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x53,
|
||||||
0x52, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x40, 0x0a, 0x0c, 0x6f,
|
0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e,
|
||||||
0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28,
|
0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||||
0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52,
|
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
|
0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a,
|
||||||
0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x3e, 0x0a,
|
0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03,
|
||||||
0x0d, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x08,
|
0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d,
|
0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a,
|
||||||
0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x32, 0x0a,
|
0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d,
|
||||||
0x14, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x49, 0x73,
|
0x70, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74,
|
||||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x66, 0x69, 0x72,
|
0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x29, 0x0a,
|
||||||
0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74,
|
0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e,
|
||||||
0x79, 0x22, 0x97, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72,
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65,
|
||||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b,
|
0x52, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43,
|
||||||
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61,
|
||||||
0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73,
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66,
|
||||||
0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49,
|
0x69, 0x67, 0x52, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x40, 0x0a,
|
||||||
0x70, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18,
|
0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x07, 0x20,
|
||||||
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73,
|
0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18,
|
0x67, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12,
|
||||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x49, 0x0a, 0x09, 0x53,
|
0x3e, 0x0a, 0x0d, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73,
|
||||||
0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45,
|
0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x73,
|
0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65,
|
||||||
0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50,
|
0x52, 0x0d, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12,
|
||||||
0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68,
|
0x32, 0x0a, 0x14, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73,
|
||||||
0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65,
|
0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x66,
|
||||||
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f,
|
0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x49, 0x73, 0x45, 0x6d,
|
||||||
0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76,
|
0x70, 0x74, 0x79, 0x22, 0x97, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65,
|
||||||
0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75,
|
||||||
0x46, 0x6c, 0x6f, 0x77, 0x12, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
|
0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49,
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
|
0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65,
|
||||||
0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76,
|
0x64, 0x49, 0x70, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x69, 0x64, 0x65, 0x72, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42,
|
0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64,
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
|
0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x49, 0x0a,
|
||||||
0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
|
0x09, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73,
|
||||||
0x69, 0x67, 0x22, 0x16, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a,
|
0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a,
|
||||||
0x0a, 0x06, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x4b,
|
0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73,
|
||||||
0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46,
|
0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73,
|
||||||
0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5b, 0x0a, 0x15, 0x50, 0x4b,
|
0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69,
|
||||||
0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46,
|
0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46,
|
||||||
0x6c, 0x6f, 0x77, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43,
|
0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44,
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61,
|
0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69,
|
||||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
|
0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64,
|
||||||
0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
|
0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xea, 0x02, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76,
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68,
|
||||||
0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c,
|
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72,
|
||||||
0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c,
|
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
|
||||||
0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
|
||||||
0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c,
|
0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f,
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f,
|
||||||
0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61,
|
0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f,
|
||||||
0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04,
|
0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e,
|
0x12, 0x0a, 0x0a, 0x06, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x1e, 0x0a, 0x1c,
|
||||||
0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70,
|
0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
|
||||||
0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69,
|
0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5b, 0x0a, 0x15,
|
||||||
0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24,
|
0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
|
||||||
0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,
|
0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
|
||||||
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70,
|
0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||||
0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20,
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x55, 0x73,
|
0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69,
|
||||||
0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a,
|
0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xea, 0x02, 0x0a, 0x0e, 0x50, 0x72,
|
||||||
0x55, 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x15, 0x41, 0x75,
|
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08,
|
||||||
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f,
|
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
|
||||||
0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f,
|
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65,
|
||||||
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
|
0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
|
||||||
0x12, 0x22, 0x0a, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73,
|
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06,
|
||||||
0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
|
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f,
|
||||||
0x55, 0x52, 0x4c, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e,
|
0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65,
|
||||||
0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18,
|
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65,
|
||||||
0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x12, 0x2e, 0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e,
|
||||||
0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77,
|
0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65,
|
||||||
0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e,
|
0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
|
||||||
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65,
|
0x12, 0x24, 0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e,
|
||||||
0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16,
|
0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e,
|
||||||
0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06,
|
0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18,
|
||||||
0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65,
|
0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a,
|
||||||
0x72, 0x61, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71,
|
0x55, 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08,
|
||||||
0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18,
|
0x52, 0x0a, 0x55, 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x15,
|
||||||
0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x22, 0xb4, 0x01, 0x0a,
|
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64,
|
||||||
0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65,
|
0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x41, 0x75, 0x74,
|
||||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69,
|
||||||
0x08, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
|
0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52,
|
||||||
0x12, 0x47, 0x0a, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72,
|
0x4c, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65,
|
||||||
0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e,
|
0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65,
|
||||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76,
|
0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44,
|
||||||
0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72,
|
0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x43, 0x75, 0x73,
|
0x09, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65,
|
||||||
0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16,
|
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74,
|
0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04,
|
||||||
0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f,
|
0x50, 0x65, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72,
|
||||||
0x6e, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e,
|
0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03,
|
||||||
0x65, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x52, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71,
|
||||||
0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x32, 0x0a, 0x07, 0x52, 0x65, 0x63,
|
0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61,
|
||||||
0x6f, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e,
|
0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49,
|
||||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65,
|
0x44, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x22, 0xb4,
|
||||||
0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x74, 0x0a,
|
0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d,
|
||||||
0x0c, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a,
|
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20,
|
||||||
0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d,
|
0x01, 0x28, 0x08, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62,
|
||||||
0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
|
0x6c, 0x65, 0x12, 0x47, 0x0a, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||||
0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x03,
|
0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54,
|
|
||||||
0x54, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a,
|
|
||||||
0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x44,
|
|
||||||
0x61, 0x74, 0x61, 0x22, 0xb3, 0x01, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76,
|
|
||||||
0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53,
|
|
||||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d,
|
|
||||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65,
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65,
|
||||||
0x72, 0x76, 0x65, 0x72, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53,
|
||||||
0x73, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01,
|
0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x43,
|
||||||
0x28, 0x08, 0x52, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x44,
|
0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
|
||||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f,
|
0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x75,
|
||||||
0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44,
|
0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d,
|
||||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20,
|
0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a,
|
||||||
0x01, 0x28, 0x08, 0x52, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
0x6f, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20,
|
||||||
0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d,
|
0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x32, 0x0a, 0x07, 0x52,
|
||||||
0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20,
|
0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70,
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65,
|
||||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12,
|
0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22,
|
||||||
0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50,
|
0x74, 0x0a, 0x0c, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12,
|
||||||
0x6f, 0x72, 0x74, 0x22, 0xf0, 0x02, 0x0a, 0x0c, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c,
|
0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e,
|
||||||
0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01,
|
0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09,
|
0x03, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73,
|
||||||
0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a,
|
||||||
0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72,
|
0x03, 0x54, 0x54, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12,
|
||||||
0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
|
0x14, 0x0a, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
|
||||||
0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37,
|
0x52, 0x44, 0x61, 0x74, 0x61, 0x22, 0xb3, 0x01, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65,
|
||||||
0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f,
|
0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d,
|
||||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65,
|
0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16,
|
||||||
0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65,
|
||||||
0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f,
|
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76,
|
||||||
0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02,
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52,
|
0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a,
|
||||||
0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72,
|
0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07,
|
||||||
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05,
|
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69,
|
0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18,
|
||||||
0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12,
|
0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d,
|
||||||
0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69,
|
0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x4e,
|
||||||
0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08,
|
0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18,
|
||||||
0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74,
|
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54,
|
||||||
0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
|
0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70,
|
||||||
0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43,
|
0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||||
0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04,
|
0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x8a, 0x03, 0x0a, 0x0c, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61,
|
||||||
0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x22, 0x38, 0x0a, 0x0e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
|
0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50,
|
||||||
0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x65, 0x74, 0x49,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x12, 0x40,
|
||||||
0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x65, 0x74, 0x49, 0x50, 0x12, 0x10,
|
0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x63,
|
0x0e, 0x32, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46,
|
||||||
0x32, 0xd1, 0x03, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53,
|
0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65,
|
||||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12,
|
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
|
0x12, 0x37, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e,
|
||||||
|
0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69,
|
||||||
|
0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f,
|
||||||
|
0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x08, 0x50, 0x72, 0x6f,
|
||||||
|
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x61,
|
||||||
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c,
|
||||||
|
0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08,
|
||||||
|
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74,
|
||||||
|
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x18, 0x0a, 0x07,
|
||||||
|
0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x36, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x50,
|
||||||
|
0x65, 0x65, 0x72, 0x49, 0x50, 0x36, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
|
||||||
|
0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f,
|
||||||
|
0x55, 0x54, 0x10, 0x01, 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a,
|
||||||
|
0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52,
|
||||||
|
0x4f, 0x50, 0x10, 0x01, 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
|
||||||
|
0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a,
|
||||||
|
0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12,
|
||||||
|
0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50,
|
||||||
|
0x10, 0x04, 0x22, 0x38, 0x0a, 0x0e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x64, 0x64,
|
||||||
|
0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x65, 0x74, 0x49, 0x50, 0x18, 0x01, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x65, 0x74, 0x49, 0x50, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61,
|
||||||
|
0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x63, 0x32, 0xd1, 0x03, 0x0a,
|
||||||
|
0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69,
|
||||||
|
0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61,
|
||||||
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
|
||||||
|
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||||
|
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
|
||||||
|
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e,
|
||||||
|
0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
|
||||||
|
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a,
|
||||||
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63,
|
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63,
|
||||||
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e,
|
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30,
|
||||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
|
0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65,
|
||||||
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a,
|
0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
|
||||||
0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74,
|
||||||
0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||||
0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76,
|
0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||||
0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65,
|
||||||
0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52,
|
0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48,
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d,
|
||||||
0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a,
|
|
||||||
0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f,
|
|
||||||
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d,
|
|
||||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
|
|
||||||
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e,
|
|
||||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
|
||||||
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65,
|
|
||||||
0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69,
|
|
||||||
0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
||||||
0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x4b, 0x43,
|
||||||
0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c,
|
||||||
0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,
|
0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||||
|
0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e,
|
||||||
|
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00,
|
||||||
|
0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||||
|
0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -117,6 +117,7 @@ message PeerSystemMeta {
|
|||||||
string sysProductName = 13;
|
string sysProductName = 13;
|
||||||
string sysManufacturer = 14;
|
string sysManufacturer = 14;
|
||||||
Environment environment = 15;
|
Environment environment = 15;
|
||||||
|
bool ipv6Supported = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LoginResponse {
|
message LoginResponse {
|
||||||
@ -182,6 +183,8 @@ message PeerConfig {
|
|||||||
SSHConfig sshConfig = 3;
|
SSHConfig sshConfig = 3;
|
||||||
// Peer fully qualified domain name
|
// Peer fully qualified domain name
|
||||||
string fqdn = 4;
|
string fqdn = 4;
|
||||||
|
// Peer's virtual IPv6 address within the Wiretrustee VPN (a Wireguard address config)
|
||||||
|
string address6 = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections
|
// NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections
|
||||||
@ -349,6 +352,7 @@ message FirewallRule {
|
|||||||
action Action = 3;
|
action Action = 3;
|
||||||
protocol Protocol = 4;
|
protocol Protocol = 4;
|
||||||
string Port = 5;
|
string Port = 5;
|
||||||
|
string PeerIP6 = 6;
|
||||||
|
|
||||||
enum direction {
|
enum direction {
|
||||||
IN = 0;
|
IN = 0;
|
||||||
|
@ -280,7 +280,8 @@ func (a *Account) getRoutesToSync(peerID string, aclPeers []*nbpeer.Peer) []*rou
|
|||||||
groupListMap := a.getPeerGroups(peerID)
|
groupListMap := a.getPeerGroups(peerID)
|
||||||
for _, peer := range aclPeers {
|
for _, peer := range aclPeers {
|
||||||
activeRoutes, _ := a.getRoutingPeerRoutes(peer.ID)
|
activeRoutes, _ := a.getRoutingPeerRoutes(peer.ID)
|
||||||
groupFilteredRoutes := a.filterRoutesByGroups(activeRoutes, groupListMap)
|
addressFamilyFilteredRoutes := a.filterRoutesByIPv6Enabled(activeRoutes, a.GetPeer(peerID).IP6 != nil && peer.IP6 != nil)
|
||||||
|
groupFilteredRoutes := a.filterRoutesByGroups(addressFamilyFilteredRoutes, groupListMap)
|
||||||
filteredRoutes := a.filterRoutesFromPeersOfSameHAGroup(groupFilteredRoutes, peerRoutesMembership)
|
filteredRoutes := a.filterRoutesFromPeersOfSameHAGroup(groupFilteredRoutes, peerRoutesMembership)
|
||||||
routes = append(routes, filteredRoutes...)
|
routes = append(routes, filteredRoutes...)
|
||||||
}
|
}
|
||||||
@ -315,6 +316,20 @@ func (a *Account) filterRoutesByGroups(routes []*route.Route, groupListMap looku
|
|||||||
return filteredRoutes
|
return filteredRoutes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filters out IPv6 routes if the peer does not support them.
|
||||||
|
func (a *Account) filterRoutesByIPv6Enabled(routes []*route.Route, v6Supported bool) []*route.Route {
|
||||||
|
if v6Supported {
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
var filteredRoutes []*route.Route
|
||||||
|
for _, rt := range routes {
|
||||||
|
if rt.Network.Addr().Is4() {
|
||||||
|
filteredRoutes = append(filteredRoutes, rt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredRoutes
|
||||||
|
}
|
||||||
|
|
||||||
// getRoutingPeerRoutes returns the enabled and disabled lists of routes that the given routing peer serves
|
// getRoutingPeerRoutes returns the enabled and disabled lists of routes that the given routing peer serves
|
||||||
// Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
|
// Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
|
||||||
// If the given is not a routing peer, then the lists are empty.
|
// If the given is not a routing peer, then the lists are empty.
|
||||||
@ -348,6 +363,10 @@ func (a *Account) getRoutingPeerRoutes(peerID string) (enabledRoutes []*route.Ro
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range a.Routes {
|
for _, r := range a.Routes {
|
||||||
|
// Skip IPv6 routes if IPv6 is currently not enabled.
|
||||||
|
if peer.IP6 == nil && r.NetworkType == route.IPv6Network {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for _, groupID := range r.PeerGroups {
|
for _, groupID := range r.PeerGroups {
|
||||||
group := a.GetGroup(groupID)
|
group := a.GetGroup(groupID)
|
||||||
if group == nil {
|
if group == nil {
|
||||||
@ -429,7 +448,7 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string, validatedPeersMap
|
|||||||
|
|
||||||
if dnsManagementStatus {
|
if dnsManagementStatus {
|
||||||
var zones []nbdns.CustomZone
|
var zones []nbdns.CustomZone
|
||||||
peersCustomZone := getPeersCustomZone(a, dnsDomain)
|
peersCustomZone := getPeersCustomZone(a, dnsDomain, peer.IP6 != nil)
|
||||||
if peersCustomZone.Domain != "" {
|
if peersCustomZone.Domain != "" {
|
||||||
zones = append(zones, peersCustomZone)
|
zones = append(zones, peersCustomZone)
|
||||||
}
|
}
|
||||||
@ -665,6 +684,17 @@ func (a *Account) getTakenIPs() []net.IP {
|
|||||||
return takenIps
|
return takenIps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Account) getTakenIP6s() []net.IP {
|
||||||
|
var takenIps []net.IP
|
||||||
|
for _, existingPeer := range a.Peers {
|
||||||
|
if existingPeer.IP6 != nil {
|
||||||
|
takenIps = append(takenIps, *existingPeer.IP6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return takenIps
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Account) getPeerDNSLabels() lookupMap {
|
func (a *Account) getPeerDNSLabels() lookupMap {
|
||||||
existingLabels := make(lookupMap)
|
existingLabels := make(lookupMap)
|
||||||
for _, peer := range a.Peers {
|
for _, peer := range a.Peers {
|
||||||
@ -1006,7 +1036,6 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
updatedAccount := account.UpdateSettings(newSettings)
|
updatedAccount := account.UpdateSettings(newSettings)
|
||||||
|
|
||||||
err = am.Store.SaveAccount(account)
|
err = am.Store.SaveAccount(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -2015,7 +2044,7 @@ func addAllGroup(account *Account) error {
|
|||||||
func newAccountWithId(accountID, userID, domain string) *Account {
|
func newAccountWithId(accountID, userID, domain string) *Account {
|
||||||
log.Debugf("creating new account")
|
log.Debugf("creating new account")
|
||||||
|
|
||||||
network := NewNetwork()
|
network := NewNetwork(true)
|
||||||
peers := make(map[string]*nbpeer.Peer)
|
peers := make(map[string]*nbpeer.Peer)
|
||||||
users := make(map[string]*User)
|
users := make(map[string]*User)
|
||||||
routes := make(map[route.ID]*route.Route)
|
routes := make(map[route.ID]*route.Route)
|
||||||
|
@ -69,14 +69,15 @@ func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Ac
|
|||||||
Key: "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=",
|
Key: "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=",
|
||||||
Name: "test-host@netbird.io",
|
Name: "test-host@netbird.io",
|
||||||
Meta: nbpeer.PeerSystemMeta{
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
Hostname: "test-host@netbird.io",
|
Hostname: "test-host@netbird.io",
|
||||||
GoOS: "linux",
|
GoOS: "linux",
|
||||||
Kernel: "Linux",
|
Kernel: "Linux",
|
||||||
Core: "21.04",
|
Core: "21.04",
|
||||||
Platform: "x86_64",
|
Platform: "x86_64",
|
||||||
OS: "Ubuntu",
|
OS: "Ubuntu",
|
||||||
WtVersion: "development",
|
WtVersion: "development",
|
||||||
UIVersion: "development",
|
UIVersion: "development",
|
||||||
|
Ipv6Supported: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -964,78 +965,136 @@ func TestAccountManager_DeleteAccount(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountManager_AddPeer(t *testing.T) {
|
func TestAccountManager_AddPeer(t *testing.T) {
|
||||||
manager, err := createManager(t)
|
|
||||||
if err != nil {
|
testCases := []struct {
|
||||||
t.Fatal(err)
|
name string
|
||||||
return
|
peerIPv6Supported bool
|
||||||
|
allGroupIPv6Enabled bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Peer and Group IPv6 enabled",
|
||||||
|
peerIPv6Supported: true,
|
||||||
|
allGroupIPv6Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer IPv6 enabled, Group IPv6 disabled",
|
||||||
|
peerIPv6Supported: true,
|
||||||
|
allGroupIPv6Enabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer IPv6 disabled, Group IPv6 enabled",
|
||||||
|
peerIPv6Supported: false,
|
||||||
|
allGroupIPv6Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer and Group IPv6 disabled",
|
||||||
|
peerIPv6Supported: false,
|
||||||
|
allGroupIPv6Enabled: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
userID := "testingUser"
|
for _, c := range testCases {
|
||||||
account, err := createAccount(manager, "test_account", userID, "netbird.cloud")
|
t.Run(c.name, func(t *testing.T) {
|
||||||
if err != nil {
|
manager, err := createManager(t)
|
||||||
t.Fatal(err)
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := "testingUser"
|
||||||
|
account, err := createAccount(manager, "test_account", userID, "netbird.cloud")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serial := account.Network.CurrentSerial() // should be 0
|
||||||
|
|
||||||
|
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error creating setup key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.Network.Serial != 0 {
|
||||||
|
t.Errorf("expecting account network to have an initial Serial=0")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err = manager.Store.GetAccount(account.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.allGroupIPv6Enabled {
|
||||||
|
unlock := manager.Store.AcquireAccountWriteLock(account.Id)
|
||||||
|
groupAll, err := account.GetGroupAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
groupAll.IPv6Enabled = true
|
||||||
|
err = manager.Store.SaveAccount(account)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expectedPeerKey := key.PublicKey().String()
|
||||||
|
expectedSetupKey := setupKey.Key
|
||||||
|
|
||||||
|
peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
|
||||||
|
Key: expectedPeerKey,
|
||||||
|
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey, Ipv6Supported: c.peerIPv6Supported},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expecting peer to be added, got failure %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err = manager.Store.GetAccount(account.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if peer.Key != expectedPeerKey {
|
||||||
|
t.Errorf("expecting just added peer to have key = %s, got %s", expectedPeerKey, peer.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !account.Network.Net.Contains(peer.IP) {
|
||||||
|
t.Errorf("expecting just added peer's IP %s to be in a network range %s", peer.IP.String(), account.Network.Net.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if peer.SetupKey != expectedSetupKey {
|
||||||
|
t.Errorf("expecting just added peer to have SetupKey = %s, got %s", expectedSetupKey, peer.SetupKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.Network.CurrentSerial() != 1 {
|
||||||
|
t.Errorf("expecting Network Serial=%d to be incremented by 1 and be equal to %d when adding new peer to account", serial, account.Network.CurrentSerial())
|
||||||
|
}
|
||||||
|
ev := getEvent(t, account.Id, manager, activity.PeerAddedWithSetupKey)
|
||||||
|
|
||||||
|
assert.NotNil(t, ev)
|
||||||
|
assert.Equal(t, account.Id, ev.AccountID)
|
||||||
|
assert.Equal(t, peer.Name, ev.Meta["name"])
|
||||||
|
assert.Equal(t, peer.FQDN(account.Domain), ev.Meta["fqdn"])
|
||||||
|
assert.Equal(t, setupKey.Id, ev.InitiatorID)
|
||||||
|
assert.Equal(t, peer.ID, ev.TargetID)
|
||||||
|
assert.Equal(t, peer.IP.String(), fmt.Sprint(ev.Meta["ip"]))
|
||||||
|
assert.Equal(t, peer.V6Setting, nbpeer.V6Auto)
|
||||||
|
if c.peerIPv6Supported && c.allGroupIPv6Enabled {
|
||||||
|
assert.NotNil(t, peer.IP6)
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, peer.IP6)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
serial := account.Network.CurrentSerial() // should be 0
|
|
||||||
|
|
||||||
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID, false)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("error creating setup key")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if account.Network.Serial != 0 {
|
|
||||||
t.Errorf("expecting account network to have an initial Serial=0")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := wgtypes.GeneratePrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
expectedPeerKey := key.PublicKey().String()
|
|
||||||
expectedSetupKey := setupKey.Key
|
|
||||||
|
|
||||||
peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
|
|
||||||
Key: expectedPeerKey,
|
|
||||||
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("expecting peer to be added, got failure %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
account, err = manager.Store.GetAccount(account.Id)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if peer.Key != expectedPeerKey {
|
|
||||||
t.Errorf("expecting just added peer to have key = %s, got %s", expectedPeerKey, peer.Key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !account.Network.Net.Contains(peer.IP) {
|
|
||||||
t.Errorf("expecting just added peer's IP %s to be in a network range %s", peer.IP.String(), account.Network.Net.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if peer.SetupKey != expectedSetupKey {
|
|
||||||
t.Errorf("expecting just added peer to have SetupKey = %s, got %s", expectedSetupKey, peer.SetupKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if account.Network.CurrentSerial() != 1 {
|
|
||||||
t.Errorf("expecting Network Serial=%d to be incremented by 1 and be equal to %d when adding new peer to account", serial, account.Network.CurrentSerial())
|
|
||||||
}
|
|
||||||
ev := getEvent(t, account.Id, manager, activity.PeerAddedWithSetupKey)
|
|
||||||
|
|
||||||
assert.NotNil(t, ev)
|
|
||||||
assert.Equal(t, account.Id, ev.AccountID)
|
|
||||||
assert.Equal(t, peer.Name, ev.Meta["name"])
|
|
||||||
assert.Equal(t, peer.FQDN(account.Domain), ev.Meta["fqdn"])
|
|
||||||
assert.Equal(t, setupKey.Id, ev.InitiatorID)
|
|
||||||
assert.Equal(t, peer.ID, ev.TargetID)
|
|
||||||
assert.Equal(t, peer.IP.String(), fmt.Sprint(ev.Meta["ip"]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountManager_AddPeerWithUserID(t *testing.T) {
|
func TestAccountManager_AddPeerWithUserID(t *testing.T) {
|
||||||
@ -1455,9 +1514,22 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
_, prefix3, err := route.ParseNetwork("2001:db8:1234:5678::/64")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, prefix4, err := route.ParseNetwork("2001:db8:1234:6789::/64")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
peer2IP6 := net.ParseIP("2001:db8:abcd:1234::12")
|
||||||
account := &Account{
|
account := &Account{
|
||||||
Peers: map[string]*nbpeer.Peer{
|
Peers: map[string]*nbpeer.Peer{
|
||||||
"peer-1": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}},
|
"peer-1": {
|
||||||
|
ID: "peer-1", Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"},
|
||||||
|
},
|
||||||
|
"peer-2": {ID: "peer-2", Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux", Ipv6Supported: true}, IP6: &peer2IP6},
|
||||||
|
"peer-3": {ID: "peer-3", Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}},
|
||||||
},
|
},
|
||||||
Groups: map[string]*group.Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}},
|
Groups: map[string]*group.Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}},
|
||||||
Routes: map[route.ID]*route.Route{
|
Routes: map[route.ID]*route.Route{
|
||||||
@ -1467,7 +1539,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
|||||||
NetID: "network-1",
|
NetID: "network-1",
|
||||||
Description: "network-1",
|
Description: "network-1",
|
||||||
Peer: "peer-1",
|
Peer: "peer-1",
|
||||||
NetworkType: 0,
|
NetworkType: route.IPv4Network,
|
||||||
Masquerade: false,
|
Masquerade: false,
|
||||||
Metric: 999,
|
Metric: 999,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
@ -1479,7 +1551,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
|||||||
NetID: "network-2",
|
NetID: "network-2",
|
||||||
Description: "network-2",
|
Description: "network-2",
|
||||||
Peer: "peer-2",
|
Peer: "peer-2",
|
||||||
NetworkType: 0,
|
NetworkType: route.IPv4Network,
|
||||||
Masquerade: false,
|
Masquerade: false,
|
||||||
Metric: 999,
|
Metric: 999,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
@ -1491,7 +1563,31 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
|||||||
NetID: "network-1",
|
NetID: "network-1",
|
||||||
Description: "network-1",
|
Description: "network-1",
|
||||||
Peer: "peer-2",
|
Peer: "peer-2",
|
||||||
NetworkType: 0,
|
NetworkType: route.IPv4Network,
|
||||||
|
Masquerade: false,
|
||||||
|
Metric: 999,
|
||||||
|
Enabled: true,
|
||||||
|
Groups: []string{"group1"},
|
||||||
|
},
|
||||||
|
"route-4": {
|
||||||
|
ID: "route-4",
|
||||||
|
Network: prefix3,
|
||||||
|
NetID: "network-3",
|
||||||
|
Description: "network-3",
|
||||||
|
Peer: "peer-1",
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Masquerade: false,
|
||||||
|
Metric: 999,
|
||||||
|
Enabled: true,
|
||||||
|
Groups: []string{"group1"},
|
||||||
|
},
|
||||||
|
"route-5": {
|
||||||
|
ID: "route-5",
|
||||||
|
Network: prefix4,
|
||||||
|
NetID: "network-4",
|
||||||
|
Description: "network-4",
|
||||||
|
Peer: "peer-2",
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
Masquerade: false,
|
Masquerade: false,
|
||||||
Metric: 999,
|
Metric: 999,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
@ -1500,17 +1596,29 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
routes := account.getRoutesToSync("peer-2", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-3"}})
|
routes := account.getRoutesToSync("peer-1", []*nbpeer.Peer{{ID: "peer-2", Key: "peer-2"}, {ID: "peer-3", Key: "peer-3"}})
|
||||||
|
|
||||||
assert.Len(t, routes, 2)
|
assert.Len(t, routes, 2)
|
||||||
routeIDs := make(map[route.ID]struct{}, 2)
|
routeIDs := make(map[route.ID]struct{}, 2)
|
||||||
for _, r := range routes {
|
for _, r := range routes {
|
||||||
routeIDs[r.ID] = struct{}{}
|
routeIDs[r.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
|
assert.Contains(t, routeIDs, route.ID("route-1"))
|
||||||
|
assert.Contains(t, routeIDs, route.ID("route-2"))
|
||||||
|
|
||||||
|
routes = account.getRoutesToSync("peer-2", []*nbpeer.Peer{{ID: "peer-1", Key: "peer-1"}, {ID: "peer-3", Key: "peer-3"}})
|
||||||
|
|
||||||
|
assert.Len(t, routes, 3)
|
||||||
|
|
||||||
|
routeIDs = make(map[route.ID]struct{}, 2)
|
||||||
|
for _, r := range routes {
|
||||||
|
routeIDs[r.ID] = struct{}{}
|
||||||
|
}
|
||||||
assert.Contains(t, routeIDs, route.ID("route-2"))
|
assert.Contains(t, routeIDs, route.ID("route-2"))
|
||||||
assert.Contains(t, routeIDs, route.ID("route-3"))
|
assert.Contains(t, routeIDs, route.ID("route-3"))
|
||||||
|
assert.Contains(t, routeIDs, route.ID("route-5"))
|
||||||
|
|
||||||
emptyRoutes := account.getRoutesToSync("peer-3", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-2"}})
|
emptyRoutes := account.getRoutesToSync("peer-3", []*nbpeer.Peer{{ID: "peer-1", Key: "peer-1"}, {ID: "peer-2", Key: "peer-2"}})
|
||||||
|
|
||||||
assert.Len(t, emptyRoutes, 0)
|
assert.Len(t, emptyRoutes, 0)
|
||||||
}
|
}
|
||||||
|
@ -139,6 +139,14 @@ const (
|
|||||||
PostureCheckUpdated Activity = 61
|
PostureCheckUpdated Activity = 61
|
||||||
// PostureCheckDeleted indicates that the user deleted a posture check
|
// PostureCheckDeleted indicates that the user deleted a posture check
|
||||||
PostureCheckDeleted Activity = 62
|
PostureCheckDeleted Activity = 62
|
||||||
|
// PeerIPv6Enabled indicates that a user enabled IPv6 for a peer
|
||||||
|
PeerIPv6Enabled Activity = 63
|
||||||
|
// PeerIPv6Disabled indicates that a user disabled IPv6 for a peer
|
||||||
|
PeerIPv6Disabled Activity = 64
|
||||||
|
// PeerIPv6InheritEnabled indicates that IPv6 was enabled for a peer due to a change in group memberships.
|
||||||
|
PeerIPv6InheritEnabled Activity = 65
|
||||||
|
// PeerIPv6InheritDisabled indicates that IPv6 was disabled for a peer due to a change in group memberships.
|
||||||
|
PeerIPv6InheritDisabled Activity = 66
|
||||||
)
|
)
|
||||||
|
|
||||||
var activityMap = map[Activity]Code{
|
var activityMap = map[Activity]Code{
|
||||||
@ -205,6 +213,10 @@ var activityMap = map[Activity]Code{
|
|||||||
PostureCheckCreated: {"Posture check created", "posture.check.created"},
|
PostureCheckCreated: {"Posture check created", "posture.check.created"},
|
||||||
PostureCheckUpdated: {"Posture check updated", "posture.check.updated"},
|
PostureCheckUpdated: {"Posture check updated", "posture.check.updated"},
|
||||||
PostureCheckDeleted: {"Posture check deleted", "posture.check.deleted"},
|
PostureCheckDeleted: {"Posture check deleted", "posture.check.deleted"},
|
||||||
|
PeerIPv6Enabled: {"Peer IPv6 enabled by user", "peer.ipv6.manual_enable"},
|
||||||
|
PeerIPv6Disabled: {"Peer IPv6 disabled by user", "peer.ipv6.manual_disable"},
|
||||||
|
PeerIPv6InheritDisabled: {"Peer IPv6 disabled due to change in group settings or membership", "peer.ipv6.inherit_disable"},
|
||||||
|
PeerIPv6InheritEnabled: {"Peer IPv6 enabled due to change in group settings or membership", "peer.ipv6.inherit_enable"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringCode returns a string code of the activity
|
// StringCode returns a string code of the activity
|
||||||
|
@ -149,7 +149,7 @@ func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig {
|
|||||||
return protoUpdate
|
return protoUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone {
|
func getPeersCustomZone(account *Account, dnsDomain string, enableIPv6 bool) nbdns.CustomZone {
|
||||||
if dnsDomain == "" {
|
if dnsDomain == "" {
|
||||||
log.Errorf("no dns domain is set, returning empty zone")
|
log.Errorf("no dns domain is set, returning empty zone")
|
||||||
return nbdns.CustomZone{}
|
return nbdns.CustomZone{}
|
||||||
@ -172,6 +172,16 @@ func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone {
|
|||||||
TTL: defaultTTL,
|
TTL: defaultTTL,
|
||||||
RData: peer.IP.String(),
|
RData: peer.IP.String(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if peer.IP6 != nil && enableIPv6 {
|
||||||
|
customZone.Records = append(customZone.Records, nbdns.SimpleRecord{
|
||||||
|
Name: dns.Fqdn(peer.DNSLabel + "." + dnsDomain),
|
||||||
|
Type: int(dns.TypeAAAA),
|
||||||
|
Class: nbdns.DefaultClass,
|
||||||
|
TTL: defaultTTL,
|
||||||
|
RData: peer.IP6.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return customZone
|
return customZone
|
||||||
@ -179,6 +189,7 @@ func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone {
|
|||||||
|
|
||||||
func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup {
|
func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup {
|
||||||
groupList := account.getPeerGroups(peerID)
|
groupList := account.getPeerGroups(peerID)
|
||||||
|
peer := account.GetPeer(peerID)
|
||||||
|
|
||||||
var peerNSGroups []*nbdns.NameServerGroup
|
var peerNSGroups []*nbdns.NameServerGroup
|
||||||
|
|
||||||
@ -189,8 +200,18 @@ func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup {
|
|||||||
for _, gID := range nsGroup.Groups {
|
for _, gID := range nsGroup.Groups {
|
||||||
_, found := groupList[gID]
|
_, found := groupList[gID]
|
||||||
if found {
|
if found {
|
||||||
if !peerIsNameserver(account.GetPeer(peerID), nsGroup) {
|
if !peerIsNameserver(peer, nsGroup) {
|
||||||
peerNSGroups = append(peerNSGroups, nsGroup.Copy())
|
filteredNsGroup := nsGroup.Copy()
|
||||||
|
var newNameserverList []nbdns.NameServer
|
||||||
|
for _, nameserver := range filteredNsGroup.NameServers {
|
||||||
|
if nameserver.IP.Is4() || peer.IP6 != nil {
|
||||||
|
newNameserverList = append(newNameserverList, nameserver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(newNameserverList) > 0 {
|
||||||
|
filteredNsGroup.NameServers = newNameserverList
|
||||||
|
peerNSGroups = append(peerNSGroups, filteredNsGroup)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ const (
|
|||||||
dnsAdminUserID = "testingAdminUser"
|
dnsAdminUserID = "testingAdminUser"
|
||||||
dnsRegularUserID = "testingRegularUser"
|
dnsRegularUserID = "testingRegularUser"
|
||||||
dnsNSGroup1 = "ns1"
|
dnsNSGroup1 = "ns1"
|
||||||
|
dnsNSGroup2 = "ns2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetDNSSettings(t *testing.T) {
|
func TestGetDNSSettings(t *testing.T) {
|
||||||
@ -184,7 +185,7 @@ func TestGetNetworkMap_DNSConfigSync(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, peer2AccountDNSConfig.DNSConfig.CustomZones, 1, "DNS config should have one custom zone for peers not in the disabled group")
|
require.Len(t, peer2AccountDNSConfig.DNSConfig.CustomZones, 1, "DNS config should have one custom zone for peers not in the disabled group")
|
||||||
require.True(t, peer2AccountDNSConfig.DNSConfig.ServiceEnable, "DNS config should have DNS service enabled for peers not in the disabled group")
|
require.True(t, peer2AccountDNSConfig.DNSConfig.ServiceEnable, "DNS config should have DNS service enabled for peers not in the disabled group")
|
||||||
require.Len(t, peer2AccountDNSConfig.DNSConfig.NameServerGroups, 1, "updated DNS config should have 1 nameserver groups since peer 2 is part of the group All")
|
require.Len(t, peer2AccountDNSConfig.DNSConfig.NameServerGroups, 2, "updated DNS config should have 2 nameserver groups since peer 2 is part of the group All and supports IPv6")
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||||
@ -215,14 +216,15 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
|
|||||||
Key: dnsPeer1Key,
|
Key: dnsPeer1Key,
|
||||||
Name: "test-host1@netbird.io",
|
Name: "test-host1@netbird.io",
|
||||||
Meta: nbpeer.PeerSystemMeta{
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
Hostname: "test-host1@netbird.io",
|
Hostname: "test-host1@netbird.io",
|
||||||
GoOS: "linux",
|
GoOS: "linux",
|
||||||
Kernel: "Linux",
|
Kernel: "Linux",
|
||||||
Core: "21.04",
|
Core: "21.04",
|
||||||
Platform: "x86_64",
|
Platform: "x86_64",
|
||||||
OS: "Ubuntu",
|
OS: "Ubuntu",
|
||||||
WtVersion: "development",
|
WtVersion: "development",
|
||||||
UIVersion: "development",
|
UIVersion: "development",
|
||||||
|
Ipv6Supported: false,
|
||||||
},
|
},
|
||||||
DNSLabel: dnsPeer1Key,
|
DNSLabel: dnsPeer1Key,
|
||||||
}
|
}
|
||||||
@ -230,16 +232,18 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
|
|||||||
Key: dnsPeer2Key,
|
Key: dnsPeer2Key,
|
||||||
Name: "test-host2@netbird.io",
|
Name: "test-host2@netbird.io",
|
||||||
Meta: nbpeer.PeerSystemMeta{
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
Hostname: "test-host2@netbird.io",
|
Hostname: "test-host2@netbird.io",
|
||||||
GoOS: "linux",
|
GoOS: "linux",
|
||||||
Kernel: "Linux",
|
Kernel: "Linux",
|
||||||
Core: "21.04",
|
Core: "21.04",
|
||||||
Platform: "x86_64",
|
Platform: "x86_64",
|
||||||
OS: "Ubuntu",
|
OS: "Ubuntu",
|
||||||
WtVersion: "development",
|
WtVersion: "development",
|
||||||
UIVersion: "development",
|
UIVersion: "development",
|
||||||
|
Ipv6Supported: true,
|
||||||
},
|
},
|
||||||
DNSLabel: dnsPeer2Key,
|
V6Setting: nbpeer.V6Enabled,
|
||||||
|
DNSLabel: dnsPeer2Key,
|
||||||
}
|
}
|
||||||
|
|
||||||
domain := "example.com"
|
domain := "example.com"
|
||||||
@ -312,6 +316,20 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
|
|||||||
Groups: []string{allGroup.ID},
|
Groups: []string{allGroup.ID},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
account.NameServerGroups[dnsNSGroup2] = &dns.NameServerGroup{
|
||||||
|
ID: dnsNSGroup2,
|
||||||
|
Name: "ns-group-2",
|
||||||
|
NameServers: []dns.NameServer{{
|
||||||
|
IP: netip.MustParseAddr("2001:4860:4860:0:0:0:0:8888"), // Google DNS
|
||||||
|
NSType: dns.UDPNameServerType,
|
||||||
|
Port: dns.DefaultDNSPort,
|
||||||
|
}},
|
||||||
|
Primary: false,
|
||||||
|
Domains: []string{"example.com"},
|
||||||
|
Enabled: true,
|
||||||
|
Groups: []string{allGroup.ID},
|
||||||
|
}
|
||||||
|
|
||||||
err = am.Store.SaveAccount(account)
|
err = am.Store.SaveAccount(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -149,6 +149,35 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *n
|
|||||||
oldGroup, exists := account.Groups[newGroup.ID]
|
oldGroup, exists := account.Groups[newGroup.ID]
|
||||||
account.Groups[newGroup.ID] = newGroup
|
account.Groups[newGroup.ID] = newGroup
|
||||||
|
|
||||||
|
// Determine peer difference for group.
|
||||||
|
addedPeers := make([]string, 0)
|
||||||
|
removedPeers := make([]string, 0)
|
||||||
|
if exists {
|
||||||
|
addedPeers = difference(newGroup.Peers, oldGroup.Peers)
|
||||||
|
removedPeers = difference(oldGroup.Peers, newGroup.Peers)
|
||||||
|
} else {
|
||||||
|
addedPeers = append(addedPeers, newGroup.Peers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to check whether IPv6 status has changed for all potentially affected peers.
|
||||||
|
peersToUpdate := make([]string, 0)
|
||||||
|
// If group previously had IPv6 enabled, need to check all old peers for changes in IPv6 status.
|
||||||
|
if exists && oldGroup.IPv6Enabled {
|
||||||
|
peersToUpdate = removedPeers
|
||||||
|
}
|
||||||
|
// If group IPv6 status changed, need to check all current peers, if it did not, but IPv6 is enabled, only check
|
||||||
|
// added peers, otherwise check no peers (as group can not affect IPv6 state).
|
||||||
|
if exists && oldGroup.IPv6Enabled != newGroup.IPv6Enabled {
|
||||||
|
peersToUpdate = append(peersToUpdate, newGroup.Peers...)
|
||||||
|
} else if newGroup.IPv6Enabled {
|
||||||
|
peersToUpdate = append(peersToUpdate, addedPeers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = am.updatePeerIPv6Status(account, userID, newGroup, peersToUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
account.Network.IncSerial()
|
account.Network.IncSerial()
|
||||||
if err = am.Store.SaveAccount(account); err != nil {
|
if err = am.Store.SaveAccount(account); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -158,13 +187,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *n
|
|||||||
|
|
||||||
// the following snippet tracks the activity and stores the group events in the event store.
|
// the following snippet tracks the activity and stores the group events in the event store.
|
||||||
// It has to happen after all the operations have been successfully performed.
|
// It has to happen after all the operations have been successfully performed.
|
||||||
addedPeers := make([]string, 0)
|
if !exists {
|
||||||
removedPeers := make([]string, 0)
|
|
||||||
if exists {
|
|
||||||
addedPeers = difference(newGroup.Peers, oldGroup.Peers)
|
|
||||||
removedPeers = difference(oldGroup.Peers, newGroup.Peers)
|
|
||||||
} else {
|
|
||||||
addedPeers = append(addedPeers, newGroup.Peers...)
|
|
||||||
am.StoreEvent(userID, newGroup.ID, accountID, activity.GroupCreated, newGroup.EventMeta())
|
am.StoreEvent(userID, newGroup.ID, accountID, activity.GroupCreated, newGroup.EventMeta())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,6 +337,14 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string)
|
|||||||
|
|
||||||
delete(account.Groups, groupID)
|
delete(account.Groups, groupID)
|
||||||
|
|
||||||
|
// Update IPv6 status of all group members if necessary.
|
||||||
|
if g.IPv6Enabled {
|
||||||
|
_, err = am.updatePeerIPv6Status(account, userId, g, g.Peers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
account.Network.IncSerial()
|
account.Network.IncSerial()
|
||||||
if err = am.Store.SaveAccount(account); err != nil {
|
if err = am.Store.SaveAccount(account); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -345,6 +376,7 @@ func (am *DefaultAccountManager) ListGroups(accountID string) ([]*nbgroup.Group,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GroupAddPeer appends peer to the group
|
// GroupAddPeer appends peer to the group
|
||||||
|
// TODO Question for devs: Is this method dead code? I can't seem to find any usages outside of tests...
|
||||||
func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string) error {
|
func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string) error {
|
||||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
@ -368,6 +400,14 @@ func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string)
|
|||||||
}
|
}
|
||||||
if add {
|
if add {
|
||||||
group.Peers = append(group.Peers, peerID)
|
group.Peers = append(group.Peers, peerID)
|
||||||
|
|
||||||
|
if group.IPv6Enabled {
|
||||||
|
// Update IPv6 status of added group member.
|
||||||
|
_, err = am.updatePeerIPv6Status(account, "", group, []string{peerID})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
account.Network.IncSerial()
|
account.Network.IncSerial()
|
||||||
@ -381,6 +421,7 @@ func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GroupDeletePeer removes peer from the group
|
// GroupDeletePeer removes peer from the group
|
||||||
|
// TODO Question for devs: Same as above, this seems like dead code
|
||||||
func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID string) error {
|
func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID string) error {
|
||||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
@ -399,6 +440,15 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID stri
|
|||||||
for i, itemID := range group.Peers {
|
for i, itemID := range group.Peers {
|
||||||
if itemID == peerID {
|
if itemID == peerID {
|
||||||
group.Peers = append(group.Peers[:i], group.Peers[i+1:]...)
|
group.Peers = append(group.Peers[:i], group.Peers[i+1:]...)
|
||||||
|
|
||||||
|
if group.IPv6Enabled {
|
||||||
|
// Update IPv6 status of deleted group member.
|
||||||
|
_, err = am.updatePeerIPv6Status(account, "", group, []string{peerID})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := am.Store.SaveAccount(account); err != nil {
|
if err := am.Store.SaveAccount(account); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -409,3 +459,24 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID stri
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (am *DefaultAccountManager) updatePeerIPv6Status(account *Account, userID string, group *nbgroup.Group, peersToUpdate []string) (bool, error) {
|
||||||
|
updated := false
|
||||||
|
for _, peer := range peersToUpdate {
|
||||||
|
peerObj := account.GetPeer(peer)
|
||||||
|
update, err := am.DeterminePeerV6(account, peerObj)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if update {
|
||||||
|
updated = true
|
||||||
|
account.UpdatePeer(peerObj)
|
||||||
|
if peerObj.IP6 != nil {
|
||||||
|
am.StoreEvent(userID, group.ID, account.Id, activity.PeerIPv6InheritEnabled, group.EventMeta())
|
||||||
|
} else {
|
||||||
|
am.StoreEvent(userID, group.ID, account.Id, activity.PeerIPv6InheritDisabled, group.EventMeta())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updated, nil
|
||||||
|
}
|
||||||
|
@ -25,6 +25,8 @@ type Group struct {
|
|||||||
// Peers list of the group
|
// Peers list of the group
|
||||||
Peers []string `gorm:"serializer:json"`
|
Peers []string `gorm:"serializer:json"`
|
||||||
|
|
||||||
|
IPv6Enabled bool
|
||||||
|
|
||||||
IntegrationReference integration_reference.IntegrationReference `gorm:"embedded;embeddedPrefix:integration_ref_"`
|
IntegrationReference integration_reference.IntegrationReference `gorm:"embedded;embeddedPrefix:integration_ref_"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +40,7 @@ func (g *Group) Copy() *Group {
|
|||||||
ID: g.ID,
|
ID: g.ID,
|
||||||
Name: g.Name,
|
Name: g.Name,
|
||||||
Issued: g.Issued,
|
Issued: g.Issued,
|
||||||
|
IPv6Enabled: g.IPv6Enabled,
|
||||||
Peers: make([]string, len(g.Peers)),
|
Peers: make([]string, len(g.Peers)),
|
||||||
IntegrationReference: g.IntegrationReference,
|
IntegrationReference: g.IntegrationReference,
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
@ -12,6 +14,8 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
groupAdminUserID = "testingAdminUser"
|
groupAdminUserID = "testingAdminUser"
|
||||||
|
groupPeer1Key = "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8="
|
||||||
|
groupPeer2Key = "/yF0+vCfv+mRR5k0dca0TrGdO/oiNeAI58gToZm5NyI="
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultAccountManager_CreateGroup(t *testing.T) {
|
func TestDefaultAccountManager_CreateGroup(t *testing.T) {
|
||||||
@ -20,7 +24,7 @@ func TestDefaultAccountManager_CreateGroup(t *testing.T) {
|
|||||||
t.Error("failed to create account manager")
|
t.Error("failed to create account manager")
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := initTestGroupAccount(am)
|
_, account, err := initTestGroupAccount(am)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("failed to init testing account")
|
t.Error("failed to init testing account")
|
||||||
}
|
}
|
||||||
@ -55,7 +59,7 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) {
|
|||||||
t.Error("failed to create account manager")
|
t.Error("failed to create account manager")
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := initTestGroupAccount(am)
|
_, account, err := initTestGroupAccount(am)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("failed to init testing account")
|
t.Error("failed to init testing account")
|
||||||
}
|
}
|
||||||
@ -131,10 +135,137 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
func TestDefaultAccountManager_GroupIPv6Consistency(t *testing.T) {
|
||||||
|
am, err := createManager(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to create account manager")
|
||||||
|
}
|
||||||
|
|
||||||
|
peers, account, err := initTestGroupAccount(am)
|
||||||
|
peer1Id := peers[0]
|
||||||
|
peer2Id := peers[1]
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to init testing account")
|
||||||
|
}
|
||||||
|
|
||||||
|
group := account.GetGroup("grp-for-ipv6")
|
||||||
|
|
||||||
|
// First, add one member to the IPv6 group before enabling IPv6.
|
||||||
|
group.Peers = append(group.Peers, peer1Id)
|
||||||
|
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||||
|
require.NoError(t, err, "unable to update group")
|
||||||
|
account, err = am.Store.GetAccount(account.Id)
|
||||||
|
require.NoError(t, err, "unable to update account")
|
||||||
|
group = account.GetGroup("grp-for-ipv6")
|
||||||
|
require.Nil(t, account.Peers[peer1Id].IP6, "peer1 should not have an IPv6 address if the group doesn't have it enabled.")
|
||||||
|
require.Nil(t, account.Peers[peer2Id].IP6, "peer2 should not have an IPv6 address.")
|
||||||
|
|
||||||
|
// Now, enable IPv6.
|
||||||
|
group.IPv6Enabled = true
|
||||||
|
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||||
|
require.NoError(t, err, "unable to update group")
|
||||||
|
account, err = am.Store.GetAccount(account.Id)
|
||||||
|
require.NoError(t, err, "unable to update account")
|
||||||
|
group = account.GetGroup("grp-for-ipv6")
|
||||||
|
require.NotNil(t, account.Peers[peer1Id].IP6, "peer1 should have an IPv6 address as it is a member of the IPv6-enabled group.")
|
||||||
|
require.Nil(t, account.Peers[peer2Id].IP6, "peer2 should not have an IPv6 address as it is not a member of the IPv6-enabled group.")
|
||||||
|
|
||||||
|
// Add the second peer.
|
||||||
|
group.Peers = append(group.Peers, peer2Id)
|
||||||
|
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||||
|
require.NoError(t, err, "unable to update group")
|
||||||
|
account, err = am.Store.GetAccount(account.Id)
|
||||||
|
require.NoError(t, err, "unable to update account")
|
||||||
|
group = account.GetGroup("grp-for-ipv6")
|
||||||
|
require.NotNil(t, account.Peers[peer1Id].IP6, "peer1 should have an IPv6 address as it is a member of the IPv6-enabled group.")
|
||||||
|
require.NotNil(t, account.Peers[peer2Id].IP6, "peer2 should have an IPv6 address as it is a member of the IPv6-enabled group.")
|
||||||
|
|
||||||
|
// Disable IPv6 and simultaneously delete the first peer.
|
||||||
|
group.IPv6Enabled = false
|
||||||
|
group.Peers = group.Peers[1:]
|
||||||
|
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||||
|
require.NoError(t, err, "unable to update group")
|
||||||
|
account, err = am.Store.GetAccount(account.Id)
|
||||||
|
require.NoError(t, err, "unable to update account")
|
||||||
|
group = account.GetGroup("grp-for-ipv6")
|
||||||
|
require.Nil(t, account.Peers[peer1Id].IP6, "peer1 should not have an IPv6 address as it is not a member of any IPv6-enabled group.")
|
||||||
|
require.Nil(t, account.Peers[peer2Id].IP6, "peer2 should not have an IPv6 address as the group has IPv6 disabled.")
|
||||||
|
|
||||||
|
// Enable IPv6 and simultaneously add the first peer again.
|
||||||
|
group.IPv6Enabled = true
|
||||||
|
group.Peers = append(group.Peers, peer1Id)
|
||||||
|
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||||
|
require.NoError(t, err, "unable to update group")
|
||||||
|
account, err = am.Store.GetAccount(account.Id)
|
||||||
|
require.NoError(t, err, "unable to update account")
|
||||||
|
require.NotNil(t, account.Peers[peer1Id].IP6, "peer1 should have an IPv6 address as it is a member of the IPv6-enabled group.")
|
||||||
|
require.NotNil(t, account.Peers[peer2Id].IP6, "peer2 should have an IPv6 address as it is a member of the IPv6-enabled group.")
|
||||||
|
|
||||||
|
// Force disable IPv6.
|
||||||
|
peer1 := account.GetPeer(peer1Id)
|
||||||
|
peer2 := account.GetPeer(peer2Id)
|
||||||
|
peer1.V6Setting = nbpeer.V6Disabled
|
||||||
|
peer2.V6Setting = nbpeer.V6Disabled
|
||||||
|
_, err = am.UpdatePeer(account.Id, groupAdminUserID, peer1)
|
||||||
|
require.NoError(t, err, "unable to update peer1")
|
||||||
|
_, err = am.UpdatePeer(account.Id, groupAdminUserID, peer2)
|
||||||
|
require.NoError(t, err, "unable to update peer2")
|
||||||
|
account, err = am.Store.GetAccount(account.Id)
|
||||||
|
require.NoError(t, err, "unable to fetch updated account")
|
||||||
|
group = account.GetGroup("grp-for-ipv6")
|
||||||
|
require.Nil(t, account.GetPeer(peer1Id).IP6, "peer1 should not have an IPv6 address as it is force disabled.")
|
||||||
|
require.Nil(t, account.GetPeer(peer2Id).IP6, "peer2 should not have an IPv6 address as it is force disabled.")
|
||||||
|
|
||||||
|
// Delete Group.
|
||||||
|
err = am.DeleteGroup(account.Id, groupAdminUserID, group.ID)
|
||||||
|
require.NoError(t, err, "unable to delete group")
|
||||||
|
account, err = am.Store.GetAccount(account.Id)
|
||||||
|
require.NoError(t, err, "unable to update account")
|
||||||
|
group = account.GetGroup("grp-for-ipv6")
|
||||||
|
require.Nil(t, group, "Group should no longer exist.")
|
||||||
|
require.Nil(t, account.Peers[peer1Id].IP6, "peer1 should not have an IPv6 address as the only IPv6-enabled group was deleted.")
|
||||||
|
require.Nil(t, account.Peers[peer2Id].IP6, "peer2 should not have an IPv6 address as the only IPv6-enabled group was deleted.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initTestGroupAccount(am *DefaultAccountManager) ([]string, *Account, error) {
|
||||||
accountID := "testingAcc"
|
accountID := "testingAcc"
|
||||||
domain := "example.com"
|
domain := "example.com"
|
||||||
|
|
||||||
|
peer1 := &nbpeer.Peer{
|
||||||
|
Key: peer1Key,
|
||||||
|
Name: "peer1",
|
||||||
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
|
Hostname: "test-host1@netbird.io",
|
||||||
|
GoOS: "linux",
|
||||||
|
Kernel: "Linux",
|
||||||
|
Core: "21.04",
|
||||||
|
Platform: "x86_64",
|
||||||
|
OS: "Ubuntu",
|
||||||
|
WtVersion: "development",
|
||||||
|
UIVersion: "development",
|
||||||
|
Ipv6Supported: true,
|
||||||
|
},
|
||||||
|
V6Setting: nbpeer.V6Auto,
|
||||||
|
DNSLabel: groupPeer1Key,
|
||||||
|
}
|
||||||
|
peer2 := &nbpeer.Peer{
|
||||||
|
Key: peer2Key,
|
||||||
|
Name: "peer2",
|
||||||
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
|
Hostname: "test-host2@netbird.io",
|
||||||
|
GoOS: "linux",
|
||||||
|
Kernel: "Linux",
|
||||||
|
Core: "21.04",
|
||||||
|
Platform: "x86_64",
|
||||||
|
OS: "Ubuntu",
|
||||||
|
WtVersion: "development",
|
||||||
|
UIVersion: "development",
|
||||||
|
Ipv6Supported: true,
|
||||||
|
},
|
||||||
|
V6Setting: nbpeer.V6Auto,
|
||||||
|
DNSLabel: groupPeer2Key,
|
||||||
|
}
|
||||||
|
|
||||||
groupForRoute := &nbgroup.Group{
|
groupForRoute := &nbgroup.Group{
|
||||||
ID: "grp-for-route",
|
ID: "grp-for-route",
|
||||||
AccountID: "account-id",
|
AccountID: "account-id",
|
||||||
@ -191,6 +322,14 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
|||||||
Peers: make([]string, 0),
|
Peers: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
groupForIPv6 := &nbgroup.Group{
|
||||||
|
ID: "grp-for-ipv6",
|
||||||
|
AccountID: "account-id",
|
||||||
|
Name: "Group for IPv6",
|
||||||
|
Issued: nbgroup.GroupIssuedAPI,
|
||||||
|
Peers: make([]string, 0),
|
||||||
|
}
|
||||||
|
|
||||||
routeResource := &route.Route{
|
routeResource := &route.Route{
|
||||||
ID: "example route",
|
ID: "example route",
|
||||||
Groups: []string{groupForRoute.ID},
|
Groups: []string{groupForRoute.ID},
|
||||||
@ -235,7 +374,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
|||||||
|
|
||||||
err := am.Store.SaveAccount(account)
|
err := am.Store.SaveAccount(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForRoute)
|
_ = am.SaveGroup(accountID, groupAdminUserID, groupForRoute)
|
||||||
@ -245,6 +384,11 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
|||||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForSetupKeys)
|
_ = am.SaveGroup(accountID, groupAdminUserID, groupForSetupKeys)
|
||||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForUsers)
|
_ = am.SaveGroup(accountID, groupAdminUserID, groupForUsers)
|
||||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForIntegration)
|
_ = am.SaveGroup(accountID, groupAdminUserID, groupForIntegration)
|
||||||
|
_ = am.SaveGroup(accountID, groupAdminUserID, groupForIPv6)
|
||||||
|
peer1, _, _ = am.AddPeer(setupKey.Key, user.Id, peer1)
|
||||||
|
peer2, _, _ = am.AddPeer(setupKey.Key, user.Id, peer2)
|
||||||
|
|
||||||
return am.Store.GetAccount(account.Id)
|
account, err = am.Store.GetAccount(account.Id)
|
||||||
|
|
||||||
|
return []string{peer1.ID, peer2.ID}, account, err
|
||||||
}
|
}
|
||||||
|
@ -287,6 +287,7 @@ func extractPeerMeta(loginReq *proto.LoginRequest) nbpeer.PeerSystemMeta {
|
|||||||
Cloud: loginReq.GetMeta().GetEnvironment().GetCloud(),
|
Cloud: loginReq.GetMeta().GetEnvironment().GetCloud(),
|
||||||
Platform: loginReq.GetMeta().GetEnvironment().GetPlatform(),
|
Platform: loginReq.GetMeta().GetEnvironment().GetPlatform(),
|
||||||
},
|
},
|
||||||
|
Ipv6Supported: loginReq.GetMeta().GetIpv6Supported(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,21 +456,31 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot
|
|||||||
|
|
||||||
func toPeerConfig(peer *nbpeer.Peer, network *Network, dnsName string) *proto.PeerConfig {
|
func toPeerConfig(peer *nbpeer.Peer, network *Network, dnsName string) *proto.PeerConfig {
|
||||||
netmask, _ := network.Net.Mask.Size()
|
netmask, _ := network.Net.Mask.Size()
|
||||||
|
address6 := ""
|
||||||
|
if network.Net6 != nil && peer.IP6 != nil {
|
||||||
|
netmask6, _ := network.Net6.Mask.Size()
|
||||||
|
address6 = fmt.Sprintf("%s/%d", peer.IP6.String(), netmask6)
|
||||||
|
}
|
||||||
fqdn := peer.FQDN(dnsName)
|
fqdn := peer.FQDN(dnsName)
|
||||||
return &proto.PeerConfig{
|
return &proto.PeerConfig{
|
||||||
Address: fmt.Sprintf("%s/%d", peer.IP.String(), netmask), // take it from the network
|
Address: fmt.Sprintf("%s/%d", peer.IP.String(), netmask), // take it from the network
|
||||||
|
Address6: address6,
|
||||||
SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled},
|
SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled},
|
||||||
Fqdn: fqdn,
|
Fqdn: fqdn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toRemotePeerConfig(peers []*nbpeer.Peer, dnsName string) []*proto.RemotePeerConfig {
|
func toRemotePeerConfig(peers []*nbpeer.Peer, dnsName string, v6Enabled bool) []*proto.RemotePeerConfig {
|
||||||
remotePeers := []*proto.RemotePeerConfig{}
|
remotePeers := []*proto.RemotePeerConfig{}
|
||||||
for _, rPeer := range peers {
|
for _, rPeer := range peers {
|
||||||
fqdn := rPeer.FQDN(dnsName)
|
fqdn := rPeer.FQDN(dnsName)
|
||||||
|
allowedIps := []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)}
|
||||||
|
if rPeer.IP6 != nil && v6Enabled {
|
||||||
|
allowedIps = append(allowedIps, fmt.Sprintf(AllowedIP6sFormat, *rPeer.IP6))
|
||||||
|
}
|
||||||
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
|
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
|
||||||
WgPubKey: rPeer.Key,
|
WgPubKey: rPeer.Key,
|
||||||
AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)},
|
AllowedIps: allowedIps,
|
||||||
SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)},
|
SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)},
|
||||||
Fqdn: fqdn,
|
Fqdn: fqdn,
|
||||||
})
|
})
|
||||||
@ -482,13 +493,13 @@ func toSyncResponse(config *Config, peer *nbpeer.Peer, turnCredentials *TURNCred
|
|||||||
|
|
||||||
pConfig := toPeerConfig(peer, networkMap.Network, dnsName)
|
pConfig := toPeerConfig(peer, networkMap.Network, dnsName)
|
||||||
|
|
||||||
remotePeers := toRemotePeerConfig(networkMap.Peers, dnsName)
|
remotePeers := toRemotePeerConfig(networkMap.Peers, dnsName, peer.IP6 != nil)
|
||||||
|
|
||||||
routesUpdate := toProtocolRoutes(networkMap.Routes)
|
routesUpdate := toProtocolRoutes(networkMap.Routes)
|
||||||
|
|
||||||
dnsUpdate := toProtocolDNSConfig(networkMap.DNSConfig)
|
dnsUpdate := toProtocolDNSConfig(networkMap.DNSConfig)
|
||||||
|
|
||||||
offlinePeers := toRemotePeerConfig(networkMap.OfflinePeers, dnsName)
|
offlinePeers := toRemotePeerConfig(networkMap.OfflinePeers, dnsName, peer.IP6 != nil)
|
||||||
|
|
||||||
firewallRules := toProtocolFirewallRules(networkMap.FirewallRules)
|
firewallRules := toProtocolFirewallRules(networkMap.FirewallRules)
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
|||||||
handler := initAccountsTestData(&server.Account{
|
handler := initAccountsTestData(&server.Account{
|
||||||
Id: accountID,
|
Id: accountID,
|
||||||
Domain: "hotmail.com",
|
Domain: "hotmail.com",
|
||||||
Network: server.NewNetwork(),
|
Network: server.NewNetwork(true),
|
||||||
Users: map[string]*server.User{
|
Users: map[string]*server.User{
|
||||||
adminUser.Id: adminUser,
|
adminUser.Id: adminUser,
|
||||||
},
|
},
|
||||||
|
@ -247,10 +247,15 @@ components:
|
|||||||
description: (Cloud only) Indicates whether peer needs approval
|
description: (Cloud only) Indicates whether peer needs approval
|
||||||
type: boolean
|
type: boolean
|
||||||
example: true
|
example: true
|
||||||
|
ipv6_enabled:
|
||||||
|
type: string
|
||||||
|
enum: [enabled, disabled, auto]
|
||||||
|
example: auto
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- ssh_enabled
|
- ssh_enabled
|
||||||
- login_expiration_enabled
|
- login_expiration_enabled
|
||||||
|
- ipv6_enabled
|
||||||
PeerBase:
|
PeerBase:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/PeerMinimum'
|
- $ref: '#/components/schemas/PeerMinimum'
|
||||||
@ -264,6 +269,10 @@ components:
|
|||||||
description: Peer's public connection IP address
|
description: Peer's public connection IP address
|
||||||
type: string
|
type: string
|
||||||
example: 35.64.0.1
|
example: 35.64.0.1
|
||||||
|
ip6:
|
||||||
|
description: Peer's IPv6 address
|
||||||
|
type: string
|
||||||
|
example: 2001:db8::0123:4567:890a:bcde
|
||||||
connected:
|
connected:
|
||||||
description: Peer to Management connection status
|
description: Peer to Management connection status
|
||||||
type: boolean
|
type: boolean
|
||||||
@ -310,6 +319,15 @@ components:
|
|||||||
description: Peer's desktop UI version
|
description: Peer's desktop UI version
|
||||||
type: string
|
type: string
|
||||||
example: 0.14.0
|
example: 0.14.0
|
||||||
|
ipv6_supported:
|
||||||
|
description: Whether this peer supports IPv6
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
ipv6_enabled:
|
||||||
|
description: Whether IPv6 is enabled for this peer.
|
||||||
|
type: string
|
||||||
|
enum: [enabled, disabled, auto]
|
||||||
|
example: auto
|
||||||
dns_label:
|
dns_label:
|
||||||
description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
||||||
type: string
|
type: string
|
||||||
@ -352,6 +370,8 @@ components:
|
|||||||
- kernel_version
|
- kernel_version
|
||||||
- last_login
|
- last_login
|
||||||
- last_seen
|
- last_seen
|
||||||
|
- ipv6_supported
|
||||||
|
- ipv6_enabled
|
||||||
- login_expiration_enabled
|
- login_expiration_enabled
|
||||||
- login_expired
|
- login_expired
|
||||||
- os
|
- os
|
||||||
@ -627,8 +647,12 @@ components:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
example: "ch8i4ug6lnn4g9hqv7m1"
|
example: "ch8i4ug6lnn4g9hqv7m1"
|
||||||
|
ipv6_enabled:
|
||||||
|
description: Whether IPv6 should be enabled for all members with IPv6 set to "auto"
|
||||||
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
|
- ipv6_enabled
|
||||||
Group:
|
Group:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/GroupMinimum'
|
- $ref: '#/components/schemas/GroupMinimum'
|
||||||
@ -639,8 +663,12 @@ components:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/PeerMinimum'
|
$ref: '#/components/schemas/PeerMinimum'
|
||||||
|
ipv6_enabled:
|
||||||
|
description: Whether IPv6 should be enabled for all members with IPv6 set to "auto"
|
||||||
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- peers
|
- peers
|
||||||
|
- ipv6_enabled
|
||||||
PolicyRuleMinimum:
|
PolicyRuleMinimum:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -88,12 +88,40 @@ const (
|
|||||||
NameserverNsTypeUdp NameserverNsType = "udp"
|
NameserverNsTypeUdp NameserverNsType = "udp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Defines values for PeerIpv6Enabled.
|
||||||
|
const (
|
||||||
|
PeerIpv6EnabledAuto PeerIpv6Enabled = "auto"
|
||||||
|
PeerIpv6EnabledDisabled PeerIpv6Enabled = "disabled"
|
||||||
|
PeerIpv6EnabledEnabled PeerIpv6Enabled = "enabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for PeerBaseIpv6Enabled.
|
||||||
|
const (
|
||||||
|
PeerBaseIpv6EnabledAuto PeerBaseIpv6Enabled = "auto"
|
||||||
|
PeerBaseIpv6EnabledDisabled PeerBaseIpv6Enabled = "disabled"
|
||||||
|
PeerBaseIpv6EnabledEnabled PeerBaseIpv6Enabled = "enabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for PeerBatchIpv6Enabled.
|
||||||
|
const (
|
||||||
|
PeerBatchIpv6EnabledAuto PeerBatchIpv6Enabled = "auto"
|
||||||
|
PeerBatchIpv6EnabledDisabled PeerBatchIpv6Enabled = "disabled"
|
||||||
|
PeerBatchIpv6EnabledEnabled PeerBatchIpv6Enabled = "enabled"
|
||||||
|
)
|
||||||
|
|
||||||
// Defines values for PeerNetworkRangeCheckAction.
|
// Defines values for PeerNetworkRangeCheckAction.
|
||||||
const (
|
const (
|
||||||
PeerNetworkRangeCheckActionAllow PeerNetworkRangeCheckAction = "allow"
|
PeerNetworkRangeCheckActionAllow PeerNetworkRangeCheckAction = "allow"
|
||||||
PeerNetworkRangeCheckActionDeny PeerNetworkRangeCheckAction = "deny"
|
PeerNetworkRangeCheckActionDeny PeerNetworkRangeCheckAction = "deny"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Defines values for PeerRequestIpv6Enabled.
|
||||||
|
const (
|
||||||
|
PeerRequestIpv6EnabledAuto PeerRequestIpv6Enabled = "auto"
|
||||||
|
PeerRequestIpv6EnabledDisabled PeerRequestIpv6Enabled = "disabled"
|
||||||
|
PeerRequestIpv6EnabledEnabled PeerRequestIpv6Enabled = "enabled"
|
||||||
|
)
|
||||||
|
|
||||||
// Defines values for PolicyRuleAction.
|
// Defines values for PolicyRuleAction.
|
||||||
const (
|
const (
|
||||||
PolicyRuleActionAccept PolicyRuleAction = "accept"
|
PolicyRuleActionAccept PolicyRuleAction = "accept"
|
||||||
@ -307,6 +335,9 @@ type Group struct {
|
|||||||
// Id Group ID
|
// Id Group ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
// Ipv6Enabled Whether IPv6 should be enabled for all members with IPv6 set to "auto"
|
||||||
|
Ipv6Enabled bool `json:"ipv6_enabled"`
|
||||||
|
|
||||||
// Issued How the group was issued (api, integration, jwt)
|
// Issued How the group was issued (api, integration, jwt)
|
||||||
Issued *GroupIssued `json:"issued,omitempty"`
|
Issued *GroupIssued `json:"issued,omitempty"`
|
||||||
|
|
||||||
@ -343,6 +374,9 @@ type GroupMinimumIssued string
|
|||||||
|
|
||||||
// GroupRequest defines model for GroupRequest.
|
// GroupRequest defines model for GroupRequest.
|
||||||
type GroupRequest struct {
|
type GroupRequest struct {
|
||||||
|
// Ipv6Enabled Whether IPv6 should be enabled for all members with IPv6 set to "auto"
|
||||||
|
Ipv6Enabled bool `json:"ipv6_enabled"`
|
||||||
|
|
||||||
// Name Group name identifier
|
// Name Group name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
@ -502,6 +536,15 @@ type Peer struct {
|
|||||||
// Ip Peer's IP address
|
// Ip Peer's IP address
|
||||||
Ip string `json:"ip"`
|
Ip string `json:"ip"`
|
||||||
|
|
||||||
|
// Ip6 Peer's IPv6 address
|
||||||
|
Ip6 *string `json:"ip6,omitempty"`
|
||||||
|
|
||||||
|
// Ipv6Enabled Whether IPv6 is enabled for this peer.
|
||||||
|
Ipv6Enabled PeerIpv6Enabled `json:"ipv6_enabled"`
|
||||||
|
|
||||||
|
// Ipv6Supported Whether this peer supports IPv6
|
||||||
|
Ipv6Supported bool `json:"ipv6_supported"`
|
||||||
|
|
||||||
// KernelVersion Peer's operating system kernel version
|
// KernelVersion Peer's operating system kernel version
|
||||||
KernelVersion string `json:"kernel_version"`
|
KernelVersion string `json:"kernel_version"`
|
||||||
|
|
||||||
@ -539,6 +582,9 @@ type Peer struct {
|
|||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PeerIpv6Enabled Whether IPv6 is enabled for this peer.
|
||||||
|
type PeerIpv6Enabled string
|
||||||
|
|
||||||
// PeerBase defines model for PeerBase.
|
// PeerBase defines model for PeerBase.
|
||||||
type PeerBase struct {
|
type PeerBase struct {
|
||||||
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
||||||
@ -574,6 +620,15 @@ type PeerBase struct {
|
|||||||
// Ip Peer's IP address
|
// Ip Peer's IP address
|
||||||
Ip string `json:"ip"`
|
Ip string `json:"ip"`
|
||||||
|
|
||||||
|
// Ip6 Peer's IPv6 address
|
||||||
|
Ip6 *string `json:"ip6,omitempty"`
|
||||||
|
|
||||||
|
// Ipv6Enabled Whether IPv6 is enabled for this peer.
|
||||||
|
Ipv6Enabled PeerBaseIpv6Enabled `json:"ipv6_enabled"`
|
||||||
|
|
||||||
|
// Ipv6Supported Whether this peer supports IPv6
|
||||||
|
Ipv6Supported bool `json:"ipv6_supported"`
|
||||||
|
|
||||||
// KernelVersion Peer's operating system kernel version
|
// KernelVersion Peer's operating system kernel version
|
||||||
KernelVersion string `json:"kernel_version"`
|
KernelVersion string `json:"kernel_version"`
|
||||||
|
|
||||||
@ -611,6 +666,9 @@ type PeerBase struct {
|
|||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PeerBaseIpv6Enabled Whether IPv6 is enabled for this peer.
|
||||||
|
type PeerBaseIpv6Enabled string
|
||||||
|
|
||||||
// PeerBatch defines model for PeerBatch.
|
// PeerBatch defines model for PeerBatch.
|
||||||
type PeerBatch struct {
|
type PeerBatch struct {
|
||||||
// AccessiblePeersCount Number of accessible peers
|
// AccessiblePeersCount Number of accessible peers
|
||||||
@ -649,6 +707,15 @@ type PeerBatch struct {
|
|||||||
// Ip Peer's IP address
|
// Ip Peer's IP address
|
||||||
Ip string `json:"ip"`
|
Ip string `json:"ip"`
|
||||||
|
|
||||||
|
// Ip6 Peer's IPv6 address
|
||||||
|
Ip6 *string `json:"ip6,omitempty"`
|
||||||
|
|
||||||
|
// Ipv6Enabled Whether IPv6 is enabled for this peer.
|
||||||
|
Ipv6Enabled PeerBatchIpv6Enabled `json:"ipv6_enabled"`
|
||||||
|
|
||||||
|
// Ipv6Supported Whether this peer supports IPv6
|
||||||
|
Ipv6Supported bool `json:"ipv6_supported"`
|
||||||
|
|
||||||
// KernelVersion Peer's operating system kernel version
|
// KernelVersion Peer's operating system kernel version
|
||||||
KernelVersion string `json:"kernel_version"`
|
KernelVersion string `json:"kernel_version"`
|
||||||
|
|
||||||
@ -686,6 +753,9 @@ type PeerBatch struct {
|
|||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PeerBatchIpv6Enabled Whether IPv6 is enabled for this peer.
|
||||||
|
type PeerBatchIpv6Enabled string
|
||||||
|
|
||||||
// PeerMinimum defines model for PeerMinimum.
|
// PeerMinimum defines model for PeerMinimum.
|
||||||
type PeerMinimum struct {
|
type PeerMinimum struct {
|
||||||
// Id Peer ID
|
// Id Peer ID
|
||||||
@ -710,12 +780,16 @@ type PeerNetworkRangeCheckAction string
|
|||||||
// PeerRequest defines model for PeerRequest.
|
// PeerRequest defines model for PeerRequest.
|
||||||
type PeerRequest struct {
|
type PeerRequest struct {
|
||||||
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
||||||
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
||||||
LoginExpirationEnabled bool `json:"login_expiration_enabled"`
|
Ipv6Enabled PeerRequestIpv6Enabled `json:"ipv6_enabled"`
|
||||||
Name string `json:"name"`
|
LoginExpirationEnabled bool `json:"login_expiration_enabled"`
|
||||||
SshEnabled bool `json:"ssh_enabled"`
|
Name string `json:"name"`
|
||||||
|
SshEnabled bool `json:"ssh_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PeerRequestIpv6Enabled defines model for PeerRequest.Ipv6Enabled.
|
||||||
|
type PeerRequestIpv6Enabled string
|
||||||
|
|
||||||
// PersonalAccessToken defines model for PersonalAccessToken.
|
// PersonalAccessToken defines model for PersonalAccessToken.
|
||||||
type PersonalAccessToken struct {
|
type PersonalAccessToken struct {
|
||||||
// CreatedAt Date the token was created
|
// CreatedAt Date the token was created
|
||||||
|
@ -3,6 +3,7 @@ package http
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@ -82,16 +83,6 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
allGroup, err := account.GetGroupAll()
|
|
||||||
if err != nil {
|
|
||||||
util.WriteError(err, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if allGroup.ID == groupID {
|
|
||||||
util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var req api.PutApiGroupsGroupIdJSONRequestBody
|
var req api.PutApiGroupsGroupIdJSONRequestBody
|
||||||
err = json.NewDecoder(r.Body).Decode(&req)
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -110,12 +101,42 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
peers = *req.Peers
|
peers = *req.Peers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allGroup, err := account.GetGroupAll()
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if allGroup.ID == groupID {
|
||||||
|
if len(peers) != len(allGroup.Peers) || req.Name != allGroup.Name {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
deduplicatedPeers := make(map[string]struct{})
|
||||||
|
for _, peer := range peers {
|
||||||
|
deduplicatedPeers[peer] = struct{}{}
|
||||||
|
}
|
||||||
|
if len(deduplicatedPeers) != len(peers) {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for peer := range deduplicatedPeers {
|
||||||
|
if slices.Contains(allGroup.Peers, peer) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
group := nbgroup.Group{
|
group := nbgroup.Group{
|
||||||
ID: groupID,
|
ID: groupID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Peers: peers,
|
Peers: peers,
|
||||||
Issued: eg.Issued,
|
Issued: eg.Issued,
|
||||||
IntegrationReference: eg.IntegrationReference,
|
IntegrationReference: eg.IntegrationReference,
|
||||||
|
IPv6Enabled: req.Ipv6Enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.accountManager.SaveGroup(account.Id, user.Id, &group); err != nil {
|
if err := h.accountManager.SaveGroup(account.Id, user.Id, &group); err != nil {
|
||||||
@ -246,6 +267,7 @@ func toGroupResponse(account *server.Account, group *nbgroup.Group) *api.Group {
|
|||||||
Id: group.ID,
|
Id: group.ID,
|
||||||
Name: group.Name,
|
Name: group.Name,
|
||||||
Issued: (*api.GroupIssued)(&group.Issued),
|
Issued: (*api.GroupIssued)(&group.Issued),
|
||||||
|
Ipv6Enabled: group.IPv6Enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pid := range group.Peers {
|
for _, pid := range group.Peers {
|
||||||
|
@ -85,11 +85,17 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v6Status := nbpeer.V6Auto
|
||||||
|
if req.Ipv6Enabled != api.PeerRequestIpv6EnabledAuto {
|
||||||
|
v6Status = nbpeer.V6Status(req.Ipv6Enabled)
|
||||||
|
}
|
||||||
|
|
||||||
update := &nbpeer.Peer{
|
update := &nbpeer.Peer{
|
||||||
ID: peerID,
|
ID: peerID,
|
||||||
SSHEnabled: req.SshEnabled,
|
SSHEnabled: req.SshEnabled,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
LoginExpirationEnabled: req.LoginExpirationEnabled,
|
LoginExpirationEnabled: req.LoginExpirationEnabled,
|
||||||
|
V6Setting: v6Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.ApprovalRequired != nil {
|
if req.ApprovalRequired != nil {
|
||||||
@ -284,11 +290,22 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD
|
|||||||
osVersion = peer.Meta.Core
|
osVersion = peer.Meta.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ip6 *string
|
||||||
|
if peer.IP6 != nil {
|
||||||
|
ip6string := peer.IP6.String()
|
||||||
|
ip6 = &ip6string
|
||||||
|
}
|
||||||
|
v6Status := api.PeerIpv6EnabledAuto
|
||||||
|
if peer.V6Setting != nbpeer.V6Auto {
|
||||||
|
v6Status = api.PeerIpv6Enabled(peer.V6Setting)
|
||||||
|
}
|
||||||
|
|
||||||
return &api.Peer{
|
return &api.Peer{
|
||||||
Id: peer.ID,
|
Id: peer.ID,
|
||||||
Name: peer.Name,
|
Name: peer.Name,
|
||||||
Ip: peer.IP.String(),
|
Ip: peer.IP.String(),
|
||||||
ConnectionIp: peer.Location.ConnectionIP.String(),
|
ConnectionIp: peer.Location.ConnectionIP.String(),
|
||||||
|
Ip6: ip6,
|
||||||
Connected: peer.Status.Connected,
|
Connected: peer.Status.Connected,
|
||||||
LastSeen: peer.Status.LastSeen,
|
LastSeen: peer.Status.LastSeen,
|
||||||
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
||||||
@ -300,6 +317,8 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD
|
|||||||
Hostname: peer.Meta.Hostname,
|
Hostname: peer.Meta.Hostname,
|
||||||
UserId: peer.UserID,
|
UserId: peer.UserID,
|
||||||
UiVersion: peer.Meta.UIVersion,
|
UiVersion: peer.Meta.UIVersion,
|
||||||
|
Ipv6Supported: peer.Meta.Ipv6Supported,
|
||||||
|
Ipv6Enabled: v6Status,
|
||||||
DnsLabel: fqdn(peer, dnsDomain),
|
DnsLabel: fqdn(peer, dnsDomain),
|
||||||
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
||||||
LastLogin: peer.LastLogin,
|
LastLogin: peer.LastLogin,
|
||||||
@ -317,12 +336,22 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn
|
|||||||
if osVersion == "" {
|
if osVersion == "" {
|
||||||
osVersion = peer.Meta.Core
|
osVersion = peer.Meta.Core
|
||||||
}
|
}
|
||||||
|
var ip6 *string
|
||||||
|
if peer.IP6 != nil {
|
||||||
|
ip6string := peer.IP6.String()
|
||||||
|
ip6 = &ip6string
|
||||||
|
}
|
||||||
|
v6Status := api.PeerBatchIpv6EnabledAuto
|
||||||
|
if peer.V6Setting != nbpeer.V6Auto {
|
||||||
|
v6Status = api.PeerBatchIpv6Enabled(peer.V6Setting)
|
||||||
|
}
|
||||||
|
|
||||||
return &api.PeerBatch{
|
return &api.PeerBatch{
|
||||||
Id: peer.ID,
|
Id: peer.ID,
|
||||||
Name: peer.Name,
|
Name: peer.Name,
|
||||||
Ip: peer.IP.String(),
|
Ip: peer.IP.String(),
|
||||||
ConnectionIp: peer.Location.ConnectionIP.String(),
|
ConnectionIp: peer.Location.ConnectionIP.String(),
|
||||||
|
Ip6: ip6,
|
||||||
Connected: peer.Status.Connected,
|
Connected: peer.Status.Connected,
|
||||||
LastSeen: peer.Status.LastSeen,
|
LastSeen: peer.Status.LastSeen,
|
||||||
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
||||||
@ -334,6 +363,8 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn
|
|||||||
Hostname: peer.Meta.Hostname,
|
Hostname: peer.Meta.Hostname,
|
||||||
UserId: peer.UserID,
|
UserId: peer.UserID,
|
||||||
UiVersion: peer.Meta.UIVersion,
|
UiVersion: peer.Meta.UIVersion,
|
||||||
|
Ipv6Supported: peer.Meta.Ipv6Supported,
|
||||||
|
Ipv6Enabled: v6Status,
|
||||||
DnsLabel: fqdn(peer, dnsDomain),
|
DnsLabel: fqdn(peer, dnsDomain),
|
||||||
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
||||||
LastLogin: peer.LastLogin,
|
LastLogin: peer.LastLogin,
|
||||||
|
@ -37,6 +37,12 @@ func initTestMetaData(peers ...*nbpeer.Peer) *PeersHandler {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if p.V6Setting == nbpeer.V6Enabled && p.IP6 == nil {
|
||||||
|
ip6 := net.ParseIP("2001:db8::dead:beef")
|
||||||
|
p.IP6 = &ip6
|
||||||
|
} else {
|
||||||
|
p.IP6 = nil
|
||||||
|
}
|
||||||
p.SSHEnabled = update.SSHEnabled
|
p.SSHEnabled = update.SSHEnabled
|
||||||
p.LoginExpirationEnabled = update.LoginExpirationEnabled
|
p.LoginExpirationEnabled = update.LoginExpirationEnabled
|
||||||
p.Name = update.Name
|
p.Name = update.Name
|
||||||
|
@ -789,6 +789,7 @@ func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error
|
|||||||
OS: "Ubuntu",
|
OS: "Ubuntu",
|
||||||
WtVersion: "development",
|
WtVersion: "development",
|
||||||
UIVersion: "development",
|
UIVersion: "development",
|
||||||
|
Ipv6Supported: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
peer2 := &nbpeer.Peer{
|
peer2 := &nbpeer.Peer{
|
||||||
@ -803,6 +804,7 @@ func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error
|
|||||||
OS: "Ubuntu",
|
OS: "Ubuntu",
|
||||||
WtVersion: "development",
|
WtVersion: "development",
|
||||||
UIVersion: "development",
|
UIVersion: "development",
|
||||||
|
Ipv6Supported: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
existingNSGroup := nbdns.NameServerGroup{
|
existingNSGroup := nbdns.NameServerGroup{
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
crand "crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"github.com/c-robinson/iplib"
|
||||||
|
"github.com/rs/xid"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/c-robinson/iplib"
|
|
||||||
"github.com/rs/xid"
|
|
||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
@ -20,11 +20,21 @@ const (
|
|||||||
SubnetSize = 16
|
SubnetSize = 16
|
||||||
// NetSize is a global network size 100.64.0.0/10
|
// NetSize is a global network size 100.64.0.0/10
|
||||||
NetSize = 10
|
NetSize = 10
|
||||||
|
// Subnet6Size is the size of an IPv6 subnet (in Bytes, not Bits)
|
||||||
|
Subnet6Size = 8
|
||||||
|
|
||||||
// AllowedIPsFormat generates Wireguard AllowedIPs format (e.g. 100.64.30.1/32)
|
// AllowedIPsFormat generates Wireguard AllowedIPs format (e.g. 100.64.30.1/32)
|
||||||
AllowedIPsFormat = "%s/32"
|
AllowedIPsFormat = "%s/32"
|
||||||
|
|
||||||
|
// AllowedIP6sFormat generates Wireguard AllowedIPs format (e.g. 2001:db8::dead:beef/128)
|
||||||
|
AllowedIP6sFormat = "%s/128"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Global random number generator for IP addresses
|
||||||
|
// Accesses to the RNG must always be protected using rngLock (RNG sources are not thread-safe)
|
||||||
|
var rng = initializeRng()
|
||||||
|
var rngLock = sync.Mutex{}
|
||||||
|
|
||||||
type NetworkMap struct {
|
type NetworkMap struct {
|
||||||
Peers []*nbpeer.Peer
|
Peers []*nbpeer.Peer
|
||||||
Network *Network
|
Network *Network
|
||||||
@ -37,6 +47,7 @@ type NetworkMap struct {
|
|||||||
type Network struct {
|
type Network struct {
|
||||||
Identifier string `json:"id"`
|
Identifier string `json:"id"`
|
||||||
Net net.IPNet `gorm:"serializer:json"`
|
Net net.IPNet `gorm:"serializer:json"`
|
||||||
|
Net6 *net.IPNet `gorm:"serializer:json"` // Can't use gob serializer, as it cannot encode nil values.
|
||||||
Dns string
|
Dns string
|
||||||
// Serial is an ID that increments by 1 when any change to the network happened (e.g. new peer has been added).
|
// Serial is an ID that increments by 1 when any change to the network happened (e.g. new peer has been added).
|
||||||
// Used to synchronize state to the client apps.
|
// Used to synchronize state to the client apps.
|
||||||
@ -45,24 +56,54 @@ type Network struct {
|
|||||||
mu sync.Mutex `json:"-" gorm:"-"`
|
mu sync.Mutex `json:"-" gorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initializeRng() *rand.Rand {
|
||||||
|
seed := make([]byte, 8)
|
||||||
|
_, err := crand.Read(seed)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s := rand.NewSource(int64(binary.LittleEndian.Uint64(seed)))
|
||||||
|
return rand.New(s)
|
||||||
|
}
|
||||||
|
|
||||||
// NewNetwork creates a new Network initializing it with a Serial=0
|
// NewNetwork creates a new Network initializing it with a Serial=0
|
||||||
// It takes a random /16 subnet from 100.64.0.0/10 (64 different subnets)
|
// It takes a random /16 subnet from 100.64.0.0/10 (64 different subnets)
|
||||||
func NewNetwork() *Network {
|
func NewNetwork(enableV6 bool) *Network {
|
||||||
|
|
||||||
n := iplib.NewNet4(net.ParseIP("100.64.0.0"), NetSize)
|
n := iplib.NewNet4(net.ParseIP("100.64.0.0"), NetSize)
|
||||||
sub, _ := n.Subnet(SubnetSize)
|
sub, _ := n.Subnet(SubnetSize)
|
||||||
|
rngLock.Lock()
|
||||||
|
intn := rng.Intn(len(sub))
|
||||||
|
rngLock.Unlock()
|
||||||
|
|
||||||
s := rand.NewSource(time.Now().Unix())
|
var n6 *net.IPNet = nil
|
||||||
r := rand.New(s)
|
if enableV6 {
|
||||||
intn := r.Intn(len(sub))
|
n6 = GenerateNetwork6()
|
||||||
|
}
|
||||||
|
|
||||||
return &Network{
|
return &Network{
|
||||||
Identifier: xid.New().String(),
|
Identifier: xid.New().String(),
|
||||||
Net: sub[intn].IPNet,
|
Net: sub[intn].IPNet,
|
||||||
|
Net6: n6,
|
||||||
Dns: "",
|
Dns: "",
|
||||||
Serial: 0}
|
Serial: 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenerateNetwork6() *net.IPNet {
|
||||||
|
addrbuf := make([]byte, 16)
|
||||||
|
addrbuf[0] = 0xfd
|
||||||
|
addrbuf[1] = 0x00
|
||||||
|
addrbuf[2] = 0xb1
|
||||||
|
addrbuf[3] = 0x4d
|
||||||
|
|
||||||
|
rngLock.Lock()
|
||||||
|
_, _ = rng.Read(addrbuf[4:Subnet6Size])
|
||||||
|
rngLock.Unlock()
|
||||||
|
|
||||||
|
n6 := iplib.NewNet6(addrbuf, Subnet6Size*8, 0).IPNet
|
||||||
|
return &n6
|
||||||
|
}
|
||||||
|
|
||||||
// IncSerial increments Serial by 1 reflecting that the network state has been changed
|
// IncSerial increments Serial by 1 reflecting that the network state has been changed
|
||||||
func (n *Network) IncSerial() {
|
func (n *Network) IncSerial() {
|
||||||
n.mu.Lock()
|
n.mu.Lock()
|
||||||
@ -81,6 +122,7 @@ func (n *Network) Copy() *Network {
|
|||||||
return &Network{
|
return &Network{
|
||||||
Identifier: n.Identifier,
|
Identifier: n.Identifier,
|
||||||
Net: n.Net,
|
Net: n.Net,
|
||||||
|
Net6: n.Net6,
|
||||||
Dns: n.Dns,
|
Dns: n.Dns,
|
||||||
Serial: n.Serial,
|
Serial: n.Serial,
|
||||||
}
|
}
|
||||||
@ -103,13 +145,38 @@ func AllocatePeerIP(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pick a random IP
|
// pick a random IP
|
||||||
s := rand.NewSource(time.Now().Unix())
|
rngLock.Lock()
|
||||||
r := rand.New(s)
|
intn := rng.Intn(len(ips))
|
||||||
intn := r.Intn(len(ips))
|
rngLock.Unlock()
|
||||||
|
|
||||||
return ips[intn], nil
|
return ips[intn], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllocatePeerIP6 pics an available IPv6 from an net.IPNet.
|
||||||
|
// This method considers already taken IPs and reuses IPs if there are gaps in takenIps.
|
||||||
|
func AllocatePeerIP6(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) {
|
||||||
|
|
||||||
|
takenIPMap := make(map[string]struct{})
|
||||||
|
takenIPMap[ipNet.IP.String()] = struct{}{}
|
||||||
|
for _, ip := range takenIps {
|
||||||
|
takenIPMap[ip.String()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
maskSize, _ := ipNet.Mask.Size()
|
||||||
|
|
||||||
|
// TODO for small subnet sizes, randomly generating values until we don't get a duplicate is inefficient and could
|
||||||
|
// lead to many loop iterations, using a method similar to IPv4 would be preferable here.
|
||||||
|
|
||||||
|
addrbuf := make(net.IP, 16)
|
||||||
|
copy(addrbuf, ipNet.IP.To16())
|
||||||
|
for duplicate := true; duplicate; _, duplicate = takenIPMap[addrbuf.String()] {
|
||||||
|
rngLock.Lock()
|
||||||
|
_, _ = rng.Read(addrbuf[(maskSize / 8):16])
|
||||||
|
rngLock.Unlock()
|
||||||
|
}
|
||||||
|
return addrbuf, nil
|
||||||
|
}
|
||||||
|
|
||||||
// generateIPs generates a list of all possible IPs of the given network excluding IPs specified in the exclusion list
|
// generateIPs generates a list of all possible IPs of the given network excluding IPs specified in the exclusion list
|
||||||
func generateIPs(ipNet *net.IPNet, exclusions map[string]struct{}) ([]net.IP, int) {
|
func generateIPs(ipNet *net.IPNet, exclusions map[string]struct{}) ([]net.IP, int) {
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -8,11 +9,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestNewNetwork(t *testing.T) {
|
func TestNewNetwork(t *testing.T) {
|
||||||
network := NewNetwork()
|
network := NewNetwork(true)
|
||||||
|
|
||||||
// generated net should be a subnet of a larger 100.64.0.0/10 net
|
// generated net should be a subnet of a larger 100.64.0.0/10 net
|
||||||
ipNet := net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 192, 0, 0}}
|
ipNet := net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 192, 0, 0}}
|
||||||
assert.Equal(t, ipNet.Contains(network.Net.IP), true)
|
assert.True(t, ipNet.Contains(network.Net.IP))
|
||||||
|
|
||||||
|
// generated IPv6 net should be a subnet of the fd00:b14d::/32 prefix.
|
||||||
|
_, ipNet6, err := net.ParseCIDR("fd00:b14d::/32")
|
||||||
|
require.NoError(t, err, "unable to parse IPv6 prefix")
|
||||||
|
assert.True(t, ipNet6.Contains(network.Net6.IP))
|
||||||
|
// IPv6 prefix should be of size /64
|
||||||
|
ones, _ := network.Net6.Mask.Size()
|
||||||
|
assert.Equal(t, ones, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAllocatePeerIP(t *testing.T) {
|
func TestAllocatePeerIP(t *testing.T) {
|
||||||
@ -38,6 +47,32 @@ func TestAllocatePeerIP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAllocatePeerIP6(t *testing.T) {
|
||||||
|
_, ipNet, err := net.ParseCIDR("2001:db8:abcd:1234::/64")
|
||||||
|
require.NoError(t, err, "unable to parse IPv6 prefix")
|
||||||
|
var ips []net.IP
|
||||||
|
// Yeah, we better not check all 2^64 possible addresses, just generating a bunch of addresses should hopefully
|
||||||
|
// reveal any possible bugs in the RNG.
|
||||||
|
for i := 0; i < 252; i++ {
|
||||||
|
ip, err := AllocatePeerIP6(*ipNet, ips)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Len(t, ips, 252)
|
||||||
|
|
||||||
|
uniq := make(map[string]struct{})
|
||||||
|
for _, ip := range ips {
|
||||||
|
if _, ok := uniq[ip.String()]; !ok {
|
||||||
|
uniq[ip.String()] = struct{}{}
|
||||||
|
} else {
|
||||||
|
t.Errorf("found duplicate IP %s", ip.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerateIPs(t *testing.T) {
|
func TestGenerateIPs(t *testing.T) {
|
||||||
ipNet := net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 255, 255, 0}}
|
ipNet := net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 255, 255, 0}}
|
||||||
ips, ipsLen := generateIPs(&ipNet, map[string]struct{}{"100.64.0.0": {}})
|
ips, ipsLen := generateIPs(&ipNet, map[string]struct{}{"100.64.0.0": {}})
|
||||||
|
@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
nbroute "github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PeerSync used as a data object between the gRPC API and AccountManager on Sync request.
|
// PeerSync used as a data object between the gRPC API and AccountManager on Sync request.
|
||||||
@ -140,7 +142,66 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, and Peer.LoginExpirationEnabled can be updated.
|
// Determines the current IPv6 status of the peer (including checks for inheritance) and generates a new or removes an
|
||||||
|
// existing IPv6 address if necessary.
|
||||||
|
// Additionally, disables IPv6 routes if peer no longer has an IPv6 address.
|
||||||
|
// Note that this change does not get persisted here.
|
||||||
|
//
|
||||||
|
// Returns a boolean that indicates whether the peer and/or the account changed and needs to be updated in the data
|
||||||
|
// source.
|
||||||
|
func (am *DefaultAccountManager) DeterminePeerV6(account *Account, peer *nbpeer.Peer) (bool, error) {
|
||||||
|
v6Setting := peer.V6Setting
|
||||||
|
if peer.V6Setting == nbpeer.V6Auto {
|
||||||
|
if peer.Meta.Ipv6Supported {
|
||||||
|
for _, group := range account.Groups {
|
||||||
|
if group.IPv6Enabled && slices.Contains(group.Peers, peer.ID) {
|
||||||
|
v6Setting = nbpeer.V6Enabled
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v6Setting == nbpeer.V6Auto {
|
||||||
|
for _, route := range account.Routes {
|
||||||
|
if route.Peer == peer.ID && route.NetworkType == nbroute.IPv6Network {
|
||||||
|
v6Setting = nbpeer.V6Enabled
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v6Setting == nbpeer.V6Auto {
|
||||||
|
v6Setting = nbpeer.V6Disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v6Setting == nbpeer.V6Enabled && peer.IP6 == nil {
|
||||||
|
if !peer.Meta.Ipv6Supported {
|
||||||
|
return false, status.Errorf(status.PreconditionFailed, "failed allocating new IPv6 for peer %s - peer does not support IPv6", peer.Name)
|
||||||
|
}
|
||||||
|
if account.Network.Net6 == nil {
|
||||||
|
account.Network.Net6 = GenerateNetwork6()
|
||||||
|
}
|
||||||
|
v6tmp, err := AllocatePeerIP6(*account.Network.Net6, account.getTakenIP6s())
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
peer.IP6 = &v6tmp
|
||||||
|
return true, nil
|
||||||
|
} else if v6Setting == nbpeer.V6Disabled && peer.IP6 != nil {
|
||||||
|
peer.IP6 = nil
|
||||||
|
|
||||||
|
for _, route := range account.Routes {
|
||||||
|
if route.NetworkType == nbroute.IPv6Network {
|
||||||
|
route.Enabled = false
|
||||||
|
account.Routes[route.ID] = route
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, Peer.V6Setting and Peer.LoginExpirationEnabled can be updated.
|
||||||
func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) {
|
func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) {
|
||||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
@ -160,6 +221,20 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nb
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if peer.V6Setting != update.V6Setting {
|
||||||
|
peer.V6Setting = update.V6Setting
|
||||||
|
prevV6 := peer.IP6
|
||||||
|
v6StatusChanged, err := am.DeterminePeerV6(account, peer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if v6StatusChanged && peer.IP6 != nil {
|
||||||
|
am.StoreEvent(userID, peer.IP6.String(), account.Id, activity.PeerIPv6Enabled, peer.EventMeta(am.GetDNSDomain()))
|
||||||
|
} else if v6StatusChanged && peer.IP6 == nil {
|
||||||
|
am.StoreEvent(userID, prevV6.String(), account.Id, activity.PeerIPv6Disabled, peer.EventMeta(am.GetDNSDomain()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if peer.SSHEnabled != update.SSHEnabled {
|
if peer.SSHEnabled != update.SSHEnabled {
|
||||||
peer.SSHEnabled = update.SSHEnabled
|
peer.SSHEnabled = update.SSHEnabled
|
||||||
event := activity.PeerSSHEnabled
|
event := activity.PeerSSHEnabled
|
||||||
@ -431,6 +506,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
|
|||||||
Key: peer.Key,
|
Key: peer.Key,
|
||||||
SetupKey: upperKey,
|
SetupKey: upperKey,
|
||||||
IP: nextIp,
|
IP: nextIp,
|
||||||
|
IP6: nil,
|
||||||
Meta: peer.Meta,
|
Meta: peer.Meta,
|
||||||
Name: peer.Meta.Hostname,
|
Name: peer.Meta.Hostname,
|
||||||
DNSLabel: newLabel,
|
DNSLabel: newLabel,
|
||||||
@ -443,6 +519,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
|
|||||||
LoginExpirationEnabled: addedByUser,
|
LoginExpirationEnabled: addedByUser,
|
||||||
Ephemeral: ephemeral,
|
Ephemeral: ephemeral,
|
||||||
Location: peer.Location,
|
Location: peer.Location,
|
||||||
|
V6Setting: peer.V6Setting, // empty string "" corresponds to "auto"
|
||||||
}
|
}
|
||||||
|
|
||||||
// add peer to 'All' group
|
// add peer to 'All' group
|
||||||
@ -475,6 +552,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
|
|||||||
|
|
||||||
newPeer = am.integratedPeerValidator.PreparePeer(account.Id, newPeer, account.GetPeerGroupsList(newPeer.ID), account.Settings.Extra)
|
newPeer = am.integratedPeerValidator.PreparePeer(account.Id, newPeer, account.GetPeerGroupsList(newPeer.ID), account.Settings.Extra)
|
||||||
|
|
||||||
|
_, err = am.DeterminePeerV6(account, newPeer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if addedByUser {
|
if addedByUser {
|
||||||
user, err := account.FindUser(userID)
|
user, err := account.FindUser(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -676,6 +758,14 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw
|
|||||||
shouldStoreAccount = true
|
shouldStoreAccount = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updated, err = am.DeterminePeerV6(account, peer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if updated {
|
||||||
|
shouldStoreAccount = true
|
||||||
|
}
|
||||||
|
|
||||||
peer, err = am.checkAndUpdatePeerSSHKey(peer, account, login.SSHKey)
|
peer, err = am.checkAndUpdatePeerSSHKey(peer, account, login.SSHKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -20,6 +20,8 @@ type Peer struct {
|
|||||||
SetupKey string
|
SetupKey string
|
||||||
// IP address of the Peer
|
// IP address of the Peer
|
||||||
IP net.IP `gorm:"serializer:json"`
|
IP net.IP `gorm:"serializer:json"`
|
||||||
|
// IPv6 address of the Peer
|
||||||
|
IP6 *net.IP `gorm:"uniqueIndex:idx_peers_account_id_ip6"`
|
||||||
// Meta is a Peer system meta data
|
// Meta is a Peer system meta data
|
||||||
Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"`
|
Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"`
|
||||||
// Name is peer's name (machine name)
|
// Name is peer's name (machine name)
|
||||||
@ -44,10 +46,23 @@ type Peer struct {
|
|||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
// Indicate ephemeral peer attribute
|
// Indicate ephemeral peer attribute
|
||||||
Ephemeral bool
|
Ephemeral bool
|
||||||
// Geo location based on connection IP
|
// Geolocation based on connection IP
|
||||||
Location Location `gorm:"embedded;embeddedPrefix:location_"`
|
Location Location `gorm:"embedded;embeddedPrefix:location_"`
|
||||||
|
// Whether IPv6 should be enabled or not.
|
||||||
|
V6Setting V6Status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type V6Status string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Inherit IPv6 settings from groups (=> if one group the peer is a member of has IPv6 enabled, it will be enabled).
|
||||||
|
V6Auto V6Status = ""
|
||||||
|
// Enable IPv6 regardless of group settings, as long as it is supported.
|
||||||
|
V6Enabled V6Status = "enabled"
|
||||||
|
// Disable IPv6 regardless of group settings.
|
||||||
|
V6Disabled V6Status = "disabled"
|
||||||
|
)
|
||||||
|
|
||||||
type PeerStatus struct { //nolint:revive
|
type PeerStatus struct { //nolint:revive
|
||||||
// LastSeen is the last time peer was connected to the management service
|
// LastSeen is the last time peer was connected to the management service
|
||||||
LastSeen time.Time
|
LastSeen time.Time
|
||||||
@ -96,6 +111,7 @@ type PeerSystemMeta struct { //nolint:revive
|
|||||||
SystemProductName string
|
SystemProductName string
|
||||||
SystemManufacturer string
|
SystemManufacturer string
|
||||||
Environment Environment `gorm:"serializer:json"`
|
Environment Environment `gorm:"serializer:json"`
|
||||||
|
Ipv6Supported bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
|
func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
|
||||||
@ -130,7 +146,8 @@ func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
|
|||||||
p.SystemProductName == other.SystemProductName &&
|
p.SystemProductName == other.SystemProductName &&
|
||||||
p.SystemManufacturer == other.SystemManufacturer &&
|
p.SystemManufacturer == other.SystemManufacturer &&
|
||||||
p.Environment.Cloud == other.Environment.Cloud &&
|
p.Environment.Cloud == other.Environment.Cloud &&
|
||||||
p.Environment.Platform == other.Environment.Platform
|
p.Environment.Platform == other.Environment.Platform &&
|
||||||
|
p.Ipv6Supported == other.Ipv6Supported
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user.
|
// AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user.
|
||||||
@ -150,6 +167,7 @@ func (p *Peer) Copy() *Peer {
|
|||||||
Key: p.Key,
|
Key: p.Key,
|
||||||
SetupKey: p.SetupKey,
|
SetupKey: p.SetupKey,
|
||||||
IP: p.IP,
|
IP: p.IP,
|
||||||
|
IP6: p.IP6,
|
||||||
Meta: p.Meta,
|
Meta: p.Meta,
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
DNSLabel: p.DNSLabel,
|
DNSLabel: p.DNSLabel,
|
||||||
@ -162,6 +180,7 @@ func (p *Peer) Copy() *Peer {
|
|||||||
CreatedAt: p.CreatedAt,
|
CreatedAt: p.CreatedAt,
|
||||||
Ephemeral: p.Ephemeral,
|
Ephemeral: p.Ephemeral,
|
||||||
Location: p.Location,
|
Location: p.Location,
|
||||||
|
V6Setting: p.V6Setting,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -136,6 +137,112 @@ func TestAccountManager_GetNetworkMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefaultAccountManager_DeterminePeerV6(t *testing.T) {
|
||||||
|
manager, err := createManager(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedId := "test_account"
|
||||||
|
userId := "account_creator"
|
||||||
|
account, err := createAccount(manager, expectedId, userId, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userId, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error creating setup key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKey1, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peer1, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
|
||||||
|
Key: peerKey1.PublicKey().String(),
|
||||||
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
|
Hostname: "test-peer-1",
|
||||||
|
Ipv6Supported: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expecting peer to be added, got failure %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKey2, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peer2, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
|
||||||
|
Key: peerKey2.PublicKey().String(),
|
||||||
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
|
Hostname: "test-peer-2",
|
||||||
|
Ipv6Supported: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expecting peer to be added, got failure %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err = manager.Store.GetAccount(account.Id)
|
||||||
|
require.NoError(t, err, "unable to fetch updated account")
|
||||||
|
|
||||||
|
// Check if automatic setting defaults to "false".
|
||||||
|
// (Other tests for interactions between the automatic setting and group/route memberships are already covered in
|
||||||
|
// group_test.go and route_test.go)
|
||||||
|
_, err = manager.DeterminePeerV6(account, peer1)
|
||||||
|
require.NoError(t, err, "unable to determine effective peer IPv6 status")
|
||||||
|
_, err = manager.DeterminePeerV6(account, peer2)
|
||||||
|
require.NoError(t, err, "unable to determine effective peer IPv6 status")
|
||||||
|
require.Nil(t, peer1.IP6, "peer1 IPv6 address did not default to nil.")
|
||||||
|
require.Nil(t, peer1.IP6, "peer2 IPv6 address did not default to nil.")
|
||||||
|
|
||||||
|
peer1.V6Setting = nbpeer.V6Disabled
|
||||||
|
peer2.V6Setting = nbpeer.V6Disabled
|
||||||
|
_, err = manager.DeterminePeerV6(account, peer1)
|
||||||
|
require.NoError(t, err, "unable to determine effective peer IPv6 status")
|
||||||
|
_, err = manager.DeterminePeerV6(account, peer2)
|
||||||
|
require.NoError(t, err, "unable to determine effective peer IPv6 status")
|
||||||
|
require.Nil(t, peer1.IP6, "peer1 IPv6 address is not nil even though it is force disabled.")
|
||||||
|
require.Nil(t, peer2.IP6, "peer2 IPv6 address is not nil even though it is force disabled and unsupported.")
|
||||||
|
|
||||||
|
peer1.V6Setting = nbpeer.V6Enabled
|
||||||
|
peer2.V6Setting = nbpeer.V6Enabled
|
||||||
|
_, err = manager.DeterminePeerV6(account, peer1)
|
||||||
|
require.NoError(t, err, "unable to determine effective peer IPv6 status")
|
||||||
|
_, err = manager.DeterminePeerV6(account, peer2)
|
||||||
|
require.Error(t, err, "determining peer2 IPv6 address should fail as it is force enabled, but unsupported.")
|
||||||
|
require.NotNil(t, peer1.IP6, "peer1 IPv6 address is nil even though it is force enabled.")
|
||||||
|
require.Nil(t, peer2.IP6, "peer2 IPv6 address is not nil even though it is unsupported.")
|
||||||
|
|
||||||
|
// Test whether disabling IPv6 will disable IPv6 routes.
|
||||||
|
allGroup, err := account.GetGroupAll()
|
||||||
|
require.NoError(t, err, "unable to retrieve all group")
|
||||||
|
route, err := manager.CreateRoute(account.Id, "2001:db8:2345:6789::/64", peer1.ID, make([]string, 0), "testroute", "testnet", false, 9999, []string{allGroup.ID}, true, userID)
|
||||||
|
require.NoError(t, err, "unable to create test IPv6 route")
|
||||||
|
require.True(t, route.Enabled, "created IPv6 test route should be enabled")
|
||||||
|
|
||||||
|
peer1.V6Setting = nbpeer.V6Disabled
|
||||||
|
_, err = manager.UpdatePeer(account.Id, userID, peer1)
|
||||||
|
require.NoError(t, err, "unable to update peer")
|
||||||
|
|
||||||
|
account, err = manager.Store.GetAccount(account.Id)
|
||||||
|
require.NoError(t, err, "unable to fetch updated account")
|
||||||
|
|
||||||
|
route = account.Routes[route.ID]
|
||||||
|
require.False(t, route.Enabled, "disabling IPv6 for a peer should disable all of its IPv6 routes.")
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) {
|
func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) {
|
||||||
// TODO: disable until we start use policy again
|
// TODO: disable until we start use policy again
|
||||||
t.Skip()
|
t.Skip()
|
||||||
|
@ -195,6 +195,8 @@ type FirewallRule struct {
|
|||||||
// PeerIP of the peer
|
// PeerIP of the peer
|
||||||
PeerIP string
|
PeerIP string
|
||||||
|
|
||||||
|
PeerIP6 string
|
||||||
|
|
||||||
// Direction of the traffic
|
// Direction of the traffic
|
||||||
Direction int
|
Direction int
|
||||||
|
|
||||||
@ -278,8 +280,14 @@ func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*nbpeer.Peer, in
|
|||||||
peersExists[peer.ID] = struct{}{}
|
peersExists[peer.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ip6 := ""
|
||||||
|
if peer.IP6 != nil {
|
||||||
|
ip6 = peer.IP6.String()
|
||||||
|
}
|
||||||
|
|
||||||
fr := FirewallRule{
|
fr := FirewallRule{
|
||||||
PeerIP: peer.IP.String(),
|
PeerIP: peer.IP.String(),
|
||||||
|
PeerIP6: ip6,
|
||||||
Direction: direction,
|
Direction: direction,
|
||||||
Action: string(rule.Action),
|
Action: string(rule.Action),
|
||||||
Protocol: string(rule.Protocol),
|
Protocol: string(rule.Protocol),
|
||||||
@ -287,6 +295,7 @@ func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*nbpeer.Peer, in
|
|||||||
|
|
||||||
if isAll {
|
if isAll {
|
||||||
fr.PeerIP = "0.0.0.0"
|
fr.PeerIP = "0.0.0.0"
|
||||||
|
fr.PeerIP6 = "::"
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleID := (rule.ID + fr.PeerIP + strconv.Itoa(direction) +
|
ruleID := (rule.ID + fr.PeerIP + strconv.Itoa(direction) +
|
||||||
@ -474,6 +483,7 @@ func toProtocolFirewallRules(update []*FirewallRule) []*proto.FirewallRule {
|
|||||||
|
|
||||||
result[i] = &proto.FirewallRule{
|
result[i] = &proto.FirewallRule{
|
||||||
PeerIP: update[i].PeerIP,
|
PeerIP: update[i].PeerIP,
|
||||||
|
PeerIP6: update[i].PeerIP6,
|
||||||
Direction: direction,
|
Direction: direction,
|
||||||
Action: action,
|
Action: action,
|
||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
|
@ -14,16 +14,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestAccount_getPeersByPolicy(t *testing.T) {
|
func TestAccount_getPeersByPolicy(t *testing.T) {
|
||||||
|
peerAIP6 := net.ParseIP("2001:db8:abcd:1234::2")
|
||||||
|
peerBIP6 := net.ParseIP("2001:db8:abcd:1234::3")
|
||||||
account := &Account{
|
account := &Account{
|
||||||
Peers: map[string]*nbpeer.Peer{
|
Peers: map[string]*nbpeer.Peer{
|
||||||
"peerA": {
|
"peerA": {
|
||||||
ID: "peerA",
|
ID: "peerA",
|
||||||
IP: net.ParseIP("100.65.14.88"),
|
IP: net.ParseIP("100.65.14.88"),
|
||||||
|
IP6: &peerAIP6,
|
||||||
Status: &nbpeer.PeerStatus{},
|
Status: &nbpeer.PeerStatus{},
|
||||||
},
|
},
|
||||||
"peerB": {
|
"peerB": {
|
||||||
ID: "peerB",
|
ID: "peerB",
|
||||||
IP: net.ParseIP("100.65.80.39"),
|
IP: net.ParseIP("100.65.80.39"),
|
||||||
|
IP6: &peerBIP6,
|
||||||
Status: &nbpeer.PeerStatus{},
|
Status: &nbpeer.PeerStatus{},
|
||||||
},
|
},
|
||||||
"peerC": {
|
"peerC": {
|
||||||
@ -161,6 +165,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
|||||||
epectedFirewallRules := []*FirewallRule{
|
epectedFirewallRules := []*FirewallRule{
|
||||||
{
|
{
|
||||||
PeerIP: "0.0.0.0",
|
PeerIP: "0.0.0.0",
|
||||||
|
PeerIP6: "::",
|
||||||
Direction: firewallRuleDirectionIN,
|
Direction: firewallRuleDirectionIN,
|
||||||
Action: "accept",
|
Action: "accept",
|
||||||
Protocol: "all",
|
Protocol: "all",
|
||||||
@ -168,6 +173,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
PeerIP: "0.0.0.0",
|
PeerIP: "0.0.0.0",
|
||||||
|
PeerIP6: "::",
|
||||||
Direction: firewallRuleDirectionOUT,
|
Direction: firewallRuleDirectionOUT,
|
||||||
Action: "accept",
|
Action: "accept",
|
||||||
Protocol: "all",
|
Protocol: "all",
|
||||||
@ -175,6 +181,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
PeerIP: "100.65.14.88",
|
PeerIP: "100.65.14.88",
|
||||||
|
PeerIP6: "2001:db8:abcd:1234::2",
|
||||||
Direction: firewallRuleDirectionIN,
|
Direction: firewallRuleDirectionIN,
|
||||||
Action: "accept",
|
Action: "accept",
|
||||||
Protocol: "all",
|
Protocol: "all",
|
||||||
@ -182,6 +189,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
PeerIP: "100.65.14.88",
|
PeerIP: "100.65.14.88",
|
||||||
|
PeerIP6: "2001:db8:abcd:1234::2",
|
||||||
Direction: firewallRuleDirectionOUT,
|
Direction: firewallRuleDirectionOUT,
|
||||||
Action: "accept",
|
Action: "accept",
|
||||||
Protocol: "all",
|
Protocol: "all",
|
||||||
@ -678,6 +686,7 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
|||||||
expectedFirewallRules := []*FirewallRule{
|
expectedFirewallRules := []*FirewallRule{
|
||||||
{
|
{
|
||||||
PeerIP: "0.0.0.0",
|
PeerIP: "0.0.0.0",
|
||||||
|
PeerIP6: "::",
|
||||||
Direction: firewallRuleDirectionOUT,
|
Direction: firewallRuleDirectionOUT,
|
||||||
Action: "accept",
|
Action: "accept",
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
@ -180,6 +181,25 @@ func (am *DefaultAccountManager) CreateRoute(accountID, network, peerID string,
|
|||||||
|
|
||||||
account.Routes[newRoute.ID] = &newRoute
|
account.Routes[newRoute.ID] = &newRoute
|
||||||
|
|
||||||
|
// IPv6 route must only be created with IPv6 enabled peers, creating an IPv6 enabled route may enable IPv6 for
|
||||||
|
// peers with V6Setting = Auto.
|
||||||
|
if peerID != "" && prefixType == route.IPv6Network && newRoute.Enabled {
|
||||||
|
peer := account.GetPeer(peerID)
|
||||||
|
if peer.V6Setting == nbpeer.V6Disabled || !peer.Meta.Ipv6Supported {
|
||||||
|
return nil, status.Errorf(
|
||||||
|
status.InvalidArgument,
|
||||||
|
"IPv6 must be enabled for peer %s to be used in route %s",
|
||||||
|
peer.Name, newPrefix.String())
|
||||||
|
} else if peer.IP6 == nil {
|
||||||
|
_, err = am.DeterminePeerV6(account, peer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
account.UpdatePeer(peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
account.Network.IncSerial()
|
account.Network.IncSerial()
|
||||||
if err = am.Store.SaveAccount(account); err != nil {
|
if err = am.Store.SaveAccount(account); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -222,6 +242,16 @@ func (am *DefaultAccountManager) SaveRoute(accountID, userID string, routeToSave
|
|||||||
return status.Errorf(status.InvalidArgument, "peer with ID and peer groups should not be provided at the same time")
|
return status.Errorf(status.InvalidArgument, "peer with ID and peer groups should not be provided at the same time")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if routeToSave.Peer != "" {
|
||||||
|
peer := account.GetPeer(routeToSave.Peer)
|
||||||
|
if peer == nil {
|
||||||
|
return status.Errorf(status.InvalidArgument, "provided peer does not exist")
|
||||||
|
}
|
||||||
|
if routeToSave.NetworkType == route.IPv6Network && routeToSave.Enabled && (!peer.Meta.Ipv6Supported || peer.V6Setting == nbpeer.V6Disabled) {
|
||||||
|
return status.Errorf(status.InvalidArgument, "peer with IPv6 disabled can't be used for IPv6 route")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(routeToSave.PeerGroups) > 0 {
|
if len(routeToSave.PeerGroups) > 0 {
|
||||||
err = validateGroups(routeToSave.PeerGroups, account.Groups)
|
err = validateGroups(routeToSave.PeerGroups, account.Groups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -239,8 +269,49 @@ func (am *DefaultAccountManager) SaveRoute(accountID, userID string, routeToSave
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldRoute := account.Routes[routeToSave.ID]
|
||||||
|
|
||||||
account.Routes[routeToSave.ID] = routeToSave
|
account.Routes[routeToSave.ID] = routeToSave
|
||||||
|
|
||||||
|
// Check if old peer's IPv6 status needs to be recalculated.
|
||||||
|
// Must happen if route is an IPv6 route, and either:
|
||||||
|
// - The routing peer has changed
|
||||||
|
// - The route has been disabled
|
||||||
|
// - (the route has been enabled) => caught in the next if-block
|
||||||
|
if oldRoute.Peer != "" && routeToSave.NetworkType == route.IPv6Network && ((oldRoute.Enabled && !routeToSave.Enabled) || oldRoute.Peer != routeToSave.Peer) {
|
||||||
|
oldPeer := account.GetPeer(oldRoute.Peer)
|
||||||
|
if oldPeer.V6Setting == nbpeer.V6Auto {
|
||||||
|
changed, err := am.DeterminePeerV6(account, oldPeer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
account.UpdatePeer(oldPeer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if new peer's IPv6 status needs to be recalculated.
|
||||||
|
// Must happen if route is an IPv6 route, and either:
|
||||||
|
// - The routing peer has changed
|
||||||
|
// - The route has been enabled
|
||||||
|
// - (The route has been disabled) => caught in previous if-block
|
||||||
|
if oldRoute.Peer != "" && routeToSave.NetworkType == route.IPv6Network && routeToSave.Enabled && (!oldRoute.Enabled || oldRoute.Peer != routeToSave.Peer) {
|
||||||
|
newPeer := account.GetPeer(routeToSave.Peer)
|
||||||
|
if newPeer.V6Setting == nbpeer.V6Disabled || !newPeer.Meta.Ipv6Supported {
|
||||||
|
return status.Errorf(
|
||||||
|
status.InvalidArgument,
|
||||||
|
"IPv6 must be enabled for peer %s to be used in route %s",
|
||||||
|
newPeer.Name, routeToSave.Network.String())
|
||||||
|
} else if newPeer.IP6 == nil {
|
||||||
|
_, err = am.DeterminePeerV6(account, newPeer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account.UpdatePeer(newPeer)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
account.Network.IncSerial()
|
account.Network.IncSerial()
|
||||||
if err = am.Store.SaveAccount(account); err != nil {
|
if err = am.Store.SaveAccount(account); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -269,6 +340,21 @@ func (am *DefaultAccountManager) DeleteRoute(accountID string, routeID route.ID,
|
|||||||
}
|
}
|
||||||
delete(account.Routes, routeID)
|
delete(account.Routes, routeID)
|
||||||
|
|
||||||
|
// If the route was an IPv6 route, deleting it may update the automatic IPv6 enablement status of its routing peers,
|
||||||
|
// check if this is the case and update accordingly.
|
||||||
|
if routy.Peer != "" && routy.Enabled && routy.NetworkType == route.IPv6Network {
|
||||||
|
oldPeer := account.GetPeer(routy.Peer)
|
||||||
|
if oldPeer.V6Setting == nbpeer.V6Auto {
|
||||||
|
changed, err := am.DeterminePeerV6(account, oldPeer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
account.UpdatePeer(oldPeer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
account.Network.IncSerial()
|
account.Network.IncSerial()
|
||||||
if err = am.Store.SaveAccount(account); err != nil {
|
if err = am.Store.SaveAccount(account); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -319,6 +319,88 @@ func TestCreateRoute(t *testing.T) {
|
|||||||
errFunc: require.Error,
|
errFunc: require.Error,
|
||||||
shouldCreate: false,
|
shouldCreate: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 route on peer with disabled IPv6 should fail",
|
||||||
|
inputArgs: input{
|
||||||
|
network: "2001:db8:7654:3210::/64",
|
||||||
|
netID: "NewId",
|
||||||
|
peerKey: peer4ID,
|
||||||
|
description: "",
|
||||||
|
masquerade: false,
|
||||||
|
metric: 9999,
|
||||||
|
enabled: true,
|
||||||
|
groups: []string{routeGroup1},
|
||||||
|
},
|
||||||
|
errFunc: require.Error,
|
||||||
|
shouldCreate: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 route on peer with unsupported IPv6 should fail",
|
||||||
|
inputArgs: input{
|
||||||
|
network: "2001:db8:7654:3210::/64",
|
||||||
|
netID: "NewId",
|
||||||
|
peerKey: peer5ID,
|
||||||
|
description: "",
|
||||||
|
masquerade: false,
|
||||||
|
metric: 9999,
|
||||||
|
enabled: true,
|
||||||
|
groups: []string{routeGroup1},
|
||||||
|
},
|
||||||
|
errFunc: require.Error,
|
||||||
|
shouldCreate: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 route on peer with automatic IPv6 setting should succeed",
|
||||||
|
inputArgs: input{
|
||||||
|
network: "2001:db8:7654:3210::/64",
|
||||||
|
netID: "NewId",
|
||||||
|
peerKey: peer1ID,
|
||||||
|
description: "",
|
||||||
|
masquerade: false,
|
||||||
|
metric: 9999,
|
||||||
|
enabled: true,
|
||||||
|
groups: []string{routeGroup1},
|
||||||
|
},
|
||||||
|
errFunc: require.NoError,
|
||||||
|
shouldCreate: true,
|
||||||
|
expectedRoute: &route.Route{
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:7654:3210::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
NetID: "NewId",
|
||||||
|
Peer: peer1ID,
|
||||||
|
Description: "",
|
||||||
|
Masquerade: false,
|
||||||
|
Metric: 9999,
|
||||||
|
Enabled: true,
|
||||||
|
Groups: []string{routeGroup1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 route on peer with force enabled IPv6 setting should succeed",
|
||||||
|
inputArgs: input{
|
||||||
|
network: "2001:db8:7654:3211::/64",
|
||||||
|
netID: "NewId",
|
||||||
|
peerKey: peer2ID,
|
||||||
|
description: "",
|
||||||
|
masquerade: false,
|
||||||
|
metric: 9999,
|
||||||
|
enabled: true,
|
||||||
|
groups: []string{routeGroup1},
|
||||||
|
},
|
||||||
|
errFunc: require.NoError,
|
||||||
|
shouldCreate: true,
|
||||||
|
expectedRoute: &route.Route{
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:7654:3211::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
NetID: "NewId",
|
||||||
|
Peer: peer2ID,
|
||||||
|
Description: "",
|
||||||
|
Masquerade: false,
|
||||||
|
Metric: 9999,
|
||||||
|
Enabled: true,
|
||||||
|
Groups: []string{routeGroup1},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
@ -377,6 +459,8 @@ func TestCreateRoute(t *testing.T) {
|
|||||||
func TestSaveRoute(t *testing.T) {
|
func TestSaveRoute(t *testing.T) {
|
||||||
validPeer := peer2ID
|
validPeer := peer2ID
|
||||||
validUsedPeer := peer5ID
|
validUsedPeer := peer5ID
|
||||||
|
ipv6DisabledPeer := peer4ID
|
||||||
|
ipv6UnsupportedPeer := peer5ID
|
||||||
invalidPeer := "nonExisting"
|
invalidPeer := "nonExisting"
|
||||||
validPrefix := netip.MustParsePrefix("192.168.0.0/24")
|
validPrefix := netip.MustParsePrefix("192.168.0.0/24")
|
||||||
invalidPrefix, _ := netip.ParsePrefix("192.168.0.0/34")
|
invalidPrefix, _ := netip.ParsePrefix("192.168.0.0/34")
|
||||||
@ -467,7 +551,7 @@ func TestSaveRoute(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Both peer and peers_roup Provided Should Fail",
|
name: "Both peer and peers_group Provided Should Fail",
|
||||||
existingRoute: &route.Route{
|
existingRoute: &route.Route{
|
||||||
ID: "testingRoute",
|
ID: "testingRoute",
|
||||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||||
@ -517,6 +601,38 @@ func TestSaveRoute(t *testing.T) {
|
|||||||
newPeer: &invalidPeer,
|
newPeer: &invalidPeer,
|
||||||
errFunc: require.Error,
|
errFunc: require.Error,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 disabled host should not be allowed as peer for IPv6 route",
|
||||||
|
existingRoute: &route.Route{
|
||||||
|
ID: "testingRoute",
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:4321:5678::/64"),
|
||||||
|
NetID: validNetID,
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Description: "super",
|
||||||
|
Masquerade: false,
|
||||||
|
Metric: 9999,
|
||||||
|
Enabled: true,
|
||||||
|
Groups: []string{routeGroup1},
|
||||||
|
},
|
||||||
|
newPeer: &ipv6DisabledPeer,
|
||||||
|
errFunc: require.Error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 unsupported host should not be allowed as peer for IPv6 route",
|
||||||
|
existingRoute: &route.Route{
|
||||||
|
ID: "testingRoute",
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:4321:5678::/64"),
|
||||||
|
NetID: validNetID,
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
Description: "super",
|
||||||
|
Masquerade: false,
|
||||||
|
Metric: 9999,
|
||||||
|
Enabled: true,
|
||||||
|
Groups: []string{routeGroup1},
|
||||||
|
},
|
||||||
|
newPeer: &ipv6UnsupportedPeer,
|
||||||
|
errFunc: require.Error,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid Metric Should Fail",
|
name: "Invalid Metric Should Fail",
|
||||||
existingRoute: &route.Route{
|
existingRoute: &route.Route{
|
||||||
@ -772,7 +888,7 @@ func TestDeleteRoute(t *testing.T) {
|
|||||||
ID: "testingRoute",
|
ID: "testingRoute",
|
||||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||||
NetworkType: route.IPv4Network,
|
NetworkType: route.IPv4Network,
|
||||||
Peer: peer1Key,
|
Peer: peer1ID,
|
||||||
Description: "super",
|
Description: "super",
|
||||||
Masquerade: false,
|
Masquerade: false,
|
||||||
Metric: 9999,
|
Metric: 9999,
|
||||||
@ -812,6 +928,77 @@ func TestDeleteRoute(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteIPv6Consistency(t *testing.T) {
|
||||||
|
testingRoute := &route.Route{
|
||||||
|
ID: "testingRoute",
|
||||||
|
Network: netip.MustParsePrefix("2001:db8:0987:6543::/64"),
|
||||||
|
NetworkType: route.IPv6Network,
|
||||||
|
NetID: existingRouteID,
|
||||||
|
Peer: peer1ID,
|
||||||
|
Description: "super",
|
||||||
|
Masquerade: false,
|
||||||
|
Metric: 9999,
|
||||||
|
Enabled: false,
|
||||||
|
Groups: []string{routeGroup1},
|
||||||
|
}
|
||||||
|
|
||||||
|
am, err := createRouterManager(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to create account manager")
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := initTestRouteAccount(t, am)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to init testing account")
|
||||||
|
}
|
||||||
|
|
||||||
|
account.Routes[testingRoute.ID] = testingRoute
|
||||||
|
|
||||||
|
err = am.Store.SaveAccount(account)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to save account")
|
||||||
|
}
|
||||||
|
|
||||||
|
savedAccount, err := am.Store.GetAccount(account.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to retrieve saved account with error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testingRoute = savedAccount.Routes[testingRoute.ID]
|
||||||
|
testingRoute.Enabled = true
|
||||||
|
|
||||||
|
err = am.SaveRoute(account.Id, userID, testingRoute)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to save route")
|
||||||
|
}
|
||||||
|
|
||||||
|
savedAccount, err = am.Store.GetAccount(account.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to retrieve saved account with error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peer := savedAccount.GetPeer(peer1ID)
|
||||||
|
require.NotNil(t, peer.IP6, "peer with enabled IPv6 route in automatic setting must have IPv6 active.")
|
||||||
|
|
||||||
|
err = am.DeleteRoute(account.Id, testingRoute.ID, userID)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("deleting route failed with error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
savedAccount, err = am.Store.GetAccount(account.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to retrieve saved account with error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, found := savedAccount.Routes[testingRoute.ID]
|
||||||
|
if found {
|
||||||
|
t.Error("route shouldn't be found after delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
peer = savedAccount.GetPeer(peer1ID)
|
||||||
|
require.Nil(t, peer.IP6, "disabling the only IPv6 route for a peer with automatic IPv6 setting should disable IPv6")
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetNetworkMap_RouteSyncPeerGroups(t *testing.T) {
|
func TestGetNetworkMap_RouteSyncPeerGroups(t *testing.T) {
|
||||||
baseRoute := &route.Route{
|
baseRoute := &route.Route{
|
||||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||||
@ -1055,14 +1242,15 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
|||||||
Name: "test-host1@netbird.io",
|
Name: "test-host1@netbird.io",
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Meta: nbpeer.PeerSystemMeta{
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
Hostname: "test-host1@netbird.io",
|
Hostname: "test-host1@netbird.io",
|
||||||
GoOS: "linux",
|
GoOS: "linux",
|
||||||
Kernel: "Linux",
|
Kernel: "Linux",
|
||||||
Core: "21.04",
|
Core: "21.04",
|
||||||
Platform: "x86_64",
|
Platform: "x86_64",
|
||||||
OS: "Ubuntu",
|
OS: "Ubuntu",
|
||||||
WtVersion: "development",
|
WtVersion: "development",
|
||||||
UIVersion: "development",
|
UIVersion: "development",
|
||||||
|
Ipv6Supported: true,
|
||||||
},
|
},
|
||||||
Status: &nbpeer.PeerStatus{},
|
Status: &nbpeer.PeerStatus{},
|
||||||
}
|
}
|
||||||
@ -1073,24 +1261,32 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
ips = account.getTakenIP6s()
|
||||||
|
peer2IP6, err := AllocatePeerIP6(account.Network.Net, ips)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
peer2 := &nbpeer.Peer{
|
peer2 := &nbpeer.Peer{
|
||||||
IP: peer2IP,
|
IP: peer2IP,
|
||||||
|
IP6: &peer2IP6,
|
||||||
ID: peer2ID,
|
ID: peer2ID,
|
||||||
Key: peer2Key,
|
Key: peer2Key,
|
||||||
Name: "test-host2@netbird.io",
|
Name: "test-host2@netbird.io",
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Meta: nbpeer.PeerSystemMeta{
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
Hostname: "test-host2@netbird.io",
|
Hostname: "test-host2@netbird.io",
|
||||||
GoOS: "linux",
|
GoOS: "linux",
|
||||||
Kernel: "Linux",
|
Kernel: "Linux",
|
||||||
Core: "21.04",
|
Core: "21.04",
|
||||||
Platform: "x86_64",
|
Platform: "x86_64",
|
||||||
OS: "Ubuntu",
|
OS: "Ubuntu",
|
||||||
WtVersion: "development",
|
WtVersion: "development",
|
||||||
UIVersion: "development",
|
UIVersion: "development",
|
||||||
|
Ipv6Supported: true,
|
||||||
},
|
},
|
||||||
Status: &nbpeer.PeerStatus{},
|
V6Setting: nbpeer.V6Enabled,
|
||||||
|
Status: &nbpeer.PeerStatus{},
|
||||||
}
|
}
|
||||||
account.Peers[peer2.ID] = peer2
|
account.Peers[peer2.ID] = peer2
|
||||||
|
|
||||||
@ -1099,24 +1295,32 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
ips = account.getTakenIP6s()
|
||||||
|
peer3IP6, err := AllocatePeerIP6(account.Network.Net, ips)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
peer3 := &nbpeer.Peer{
|
peer3 := &nbpeer.Peer{
|
||||||
IP: peer3IP,
|
IP: peer3IP,
|
||||||
|
IP6: &peer3IP6,
|
||||||
ID: peer3ID,
|
ID: peer3ID,
|
||||||
Key: peer3Key,
|
Key: peer3Key,
|
||||||
Name: "test-host3@netbird.io",
|
Name: "test-host3@netbird.io",
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Meta: nbpeer.PeerSystemMeta{
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
Hostname: "test-host3@netbird.io",
|
Hostname: "test-host3@netbird.io",
|
||||||
GoOS: "darwin",
|
GoOS: "darwin",
|
||||||
Kernel: "Darwin",
|
Kernel: "Darwin",
|
||||||
Core: "13.4.1",
|
Core: "13.4.1",
|
||||||
Platform: "arm64",
|
Platform: "arm64",
|
||||||
OS: "darwin",
|
OS: "darwin",
|
||||||
WtVersion: "development",
|
WtVersion: "development",
|
||||||
UIVersion: "development",
|
UIVersion: "development",
|
||||||
|
Ipv6Supported: true,
|
||||||
},
|
},
|
||||||
Status: &nbpeer.PeerStatus{},
|
V6Setting: nbpeer.V6Enabled,
|
||||||
|
Status: &nbpeer.PeerStatus{},
|
||||||
}
|
}
|
||||||
account.Peers[peer3.ID] = peer3
|
account.Peers[peer3.ID] = peer3
|
||||||
|
|
||||||
@ -1133,14 +1337,15 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
|||||||
Name: "test-host4@netbird.io",
|
Name: "test-host4@netbird.io",
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Meta: nbpeer.PeerSystemMeta{
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
Hostname: "test-host4@netbird.io",
|
Hostname: "test-host4@netbird.io",
|
||||||
GoOS: "linux",
|
GoOS: "linux",
|
||||||
Kernel: "Linux",
|
Kernel: "Linux",
|
||||||
Core: "21.04",
|
Core: "21.04",
|
||||||
Platform: "x86_64",
|
Platform: "x86_64",
|
||||||
OS: "Ubuntu",
|
OS: "Ubuntu",
|
||||||
WtVersion: "development",
|
WtVersion: "development",
|
||||||
UIVersion: "development",
|
UIVersion: "development",
|
||||||
|
Ipv6Supported: false,
|
||||||
},
|
},
|
||||||
Status: &nbpeer.PeerStatus{},
|
Status: &nbpeer.PeerStatus{},
|
||||||
}
|
}
|
||||||
@ -1159,16 +1364,18 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
|||||||
Name: "test-host4@netbird.io",
|
Name: "test-host4@netbird.io",
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Meta: nbpeer.PeerSystemMeta{
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
Hostname: "test-host4@netbird.io",
|
Hostname: "test-host4@netbird.io",
|
||||||
GoOS: "linux",
|
GoOS: "linux",
|
||||||
Kernel: "Linux",
|
Kernel: "Linux",
|
||||||
Core: "21.04",
|
Core: "21.04",
|
||||||
Platform: "x86_64",
|
Platform: "x86_64",
|
||||||
OS: "Ubuntu",
|
OS: "Ubuntu",
|
||||||
WtVersion: "development",
|
WtVersion: "development",
|
||||||
UIVersion: "development",
|
UIVersion: "development",
|
||||||
|
Ipv6Supported: true,
|
||||||
},
|
},
|
||||||
Status: &nbpeer.PeerStatus{},
|
Status: &nbpeer.PeerStatus{},
|
||||||
|
V6Setting: nbpeer.V6Disabled,
|
||||||
}
|
}
|
||||||
account.Peers[peer5.ID] = peer5
|
account.Peers[peer5.ID] = peer5
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user