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  }