github.com/richardwilkes/toolbox@v1.121.0/xio/network/addresses.go (about) 1 // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved. 2 // 3 // This Source Code Form is subject to the terms of the Mozilla Public 4 // License, version 2.0. If a copy of the MPL was not distributed with 5 // this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 // 7 // This Source Code Form is "Incompatible With Secondary Licenses", as 8 // defined by the Mozilla Public License, version 2.0. 9 10 // Package network provides network-related utilities. 11 package network 12 13 import ( 14 "math/rand/v2" 15 "net" 16 "sort" 17 "strings" 18 "time" 19 20 "github.com/richardwilkes/toolbox/collection" 21 "github.com/richardwilkes/toolbox/txt" 22 ) 23 24 // Constants for common network addresses. 25 const ( 26 IPv4LoopbackAddress = "127.0.0.1" 27 IPv6LoopbackAddress = "::1" 28 LocalHost = "localhost" 29 ) 30 31 // PrimaryIPAddress returns the primary IP address. 32 func PrimaryIPAddress() string { 33 // Try up to 3 times in case of transient errors 34 for i := 0; i < 3; i++ { 35 if addresses, err := net.InterfaceAddrs(); err == nil { 36 var fallback string 37 for _, address := range addresses { 38 var ip net.IP 39 switch v := address.(type) { 40 case *net.IPNet: 41 ip = v.IP 42 case *net.IPAddr: 43 ip = v.IP 44 default: 45 continue 46 } 47 if ip.IsGlobalUnicast() { 48 if ip.To4() != nil { 49 return ip.String() 50 } 51 if fallback == "" { 52 fallback = ip.String() 53 } 54 } 55 } 56 if fallback != "" { 57 return fallback 58 } 59 } 60 //nolint:gosec // Yes, it is ok to use a weak prng here 61 time.Sleep(time.Duration(100+rand.IntN(50)) * time.Millisecond) 62 } 63 return IPv4LoopbackAddress 64 } 65 66 // PrimaryAddress returns the primary hostname and its associated IP address and MAC address. 67 func PrimaryAddress() (hostname, ipAddress, macAddress string) { 68 // Try up to 3 times in case of transient errors 69 for i := 0; i < 3; i++ { 70 lowest := 1000000 71 for address, iFace := range ActiveAddresses() { 72 if iFace.Index < lowest { 73 lowest = iFace.Index 74 hostname = address 75 macAddress = iFace.HardwareAddr.String() 76 } 77 } 78 if hostname != "" { 79 if ips, err := net.LookupIP(hostname); err == nil && len(ips) > 0 { 80 for _, ip := range ips { 81 if ip.To4() != nil { 82 ipAddress = ip.String() 83 break 84 } else if ipAddress == "" { 85 ipAddress = ip.String() 86 } 87 } 88 if ipAddress != "" { 89 return hostname, ipAddress, macAddress 90 } 91 } 92 } 93 //nolint:gosec // Yes, it is ok to use a weak prng here 94 time.Sleep(time.Duration(100+rand.IntN(50)) * time.Millisecond) 95 } 96 return LocalHost, IPv4LoopbackAddress, "00:00:00:00:00:00" 97 } 98 99 // ActiveAddresses determines the best address for each active network interface. IPv4 addresses will be selected over 100 // IPv6 addresses on the same interface. Numeric addresses are resolved into names where possible. 101 func ActiveAddresses() map[string]net.Interface { 102 result := make(map[string]net.Interface) 103 if iFaces, err := net.Interfaces(); err == nil { 104 for _, iFace := range iFaces { 105 const interesting = net.FlagUp | net.FlagBroadcast 106 if iFace.Flags&interesting == interesting { 107 if name := Address(iFace); name != "" { 108 result[name] = iFace 109 } 110 } 111 } 112 } 113 return result 114 } 115 116 // Address returns the best address for the network interface. IPv4 addresses will be selected over IPv6 addresses on 117 // the same interface. Numeric addresses are resolved into names where possible. An empty string will be returned if the 118 // network interface cannot be resolved into an IPv4 or IPv6 address. 119 func Address(iFace net.Interface) string { 120 if addrs, err := iFace.Addrs(); err == nil { 121 var fallback string 122 for _, addr := range addrs { 123 var ip net.IP 124 switch v := addr.(type) { 125 case *net.IPNet: 126 ip = v.IP 127 case *net.IPAddr: 128 ip = v.IP 129 default: 130 continue 131 } 132 if ip.IsGlobalUnicast() { 133 ipAddr := ip.String() 134 var names []string 135 if names, err = net.LookupAddr(ipAddr); err == nil { 136 if len(names) > 0 { 137 name := strings.TrimSuffix(names[0], ".") 138 if ip.To4() != nil { 139 return name 140 } 141 if fallback == "" { 142 fallback = name 143 } 144 continue 145 } 146 } 147 if ip.To4() != nil { 148 return ipAddr 149 } 150 if fallback == "" { 151 fallback = ipAddr 152 } 153 } 154 } 155 if fallback != "" { 156 return fallback 157 } 158 } 159 return "" 160 } 161 162 // AddressesForHost returns the addresses/names for the given host. If an IP number is passed in, then it will be 163 // returned. If a host name is passed in, the host name plus the IP address(es) it resolves to will be returned. If the 164 // empty string is passed in, then the host names and IP addresses for all active interfaces will be returned. 165 func AddressesForHost(host string) []string { 166 ss := collection.NewSet[string]() 167 if host == "" { // All address on machine 168 if iFaces, err := net.Interfaces(); err == nil { 169 for _, iFace := range iFaces { 170 const interesting = net.FlagUp | net.FlagBroadcast 171 if iFace.Flags&interesting == interesting { 172 var addrs []net.Addr 173 if addrs, err = iFace.Addrs(); err == nil { 174 for _, addr := range addrs { 175 var ip net.IP 176 switch v := addr.(type) { 177 case *net.IPNet: 178 ip = v.IP 179 case *net.IPAddr: 180 ip = v.IP 181 default: 182 continue 183 } 184 if ip.IsGlobalUnicast() { 185 ss.Add(ip.String()) 186 var names []string 187 if names, err = net.LookupAddr(ip.String()); err == nil { 188 for _, name := range names { 189 ss.Add(strings.TrimSuffix(name, ".")) 190 } 191 } 192 } 193 } 194 } 195 } 196 } 197 } 198 } else { 199 ss.Add(host) 200 if net.ParseIP(host) == nil { 201 if ips, err := net.LookupIP(host); err == nil && len(ips) > 0 { 202 for _, ip := range ips { 203 ss.Add(ip.String()) 204 } 205 } 206 } 207 } 208 for _, one := range []string{"::", IPv6LoopbackAddress, IPv4LoopbackAddress} { 209 if ss.Contains(one) { 210 delete(ss, one) 211 ss.Add(LocalHost) 212 } 213 } 214 addrs := ss.Values() 215 sort.Slice(addrs, func(i, j int) bool { 216 isName1 := net.ParseIP(addrs[i]) == nil 217 isName2 := net.ParseIP(addrs[j]) == nil 218 if isName1 == isName2 { 219 return txt.NaturalLess(addrs[i], addrs[j], true) 220 } 221 return isName1 222 }) 223 return addrs 224 }