Skip to content

Commit

Permalink
Sync peers FQDN (#584)
Browse files Browse the repository at this point in the history
Use stdout and stderr log path only if on Linux and attempt to create the path

Update status system with FQDN fields and 
status command to display the domain names of remote and local peers

Set some DNS logs to tracing

update readme file
mlsmaycon authored Nov 26, 2022

Verified

This commit was signed with the committer’s verified signature.
EasterTheBunny Awbrey Hughlett
1 parent fcf7786 commit 20a73e3
Showing 17 changed files with 405 additions and 257 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<strong>:hatching_chick: New Release! User Invites.</strong>
<strong>:hatching_chick: New Release! DNS support.</strong>
<a href="https://github.com/netbirdio/netbird/releases">
Learn more
</a>
@@ -55,9 +55,9 @@ NetBird uses [NAT traversal techniques](https://en.wikipedia.org/wiki/Interactiv
- \[x] Access Controls - groups & rules.
- \[x] Remote SSH access without managing SSH keys.
- \[x] Network Routes.
- \[x] Private DNS.

**Coming soon:**
- \[ ] Private DNS.
- \[ ] Mobile clients.
- \[ ] Network Activity Monitoring.

21 changes: 19 additions & 2 deletions client/cmd/service_installer.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ package cmd

import (
"context"
"os"
"path/filepath"
"runtime"

@@ -38,13 +39,29 @@ var installCmd = &cobra.Command{

if logFile != "console" {
svcConfig.Arguments = append(svcConfig.Arguments, "--log-file", logFile)
svcConfig.Option["LogOutput"] = true
svcConfig.Option["LogDirectory"] = filepath.Dir(logFile)
}

if runtime.GOOS == "linux" {
// Respected only by systemd systems
svcConfig.Dependencies = []string{"After=network.target syslog.target"}

if logFile != "console" {
setStdLogPath := true
dir := filepath.Dir(logFile)

_, err := os.Stat(dir)
if err != nil {
err = os.MkdirAll(dir, 0750)
if err != nil {
setStdLogPath = false
}
}

if setStdLogPath {
svcConfig.Option["LogOutput"] = true
svcConfig.Option["LogDirectory"] = dir
}
}
}

ctx, cancel := context.WithCancel(cmd.Context())
7 changes: 6 additions & 1 deletion client/cmd/status.go
Original file line number Diff line number Diff line change
@@ -122,6 +122,7 @@ func fromProtoFullStatus(pbFullStatus *proto.FullStatus) nbStatus.FullStatus {
fullStatus.LocalPeerState.IP = localPeerState.GetIP()
fullStatus.LocalPeerState.PubKey = localPeerState.GetPubKey()
fullStatus.LocalPeerState.KernelInterface = localPeerState.GetKernelInterface()
fullStatus.LocalPeerState.FQDN = localPeerState.GetFqdn()

var peersState []nbStatus.PeerState

@@ -136,6 +137,7 @@ func fromProtoFullStatus(pbFullStatus *proto.FullStatus) nbStatus.FullStatus {
Direct: pbPeerState.GetDirect(),
LocalIceCandidateType: pbPeerState.GetLocalIceCandidateType(),
RemoteIceCandidateType: pbPeerState.GetRemoteIceCandidateType(),
FQDN: pbPeerState.GetFqdn(),
}
peersState = append(peersState, peerState)
}
@@ -196,6 +198,7 @@ func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonSta
"%s"+ // daemon status
"Management: %s%s\n"+
"Signal: %s%s\n"+
"Domain: %s\n"+
"NetBird IP: %s\n"+
"Interface type: %s\n"+
"Peers count: %s\n",
@@ -206,6 +209,7 @@ func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonSta
managementStatusURL,
signalConnString,
signalStatusURL,
fullStatus.LocalPeerState.FQDN,
interfaceIP,
interfaceTypeString,
peersCountString,
@@ -266,7 +270,7 @@ func parsePeers(peers []nbStatus.PeerState, printDetail bool) (string, int) {
}

peerString := fmt.Sprintf(
"\n Peer:\n"+
"\n %s:\n"+
" NetBird IP: %s\n"+
" Public key: %s\n"+
" Status: %s\n"+
@@ -275,6 +279,7 @@ func parsePeers(peers []nbStatus.PeerState, printDetail bool) (string, int) {
" Direct: %t\n"+
" ICE candidate (Local/Remote): %s/%s\n"+
" Last connection update: %s\n",
peerState.FQDN,
peerState.IP,
peerState.PubKey,
peerState.ConnStatus,
3 changes: 2 additions & 1 deletion client/internal/connect.go
Original file line number Diff line number Diff line change
@@ -109,6 +109,7 @@ func RunClient(ctx context.Context, config *Config, statusRecorder *nbStatus.Sta
IP: loginResp.GetPeerConfig().GetAddress(),
PubKey: myPrivateKey.PublicKey().String(),
KernelInterface: iface.WireguardModuleIsLoaded(),
FQDN: loginResp.GetPeerConfig().GetFqdn(),
}

statusRecorder.UpdateLocalPeerState(localPeerState)
@@ -192,7 +193,7 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe
WgPrivateKey: key,
WgPort: config.WgPort,
SSHKey: []byte(config.SSHKey),
NATExternalIPs: config.NATExternalIPs,
NATExternalIPs: config.NATExternalIPs,
}

if config.PreSharedKey != "" {
2 changes: 1 addition & 1 deletion client/internal/dns/local.go
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ type localResolver struct {

// ServeDNS handles a DNS request
func (d *localResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
log.Debugf("received question: %#v\n", r.Question[0])
log.Tracef("received question: %#v\n", r.Question[0])
replyMessage := &dns.Msg{}
replyMessage.SetReply(r)
replyMessage.RecursionAvailable = true
2 changes: 1 addition & 1 deletion client/internal/dns/upstream.go
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ type upstreamResolver struct {
// ServeDNS handles a DNS request
func (u *upstreamResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {

log.Debugf("received an upstream question: %#v", r.Question[0])
log.Tracef("received an upstream question: %#v", r.Question[0])

select {
case <-u.parentCTX.Done():
49 changes: 33 additions & 16 deletions client/internal/engine.go
Original file line number Diff line number Diff line change
@@ -281,9 +281,15 @@ func (e *Engine) modifyPeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
// first, check if peers have been modified
var modified []*mgmProto.RemotePeerConfig
for _, p := range peersUpdate {
if peerConn, ok := e.peerConns[p.GetWgPubKey()]; ok {
peerPubKey := p.GetWgPubKey()
if peerConn, ok := e.peerConns[peerPubKey]; ok {
if peerConn.GetConf().ProxyConfig.AllowedIps != strings.Join(p.AllowedIps, ",") {
modified = append(modified, p)
continue
}
err := e.statusRecorder.UpdatePeerFQDN(peerPubKey, p.GetFqdn())
if err != nil {
log.Warnf("error updating peer's %s fqdn in the status recorder, got error: %v", peerPubKey, err)
}
}
}
@@ -543,6 +549,13 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
}
}

e.statusRecorder.UpdateLocalPeerState(nbstatus.LocalPeerState{
IP: e.config.WgAddr,
PubKey: e.config.WgPrivateKey.PublicKey().String(),
KernelInterface: iface.WireguardModuleIsLoaded(),
FQDN: conf.GetFqdn(),
})

return nil
}

@@ -766,6 +779,10 @@ func (e *Engine) addNewPeer(peerConfig *mgmProto.RemotePeerConfig) error {

go e.connWorker(conn, peerKey)
}
err := e.statusRecorder.UpdatePeerFQDN(peerKey, peerConfig.Fqdn)
if err != nil {
log.Warnf("error updating peer's %s fqdn in the status recorder, got error: %v", peerKey, err)
}
return nil
}

@@ -842,7 +859,7 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
UDPMuxSrflx: e.udpMuxSrflx,
ProxyConfig: proxyConfig,
LocalWgPort: e.config.WgPort,
NATExternalIPs: e.parseNATExternalIPMappings(),
NATExternalIPs: e.parseNATExternalIPMappings(),
}

peerConn, err := peer.NewConn(config, e.statusRecorder)
@@ -937,10 +954,10 @@ func (e *Engine) receiveSignalEvents() {
e.signal.WaitStreamConnected()
}

func (e* Engine) parseNATExternalIPMappings() []string {
func (e *Engine) parseNATExternalIPMappings() []string {
var mappedIPs []string
var ignoredIFaces = make(map[string]interface{})
for _, iFace := range(e.config.IFaceBlackList) {
for _, iFace := range e.config.IFaceBlackList {
ignoredIFaces[iFace] = nil
}
for _, mapping := range e.config.NATExternalIPs {
@@ -991,22 +1008,22 @@ func (e* Engine) parseNATExternalIPMappings() []string {
}

func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
iface, err := net.InterfaceByName(ifaceName)
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
return nil, err
}
return findIPFromInterface(iface)
return nil, err
}
return findIPFromInterface(iface)
}

func findIPFromInterface(iface *net.Interface) (net.IP, error) {
ifaceAddrs, err := iface.Addrs()
ifaceAddrs, err := iface.Addrs()
if err != nil {
return nil, err
}
for _, addr := range ifaceAddrs {
if ipv4Addr := addr.(*net.IPNet).IP.To4(); ipv4Addr != nil {
return nil, err
}
for _, addr := range ifaceAddrs {
if ipv4Addr := addr.(*net.IPNet).IP.To4(); ipv4Addr != nil {
return ipv4Addr, nil
}
}
}
}
return nil, fmt.Errorf("interface %s don't have an ipv4 address", iface.Name)
}
}
191 changes: 105 additions & 86 deletions client/proto/daemon.pb.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions client/proto/daemon.proto
Original file line number Diff line number Diff line change
@@ -105,13 +105,15 @@ message PeerState {
bool direct = 6;
string localIceCandidateType = 7;
string remoteIceCandidateType =8;
string fqdn = 9;
}

// LocalPeerState contains the latest state of the local peer
message LocalPeerState {
string IP = 1;
string pubKey = 2;
bool kernelInterface =3;
string fqdn = 4;
}

// SignalState contains the latest state of a signal connection
15 changes: 14 additions & 1 deletion client/proto/generate.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
#!/bin/bash
set -e

if ! which realpath > /dev/null 2>&1
then
echo realpath is not installed
echo run: brew install coreutils
exit 1
fi

old_pwd=$(pwd)
script_path=$(dirname $(realpath "$0"))
cd "$script_path"
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
protoc -I proto/ proto/daemon.proto --go_out=. --go-grpc_out=.
protoc -I ./ ./daemon.proto --go_out=../ --go-grpc_out=../
cd "$old_pwd"
2 changes: 2 additions & 0 deletions client/server/server.go
Original file line number Diff line number Diff line change
@@ -475,6 +475,7 @@ func toProtoFullStatus(fullStatus nbStatus.FullStatus) *proto.FullStatus {
pbFullStatus.LocalPeerState.IP = fullStatus.LocalPeerState.IP
pbFullStatus.LocalPeerState.PubKey = fullStatus.LocalPeerState.PubKey
pbFullStatus.LocalPeerState.KernelInterface = fullStatus.LocalPeerState.KernelInterface
pbFullStatus.LocalPeerState.Fqdn = fullStatus.LocalPeerState.FQDN

for _, peerState := range fullStatus.Peers {
pbPeerState := &proto.PeerState{
@@ -486,6 +487,7 @@ func toProtoFullStatus(fullStatus nbStatus.FullStatus) *proto.FullStatus {
Direct: peerState.Direct,
LocalIceCandidateType: peerState.LocalIceCandidateType,
RemoteIceCandidateType: peerState.RemoteIceCandidateType,
Fqdn: peerState.FQDN,
}
pbFullStatus.Peers = append(pbFullStatus.Peers, pbPeerState)
}
18 changes: 18 additions & 0 deletions client/status/status.go
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import (
type PeerState struct {
IP string
PubKey string
FQDN string
ConnStatus string
ConnStatusUpdate time.Time
Relayed bool
@@ -23,6 +24,7 @@ type LocalPeerState struct {
IP string
PubKey string
KernelInterface bool
FQDN string
}

// SignalState contains the latest state of a signal connection
@@ -136,6 +138,22 @@ func (d *Status) UpdatePeerState(receivedState PeerState) error {
return nil
}

// UpdatePeerFQDN update peer's state fqdn only
func (d *Status) UpdatePeerFQDN(peerPubKey, fqdn string) error {
d.mux.Lock()
defer d.mux.Unlock()

peerState, ok := d.peers[peerPubKey]
if !ok {
return errors.New("peer doesn't exist")
}

peerState.FQDN = fqdn
d.peers[peerPubKey] = peerState

return nil
}

// GetPeerStateChangeNotifier returns a change notifier channel for a peer
func (d *Status) GetPeerStateChangeNotifier(peer string) <-chan struct{} {
d.mux.Lock()
18 changes: 18 additions & 0 deletions client/status/status_test.go
Original file line number Diff line number Diff line change
@@ -54,6 +54,24 @@ func TestUpdatePeerState(t *testing.T) {
assert.Equal(t, ip, state.IP, "ip should be equal")
}

func TestStatus_UpdatePeerFQDN(t *testing.T) {
key := "abc"
fqdn := "peer-a.netbird.local"
status := NewRecorder()
peerState := PeerState{
PubKey: key,
}

status.peers[key] = peerState

err := status.UpdatePeerFQDN(key, fqdn)
assert.NoError(t, err, "shouldn't return error")

state, exists := status.peers[key]
assert.True(t, exists, "state should be found")
assert.Equal(t, fqdn, state.FQDN, "fqdn should be equal")
}

func TestGetPeerStateChangeNotifierLogic(t *testing.T) {
key := "abc"
ip := "10.10.10.10"
295 changes: 158 additions & 137 deletions management/proto/management.pb.go

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions management/proto/management.proto
Original file line number Diff line number Diff line change
@@ -158,6 +158,8 @@ message PeerConfig {

// SSHConfig of the peer.
SSHConfig sshConfig = 3;
// Peer fully qualified domain name
string fqdn = 4;
}

// NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections
@@ -196,6 +198,9 @@ message RemotePeerConfig {
// SSHConfig is a SSH config of the remote peer. SSHConfig.sshPubKey should be ignored because peer knows it's SSH key.
SSHConfig sshConfig = 3;

// Peer fully qualified domain name
string fqdn = 4;

}

// SSHConfig represents SSH configurations of a peer.
26 changes: 18 additions & 8 deletions management/server/grpcserver.go
Original file line number Diff line number Diff line change
@@ -263,7 +263,7 @@ func (s *GRPCServer) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest)
return nil, status.Errorf(codes.Internal, "unable to fetch network map after registering peer, error: %v", err)
}

update := toSyncResponse(s.config, remotePeer, nil, remotePeerNetworkMap)
update := toSyncResponse(s.config, remotePeer, nil, remotePeerNetworkMap, s.accountManager.GetDNSDomain())
err = s.peersUpdateManager.SendUpdate(remotePeer.Key, &UpdateMessage{Update: update})
if err != nil {
// todo rethink if we should keep this return
@@ -361,7 +361,7 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p
// if peer has reached this point then it has logged in
loginResp := &proto.LoginResponse{
WiretrusteeConfig: toWiretrusteeConfig(s.config, nil),
PeerConfig: toPeerConfig(peer, network),
PeerConfig: toPeerConfig(peer, network, s.accountManager.GetDNSDomain()),
}
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, loginResp)
if err != nil {
@@ -433,32 +433,42 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot
}
}

func toPeerConfig(peer *Peer, network *Network) *proto.PeerConfig {
func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfig {
netmask, _ := network.Net.Mask.Size()
fqdn := ""
if dnsName != "" {
fqdn = peer.DNSLabel + "." + dnsName
}
return &proto.PeerConfig{
Address: fmt.Sprintf("%s/%d", peer.IP.String(), netmask), // take it from the network
SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled},
Fqdn: fqdn,
}
}

func toRemotePeerConfig(peers []*Peer) []*proto.RemotePeerConfig {
func toRemotePeerConfig(peers []*Peer, dnsName string) []*proto.RemotePeerConfig {
remotePeers := []*proto.RemotePeerConfig{}
for _, rPeer := range peers {
fqdn := ""
if dnsName != "" {
fqdn = rPeer.DNSLabel + "." + dnsName
}
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
WgPubKey: rPeer.Key,
AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)},
SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)},
Fqdn: fqdn,
})
}
return remotePeers
}

func toSyncResponse(config *Config, peer *Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap) *proto.SyncResponse {
func toSyncResponse(config *Config, peer *Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap, dnsName string) *proto.SyncResponse {
wtConfig := toWiretrusteeConfig(config, turnCredentials)

pConfig := toPeerConfig(peer, networkMap.Network)
pConfig := toPeerConfig(peer, networkMap.Network, dnsName)

remotePeers := toRemotePeerConfig(networkMap.Peers)
remotePeers := toRemotePeerConfig(networkMap.Peers, dnsName)

routesUpdate := toProtocolRoutes(networkMap.Routes)

@@ -501,7 +511,7 @@ func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.
} else {
turnCredentials = nil
}
plainResp := toSyncResponse(s.config, peer, turnCredentials, networkMap)
plainResp := toSyncResponse(s.config, peer, turnCredentials, networkMap, s.accountManager.GetDNSDomain())

encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
if err != nil {
2 changes: 1 addition & 1 deletion management/server/peer.go
Original file line number Diff line number Diff line change
@@ -581,7 +581,7 @@ func (am *DefaultAccountManager) updateAccountPeers(account *Account) error {
return err
}

update := toSyncResponse(nil, peer, nil, remotePeerNetworkMap)
update := toSyncResponse(nil, peer, nil, remotePeerNetworkMap, am.GetDNSDomain())
err = am.peersUpdateManager.SendUpdate(peer.Key, &UpdateMessage{Update: update})
if err != nil {
return err

0 comments on commit 20a73e3

Please sign in to comment.