diff --git a/logic/network.go b/logic/network.go index bc4583de..a0368da6 100644 --- a/logic/network.go +++ b/logic/network.go @@ -2,8 +2,11 @@ package logic import ( "net" + "os/exec" + "strings" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/netclient/ncutils" ) // GetLocalIP - gets the local ip @@ -61,3 +64,50 @@ func GetLocalIP(node models.Node) string { } return local } + +// == Private == + +func deleteInterface(ifacename string, postdown string) error { + var err error + if !ncutils.IsKernel() { + err = RemoveConf(ifacename, true) + } else { + ipExec, errN := exec.LookPath("ip") + err = errN + if err != nil { + ncutils.PrintLog(err.Error(), 1) + } + _, err = ncutils.RunCmd(ipExec+" link del "+ifacename, false) + if postdown != "" { + runcmds := strings.Split(postdown, "; ") + err = ncutils.RunCmds(runcmds, true) + } + } + return err +} + +func isInterfacePresent(iface string, address string) (string, bool) { + var interfaces []net.Interface + var err error + interfaces, err = net.Interfaces() + if err != nil { + Log("ERROR: could not read interfaces", 0) + return "", true + } + for _, currIface := range interfaces { + var currAddrs []net.Addr + currAddrs, err = currIface.Addrs() + if err != nil || len(currAddrs) == 0 { + continue + } + for _, addr := range currAddrs { + Log("looking at addresses "+addr.String()+" compared to "+address, 0) + if addr.String() == address && currIface.Name != iface { + Log("found it", 0) + // return old iface and false + return currIface.Name, false + } + } + } + return "", true +} diff --git a/logic/server.go b/logic/server.go index 9163bd3b..2a546e0c 100644 --- a/logic/server.go +++ b/logic/server.go @@ -4,6 +4,7 @@ import ( "errors" "log" "net" + "os" "runtime" "strconv" "strings" @@ -35,7 +36,7 @@ func ServerJoin(cfg config.ClientConfig, privateKey string) error { cfg.Node.Endpoint, err = ncutils.GetPublicIP() } if err != nil || cfg.Node.Endpoint == "" { - ncutils.Log("Error setting cfg.Node.Endpoint.") + Log("Error setting cfg.Node.Endpoint.", 0) return err } } @@ -117,9 +118,9 @@ func ServerJoin(cfg config.ClientConfig, privateKey string) error { return err } - peers, hasGateway, gateways, err := GetServerPeers(node.MacAddress, cfg.Network, cfg.Server.GRPCAddress, node.IsDualStack == "yes", node.IsIngressGateway == "yes", node.IsServer == "yes") + peers, hasGateway, gateways, err := GetServerPeers(node.MacAddress, cfg.Network, node.IsDualStack == "yes", node.IsIngressGateway == "yes") if err != nil && !ncutils.IsEmptyRecord(err) { - ncutils.Log("failed to retrieve peers") + Log("failed to retrieve peers", 1) return err } @@ -131,22 +132,84 @@ func ServerJoin(cfg config.ClientConfig, privateKey string) error { return nil } +// ServerPull - pulls current config/peers for server +func ServerPull(mac string, network string) error { + + var serverNode models.Node + var err error + serverNode, err = GetNode(mac, network) + if err != nil { + return err + } + + if serverNode.IPForwarding == "yes" { + if err = setIPForwardingLinux(); err != nil { + return err + } + } + serverNode.OS = runtime.GOOS + + if serverNode.PullChanges == "yes" { + // check for interface change + var isIfacePresent bool + var oldIfaceName string + // checks if address is in use by another interface + oldIfaceName, isIfacePresent = isInterfacePresent(serverNode.Interface, serverNode.Address) + if !isIfacePresent { + if err = deleteInterface(oldIfaceName, serverNode.PostDown); err != nil { + Log("could not delete old interface "+oldIfaceName, 1) + } + } + serverNode.PullChanges = "no" + if err = setWGConfig(serverNode, network, false); err != nil { + return err + } + // handle server side update + if err = serverNode.Update(&serverNode); err != nil { + return err + } + } else { + if err = setWGConfig(serverNode, network, true); err != nil { + if errors.Is(err, os.ErrNotExist) { + return ServerPull(serverNode.MacAddress, serverNode.Network) + } else { + return err + } + } + } + + return nil +} + // ServerPush - pushes config changes for server checkins/join func ServerPush(mac string, network string) error { var serverNode models.Node var err error serverNode, err = GetNode(mac, network) - if err != nil && !ncutils.IsEmptyRecord(err) { + if err != nil /* && !ncutils.IsEmptyRecord(err) May not be necessary */ { return err } serverNode.OS = runtime.GOOS serverNode.SetLastCheckIn() - err = serverNode.Update(&serverNode) - return err + return serverNode.Update(&serverNode) } -func GetServerPeers(macaddress string, network string, server string, dualstack bool, isIngressGateway bool, isServer bool) ([]wgtypes.PeerConfig, bool, []string, error) { +// ServerLeave - removes a server node +func ServerLeave(mac string, network string) error { + + var serverNode models.Node + var err error + serverNode, err = GetNode(mac, network) + if err != nil { + return err + } + serverNode.SetID() + return DeleteNode(serverNode.ID, true) +} + +// GetServerPeers - gets peers of server +func GetServerPeers(macaddress string, network string, dualstack bool, isIngressGateway bool) ([]wgtypes.PeerConfig, bool, []string, error) { hasGateway := false var err error var gateways []string @@ -277,7 +340,7 @@ func GetServerPeers(macaddress string, network string, server string, dualstack peers = append(peers, peer) } if isIngressGateway { - extPeers, err := GetServerExtPeers(macaddress, network, server, dualstack) + extPeers, err := GetServerExtPeers(macaddress, network, dualstack) if err == nil { peers = append(peers, extPeers...) } else { @@ -288,7 +351,7 @@ func GetServerPeers(macaddress string, network string, server string, dualstack } // GetServerExtPeers - gets the extpeers for a client -func GetServerExtPeers(macaddress string, network string, server string, dualstack bool) ([]wgtypes.PeerConfig, error) { +func GetServerExtPeers(macaddress string, network string, dualstack bool) ([]wgtypes.PeerConfig, error) { var peers []wgtypes.PeerConfig var nodecfg models.Node var extPeers []models.Node diff --git a/logic/util.go b/logic/util.go index 42a59085..5001c058 100644 --- a/logic/util.go +++ b/logic/util.go @@ -13,6 +13,7 @@ import ( "github.com/gravitl/netmaker/dnslogic" "github.com/gravitl/netmaker/functions" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/netclient/ncutils" "github.com/gravitl/netmaker/relay" "github.com/gravitl/netmaker/servercfg" "golang.org/x/crypto/bcrypt" @@ -296,6 +297,26 @@ func setPeerInfo(node models.Node) models.Node { func Log(message string, loglevel int) { log.SetFlags(log.Flags() &^ (log.Llongfile | log.Lshortfile)) if int32(loglevel) <= servercfg.GetVerbose() && servercfg.GetVerbose() != 0 { - log.Println(message) + log.Println("[netmaker] " + message) } } + +// == Private Methods == + +func setIPForwardingLinux() error { + out, err := ncutils.RunCmd("sysctl net.ipv4.ip_forward", true) + if err != nil { + log.Println("WARNING: Error encountered setting ip forwarding. This can break functionality.") + return err + } else { + s := strings.Fields(string(out)) + if s[2] != "1" { + _, err = ncutils.RunCmd("sysctl -w net.ipv4.ip_forward=1", true) + if err != nil { + log.Println("WARNING: Error encountered setting ip forwarding. You may want to investigate this.") + return err + } + } + } + return nil +} diff --git a/logic/wireguard.go b/logic/wireguard.go index 2be69616..cbe4954e 100644 --- a/logic/wireguard.go +++ b/logic/wireguard.go @@ -12,7 +12,6 @@ import ( "time" "github.com/gravitl/netmaker/models" - "github.com/gravitl/netmaker/netclient/local" "github.com/gravitl/netmaker/netclient/ncutils" "golang.zx2c4.com/wireguard/wgctrl" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" @@ -38,6 +37,27 @@ func GetSystemPeers(node *models.Node) (map[string]string, error) { return peers, nil } +func setWGConfig(node models.Node, network string, peerupdate bool) error { + + node.SetID() + peers, hasGateway, gateways, err := GetServerPeers(node.MacAddress, node.Network, node.IsDualStack == "yes", node.IsIngressGateway == "yes") + if err != nil { + return err + } + privkey, err := FetchPrivKey(node.ID) + if err != nil { + return err + } + if peerupdate { + var iface string + iface = node.Interface + err = setServerPeers(iface, node.PersistentKeepalive, peers) + } else { + err = initWireguard(&node, privkey, peers, hasGateway, gateways) + } + return err +} + func initWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig, hasGateway bool, gateways []string) error { key, err := wgtypes.ParseKey(privkey) @@ -90,12 +110,7 @@ func initWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig } // spin up userspace + apply the conf file var deviceiface string - if ncutils.IsMac() { - deviceiface, err = local.GetMacIface(node.Address) - if err != nil || deviceiface == "" { - deviceiface = ifacename - } - } + deviceiface = ifacename d, _ := wgclient.Device(deviceiface) for d != nil && d.Name == deviceiface { _ = RemoveConf(ifacename, false) // remove interface first @@ -104,7 +119,7 @@ func initWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig } err = applyWGQuickConf(confPath) if err != nil { - ncutils.PrintLog("failed to create wireguard interface", 1) + Log("failed to create wireguard interface", 1) return err } } else { @@ -202,3 +217,77 @@ func removeWGQuickConf(confPath string, printlog bool) error { } return nil } + +func setServerPeers(iface string, keepalive int32, peers []wgtypes.PeerConfig) error { + + client, err := wgctrl.New() + if err != nil { + ncutils.PrintLog("failed to start wgctrl", 0) + return err + } + + device, err := client.Device(iface) + if err != nil { + ncutils.PrintLog("failed to parse interface", 0) + return err + } + devicePeers := device.Peers + if len(devicePeers) > 1 && len(peers) == 0 { + ncutils.PrintLog("no peers pulled", 1) + return err + } + + for _, peer := range peers { + + for _, currentPeer := range devicePeers { + if currentPeer.AllowedIPs[0].String() == peer.AllowedIPs[0].String() && + currentPeer.PublicKey.String() != peer.PublicKey.String() { + _, err := ncutils.RunCmd("wg set "+iface+" peer "+currentPeer.PublicKey.String()+" remove", true) + if err != nil { + log.Println("error removing peer", peer.Endpoint.String()) + } + } + } + udpendpoint := peer.Endpoint.String() + var allowedips string + var iparr []string + for _, ipaddr := range peer.AllowedIPs { + iparr = append(iparr, ipaddr.String()) + } + allowedips = strings.Join(iparr, ",") + keepAliveString := strconv.Itoa(int(keepalive)) + if keepAliveString == "0" { + keepAliveString = "5" + } + if peer.Endpoint != nil { + _, err = ncutils.RunCmd("wg set "+iface+" peer "+peer.PublicKey.String()+ + " endpoint "+udpendpoint+ + " persistent-keepalive "+keepAliveString+ + " allowed-ips "+allowedips, true) + } else { + _, err = ncutils.RunCmd("wg set "+iface+" peer "+peer.PublicKey.String()+ + " persistent-keepalive "+keepAliveString+ + " allowed-ips "+allowedips, true) + } + if err != nil { + log.Println("error setting peer", peer.PublicKey.String()) + } + } + + for _, currentPeer := range devicePeers { + shouldDelete := true + for _, peer := range peers { + if peer.AllowedIPs[0].String() == currentPeer.AllowedIPs[0].String() { + shouldDelete = false + } + } + if shouldDelete { + output, err := ncutils.RunCmd("wg set "+iface+" peer "+currentPeer.PublicKey.String()+" remove", true) + if err != nil { + log.Println(output, "error removing peer", currentPeer.PublicKey.String()) + } + } + } + + return nil +} diff --git a/netclient/functions/checkin.go b/netclient/functions/checkin.go index aa6eff12..78fee725 100644 --- a/netclient/functions/checkin.go +++ b/netclient/functions/checkin.go @@ -9,7 +9,6 @@ import ( "strings" nodepb "github.com/gravitl/netmaker/grpc" - "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/netclient/auth" "github.com/gravitl/netmaker/netclient/config" @@ -197,11 +196,6 @@ func Pull(network string, manual bool) (*models.Node, error) { if err = json.Unmarshal([]byte(readres.Data), &resNode); err != nil { return nil, err } - } else { // handle server side read - resNode, err = logic.GetNode(node.MacAddress, node.Network) - if err != nil && !ncutils.IsEmptyRecord(err) { - return nil, err - } } // ensure that the OS never changes resNode.OS = runtime.GOOS diff --git a/serverctl/serverctl.go b/serverctl/serverctl.go index 92d510b7..2d560849 100644 --- a/serverctl/serverctl.go +++ b/serverctl/serverctl.go @@ -8,6 +8,7 @@ import ( "os" "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" nccommand "github.com/gravitl/netmaker/netclient/command" "github.com/gravitl/netmaker/netclient/config" @@ -31,30 +32,6 @@ func GetServerWGConf() (models.IntClient, error) { return models.IntClient{}, errors.New("could not find comms server") } -// InstallNetclient netclient installation for server - depricated -func InstallNetclient() error { - - netclientPath := ncutils.GetNetclientPath() - if ncutils.IsWindows() { - netclientPath += "\\" - } else { - netclientPath += "/" - } - if !FileExists(netclientPath + "netclient") { - var err error - if ncutils.IsWindows() { - _, err = copy(".\\netclient\\netclient", netclientPath+"netclient") - } else { - _, err = copy("./netclient/netclient", netclientPath+"netclient") - } - if err != nil { - log.Println("could not create " + netclientPath + "netclient") - return err - } - } - return nil -} - // FileExists - checks if local file exists func FileExists(f string) bool { info, err := os.Stat(f) @@ -95,7 +72,7 @@ func copy(src, dst string) (int64, error) { // RemoveNetwork - removes a network locally on server func RemoveNetwork(network string) (bool, error) { - err := nccommand.Leave(config.ClientConfig{Network: network}) + err := logic.ServerLeave(servercfg.GetNodeID(), network) return true, err } @@ -188,15 +165,15 @@ func SyncNetworks(servernets []models.Network) error { // AddNetwork - add a network to server in client mode func AddNetwork(network string) (bool, error) { - err := nccommand.Join(config.ClientConfig{ + err := logic.ServerJoin(config.ClientConfig{ Network: network, Daemon: "off", Node: models.Node{ - Network: network, - IsServer: "yes", - DNSOn: "no", - Name: models.NODE_SERVER_NAME, - MacAddress: servercfg.GetNodeID(), + Network: network, + IsServer: "yes", + DNSOn: "no", + Name: models.NODE_SERVER_NAME, + MacAddress: servercfg.GetNodeID(), }, }, "") log.Println("[netmaker] Server added to network " + network)