github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/goip/ip.go (about) 1 package goip 2 3 import ( 4 "fmt" 5 "log" 6 "net" 7 "os/exec" 8 "path/filepath" 9 "regexp" 10 "strings" 11 ) 12 13 // IsIPv4 tells a string if in IPv4 format. 14 func IsIPv4(address string) bool { 15 return strings.Count(address, ":") < 2 16 } 17 18 // IsIPv6 tells a string if in IPv6 format. 19 func IsIPv6(address string) bool { 20 return strings.Count(address, ":") >= 2 21 } 22 23 // ListAllIPv4 list all IPv4 addresses. 24 // ifaceNames are used to specified interface names (filename wild match pattern supported also, like eth*). 25 func ListAllIPv4(ifaceNames ...string) ([]string, error) { 26 ips := make([]string, 0) 27 28 _, err := ListAllIP(func(ip net.IP) (yes bool) { 29 s := ip.String() 30 if yes = IsIPv4(s); yes { 31 ips = append(ips, s) 32 } 33 34 return yes 35 }, ifaceNames...) 36 37 return ips, err 38 } 39 40 // ListAllIPv6 list all IPv6 addresses. 41 // ifaceNames are used to specified interface names (filename wild match pattern supported also, like eth*). 42 func ListAllIPv6(ifaceNames ...string) ([]string, error) { 43 ips := make([]string, 0) 44 45 _, err := ListAllIP(func(ip net.IP) (yes bool) { 46 s := ip.String() 47 if yes = IsIPv6(s); yes { 48 ips = append(ips, s) 49 } 50 51 return yes 52 }, ifaceNames...) 53 54 return ips, err 55 } 56 57 // ListIfaceNames list all net interface names. 58 func ListIfaceNames() (names []string) { 59 list, err := net.Interfaces() 60 if err != nil { 61 return nil 62 } 63 64 for _, i := range list { 65 f := i.Flags 66 if i.HardwareAddr == nil || f&net.FlagUp == 0 || f&net.FlagLoopback == 1 { 67 continue 68 } 69 70 names = append(names, i.Name) 71 } 72 73 return names 74 } 75 76 // ListAllIP list all IP addresses. 77 func ListAllIP(predicate func(net.IP) bool, ifaceNames ...string) ([]net.IP, error) { 78 list, err := net.Interfaces() 79 if err != nil { 80 return nil, fmt.Errorf("failed to get interfaces, err: %w", err) 81 } 82 83 ips := make([]net.IP, 0) 84 matcher := NewIfaceNameMatcher(ifaceNames) 85 86 for _, i := range list { 87 f := i.Flags 88 if i.HardwareAddr == nil || 89 f&net.FlagUp != net.FlagUp || 90 f&net.FlagLoopback == net.FlagLoopback || 91 !matcher.Matches(i.Name) { 92 continue 93 } 94 95 addrs, err := i.Addrs() 96 if err != nil { 97 continue 98 } 99 100 ips = collectAddresses(predicate, addrs, ips) 101 } 102 103 return ips, nil 104 } 105 106 func collectAddresses(predicate func(net.IP) bool, addrs []net.Addr, ips []net.IP) []net.IP { 107 for _, a := range addrs { 108 var ip net.IP 109 switch v := a.(type) { 110 case *net.IPAddr: 111 ip = v.IP 112 case *net.IPNet: 113 ip = v.IP 114 default: 115 continue 116 } 117 118 if !ContainsIS(ips, ip) && predicate(ip) { 119 ips = append(ips, ip) 120 } 121 } 122 123 return ips 124 } 125 126 func ContainsIS(ips []net.IP, ip net.IP) bool { 127 for _, j := range ips { 128 if j.Equal(ip) { 129 return true 130 } 131 } 132 133 return false 134 } 135 136 // Outbound gets preferred outbound ip of this machine. 137 func Outbound() string { 138 conn, err := net.Dial("udp", "8.8.8.8:80") 139 if err != nil { 140 return "" 141 } 142 143 defer conn.Close() 144 145 s := conn.LocalAddr().String() 146 return s[:strings.LastIndex(s, ":")] 147 } 148 149 // MainIP tries to get the main IP address and the IP addresses. 150 func MainIP(ifaceName ...string) (string, []string) { 151 return MainIPVerbose(false, ifaceName...) 152 } 153 154 // MainIPVerbose tries to get the main IP address and the IP addresses. 155 func MainIPVerbose(verbose bool, ifaceName ...string) (string, []string) { 156 ips, _ := ListAllIPv4(ifaceName...) 157 if len(ips) == 1 { 158 return ips[0], ips 159 } 160 161 if s := findMainIPByIfconfig(verbose, ifaceName); s != "" { 162 return s, ips 163 } 164 165 if out := Outbound(); out != "" && contains(ips, out) { 166 return out, ips 167 } 168 169 if len(ips) > 0 { 170 return ips[0], ips 171 } 172 173 return "", nil 174 } 175 176 func findMainIPByIfconfig(verbose bool, ifaceName []string) string { 177 names := ListIfaceNames() 178 if verbose { 179 log.Printf("iface names: %s", names) 180 } 181 182 var matchedNames []string 183 matcher := NewIfaceNameMatcher(ifaceName) 184 for _, n := range names { 185 if matcher.Matches(n) { 186 matchedNames = append(matchedNames, n) 187 } 188 } 189 190 if verbose && len(matchedNames) < len(names) { 191 log.Printf("matchedNames: %s", matchedNames) 192 } 193 194 if len(matchedNames) == 0 { 195 return "" 196 } 197 198 name := matchedNames[0] 199 for _, n := range matchedNames { 200 // for en0 on mac or eth0 on linux 201 if strings.HasPrefix(n, "e") && strings.HasSuffix(n, "0") { 202 name = n 203 break 204 } 205 } 206 207 /* 208 [root@tencent-beta17 ~]# ifconfig eth0 209 eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 210 inet 192.168.108.7 netmask 255.255.255.0 broadcast 192.168.108.255 211 ether 52:54:00:ef:16:bd txqueuelen 1000 (Ethernet) 212 RX packets 1838617728 bytes 885519190162 (824.7 GiB) 213 RX errors 0 dropped 0 overruns 0 frame 0 214 TX packets 1665532349 bytes 808544539610 (753.0 GiB) 215 TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 216 */ 217 re := regexp.MustCompile(`inet\s+([\w.]+?)\s+`) 218 if verbose { 219 log.Printf("exec comd: ifconfig %s", name) 220 } 221 c := exec.Command("ifconfig", name) 222 if co, err := c.Output(); err == nil { 223 if verbose { 224 log.Printf("output: %s", co) 225 } 226 sub := re.FindStringSubmatch(string(co)) 227 if len(sub) > 1 { 228 if verbose { 229 log.Printf("found: %s", sub[1]) 230 } 231 return sub[1] 232 } 233 } else if verbose { 234 log.Printf("error: %v", err) 235 } 236 237 return "" 238 } 239 240 func contains(ss []string, s string) bool { 241 for _, v := range ss { 242 if v == s { 243 return true 244 } 245 } 246 247 return false 248 } 249 250 // MakeSliceMap makes a map[string]bool from the string slice. 251 func MakeSliceMap(ss []string) map[string]bool { 252 m := make(map[string]bool) 253 254 for _, s := range ss { 255 if s != "" { 256 m[s] = true 257 } 258 } 259 260 return m 261 } 262 263 type IfaceNameMatcher struct { 264 ifacePatterns map[string]bool 265 } 266 267 func NewIfaceNameMatcher(ss []string) IfaceNameMatcher { 268 return IfaceNameMatcher{ifacePatterns: MakeSliceMap(ss)} 269 } 270 271 func (i IfaceNameMatcher) Matches(name string) bool { 272 if len(i.ifacePatterns) == 0 { 273 return true 274 } 275 276 if _, ok := i.ifacePatterns[name]; ok { 277 return true 278 } 279 280 for k := range i.ifacePatterns { 281 if ok, _ := filepath.Match(k, name); ok { 282 return true 283 } 284 } 285 286 for k := range i.ifacePatterns { 287 if strings.Contains(k, name) { 288 return true 289 } 290 } 291 292 return false 293 }