diff --git a/config/config.go b/config/config.go index 3af75876..95c416f1 100644 --- a/config/config.go +++ b/config/config.go @@ -76,12 +76,18 @@ var ( // Config configuration for netclient and host as a whole type Config struct { models.Host - PrivateKey wgtypes.Key `json:"privatekey" yaml:"privatekey"` - TrafficKeyPrivate []byte `json:"traffickeyprivate" yaml:"traffickeyprivate"` - HostPeers []wgtypes.PeerConfig `json:"-" yaml:"-"` - InitType InitType `json:"inittype" yaml:"inittype"` - OriginalDefaultGatewayIp net.IP `json:"original_default_gateway_ip_old" yaml:"original_default_gateway_ip_old"` - CurrGwNmIP net.IP `json:"curr_gw_nm_ip" yaml:"curr_gw_nm_ip"` + PrivateKey wgtypes.Key `json:"privatekey" yaml:"privatekey"` + TrafficKeyPrivate []byte `json:"traffickeyprivate" yaml:"traffickeyprivate"` + HostPeers []wgtypes.PeerConfig `json:"-" yaml:"-"` + InitType InitType `json:"inittype" yaml:"inittype"` + //for Internet gateway + OriginalDefaultGatewayIp net.IP `json:"original_default_gateway_ip_old" yaml:"original_default_gateway_ip_old"` + CurrGwNmIP net.IP `json:"curr_gw_nm_ip" yaml:"curr_gw_nm_ip"` + //for manage DNS + DNSManagerType string `json:"dns_manager_type" yaml:"dns_manager_type"` + NameServers []string `json:"name_servers" yaml:"name_servers"` + DNSSearch string `json:"dns_search" yaml:"dns_search"` + DNSOptions string `json:"dns_options" yaml:"dns_options"` } func init() { diff --git a/dns/config.go b/dns/config.go new file mode 100644 index 00000000..6a91b85a --- /dev/null +++ b/dns/config.go @@ -0,0 +1,123 @@ +package dns + +import ( + "encoding/json" + "os" + "sort" + "sync" + + "github.com/gravitl/netclient/config" + "golang.org/x/exp/slog" +) + +const ( + DNS_MANAGER_STUB = "stub" // '/run/systemd/resolve/stub-resolv.conf' + DNS_MANAGER_UPLINK = "uplink" // '/run/systemd/resolve/resolv.conf' + DNS_MANAGER_RESOLVECONF = "resolveconf" // 'generated by resolvconf(8)' + DNS_MANAGER_FILE = "file" // other than above +) + +var ( + dnsConfigPath = config.GetNetclientPath() + "dns.json" +) + +type DNSConfig struct { + Domains []string `json:"domains"` + DefaultDomain string `json:"default_domain"` + DNSSearch string `json:"dns_search"` +} + +var dnsJsonMutex = sync.Mutex{} + +// sync up DNS related config to dns.json +func syncDNSJsonFile() error { + dnsJsonMutex.Lock() + defer dnsJsonMutex.Unlock() + + //if dns.json existed, delete it at first + _, err := os.Stat(dnsConfigPath) + if err == nil { + err = os.Remove(dnsConfigPath) + if err != nil { + slog.Error("error deleting file", "error", dnsConfigPath, err.Error()) + } + } + + // read from config and build DNSConfig + dnsConfig := &DNSConfig{} + if config.Netclient().DNSSearch != "" { + dnsConfig.DNSSearch = config.Netclient().DNSSearch + } else { + dnsConfig.DNSSearch = "." + } + + defaultDomain := config.GetServer(config.CurrServer).DefaultDomain + if defaultDomain != "" { + dnsConfig.DefaultDomain = defaultDomain + } + + domains := []string{} + for _, v := range config.GetNodes() { + domains = append(domains, v.Network) + } + sort.Strings(domains) + dnsConfig.Domains = domains + + //write the DNSconfig to dns.json + f, err := os.OpenFile(dnsConfigPath, os.O_CREATE|os.O_WRONLY, 0700) + if err != nil { + slog.Error("error opening file", "error", dnsConfigPath, err.Error()) + return err + } + defer f.Close() + + j := json.NewEncoder(f) + j.SetIndent("", " ") + err = j.Encode(dnsConfig) + if err != nil { + slog.Error("error encoding file", "error", dnsConfigPath, err.Error()) + return err + } + + return nil +} + +// read dns.json file to DNSConfig object +func readDNSJsonFile() (dnsConfig DNSConfig, err error) { + dnsJsonMutex.Lock() + defer dnsJsonMutex.Unlock() + + if _, err := os.Stat(dnsConfigPath); err != nil { + if os.IsNotExist(err) { + slog.Error("file is not existed", "error", dnsConfigPath, err.Error()) + return DNSConfig{}, err + } + } + + f, err := os.Open(dnsConfigPath) + if err != nil { + slog.Error("error opening file", "error", dnsConfigPath, err.Error()) + return DNSConfig{}, err + } + defer f.Close() + if err = json.NewDecoder(f).Decode(&dnsConfig); err != nil { + slog.Error("error decoding file", "error", dnsConfigPath, err.Error()) + return DNSConfig{}, err + } + + return dnsConfig, nil +} + +// Clean up the dns.json file +func cleanDNSJsonFile() error { + dnsJsonMutex.Lock() + defer dnsJsonMutex.Unlock() + //delete dns.json file + err := os.Remove(dnsConfigPath) + if err != nil { + slog.Error("error removing file", "error", dnsConfigPath, err.Error()) + return err + } + + return nil +} diff --git a/dns/config_darwin.go b/dns/config_darwin.go new file mode 100644 index 00000000..4f683098 --- /dev/null +++ b/dns/config_darwin.go @@ -0,0 +1,16 @@ +package dns + +func FlushLocalDnsCache() (err error) { + return nil +} + +func SetupDNSConfig() (err error) { + return nil +} + +func RestoreDNSConfig() (err error) { + return nil +} + +func InitDNSConfig() { +} diff --git a/dns/config_linux.go b/dns/config_linux.go new file mode 100644 index 00000000..3a5ad4ea --- /dev/null +++ b/dns/config_linux.go @@ -0,0 +1,680 @@ +package dns + +import ( + "errors" + "fmt" + "io" + "os" + "slices" + "sort" + "strings" + "sync" + "time" + + "github.com/gravitl/netclient/config" + "github.com/gravitl/netclient/ncutils" + "golang.org/x/exp/slog" +) + +var dnsConfigMutex = sync.Mutex{} // used to mutex functions of the DNS + +const ( + resolvconfFilePath = "/etc/resolv.conf" + resolvconfFileBkpPath = "/etc/netclient/resolv.conf.nm.bkp" + resolvUplinkPath = "/etc/systemd/resolved.conf" + resolvUplinkBkpPath = "/etc/netclient/resolved.conf.nm.bkp" + resolvconfUplinkPath = "/run/systemd/resolve/resolv.conf" +) + +func isStubSupported() bool { + return config.Netclient().DNSManagerType == DNS_MANAGER_STUB +} + +func isUplinkSupported() bool { + return config.Netclient().DNSManagerType == DNS_MANAGER_UPLINK +} + +func isResolveconfSupported() bool { + return config.Netclient().DNSManagerType == DNS_MANAGER_RESOLVECONF +} + +// func isFileSupported() bool { +// return config.Netclient().DNSManagerType == DNS_MANAGER_FILE +// } + +// Flush local DNS cache +func FlushLocalDnsCache() (err error) { + dnsConfigMutex.Lock() + defer dnsConfigMutex.Unlock() + if isStubSupported() || isUplinkSupported() { + _, err = ncutils.RunCmd("resolvectl flush-caches", false) + if err != nil { + slog.Warn("Flush local DNS domain caches failed", "error", err.Error()) + } + } + return err +} + +// Entry point to setup DNS settings +func SetupDNSConfig() (err error) { + dnsConfigMutex.Lock() + defer dnsConfigMutex.Unlock() + if isStubSupported() { + err = setupResolvectl() + } else if isUplinkSupported() { + err = setupResolveUplink() + } else if isResolveconfSupported() { + } else { + err = setupResolveconf() + } + + //write to dns.json + syncDNSJsonFile() + + return err +} + +// Entry point to restore DNS settings +func RestoreDNSConfig() (err error) { + dnsConfigMutex.Lock() + defer dnsConfigMutex.Unlock() + if isStubSupported() { + + } else if isUplinkSupported() { + err = restoreResolveUplink() + } else if isResolveconfSupported() { + } else { + err = restoreResolveconf() + } + + cleanDNSJsonFile() + + return err +} + +func buildAddConfigContentUplink() ([]string, error) { + + dnsIp := GetDNSServerInstance().AddrStr + if dnsIp == "" { + return []string{}, errors.New("no listener is running") + } + + dnsIp = getIpFromServerString(dnsIp) + ns := "DNS=" + dnsIp + + f, err := os.Open(resolvUplinkPath) + if err != nil { + slog.Error("error opending file", "error", resolvUplinkPath, err.Error()) + return []string{}, err + } + defer f.Close() + + rawBytes, err := io.ReadAll(f) + if err != nil { + slog.Error("error reading file", "error", resolvUplinkPath, err.Error()) + return []string{}, err + } + lines := strings.Split(string(rawBytes), "\n") + lNo := 21 + for i, line := range lines { + if strings.HasPrefix(line, "#DNS=") { + lNo = i + break + } + } + + lines = slices.Insert(lines, lNo, ns) + + return lines, nil +} + +func setupResolveUplink() (err error) { + + // backup /etc/systemd/resolved.conf + err = backupResolveconfFile(resolvUplinkPath, resolvUplinkBkpPath) + if err != nil { + slog.Error("could not backup ", resolvUplinkPath, "error", err.Error()) + return err + } + + // add nameserver + lines, err := buildAddConfigContentUplink() + if err != nil { + slog.Error("could not build config content", "error", err.Error()) + return err + } + + f, err := os.OpenFile(resolvUplinkPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0700) + if err != nil { + slog.Error("error opending file", "error", resolvUplinkPath, err.Error()) + return err + } + defer f.Close() + + for _, v := range lines { + if v != "" { + _, err = fmt.Fprintln(f, v) + if err != nil { + slog.Error("error writing file", "error", resolvUplinkPath, err.Error()) + return err + } + } + } + + _, err = ncutils.RunCmd("systemctl restart systemd-resolved", false) + if err != nil { + slog.Error("restart systemd-resolved failed", "error", err.Error()) + return err + } + + return nil +} + +func setupResolvectl() (err error) { + + dnsIp := GetDNSServerInstance().AddrStr + if dnsIp == "" { + return errors.New("no listener is running") + } + if len(config.GetNodes()) == 0 { + return errors.New("no network joint") + } + + dnsIp = getIpFromServerString(dnsIp) + + _, err = ncutils.RunCmd(fmt.Sprintf("resolvectl dns netmaker %s", dnsIp), false) + if err != nil { + slog.Warn("add DNS IP for netmaker failed", "error", err.Error()) + } + + domains := "" + for _, v := range config.GetNodes() { + domains = domains + " " + v.Network + } + defaultDomain := config.GetServer(config.CurrServer).DefaultDomain + if defaultDomain != "" { + domains = domains + " " + defaultDomain + } + + _, err = ncutils.RunCmd(fmt.Sprintf("resolvectl domain netmaker %s", domains), false) + if err != nil { + slog.Warn("add DNS domain for netmaker failed", "error", err.Error()) + } + + time.Sleep(1 * time.Second) + _, err = ncutils.RunCmd("resolvectl flush-caches", false) + if err != nil { + slog.Warn("Flush local DNS domain caches failed", "error", err.Error()) + } + + return nil +} + +func getIpFromServerString(addrStr string) string { + s := "" + s = addrStr[0:strings.LastIndex(addrStr, ":")] + + if strings.Contains(s, "[") { + s = strings.ReplaceAll(s, "[", "") + } + + if strings.Contains(s, "]") { + s = strings.ReplaceAll(s, "]", "") + } + + return s +} + +func backupResolveconfFile(src, dst string) error { + + _, err := os.Stat(dst) + if err != nil { + src_file, err := os.Open(src) + if err != nil { + slog.Error("could not open ", src, "error", err.Error()) + return err + } + defer src_file.Close() + dst_file, err := os.Create(dst) + if err != nil { + slog.Error("could not open ", dst, "error", err.Error()) + return err + } + defer dst_file.Close() + + _, err = io.Copy(dst_file, src_file) + if err != nil { + slog.Error("could not backup ", src, "error", err.Error()) + return err + } + } + return nil +} + +func buildAddConfigContent() ([]string, error) { + + //get nameserver and search domain + ns, domains, err := getNSAndDomains() + if err != nil { + slog.Error("error in getting getNSAndDomains", "error", err.Error()) + return []string{}, err + } + + f, err := os.Open(resolvconfFilePath) + if err != nil { + slog.Error("error opending file", "error", resolvconfFilePath, err.Error()) + return []string{}, err + } + defer f.Close() + + rawBytes, err := io.ReadAll(f) + if err != nil { + slog.Error("error reading file", "error", resolvconfFilePath, err.Error()) + return []string{}, err + } + lines := strings.Split(string(rawBytes), "\n") + lNo := 0 + for i, line := range lines { + if strings.HasPrefix(line, "nameserver") { + lNo = i + break + } + } + + lines = slices.Insert(lines, lNo, ns) + lines = slices.Insert(lines, lNo, domains) + + return lines, nil +} + +func setupResolveconf() error { + + // backup /etc/resolv.conf + err := backupResolveconfFile(resolvconfFilePath, resolvconfFileBkpPath) + if err != nil { + slog.Error("could not backup ", resolvconfFilePath, "error", err.Error()) + return err + } + + // add nameserver and search domain + lines, err := buildAddConfigContent() + if err != nil { + slog.Error("could not build config content", "error", err.Error()) + return err + } + + f, err := os.OpenFile(resolvconfFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0700) + if err != nil { + slog.Error("error opending file", "error", resolvconfFilePath, err.Error()) + return err + } + defer f.Close() + + for _, v := range lines { + if v != "" { + _, err = fmt.Fprintln(f, v) + if err != nil { + slog.Error("error writing file", "error", resolvconfFilePath, err.Error()) + return err + } + } + } + + return nil +} + +func getNSAndDomains() (string, string, error) { + + dnsIp := GetDNSServerInstance().AddrStr + if dnsIp == "" { + return "", "", errors.New("no listener is running") + } + if len(config.GetNodes()) == 0 { + return "", "", errors.New("no network joint") + } + + dStrings := []string{} + for _, v := range config.GetNodes() { + dStrings = append(dStrings, v.Network) + } + sort.Strings(dStrings) + + domains := "search" + for _, v := range dStrings { + domains = domains + " " + v + } + + defaultDomain := config.GetServer(config.CurrServer).DefaultDomain + if defaultDomain != "" { + domains = domains + " " + defaultDomain + } + if config.Netclient().DNSSearch != "" { + domains = domains + " " + config.Netclient().DNSSearch + } else { + domains = domains + " ." + } + + dnsIp = getIpFromServerString(dnsIp) + + ns := "nameserver" + ns = ns + " " + dnsIp + + return ns, domains, nil +} + +func getDomains() (string, error) { + + dnsConfig, err := readDNSJsonFile() + if err != nil { + return "", err + } + + domains := "search" + for _, v := range dnsConfig.Domains { + domains = domains + " " + v + } + + defaultDomain := dnsConfig.DefaultDomain + if defaultDomain != "" { + domains = domains + " " + defaultDomain + } + dnsSearch := dnsConfig.DNSSearch + if dnsSearch != "" { + domains = domains + " " + dnsSearch + } + + return domains, nil +} + +func buildDeleteConfigContentUplink() ([]string, error) { + + //get nameserver + dnsIp := GetDNSServerInstance().AddrStr + if dnsIp == "" { + return []string{}, errors.New("no listener is running") + } + + f, err := os.Open(resolvUplinkPath) + if err != nil { + slog.Error("error opending file", "error", resolvUplinkPath, err.Error()) + return []string{}, err + } + defer f.Close() + + rawBytes, err := io.ReadAll(f) + if err != nil { + slog.Error("error reading file", "error", resolvUplinkPath, err.Error()) + return []string{}, err + } + lines := strings.Split(string(rawBytes), "\n") + + dnsIp = getIpFromServerString(dnsIp) + ns := "DNS=" + dnsIp + + lNo := 21 + for i, line := range lines { + if strings.Contains(line, ns) { + lNo = i + break + } + } + + lines = slices.Delete(lines, lNo, lNo+1) + + return lines, nil +} + +func buildDeleteConfigContent() ([]string, error) { + f, err := os.Open(resolvconfFilePath) + if err != nil { + slog.Error("error opending file", "error", resolvconfFilePath, err.Error()) + return []string{}, err + } + defer f.Close() + + rawBytes, err := io.ReadAll(f) + if err != nil { + slog.Error("error reading file", "error", resolvconfFilePath, err.Error()) + return []string{}, err + } + lines := strings.Split(string(rawBytes), "\n") + + //get search domain + domains, err := getDomains() + if err != nil { + slog.Warn("error in getting getDomains", "error", err.Error()) + return []string{}, err + } + + lNo := 100 + for i, line := range lines { + if strings.Contains(line, domains) { + lNo = i + break + } + } + + lines = slices.Delete(lines, lNo, lNo+1) + lines = slices.Delete(lines, lNo, lNo+1) + + return lines, nil +} + +func restoreResolveUplink() error { + + lines, err := buildDeleteConfigContentUplink() + if err != nil { + slog.Warn("could not build config content", "error", err.Error()) + return err + } + + err = writeConfigUplink(lines) + if err != nil { + slog.Warn("could not write config content", "error", err.Error()) + return err + } + time.Sleep(1 * time.Second) + + _, err = ncutils.RunCmd("systemctl restart systemd-resolved", false) + if err != nil { + slog.Warn("restart systemd-resolved failed", "error", err.Error()) + //remove the nameserver from file directly + removeNSUplink() + return err + } + + return nil +} + +func writeConfigUplink(lines []string) error { + + f, err := os.OpenFile(resolvUplinkPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0700) + if err != nil { + slog.Error("error opending file", "error", resolvUplinkPath, err.Error()) + return err + } + defer f.Close() + + for _, v := range lines { + if v != "" { + _, err = fmt.Fprintln(f, v) + if err != nil { + slog.Error("error writing file", "error", resolvUplinkPath, err.Error()) + return err + } + } + } + return nil +} + +func buildDeleteConfigUplink() ([]string, error) { + //get nameserver + dnsIp := GetDNSServerInstance().AddrStr + if dnsIp == "" { + return []string{}, errors.New("no listener is running") + } + + f, err := os.Open(resolvconfFilePath) + if err != nil { + slog.Error("error opending file", "error", resolvconfFilePath, err.Error()) + return []string{}, err + } + defer f.Close() + + rawBytes, err := io.ReadAll(f) + if err != nil { + slog.Error("error reading file", "error", resolvconfFilePath, err.Error()) + return []string{}, err + } + lines := strings.Split(string(rawBytes), "\n") + + //get search domain + dnsIp = getIpFromServerString(dnsIp) + ns := "nameserver " + dnsIp + + lNo := 100 + for i, line := range lines { + if strings.Contains(line, ns) { + lNo = i + break + } + } + + lines = slices.Delete(lines, lNo, lNo+1) + + return lines, nil +} + +func removeNSUplink() error { + lines, err := buildDeleteConfigUplink() + if err != nil { + slog.Warn("could not build config content", "error", err.Error()) + return err + } + + f, err := os.OpenFile(resolvconfUplinkPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0700) + if err != nil { + slog.Error("error opending file", "error", resolvconfUplinkPath, err.Error()) + return err + } + defer f.Close() + + for _, v := range lines { + if v != "" { + _, err = fmt.Fprintln(f, v) + if err != nil { + slog.Error("error writing file", "error", resolvconfUplinkPath, err.Error()) + return err + } + } + } + + return nil + +} + +func restoreResolveconf() error { + + lines, err := buildDeleteConfigContent() + if err != nil { + slog.Warn("could not build config content", "error", err.Error()) + return err + } + + f, err := os.OpenFile(resolvconfFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0700) + if err != nil { + slog.Error("error opending file", "error", resolvconfFilePath, err.Error()) + return err + } + defer f.Close() + + for _, v := range lines { + if v != "" { + _, err = fmt.Fprintln(f, v) + if err != nil { + slog.Error("error writing file", "error", resolvconfFilePath, err.Error()) + return err + } + } + } + + return nil +} + +// Entry point to read current DNS settings and write to config +func InitDNSConfig() { + dnsConfigMutex.Lock() + defer dnsConfigMutex.Unlock() + f, err := os.Open(resolvconfFilePath) + if err != nil { + slog.Error("error opending file", "error", resolvconfFilePath, err.Error()) + return + } + defer f.Close() + + rawBytes, err := io.ReadAll(f) + if err != nil { + slog.Error("error reading file", "error", resolvconfFilePath, err.Error()) + return + } + lines := strings.Split(string(rawBytes), "\n") + nslist := []string{} + for i, line := range lines { + if i == 0 { + if strings.Contains(line, "/run/systemd/resolve/stub-resolv.conf") { + config.Netclient().DNSManagerType = DNS_MANAGER_STUB + } else if strings.Contains(line, "/run/systemd/resolve/resolv.conf") { + config.Netclient().DNSManagerType = DNS_MANAGER_UPLINK + } else if strings.Contains(line, "generated by resolvconf(8)") { + config.Netclient().DNSManagerType = DNS_MANAGER_RESOLVECONF + } else { + config.Netclient().DNSManagerType = DNS_MANAGER_FILE + } + continue + //for ubuntu 20 + } else if i == 3 { + if strings.Contains(line, "DNS stub resolver") { + config.Netclient().DNSManagerType = DNS_MANAGER_STUB + } + continue + } else { + if strings.HasPrefix(line, "nameserver") { + ns := strings.TrimSpace(line[11:]) + if ns != "127.0.0.53" && ns != "127.0.0.54" { + nslist = append(nslist, ns) + } + } + if strings.HasPrefix(line, "search") { + config.Netclient().DNSSearch = strings.TrimSpace(line[7:]) + } + if strings.HasPrefix(line, "options") { + config.Netclient().DNSOptions = strings.TrimSpace(line[8:]) + } + } + } + + //For stub and uplink mode, 127.0.0.53 is contained in resolve.conf file, this is to get the real upstream dns servers + if len(nslist) == 0 && config.Netclient().DNSManagerType != DNS_MANAGER_FILE { + output, err := ncutils.RunCmd("resolvectl status", false) + if err != nil { + slog.Error("resolvectl status command failed", "error", err.Error()) + } else { + lines := strings.Split(output, "\n") + + for _, l := range lines { + if strings.HasPrefix(strings.TrimSpace(l), "DNS Servers:") { + + t := strings.TrimSpace(strings.TrimSpace(l)[12:]) + ll := strings.Split(t, " ") + if len(ll) > 0 { + nslist = append(nslist, ll...) + } + + break + } + } + } + } + + config.Netclient().NameServers = nslist +} diff --git a/dns/config_windows.go b/dns/config_windows.go new file mode 100644 index 00000000..4f683098 --- /dev/null +++ b/dns/config_windows.go @@ -0,0 +1,16 @@ +package dns + +func FlushLocalDnsCache() (err error) { + return nil +} + +func SetupDNSConfig() (err error) { + return nil +} + +func RestoreDNSConfig() (err error) { + return nil +} + +func InitDNSConfig() { +} diff --git a/dns/dns.go b/dns/dns.go new file mode 100644 index 00000000..27f9480f --- /dev/null +++ b/dns/dns.go @@ -0,0 +1,94 @@ +package dns + +import ( + "errors" + "fmt" + "net" + "strings" + "sync" + + "github.com/gravitl/netclient/config" + "github.com/gravitl/netmaker/models" + "github.com/miekg/dns" +) + +var dnsSyncMutex = sync.Mutex{} // used to mutex functions of the DNS + +type dnsRecord struct { + Name string + // Type of record, 1 for A, 5 for CNAME, 28 for AAAA + Type uint16 + RData string +} + +// new a dnsRecord object +func newDNSRecord(name string, t uint16, dest string) dnsRecord { + return dnsRecord{ + Name: name, + Type: t, + RData: dest, + } +} + +func buildDNSEntryKey(name string, t uint16) string { + return fmt.Sprintf("%s.%d", name, t) +} + +// Sync up the DNS entries with NM server +func SyncDNS(network string, dnsEntries []models.DNSEntry) error { + dnsSyncMutex.Lock() + defer dnsSyncMutex.Unlock() + if len(dnsEntries) == 0 { + return errors.New("no DNS entry") + } + + defaultDomain := config.GetServer(config.CurrServer).DefaultDomain + + dnsEntryMap := []dnsRecord{} + + for _, v := range dnsEntries { + + if !strings.HasSuffix(v.Name, v.Network) { + if !strings.HasSuffix(v.Name, defaultDomain) { + continue + } + } + + if v.Address != "" { + if ipv4 := net.ParseIP(v.Address).To4(); ipv4 != nil { + r := newDNSRecord(v.Name, dns.TypeA, v.Address) + dnsEntryMap = append(dnsEntryMap, r) + } + } + + if v.Address6 != "" { + if ipv4 := net.ParseIP(v.Address6).To4(); ipv4 == nil { + r := newDNSRecord(v.Name, dns.TypeAAAA, v.Address6) + dnsEntryMap = append(dnsEntryMap, r) + } + } + } + + //update the dns records for given network + GetDNSResolverInstance().DnsEntriesCacheMap[network] = dnsEntryMap + + //Refresh dns store + GetDNSResolverInstance().DnsEntriesCacheStore = make(map[string]dns.RR) + for _, v := range GetDNSResolverInstance().DnsEntriesCacheMap { + for _, d := range v { + if d.Type == dns.TypeA { + GetDNSResolverInstance().RegisterA(d) + continue + } + if d.Type == dns.TypeAAAA { + GetDNSResolverInstance().RegisterAAAA(d) + continue + } + } + } + + //Flush local dns cache if any + FlushLocalDnsCache() + + return nil +} diff --git a/dns/listener.go b/dns/listener.go new file mode 100644 index 00000000..1e32a4e1 --- /dev/null +++ b/dns/listener.go @@ -0,0 +1,133 @@ +package dns + +import ( + "context" + "slices" + "sync" + "time" + + "github.com/gravitl/netclient/config" + "github.com/miekg/dns" + "golang.org/x/exp/slog" +) + +var dnsMutex = sync.Mutex{} // used to mutex functions of the DNS + +type DNSServer struct { + DnsServer []*dns.Server + AddrList []string + AddrStr string +} + +var dnsServer *DNSServer + +func init() { + dnsServer = &DNSServer{} +} + +// GetInstance +func GetDNSServerInstance() *DNSServer { + return dnsServer +} + +// Start the DNS listener +func (dnsServer *DNSServer) Start() { + dnsMutex.Lock() + defer dnsMutex.Unlock() + if dnsServer.AddrStr != "" { + return + } + + if len(config.GetNodes()) == 0 { + return + } + + for _, v := range config.GetNodes() { + node := v + if v.Connected { + + lIp := "" + if node.Address6.IP != nil { + lIp = "[" + node.Address6.IP.String() + "]:53" + } + if node.Address.IP != nil { + lIp = node.Address.IP.String() + ":53" + } + + if lIp == "" { + continue + } + + dns.HandleFunc(".", handleDNSRequest) + + srv := &dns.Server{ + Net: "udp", + Addr: lIp, + UDPSize: 65535, + ReusePort: true, + ReuseAddr: true, + } + + dnsServer.AddrStr = lIp + dnsServer.AddrList = append(dnsServer.AddrList, lIp) + dnsServer.DnsServer = append(dnsServer.DnsServer, srv) + + go func(dnsServer *DNSServer) { + err := srv.ListenAndServe() + if err != nil { + slog.Error("error in starting dns server", "error", lIp, err.Error()) + dnsServer.AddrStr = "" + dnsServer.AddrList = slices.Delete(dnsServer.AddrList, len(dnsServer.AddrList)-1, len(dnsServer.AddrList)) + dnsServer.DnsServer = slices.Delete(dnsServer.DnsServer, len(dnsServer.DnsServer)-1, len(dnsServer.DnsServer)) + } + }(dnsServer) + } + } + + time.Sleep(time.Second * 1) + //if listener failed to start, do not make DNS changes + if len(dnsServer.AddrList) == 0 || len(dnsServer.DnsServer) == 0 { + return + } + + //Setup DNS config for Linux + if config.Netclient().Host.OS == "linux" { + err := SetupDNSConfig() + if err != nil { + slog.Error("setup DNS conig failed", "error", err.Error()) + } + } + + slog.Info("DNS server listens on: ", "Info", dnsServer.AddrStr) +} + +// Stop the DNS listener +func (dnsServer *DNSServer) Stop() { + dnsMutex.Lock() + defer dnsMutex.Unlock() + if len(dnsServer.AddrList) == 0 || len(dnsServer.DnsServer) == 0 { + return + } + + //restore DNS config for Linux + if config.Netclient().Host.OS == "linux" { + err := RestoreDNSConfig() + if err != nil { + slog.Warn("Restore DNS conig failed", "error", err.Error()) + } + } + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + for _, v := range dnsServer.DnsServer { + err := v.ShutdownContext(ctx) + if err != nil { + slog.Error("could not shutdown DNS server", "error", err.Error()) + } + } + + dnsServer.AddrStr = "" + dnsServer.AddrList = []string{} + dnsServer.DnsServer = []*dns.Server{} +} diff --git a/dns/resolver.go b/dns/resolver.go new file mode 100644 index 00000000..7d86bef4 --- /dev/null +++ b/dns/resolver.go @@ -0,0 +1,149 @@ +package dns + +import ( + "net" + "strings" + "sync" + + "github.com/gravitl/netclient/config" + "github.com/miekg/dns" + "golang.org/x/exp/slog" +) + +const ( + ttlTimeout = 3600 +) + +var dnsMapMutex = sync.Mutex{} // used to mutex functions of the DNS + +type DNSResolver struct { + DnsEntriesCacheStore map[string]dns.RR + DnsEntriesCacheMap map[string][]dnsRecord +} + +var DnsResolver *DNSResolver + +func init() { + DnsResolver = &DNSResolver{ + DnsEntriesCacheStore: make(map[string]dns.RR), + DnsEntriesCacheMap: make(map[string][]dnsRecord), + } +} + +// GetInstance +func GetDNSResolverInstance() *DNSResolver { + return DnsResolver +} + +func isInternetGW() bool { + for _, v := range config.GetNodes() { + if v.IsIngressGateway { + return true + } + } + + return false +} + +// ServeDNS handles a DNS request +func handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { + slog.Info("receiving DNS query request", "Info", r.Question[0]) + reply := &dns.Msg{} + reply.SetReply(r) + reply.RecursionAvailable = true + reply.RecursionDesired = true + reply.Rcode = dns.RcodeSuccess + + resp := GetDNSResolverInstance().Lookup(r) + if resp != nil { + reply.Answer = append(reply.Answer, resp) + } else { + nslist := config.Netclient().NameServers + if config.Netclient().CurrGwNmIP != nil { + nslist = []string{} + nslist = append(nslist, config.Netclient().CurrGwNmIP.String()) + } else if isInternetGW() { + nslist = []string{} + nslist = append(nslist, "8.8.8.8") + nslist = append(nslist, "8.8.4.4") + nslist = append(nslist, "2001:4860:4860::8888") + nslist = append(nslist, "2001:4860:4860::8844") + } + + gotResult := false + client := &dns.Client{} + for _, v := range nslist { + if strings.Contains(v, ":") { + v = "[" + v + "]" + } + resp, _, err := client.Exchange(r, v+":53") + if err != nil { + continue + } + + if resp.Rcode != dns.RcodeSuccess { + continue + } + + if len(resp.Answer) > 0 { + reply.Answer = append(reply.Answer, resp.Answer...) + gotResult = true + break + } + } + + if !gotResult { + reply.Rcode = dns.RcodeNameError + } + } + + err := w.WriteMsg(reply) + if err != nil { + slog.Error("write DNS response message error: ", "error", err.Error()) + } +} + +// Register A record +func (d *DNSResolver) RegisterA(record dnsRecord) error { + dnsMapMutex.Lock() + defer dnsMapMutex.Unlock() + + r := new(dns.A) + r.Hdr = dns.RR_Header{Name: dns.Fqdn(record.Name), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: ttlTimeout} + r.A = net.ParseIP(record.RData) + + d.DnsEntriesCacheStore[buildDNSEntryKey(record.Name, record.Type)] = r + + slog.Info("registering A record successfully", "Info", d.DnsEntriesCacheStore[buildDNSEntryKey(record.Name, record.Type)]) + + return nil +} + +// Register AAAA record +func (d *DNSResolver) RegisterAAAA(record dnsRecord) error { + dnsMapMutex.Lock() + defer dnsMapMutex.Unlock() + + r := new(dns.AAAA) + r.Hdr = dns.RR_Header{Name: dns.Fqdn(record.Name), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttlTimeout} + r.AAAA = net.ParseIP(record.RData) + + d.DnsEntriesCacheStore[buildDNSEntryKey(record.Name, record.Type)] = r + + slog.Info("registering AAAA record successfully", "Info", d.DnsEntriesCacheStore[buildDNSEntryKey(record.Name, record.Type)]) + + return nil +} + +// Lookup DNS entry in local directory +func (d *DNSResolver) Lookup(m *dns.Msg) dns.RR { + dnsMapMutex.Lock() + defer dnsMapMutex.Unlock() + q := m.Question[0] + r, ok := d.DnsEntriesCacheStore[buildDNSEntryKey(strings.TrimSuffix(q.Name, "."), q.Qtype)] + if !ok { + return nil + } + + return r +} diff --git a/functions/daemon.go b/functions/daemon.go index 059e1385..3b4306b0 100644 --- a/functions/daemon.go +++ b/functions/daemon.go @@ -16,6 +16,7 @@ import ( "github.com/gravitl/netclient/cache" "github.com/gravitl/netclient/config" "github.com/gravitl/netclient/daemon" + "github.com/gravitl/netclient/dns" "github.com/gravitl/netclient/firewall" "github.com/gravitl/netclient/local" "github.com/gravitl/netclient/ncutils" @@ -75,6 +76,7 @@ func Daemon() { select { case <-quit: slog.Info("shutting down netclient daemon") + dns.GetDNSServerInstance().Stop() //check if it needs to restore the default gateway checkAndRestoreDefaultGateway() closeRoutines([]context.CancelFunc{ @@ -85,6 +87,7 @@ func Daemon() { return case <-reset: slog.Info("received reset") + dns.GetDNSServerInstance().Stop() //check if it needs to restore the default gateway checkAndRestoreDefaultGateway() closeRoutines([]context.CancelFunc{ @@ -148,6 +151,11 @@ func startGoRoutines(wg *sync.WaitGroup) context.CancelFunc { } updateConfig := false + if config.Netclient().Host.OS == "linux" { + dns.InitDNSConfig() + updateConfig = true + } + if !config.Netclient().IsStaticPort { if freeport, err := ncutils.GetFreePort(config.Netclient().ListenPort); err != nil { slog.Warn("no free ports available for use by netclient", "error", err.Error()) @@ -272,6 +280,14 @@ func startGoRoutines(wg *sync.WaitGroup) context.CancelFunc { wg.Add(1) go mqFallback(ctx, wg) + if server.ManageDNS { + if dns.GetDNSServerInstance().AddrStr == "" { + dns.GetDNSServerInstance().Start() + } + } else { + dns.GetDNSServerInstance().Stop() + } + return cancel } @@ -318,6 +334,9 @@ func setupMQTT(server *config.Server) error { for _, node := range nodes { node := node setSubscriptions(client, &node) + if server.ManageDNS { + setDNSSubscriptions(client, &node) + } } setHostSubscription(client, server.Name) checkin() @@ -378,6 +397,9 @@ func setupMQTTSingleton(server *config.Server, publishOnly bool) error { for _, node := range nodes { node := node setSubscriptions(client, &node) + if server.ManageDNS { + setDNSSubscriptions(client, &node) + } } setHostSubscription(client, server.Name) } @@ -433,6 +455,20 @@ func setSubscriptions(client mqtt.Client, node *config.Node) { slog.Info("subscribed to updates for node", "node", node.ID, "network", node.Network) } +// setDNSSubscriptions sets MQ client subscriptions for a specific node config +// should be called for each node belonging to a given server +func setDNSSubscriptions(client mqtt.Client, node *config.Node) { + if token := client.Subscribe(fmt.Sprintf("host/dns/sync/%s", node.Network), 0, mqtt.MessageHandler(DNSSync)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil { + if token.Error() == nil { + slog.Error("unable to subscribe to DNS sync for node ", "node", node.ID, "error", "connection timeout") + } else { + slog.Error("unable to subscribe to DNS sync for node ", "node", node.ID, "error", token.Error()) + } + return + } + slog.Info("subscribed to DNS sync for node", "node", node.ID, "network", node.Network) +} + // should only ever use node client configs func decryptMsg(serverName string, msg []byte) ([]byte, error) { if len(msg) <= 24 { // make sure message is of appropriate length @@ -493,6 +529,15 @@ func unsubscribeNode(client mqtt.Client, node *config.Node) { ok = false } // peer updates belong to host now + if token := client.Unsubscribe(fmt.Sprintf("host/dns/sync/%s", node.Network)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil { + if token.Error() == nil { + slog.Error("unable to unsubscribe from DNS sync for node ", "node", node.ID, "error", "connection timeout") + } else { + slog.Error("unable to unsubscribe from DNS sync for node ", "node", node.ID, "error", token.Error()) + } + ok = false + } + if ok { slog.Info("unsubscribed from updates for node", "node", node.ID, "network", node.Network) } diff --git a/functions/mqhandlers.go b/functions/mqhandlers.go index 6f0bf6fd..4b87761d 100644 --- a/functions/mqhandlers.go +++ b/functions/mqhandlers.go @@ -16,6 +16,7 @@ import ( "github.com/gravitl/netclient/cache" "github.com/gravitl/netclient/config" "github.com/gravitl/netclient/daemon" + "github.com/gravitl/netclient/dns" "github.com/gravitl/netclient/firewall" "github.com/gravitl/netclient/ncutils" "github.com/gravitl/netclient/networking" @@ -98,6 +99,10 @@ func NodeUpdate(client mqtt.Client, msg mqtt.Message) { } wireguard.SetRoutesFromCache() time.Sleep(time.Second) + if server.ManageDNS { + dns.GetDNSServerInstance().Stop() + dns.GetDNSServerInstance().Start() + } doneErr := publishSignal(&newNode, DONE) if doneErr != nil { @@ -108,6 +113,26 @@ func NodeUpdate(client mqtt.Client, msg mqtt.Message) { } } +// DNSSync -- mqtt message handler for host/dns/sync/ topic +func DNSSync(client mqtt.Client, msg mqtt.Message) { + + network := parseServerFromTopic(msg.Topic()) + + var dnsEntries []models.DNSEntry + err := json.Unmarshal([]byte(msg.Payload()), &dnsEntries) + if err != nil { + slog.Error("error unmarshalling DNS data", "error", err) + return + } + + if len(dnsEntries) > 0 { + err = dns.SyncDNS(network, dnsEntries) + if err != nil { + slog.Error("synchronize DNS error ", "error", err.Error()) + } + } +} + // HostPeerUpdate - mq handler for host peer update peers/host// func HostPeerUpdate(client mqtt.Client, msg mqtt.Message) { var peerUpdate models.HostPeerUpdate @@ -203,6 +228,21 @@ func HostPeerUpdate(client mqtt.Client, msg mqtt.Message) { cache.SkipEndpointCache = sync.Map{} } + + if peerUpdate.ManageDNS != server.ManageDNS { + server.ManageDNS = peerUpdate.ManageDNS + config.UpdateServer(serverName, *server) + _ = config.WriteServerConfig() + if peerUpdate.ManageDNS { + dns.GetDNSServerInstance().Start() + } else { + dns.GetDNSServerInstance().Stop() + } + } + if server.ManageDNS && config.Netclient().DNSManagerType == dns.DNS_MANAGER_STUB { + dns.SetupDNSConfig() + } + handleFwUpdate(serverName, &peerUpdate.FwUpdate) } @@ -279,6 +319,9 @@ func HostUpdate(client mqtt.Client, msg mqtt.Message) { slog.Error("failed to response with ACK to server", "server", serverName, "error", err) } setSubscriptions(client, &nodeCfg) + if server.ManageDNS { + setDNSSubscriptions(client, &nodeCfg) + } resetInterface = true case models.DeleteHost: clearRetainedMsg(client, msg.Topic()) @@ -363,6 +406,20 @@ func resetInterfaceFunc() { slog.Error("failed to set peers", err) } wireguard.SetRoutesFromCache() + + server := config.GetServer(config.CurrServer) + if server == nil { + return + } + if server.ManageDNS { + if dns.GetDNSServerInstance().AddrStr == "" { + dns.GetDNSServerInstance().Start() + } + //Setup resolveconf for Linux + if config.Netclient().Host.OS == "linux" && dns.GetDNSServerInstance().AddrStr != "" && config.Netclient().DNSManagerType == dns.DNS_MANAGER_STUB { + dns.SetupDNSConfig() + } + } } // handleEndpointDetection - select best interface for each peer and set it as endpoint diff --git a/functions/uninstall.go b/functions/uninstall.go index 8a4409d7..246a043a 100644 --- a/functions/uninstall.go +++ b/functions/uninstall.go @@ -11,6 +11,7 @@ import ( "github.com/gravitl/netclient/auth" "github.com/gravitl/netclient/config" "github.com/gravitl/netclient/daemon" + "github.com/gravitl/netclient/dns" "github.com/gravitl/netclient/wireguard" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/models" @@ -61,7 +62,12 @@ func LeaveNetwork(network string, isDaemon bool) ([]error, error) { } // re-configure interface if daemon is calling leave if isDaemon { + dns.GetDNSServerInstance().Stop() faults = resetInterfaceUninstall(faults) + server := config.GetServer(config.CurrServer) + if server != nil && server.ManageDNS { + dns.GetDNSServerInstance().Start() + } } else { // was called from CLI so restart daemon if err := daemon.Restart(); err != nil { faults = append(faults, fmt.Errorf("could not restart daemon after leave - %v", err.Error())) diff --git a/go.mod b/go.mod index 5ddb5fbb..cf27b216 100644 --- a/go.mod +++ b/go.mod @@ -14,13 +14,13 @@ require ( github.com/google/nftables v0.1.0 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 - github.com/gravitl/netmaker v0.24.3-0.20240704124720-780da3b69617 + github.com/gravitl/netmaker v0.25.1-0.20241018065020-d7c11711b710 github.com/gravitl/tcping v0.1.2-0.20230801110928-546055ebde06 - github.com/gravitl/txeh v0.0.0-20230509181318-3778c58bd69f github.com/guumaster/hostctl v1.1.4 github.com/hashicorp/go-version v1.7.0 github.com/kr/pretty v0.3.1 github.com/matryer/is v1.4.1 + github.com/miekg/dns v1.1.62 github.com/minio/selfupdate v0.6.0 github.com/sasha-s/go-deadlock v0.3.5 github.com/spf13/cobra v1.8.1 @@ -37,6 +37,7 @@ require ( golang.zx2c4.com/wireguard/windows v0.5.3 gopkg.in/yaml.v3 v3.0.1 gortc.io/stun v1.23.0 + ) require ( @@ -52,7 +53,7 @@ require ( github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.22.0 // indirect + github.com/go-playground/validator/v10 v10.22.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect @@ -63,7 +64,7 @@ require ( github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/mdlayher/genetlink v1.2.0 // indirect github.com/mdlayher/netlink v1.6.2 // indirect github.com/mdlayher/socket v0.2.3 // indirect @@ -74,12 +75,14 @@ require ( github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 // indirect + github.com/posthog/posthog-go v1.2.24 // indirect github.com/rivo/uniseg v0.4.6 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/rqlite/gorqlite v0.0.0-20240122221808-a8a425b1a6aa // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/seancfoley/bintree v1.3.1 // indirect + github.com/seancfoley/ipaddress-go v1.7.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect @@ -87,13 +90,12 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/txn2/txeh v1.5.5 // indirect github.com/vishvananda/netns v0.0.4 // indirect - github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/mod v0.17.0 // indirect + golang.org/x/mod v0.18.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/text v0.19.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/tools v0.22.0 // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 17e154db..1e71c8a6 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,6 @@ aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= @@ -11,7 +10,6 @@ github.com/c-robinson/iplib v1.0.8 h1:exDRViDyL9UBLcfmlxxkY5odWX5092nPsQIykHXhIn github.com/c-robinson/iplib v1.0.8/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo= github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc= github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -46,8 +44,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= -github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -68,12 +66,10 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gravitl/netmaker v0.24.3-0.20240704124720-780da3b69617 h1:9D9BVv15xlsXFAu0uZuSO2OBwyH/7pH9lDlYvAWQV3o= -github.com/gravitl/netmaker v0.24.3-0.20240704124720-780da3b69617/go.mod h1:1kLU1xomJ5fnLC3zd697fdYbRzTSOwh2WfEBglZDT/Q= +github.com/gravitl/netmaker v0.25.1-0.20241018065020-d7c11711b710 h1:J+/2sbcxPJmTxIt7dobTwnj9Ts8KDnGudEjPtVNpX/g= +github.com/gravitl/netmaker v0.25.1-0.20241018065020-d7c11711b710/go.mod h1:iFgYDpDXpeFJw3EWgm64HymNl1e2DEr9PCbHud/PLxA= github.com/gravitl/tcping v0.1.2-0.20230801110928-546055ebde06 h1:g2fBXRNT9eiQohyHcoME3SVmeG7OKoJPWrs7A+009kU= github.com/gravitl/tcping v0.1.2-0.20230801110928-546055ebde06/go.mod h1:12iViYKWAzRPj5/oEGAaD7Wje+Nuz8M9eDJbV7qhKAA= -github.com/gravitl/txeh v0.0.0-20230509181318-3778c58bd69f h1:XzsYovKdrDvj2z2HEHoeHU67+JIEFMHQKHU6oU+1fVE= -github.com/gravitl/txeh v0.0.0-20230509181318-3778c58bd69f/go.mod h1:Nqo/7iOJSVP1JRSUv+FkZ0FgBjK89gjU0D/V8nH4xy8= github.com/guumaster/hostctl v1.1.4 h1:4zb9wEurBlz/hQiXFz9feHHfunf7oj+9serAH8ohGuM= github.com/guumaster/hostctl v1.1.4/go.mod h1:2o7cm8eV8vVWWB611tdVfsUVPziD0KECwzPKLfOzwN8= github.com/guumaster/tablewriter v0.0.10 h1:A0HD94yMdt4usgxBjoEceNeE0XMJ027euoHAzsPqBQs= @@ -104,8 +100,8 @@ github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU= github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ= github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= @@ -114,6 +110,8 @@ github.com/mdlayher/netlink v1.6.2/go.mod h1:O1HXX2sIWSMJ3Qn1BYZk1yZM+7iMki/uYGG github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU= @@ -138,15 +136,14 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on62KZOci/VLijlkdF/yeWU262BQgvcjE= -github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU= +github.com/posthog/posthog-go v1.2.24 h1:A+iG4saBJemo++VDlcWovbYf8KFFNUfrCoJtsc40RPA= +github.com/posthog/posthog-go v1.2.24/go.mod h1:uYC2l1Yktc8E+9FAHJ9QZG4vQf/NHJPD800Hsm7DzoM= github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rqlite/gorqlite v0.0.0-20240122221808-a8a425b1a6aa h1:hxMLFbj+F444JAS5nUQxTDZwUxwCRqg3WkNqhiDzXrM= github.com/rqlite/gorqlite v0.0.0-20240122221808-a8a425b1a6aa/go.mod h1:xF/KoXmrRyahPfo5L7Szb5cAAUl53dMWBh9cMruGEZg= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= @@ -154,7 +151,10 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/seancfoley/bintree v1.3.1 h1:cqmmQK7Jm4aw8gna0bP+huu5leVOgHGSJBEpUx3EXGI= +github.com/seancfoley/bintree v1.3.1/go.mod h1:hIUabL8OFYyFVTQ6azeajbopogQc2l5C/hiXMcemWNU= +github.com/seancfoley/ipaddress-go v1.7.0 h1:vWp3SR3k+HkV3aKiNO2vEe6xbVxS0x/Ixw6hgyP238s= +github.com/seancfoley/ipaddress-go v1.7.0/go.mod h1:TQRZgv+9jdvzHmKoPGBMxyiaVmoI0rYpfEk8Q/sL/Iw= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= @@ -185,13 +185,10 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/txn2/txeh v1.5.5 h1:UN4e/lCK5HGw/gGAi2GCVrNKg0GTCUWs7gs5riaZlz4= github.com/txn2/txeh v1.5.5/go.mod h1:qYzGG9kCzeVEI12geK4IlanHWY8X4uy/I3NcW7mk8g4= -github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g= -github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= @@ -209,8 +206,8 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjs golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -266,8 +263,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -285,7 +282,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=