mirror of
https://github.com/netbirdio/netbird.git
synced 2024-12-14 02:41:34 +01:00
0c039274a4
This update adds new relay integration for NetBird clients. The new relay is based on web sockets and listens on a single port. - Adds new relay implementation with websocket with single port relaying mechanism - refactor peer connection logic, allowing upgrade and downgrade from/to P2P connection - peer connections are faster since it connects first to relay and then upgrades to P2P - maintains compatibility with old clients by not using the new relay - updates infrastructure scripts with new relay service
608 lines
16 KiB
Go
608 lines
16 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
|
|
"github.com/netbirdio/netbird/client/proto"
|
|
"github.com/netbirdio/netbird/version"
|
|
)
|
|
|
|
func init() {
|
|
loc, err := time.LoadLocation("UTC")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
time.Local = loc
|
|
}
|
|
|
|
var resp = &proto.StatusResponse{
|
|
Status: "Connected",
|
|
FullStatus: &proto.FullStatus{
|
|
Peers: []*proto.PeerState{
|
|
{
|
|
IP: "192.168.178.101",
|
|
PubKey: "Pubkey1",
|
|
Fqdn: "peer-1.awesome-domain.com",
|
|
ConnStatus: "Connected",
|
|
ConnStatusUpdate: timestamppb.New(time.Date(2001, time.Month(1), 1, 1, 1, 1, 0, time.UTC)),
|
|
Relayed: false,
|
|
LocalIceCandidateType: "",
|
|
RemoteIceCandidateType: "",
|
|
LocalIceCandidateEndpoint: "",
|
|
RemoteIceCandidateEndpoint: "",
|
|
LastWireguardHandshake: timestamppb.New(time.Date(2001, time.Month(1), 1, 1, 1, 2, 0, time.UTC)),
|
|
BytesRx: 200,
|
|
BytesTx: 100,
|
|
Routes: []string{
|
|
"10.1.0.0/24",
|
|
},
|
|
Latency: durationpb.New(time.Duration(10000000)),
|
|
},
|
|
{
|
|
IP: "192.168.178.102",
|
|
PubKey: "Pubkey2",
|
|
Fqdn: "peer-2.awesome-domain.com",
|
|
ConnStatus: "Connected",
|
|
ConnStatusUpdate: timestamppb.New(time.Date(2002, time.Month(2), 2, 2, 2, 2, 0, time.UTC)),
|
|
Relayed: true,
|
|
LocalIceCandidateType: "relay",
|
|
RemoteIceCandidateType: "prflx",
|
|
LocalIceCandidateEndpoint: "10.0.0.1:10001",
|
|
RemoteIceCandidateEndpoint: "10.0.10.1:10002",
|
|
LastWireguardHandshake: timestamppb.New(time.Date(2002, time.Month(2), 2, 2, 2, 3, 0, time.UTC)),
|
|
BytesRx: 2000,
|
|
BytesTx: 1000,
|
|
Latency: durationpb.New(time.Duration(10000000)),
|
|
},
|
|
},
|
|
ManagementState: &proto.ManagementState{
|
|
URL: "my-awesome-management.com:443",
|
|
Connected: true,
|
|
Error: "",
|
|
},
|
|
SignalState: &proto.SignalState{
|
|
URL: "my-awesome-signal.com:443",
|
|
Connected: true,
|
|
Error: "",
|
|
},
|
|
Relays: []*proto.RelayState{
|
|
{
|
|
URI: "stun:my-awesome-stun.com:3478",
|
|
Available: true,
|
|
Error: "",
|
|
},
|
|
{
|
|
URI: "turns:my-awesome-turn.com:443?transport=tcp",
|
|
Available: false,
|
|
Error: "context: deadline exceeded",
|
|
},
|
|
},
|
|
LocalPeerState: &proto.LocalPeerState{
|
|
IP: "192.168.178.100/16",
|
|
PubKey: "Some-Pub-Key",
|
|
KernelInterface: true,
|
|
Fqdn: "some-localhost.awesome-domain.com",
|
|
Routes: []string{
|
|
"10.10.0.0/24",
|
|
},
|
|
},
|
|
DnsServers: []*proto.NSGroupState{
|
|
{
|
|
Servers: []string{
|
|
"8.8.8.8:53",
|
|
},
|
|
Domains: nil,
|
|
Enabled: true,
|
|
Error: "",
|
|
},
|
|
{
|
|
Servers: []string{
|
|
"1.1.1.1:53",
|
|
"2.2.2.2:53",
|
|
},
|
|
Domains: []string{
|
|
"example.com",
|
|
"example.net",
|
|
},
|
|
Enabled: false,
|
|
Error: "timeout",
|
|
},
|
|
},
|
|
},
|
|
DaemonVersion: "0.14.1",
|
|
}
|
|
|
|
var overview = statusOutputOverview{
|
|
Peers: peersStateOutput{
|
|
Total: 2,
|
|
Connected: 2,
|
|
Details: []peerStateDetailOutput{
|
|
{
|
|
IP: "192.168.178.101",
|
|
PubKey: "Pubkey1",
|
|
FQDN: "peer-1.awesome-domain.com",
|
|
Status: "Connected",
|
|
LastStatusUpdate: time.Date(2001, 1, 1, 1, 1, 1, 0, time.UTC),
|
|
ConnType: "P2P",
|
|
IceCandidateType: iceCandidateType{
|
|
Local: "",
|
|
Remote: "",
|
|
},
|
|
IceCandidateEndpoint: iceCandidateType{
|
|
Local: "",
|
|
Remote: "",
|
|
},
|
|
LastWireguardHandshake: time.Date(2001, 1, 1, 1, 1, 2, 0, time.UTC),
|
|
TransferReceived: 200,
|
|
TransferSent: 100,
|
|
Routes: []string{
|
|
"10.1.0.0/24",
|
|
},
|
|
Latency: time.Duration(10000000),
|
|
},
|
|
{
|
|
IP: "192.168.178.102",
|
|
PubKey: "Pubkey2",
|
|
FQDN: "peer-2.awesome-domain.com",
|
|
Status: "Connected",
|
|
LastStatusUpdate: time.Date(2002, 2, 2, 2, 2, 2, 0, time.UTC),
|
|
ConnType: "Relayed",
|
|
IceCandidateType: iceCandidateType{
|
|
Local: "relay",
|
|
Remote: "prflx",
|
|
},
|
|
IceCandidateEndpoint: iceCandidateType{
|
|
Local: "10.0.0.1:10001",
|
|
Remote: "10.0.10.1:10002",
|
|
},
|
|
LastWireguardHandshake: time.Date(2002, 2, 2, 2, 2, 3, 0, time.UTC),
|
|
TransferReceived: 2000,
|
|
TransferSent: 1000,
|
|
Latency: time.Duration(10000000),
|
|
},
|
|
},
|
|
},
|
|
CliVersion: version.NetbirdVersion(),
|
|
DaemonVersion: "0.14.1",
|
|
ManagementState: managementStateOutput{
|
|
URL: "my-awesome-management.com:443",
|
|
Connected: true,
|
|
Error: "",
|
|
},
|
|
SignalState: signalStateOutput{
|
|
URL: "my-awesome-signal.com:443",
|
|
Connected: true,
|
|
Error: "",
|
|
},
|
|
Relays: relayStateOutput{
|
|
Total: 2,
|
|
Available: 1,
|
|
Details: []relayStateOutputDetail{
|
|
{
|
|
URI: "stun:my-awesome-stun.com:3478",
|
|
Available: true,
|
|
Error: "",
|
|
},
|
|
{
|
|
URI: "turns:my-awesome-turn.com:443?transport=tcp",
|
|
Available: false,
|
|
Error: "context: deadline exceeded",
|
|
},
|
|
},
|
|
},
|
|
IP: "192.168.178.100/16",
|
|
PubKey: "Some-Pub-Key",
|
|
KernelInterface: true,
|
|
FQDN: "some-localhost.awesome-domain.com",
|
|
NSServerGroups: []nsServerGroupStateOutput{
|
|
{
|
|
Servers: []string{
|
|
"8.8.8.8:53",
|
|
},
|
|
Domains: nil,
|
|
Enabled: true,
|
|
Error: "",
|
|
},
|
|
{
|
|
Servers: []string{
|
|
"1.1.1.1:53",
|
|
"2.2.2.2:53",
|
|
},
|
|
Domains: []string{
|
|
"example.com",
|
|
"example.net",
|
|
},
|
|
Enabled: false,
|
|
Error: "timeout",
|
|
},
|
|
},
|
|
Routes: []string{
|
|
"10.10.0.0/24",
|
|
},
|
|
}
|
|
|
|
func TestConversionFromFullStatusToOutputOverview(t *testing.T) {
|
|
convertedResult := convertToStatusOutputOverview(resp)
|
|
|
|
assert.Equal(t, overview, convertedResult)
|
|
}
|
|
|
|
func TestSortingOfPeers(t *testing.T) {
|
|
peers := []peerStateDetailOutput{
|
|
{
|
|
IP: "192.168.178.104",
|
|
},
|
|
{
|
|
IP: "192.168.178.102",
|
|
},
|
|
{
|
|
IP: "192.168.178.101",
|
|
},
|
|
{
|
|
IP: "192.168.178.105",
|
|
},
|
|
{
|
|
IP: "192.168.178.103",
|
|
},
|
|
}
|
|
|
|
sortPeersByIP(peers)
|
|
|
|
assert.Equal(t, peers[3].IP, "192.168.178.104")
|
|
}
|
|
|
|
func TestParsingToJSON(t *testing.T) {
|
|
jsonString, _ := parseToJSON(overview)
|
|
|
|
//@formatter:off
|
|
expectedJSONString := `
|
|
{
|
|
"peers": {
|
|
"total": 2,
|
|
"connected": 2,
|
|
"details": [
|
|
{
|
|
"fqdn": "peer-1.awesome-domain.com",
|
|
"netbirdIp": "192.168.178.101",
|
|
"publicKey": "Pubkey1",
|
|
"status": "Connected",
|
|
"lastStatusUpdate": "2001-01-01T01:01:01Z",
|
|
"connectionType": "P2P",
|
|
"iceCandidateType": {
|
|
"local": "",
|
|
"remote": ""
|
|
},
|
|
"iceCandidateEndpoint": {
|
|
"local": "",
|
|
"remote": ""
|
|
},
|
|
"relayAddress": "",
|
|
"lastWireguardHandshake": "2001-01-01T01:01:02Z",
|
|
"transferReceived": 200,
|
|
"transferSent": 100,
|
|
"latency": 10000000,
|
|
"quantumResistance": false,
|
|
"routes": [
|
|
"10.1.0.0/24"
|
|
]
|
|
},
|
|
{
|
|
"fqdn": "peer-2.awesome-domain.com",
|
|
"netbirdIp": "192.168.178.102",
|
|
"publicKey": "Pubkey2",
|
|
"status": "Connected",
|
|
"lastStatusUpdate": "2002-02-02T02:02:02Z",
|
|
"connectionType": "Relayed",
|
|
"iceCandidateType": {
|
|
"local": "relay",
|
|
"remote": "prflx"
|
|
},
|
|
"iceCandidateEndpoint": {
|
|
"local": "10.0.0.1:10001",
|
|
"remote": "10.0.10.1:10002"
|
|
},
|
|
"relayAddress": "",
|
|
"lastWireguardHandshake": "2002-02-02T02:02:03Z",
|
|
"transferReceived": 2000,
|
|
"transferSent": 1000,
|
|
"latency": 10000000,
|
|
"quantumResistance": false,
|
|
"routes": null
|
|
}
|
|
]
|
|
},
|
|
"cliVersion": "development",
|
|
"daemonVersion": "0.14.1",
|
|
"management": {
|
|
"url": "my-awesome-management.com:443",
|
|
"connected": true,
|
|
"error": ""
|
|
},
|
|
"signal": {
|
|
"url": "my-awesome-signal.com:443",
|
|
"connected": true,
|
|
"error": ""
|
|
},
|
|
"relays": {
|
|
"total": 2,
|
|
"available": 1,
|
|
"details": [
|
|
{
|
|
"uri": "stun:my-awesome-stun.com:3478",
|
|
"available": true,
|
|
"error": ""
|
|
},
|
|
{
|
|
"uri": "turns:my-awesome-turn.com:443?transport=tcp",
|
|
"available": false,
|
|
"error": "context: deadline exceeded"
|
|
}
|
|
]
|
|
},
|
|
"netbirdIp": "192.168.178.100/16",
|
|
"publicKey": "Some-Pub-Key",
|
|
"usesKernelInterface": true,
|
|
"fqdn": "some-localhost.awesome-domain.com",
|
|
"quantumResistance": false,
|
|
"quantumResistancePermissive": false,
|
|
"routes": [
|
|
"10.10.0.0/24"
|
|
],
|
|
"dnsServers": [
|
|
{
|
|
"servers": [
|
|
"8.8.8.8:53"
|
|
],
|
|
"domains": null,
|
|
"enabled": true,
|
|
"error": ""
|
|
},
|
|
{
|
|
"servers": [
|
|
"1.1.1.1:53",
|
|
"2.2.2.2:53"
|
|
],
|
|
"domains": [
|
|
"example.com",
|
|
"example.net"
|
|
],
|
|
"enabled": false,
|
|
"error": "timeout"
|
|
}
|
|
]
|
|
}`
|
|
// @formatter:on
|
|
|
|
var expectedJSON bytes.Buffer
|
|
require.NoError(t, json.Compact(&expectedJSON, []byte(expectedJSONString)))
|
|
|
|
assert.Equal(t, expectedJSON.String(), jsonString)
|
|
}
|
|
|
|
func TestParsingToYAML(t *testing.T) {
|
|
yaml, _ := parseToYAML(overview)
|
|
|
|
expectedYAML :=
|
|
`peers:
|
|
total: 2
|
|
connected: 2
|
|
details:
|
|
- fqdn: peer-1.awesome-domain.com
|
|
netbirdIp: 192.168.178.101
|
|
publicKey: Pubkey1
|
|
status: Connected
|
|
lastStatusUpdate: 2001-01-01T01:01:01Z
|
|
connectionType: P2P
|
|
iceCandidateType:
|
|
local: ""
|
|
remote: ""
|
|
iceCandidateEndpoint:
|
|
local: ""
|
|
remote: ""
|
|
relayAddress: ""
|
|
lastWireguardHandshake: 2001-01-01T01:01:02Z
|
|
transferReceived: 200
|
|
transferSent: 100
|
|
latency: 10ms
|
|
quantumResistance: false
|
|
routes:
|
|
- 10.1.0.0/24
|
|
- fqdn: peer-2.awesome-domain.com
|
|
netbirdIp: 192.168.178.102
|
|
publicKey: Pubkey2
|
|
status: Connected
|
|
lastStatusUpdate: 2002-02-02T02:02:02Z
|
|
connectionType: Relayed
|
|
iceCandidateType:
|
|
local: relay
|
|
remote: prflx
|
|
iceCandidateEndpoint:
|
|
local: 10.0.0.1:10001
|
|
remote: 10.0.10.1:10002
|
|
relayAddress: ""
|
|
lastWireguardHandshake: 2002-02-02T02:02:03Z
|
|
transferReceived: 2000
|
|
transferSent: 1000
|
|
latency: 10ms
|
|
quantumResistance: false
|
|
routes: []
|
|
cliVersion: development
|
|
daemonVersion: 0.14.1
|
|
management:
|
|
url: my-awesome-management.com:443
|
|
connected: true
|
|
error: ""
|
|
signal:
|
|
url: my-awesome-signal.com:443
|
|
connected: true
|
|
error: ""
|
|
relays:
|
|
total: 2
|
|
available: 1
|
|
details:
|
|
- uri: stun:my-awesome-stun.com:3478
|
|
available: true
|
|
error: ""
|
|
- uri: turns:my-awesome-turn.com:443?transport=tcp
|
|
available: false
|
|
error: 'context: deadline exceeded'
|
|
netbirdIp: 192.168.178.100/16
|
|
publicKey: Some-Pub-Key
|
|
usesKernelInterface: true
|
|
fqdn: some-localhost.awesome-domain.com
|
|
quantumResistance: false
|
|
quantumResistancePermissive: false
|
|
routes:
|
|
- 10.10.0.0/24
|
|
dnsServers:
|
|
- servers:
|
|
- 8.8.8.8:53
|
|
domains: []
|
|
enabled: true
|
|
error: ""
|
|
- servers:
|
|
- 1.1.1.1:53
|
|
- 2.2.2.2:53
|
|
domains:
|
|
- example.com
|
|
- example.net
|
|
enabled: false
|
|
error: timeout
|
|
`
|
|
|
|
assert.Equal(t, expectedYAML, yaml)
|
|
}
|
|
|
|
func TestParsingToDetail(t *testing.T) {
|
|
// Calculate time ago based on the fixture dates
|
|
lastConnectionUpdate1 := timeAgo(overview.Peers.Details[0].LastStatusUpdate)
|
|
lastHandshake1 := timeAgo(overview.Peers.Details[0].LastWireguardHandshake)
|
|
lastConnectionUpdate2 := timeAgo(overview.Peers.Details[1].LastStatusUpdate)
|
|
lastHandshake2 := timeAgo(overview.Peers.Details[1].LastWireguardHandshake)
|
|
|
|
detail := parseToFullDetailSummary(overview)
|
|
|
|
expectedDetail := fmt.Sprintf(
|
|
`Peers detail:
|
|
peer-1.awesome-domain.com:
|
|
NetBird IP: 192.168.178.101
|
|
Public key: Pubkey1
|
|
Status: Connected
|
|
-- detail --
|
|
Connection type: P2P
|
|
ICE candidate (Local/Remote): -/-
|
|
ICE candidate endpoints (Local/Remote): -/-
|
|
Relay server address:
|
|
Last connection update: %s
|
|
Last WireGuard handshake: %s
|
|
Transfer status (received/sent) 200 B/100 B
|
|
Quantum resistance: false
|
|
Routes: 10.1.0.0/24
|
|
Latency: 10ms
|
|
|
|
peer-2.awesome-domain.com:
|
|
NetBird IP: 192.168.178.102
|
|
Public key: Pubkey2
|
|
Status: Connected
|
|
-- detail --
|
|
Connection type: Relayed
|
|
ICE candidate (Local/Remote): relay/prflx
|
|
ICE candidate endpoints (Local/Remote): 10.0.0.1:10001/10.0.10.1:10002
|
|
Relay server address:
|
|
Last connection update: %s
|
|
Last WireGuard handshake: %s
|
|
Transfer status (received/sent) 2.0 KiB/1000 B
|
|
Quantum resistance: false
|
|
Routes: -
|
|
Latency: 10ms
|
|
|
|
OS: %s/%s
|
|
Daemon version: 0.14.1
|
|
CLI version: %s
|
|
Management: Connected to my-awesome-management.com:443
|
|
Signal: Connected to my-awesome-signal.com:443
|
|
Relays:
|
|
[stun:my-awesome-stun.com:3478] is Available
|
|
[turns:my-awesome-turn.com:443?transport=tcp] is Unavailable, reason: context: deadline exceeded
|
|
Nameservers:
|
|
[8.8.8.8:53] for [.] is Available
|
|
[1.1.1.1:53, 2.2.2.2:53] for [example.com, example.net] is Unavailable, reason: timeout
|
|
FQDN: some-localhost.awesome-domain.com
|
|
NetBird IP: 192.168.178.100/16
|
|
Interface type: Kernel
|
|
Quantum resistance: false
|
|
Routes: 10.10.0.0/24
|
|
Peers count: 2/2 Connected
|
|
`, lastConnectionUpdate1, lastHandshake1, lastConnectionUpdate2, lastHandshake2, runtime.GOOS, runtime.GOARCH, overview.CliVersion)
|
|
|
|
assert.Equal(t, expectedDetail, detail)
|
|
}
|
|
|
|
func TestParsingToShortVersion(t *testing.T) {
|
|
shortVersion := parseGeneralSummary(overview, false, false, false)
|
|
|
|
expectedString := fmt.Sprintf("OS: %s/%s", runtime.GOOS, runtime.GOARCH) + `
|
|
Daemon version: 0.14.1
|
|
CLI version: development
|
|
Management: Connected
|
|
Signal: Connected
|
|
Relays: 1/2 Available
|
|
Nameservers: 1/2 Available
|
|
FQDN: some-localhost.awesome-domain.com
|
|
NetBird IP: 192.168.178.100/16
|
|
Interface type: Kernel
|
|
Quantum resistance: false
|
|
Routes: 10.10.0.0/24
|
|
Peers count: 2/2 Connected
|
|
`
|
|
|
|
assert.Equal(t, expectedString, shortVersion)
|
|
}
|
|
|
|
func TestParsingOfIP(t *testing.T) {
|
|
InterfaceIP := "192.168.178.123/16"
|
|
|
|
parsedIP := parseInterfaceIP(InterfaceIP)
|
|
|
|
assert.Equal(t, "192.168.178.123\n", parsedIP)
|
|
}
|
|
|
|
func TestTimeAgo(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
cases := []struct {
|
|
name string
|
|
input time.Time
|
|
expected string
|
|
}{
|
|
{"Now", now, "Now"},
|
|
{"Seconds ago", now.Add(-10 * time.Second), "10 seconds ago"},
|
|
{"One minute ago", now.Add(-1 * time.Minute), "1 minute ago"},
|
|
{"Minutes and seconds ago", now.Add(-(1*time.Minute + 30*time.Second)), "1 minute, 30 seconds ago"},
|
|
{"One hour ago", now.Add(-1 * time.Hour), "1 hour ago"},
|
|
{"Hours and minutes ago", now.Add(-(2*time.Hour + 15*time.Minute)), "2 hours, 15 minutes ago"},
|
|
{"One day ago", now.Add(-24 * time.Hour), "1 day ago"},
|
|
{"Multiple days ago", now.Add(-(72*time.Hour + 20*time.Minute)), "3 days ago"},
|
|
{"Zero time", time.Time{}, "-"},
|
|
{"Unix zero time", time.Unix(0, 0), "-"},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result := timeAgo(tc.input)
|
|
assert.Equal(t, tc.expected, result, "Failed %s", tc.name)
|
|
})
|
|
}
|
|
}
|