package netutil import ( "bytes" "encoding/json" "errors" "fmt" "io" "mime/multipart" "net" "net/http" "net/url" "os" "os/exec" "regexp" "runtime" "strings" "time" "github.com/duke-git/lancet/v2/fileutil" ) // GetInternalIp return internal ipv4. // Play: https://go.dev/play/p/5mbu-gFp7ei func GetInternalIp() string { addrs, err := net.InterfaceAddrs() if err != nil { panic(err.Error()) } for _, addr := range addrs { var ip net.IP switch v := addr.(type) { case *net.IPNet: ip = v.IP case *net.IPAddr: ip = v.IP } if ip != nil && (ip.IsLinkLocalUnicast() || ip.IsGlobalUnicast()) { continue } if ipv4 := ip.To4(); ipv4 != nil { return ipv4.String() } } return "" } // func GetInternalIp() string { // addr, err := net.InterfaceAddrs() // if err != nil { // panic(err.Error()) // } // for _, a := range addr { // if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() { // if ipNet.IP.To4() != nil { // return ipNet.IP.String() // } // } // } // return "" // } // GetRequestPublicIp return the requested public ip. // Play: https://go.dev/play/p/kxU-YDc_eBo func GetRequestPublicIp(req *http.Request) string { var ip string for _, ip = range strings.Split(req.Header.Get("X-Forwarded-For"), ",") { if ip = strings.TrimSpace(ip); ip != "" && !IsInternalIP(net.ParseIP(ip)) { return ip } } if ip = strings.TrimSpace(req.Header.Get("X-Real-Ip")); ip != "" && !IsInternalIP(net.ParseIP(ip)) { return ip } if ip, _, _ = net.SplitHostPort(req.RemoteAddr); !IsInternalIP(net.ParseIP(ip)) { return ip } return ip } // GetPublicIpInfo return public ip information // return the PublicIpInfo struct. // Play: https://go.dev/play/p/YDxIfozsRHR func GetPublicIpInfo() (*PublicIpInfo, error) { resp, err := http.Get("http://ip-api.com/json/") if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } var ip PublicIpInfo err = json.Unmarshal(body, &ip) if err != nil { return nil, err } return &ip, nil } // GetIps return all ipv4 of system. // Play: https://go.dev/play/p/NUFfcEmukx1 func GetIps() []string { var ips []string addrs, err := net.InterfaceAddrs() if err != nil { return ips } for _, addr := range addrs { ipNet, isValid := addr.(*net.IPNet) if isValid && !ipNet.IP.IsLoopback() { if ipNet.IP.To4() != nil { ips = append(ips, ipNet.IP.String()) } } } return ips } // GetMacAddrs get mac address. // Play: https://go.dev/play/p/Rq9UUBS_Xp1 func GetMacAddrs() []string { var macAddrs []string nets, err := net.Interfaces() if err != nil { return macAddrs } for _, net := range nets { macAddr := net.HardwareAddr.String() if len(macAddr) == 0 { continue } macAddrs = append(macAddrs, macAddr) } return macAddrs } // PublicIpInfo public ip info: country, region, isp, city, lat, lon, ip type PublicIpInfo struct { Status string `json:"status"` Country string `json:"country"` CountryCode string `json:"countryCode"` Region string `json:"region"` RegionName string `json:"regionName"` City string `json:"city"` Lat float64 `json:"lat"` Lon float64 `json:"lon"` Isp string `json:"isp"` Org string `json:"org"` As string `json:"as"` Ip string `json:"query"` } // IsPublicIP verify a ip is public or not. // Play: https://go.dev/play/p/nmktSQpJZnn func IsPublicIP(IP net.IP) bool { if IP.IsLoopback() || IP.IsLinkLocalMulticast() || IP.IsLinkLocalUnicast() { return false } if ip4 := IP.To4(); ip4 != nil { switch { case ip4[0] == 10: return false case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31: return false case ip4[0] == 192 && ip4[1] == 168: return false default: return true } } return false } // IsInternalIP verify an ip is intranet or not. // Play: https://go.dev/play/p/sYGhXbgO4Cb func IsInternalIP(IP net.IP) bool { if IP.IsLoopback() { return true } if ip4 := IP.To4(); ip4 != nil { return ip4[0] == 10 || (ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31) || (ip4[0] == 169 && ip4[1] == 254) || (ip4[0] == 192 && ip4[1] == 168) } return false } // EncodeUrl encode url. // Play: https://go.dev/play/p/bsZ6BRC4uKI func EncodeUrl(urlStr string) (string, error) { URL, err := url.Parse(urlStr) if err != nil { return "", err } URL.RawQuery = URL.Query().Encode() return URL.String(), nil } // DownloadFile will upload the file to a server. func UploadFile(filepath string, server string) (bool, error) { if !fileutil.IsExist(filepath) { return false, errors.New("file not exist") } fileInfo, err := os.Stat(filepath) if err != nil { return false, err } bodyBuffer := &bytes.Buffer{} writer := multipart.NewWriter(bodyBuffer) formFile, err := writer.CreateFormFile("uploadfile", fileInfo.Name()) if err != nil { return false, err } srcFile, err := os.Open(filepath) if err != nil { return false, err } defer srcFile.Close() _, err = io.Copy(formFile, srcFile) if err != nil { return false, err } contentType := writer.FormDataContentType() writer.Close() _, err = http.Post(server, contentType, bodyBuffer) if err != nil { return false, err } return true, nil } // DownloadFile will download the file exist in url to a local file. func DownloadFile(filepath string, url string) error { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() out, err := os.Create(filepath) if err != nil { return err } defer out.Close() _, err = io.Copy(out, resp.Body) return err } // IsPingConnected checks if can ping specified host or not. // Play: https://go.dev/play/p/q8OzTijsA87 func IsPingConnected(host string) bool { cmd := exec.Command("ping", host, "-c", "4", "-W", "6") if runtime.GOOS == "windows" { cmd = exec.Command("ping", host, "-n", "4", "-w", "6") } err := cmd.Run() if err != nil { return false } return true } // IsTelnetConnected checks if can telnet specified host or not. // Play: https://go.dev/play/p/yiLCGtQv_ZG func IsTelnetConnected(host string, port string) bool { adder := host + ":" + port conn, err := net.DialTimeout("tcp", adder, 5*time.Second) if err != nil { return false } defer conn.Close() return true } // BuildUrl builds a URL from the given params. // Play: https://go.dev/play/p/JLXl1hZK7l4 func BuildUrl(scheme, host, path string, query map[string][]string) (string, error) { if err := validateScheme(scheme); err != nil { return "", err } if path != "" { if !hostRegex.MatchString(host) { return "", fmt.Errorf("invalid host: '%s' is not a valid host", host) } } parsedUrl := &url.URL{ Scheme: scheme, Host: host, } if path == "" { parsedUrl.Path = "/" } else if !strings.HasPrefix(path, "/") { parsedUrl.Path = "/" + path } else { parsedUrl.Path = path } queryParams := parsedUrl.Query() for key, values := range query { for _, value := range values { queryParams.Add(key, value) } } parsedUrl.RawQuery = queryParams.Encode() return parsedUrl.String(), nil } var supportedSchemes = map[string]bool{ "http": true, "https": true, "ftp": true, "file": true, "mailto": true, "ws": true, // WebSocket "wss": true, // WebSocket Secure "data": true, // Data URL } func validateScheme(scheme string) error { if _, exists := supportedSchemes[scheme]; !exists { return fmt.Errorf("invalid scheme: '%s' is not supported", scheme) } return nil } var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]?)(\.[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]?)+$`) var pathRegex = regexp.MustCompile(`^\/([a-zA-Z0-9%_-]+(?:\/[a-zA-Z0-9%_-]+)*)$`) var alphaNumericRegex = regexp.MustCompile(`^[a-zA-Z0-9]+$`) // AddQueryParams adds query parameters to the given URL. // Play: https://go.dev/play/p/JLXl1hZK7l4 func AddQueryParams(urlStr string, params map[string][]string) (string, error) { parsedUrl, err := url.Parse(urlStr) if err != nil { return "", err } queryParams := parsedUrl.Query() for k, values := range params { if k == "" { return "", errors.New("empty key is not allowed") } if !alphaNumericRegex.MatchString(k) { return "", fmt.Errorf("query parameter key %s must be alphanumeric", k) } for _, v := range values { if !alphaNumericRegex.MatchString(v) { return "", fmt.Errorf("query parameter value %s must be alphanumeric", v) } queryParams.Add(k, v) } } parsedUrl.RawQuery = queryParams.Encode() return parsedUrl.String(), nil }