util.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. // package for logicing client and server code
  2. package logic
  3. import (
  4. "crypto/rand"
  5. "encoding/base32"
  6. "encoding/base64"
  7. "encoding/json"
  8. "fmt"
  9. "log/slog"
  10. "net"
  11. "net/http"
  12. "os"
  13. "reflect"
  14. "regexp"
  15. "strings"
  16. "time"
  17. "unicode"
  18. "github.com/blang/semver"
  19. "github.com/c-robinson/iplib"
  20. "github.com/gravitl/netmaker/database"
  21. "github.com/gravitl/netmaker/logger"
  22. "github.com/gravitl/netmaker/models"
  23. )
  24. // IsBase64 - checks if a string is in base64 format
  25. // This is used to validate public keys (make sure they're base64 encoded like all public keys should be).
  26. func IsBase64(s string) bool {
  27. _, err := base64.StdEncoding.DecodeString(s)
  28. return err == nil
  29. }
  30. // CheckEndpoint - checks if an endpoint is valid
  31. func CheckEndpoint(endpoint string) bool {
  32. endpointarr := strings.Split(endpoint, ":")
  33. return len(endpointarr) == 2
  34. }
  35. // FileExists - checks if local file exists
  36. func FileExists(f string) bool {
  37. info, err := os.Stat(f)
  38. if os.IsNotExist(err) {
  39. return false
  40. }
  41. return !info.IsDir()
  42. }
  43. // IsAddressInCIDR - util to see if an address is in a cidr or not
  44. func IsAddressInCIDR(address net.IP, cidr string) bool {
  45. var _, currentCIDR, cidrErr = net.ParseCIDR(cidr)
  46. if cidrErr != nil {
  47. return false
  48. }
  49. return currentCIDR.Contains(address)
  50. }
  51. // SetNetworkNodesLastModified - sets the network nodes last modified
  52. func SetNetworkNodesLastModified(networkName string) error {
  53. timestamp := time.Now().Unix()
  54. network, err := GetParentNetwork(networkName)
  55. if err != nil {
  56. return err
  57. }
  58. network.NodesLastModified = timestamp
  59. data, err := json.Marshal(&network)
  60. if err != nil {
  61. return err
  62. }
  63. err = database.Insert(networkName, string(data), database.NETWORKS_TABLE_NAME)
  64. if err != nil {
  65. return err
  66. }
  67. return nil
  68. }
  69. // RandomString - returns a random string in a charset
  70. func RandomString(length int) string {
  71. randombytes := make([]byte, length)
  72. _, err := rand.Read(randombytes)
  73. if err != nil {
  74. logger.Log(0, "random string", err.Error())
  75. return ""
  76. }
  77. return base32.StdEncoding.EncodeToString(randombytes)[:length]
  78. }
  79. // StringSliceContains - sees if a string slice contains a string element
  80. func StringSliceContains(slice []string, item string) bool {
  81. for _, s := range slice {
  82. if s == item {
  83. return true
  84. }
  85. }
  86. return false
  87. }
  88. func SetVerbosity(logLevel int) {
  89. var level slog.Level
  90. switch logLevel {
  91. case 0:
  92. level = slog.LevelInfo
  93. case 1:
  94. level = slog.LevelError
  95. case 2:
  96. level = slog.LevelWarn
  97. case 3:
  98. level = slog.LevelDebug
  99. default:
  100. level = slog.LevelInfo
  101. }
  102. // Create the logger with the chosen level
  103. handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
  104. Level: level,
  105. })
  106. logger := slog.New(handler)
  107. slog.SetDefault(logger)
  108. }
  109. // NormalizeCIDR - returns the first address of CIDR
  110. func NormalizeCIDR(address string) (string, error) {
  111. ip, IPNet, err := net.ParseCIDR(address)
  112. if err != nil {
  113. return "", err
  114. }
  115. if ip.To4() == nil {
  116. net6 := iplib.Net6FromStr(IPNet.String())
  117. IPNet.IP = net6.FirstAddress()
  118. } else {
  119. net4 := iplib.Net4FromStr(IPNet.String())
  120. IPNet.IP = net4.NetworkAddress()
  121. }
  122. return IPNet.String(), nil
  123. }
  124. // StringDifference - returns the elements in `a` that aren't in `b`.
  125. func StringDifference(a, b []string) []string {
  126. mb := make(map[string]struct{}, len(b))
  127. for _, x := range b {
  128. mb[x] = struct{}{}
  129. }
  130. var diff []string
  131. for _, x := range a {
  132. if _, found := mb[x]; !found {
  133. diff = append(diff, x)
  134. }
  135. }
  136. return diff
  137. }
  138. // CheckIfFileExists - checks if file exists or not in the given path
  139. func CheckIfFileExists(filePath string) bool {
  140. if _, err := os.Stat(filePath); os.IsNotExist(err) {
  141. return false
  142. }
  143. return true
  144. }
  145. // RemoveStringSlice - removes an element at given index i
  146. // from a given string slice
  147. func RemoveStringSlice(slice []string, i int) []string {
  148. return append(slice[:i], slice[i+1:]...)
  149. }
  150. // RemoveAllFromSlice removes every occurrence of val from s (stable order).
  151. func RemoveAllFromSlice[T comparable](s []T, val T) []T {
  152. // Reuse the underlying array: write filtered items back into s[:0].
  153. out := s[:0]
  154. for _, v := range s {
  155. if v != val {
  156. out = append(out, v)
  157. }
  158. }
  159. // out now contains only the kept items; capacity unchanged, len shrunk.
  160. return out
  161. }
  162. // IsSlicesEqual tells whether a and b contain the same elements.
  163. // A nil argument is equivalent to an empty slice.
  164. func IsSlicesEqual(a, b []string) bool {
  165. if len(a) != len(b) {
  166. return false
  167. }
  168. for i, v := range a {
  169. if v != b[i] {
  170. return false
  171. }
  172. }
  173. return true
  174. }
  175. // VersionLessThan checks if v1 < v2 semantically
  176. // dev is the latest version
  177. func VersionLessThan(v1, v2 string) (bool, error) {
  178. if v1 == "dev" {
  179. return false, nil
  180. }
  181. if v2 == "dev" {
  182. return true, nil
  183. }
  184. semVer1 := strings.TrimFunc(v1, func(r rune) bool {
  185. return !unicode.IsNumber(r)
  186. })
  187. semVer2 := strings.TrimFunc(v2, func(r rune) bool {
  188. return !unicode.IsNumber(r)
  189. })
  190. sv1, err := semver.Parse(semVer1)
  191. if err != nil {
  192. return false, fmt.Errorf("failed to parse semver1 (%s): %w", semVer1, err)
  193. }
  194. sv2, err := semver.Parse(semVer2)
  195. if err != nil {
  196. return false, fmt.Errorf("failed to parse semver2 (%s): %w", semVer2, err)
  197. }
  198. return sv1.LT(sv2), nil
  199. }
  200. // Compare any two maps with any key and value types
  201. func CompareMaps[K comparable, V any](a, b map[K]V) bool {
  202. if len(a) != len(b) {
  203. return false
  204. }
  205. for key, valA := range a {
  206. valB, ok := b[key]
  207. if !ok {
  208. return false
  209. }
  210. if !reflect.DeepEqual(valA, valB) {
  211. return false
  212. }
  213. }
  214. return true
  215. }
  216. func UniqueStrings(input []string) []string {
  217. seen := make(map[string]struct{})
  218. var result []string
  219. for _, val := range input {
  220. if _, ok := seen[val]; !ok {
  221. seen[val] = struct{}{}
  222. result = append(result, val)
  223. }
  224. }
  225. return result
  226. }
  227. func GetClientIP(r *http.Request) string {
  228. // Trust X-Forwarded-For first
  229. if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
  230. parts := strings.Split(xff, ",")
  231. return strings.TrimSpace(parts[0])
  232. }
  233. if xrip := r.Header.Get("X-Real-IP"); xrip != "" {
  234. return xrip
  235. }
  236. ip, _, err := net.SplitHostPort(r.RemoteAddr)
  237. if err != nil {
  238. return r.RemoteAddr
  239. }
  240. return ip
  241. }
  242. // CompareIfaceSlices compares two slices of Iface for deep equality (order-sensitive)
  243. func CompareIfaceSlices(a, b []models.Iface) bool {
  244. if len(a) != len(b) {
  245. return false
  246. }
  247. for i := range a {
  248. if !compareIface(a[i], b[i]) {
  249. return false
  250. }
  251. }
  252. return true
  253. }
  254. func compareIface(a, b models.Iface) bool {
  255. return a.Name == b.Name &&
  256. a.Address.IP.Equal(b.Address.IP) &&
  257. a.Address.Mask.String() == b.Address.Mask.String() &&
  258. a.AddressString == b.AddressString
  259. }
  260. // IsFQDN checks if the given string is a valid Fully Qualified Domain Name (FQDN)
  261. func IsFQDN(domain string) bool {
  262. // Basic check to ensure the domain is not empty and has at least one dot (.)
  263. if domain == "" || !strings.Contains(domain, ".") {
  264. return false
  265. }
  266. // Regular expression for validating FQDN (basic check for valid characters and structure)
  267. fqdnRegex := `^(?i)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$`
  268. re := regexp.MustCompile(fqdnRegex)
  269. return re.MatchString(domain)
  270. }