109 lines
2.4 KiB
Go
109 lines
2.4 KiB
Go
package util
|
|
|
|
import (
|
|
"net"
|
|
"strings"
|
|
)
|
|
|
|
// isPrivateIPv4 reports whether ip is an RFC1918 private IPv4 address.
|
|
func isPrivateIPv4(ip net.IP) bool {
|
|
if ip == nil {
|
|
return false
|
|
}
|
|
ip4 := ip.To4()
|
|
if ip4 == nil {
|
|
return false
|
|
}
|
|
// 10.0.0.0/8
|
|
if ip4[0] == 10 {
|
|
return true
|
|
}
|
|
// 172.16.0.0/12
|
|
if ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31 {
|
|
return true
|
|
}
|
|
// 192.168.0.0/16
|
|
if ip4[0] == 192 && ip4[1] == 168 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// LocalIPv4s returns all local IPv4 addresses. If privateOnly is true, only returns RFC1918 addresses.
|
|
func LocalIPv4s(privateOnly bool) []string {
|
|
var ips []string
|
|
ifaces, err := net.Interfaces()
|
|
if err != nil {
|
|
return ips
|
|
}
|
|
for _, iface := range ifaces {
|
|
// Skip interfaces that are down or loopback only
|
|
if (iface.Flags&net.FlagUp) == 0 || (iface.Flags&net.FlagLoopback) != 0 {
|
|
continue
|
|
}
|
|
addrs, err := iface.Addrs()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, a := range addrs {
|
|
var ip net.IP
|
|
switch v := a.(type) {
|
|
case *net.IPNet:
|
|
ip = v.IP
|
|
case *net.IPAddr:
|
|
ip = v.IP
|
|
}
|
|
if ip == nil {
|
|
continue
|
|
}
|
|
ip4 := ip.To4()
|
|
if ip4 == nil {
|
|
continue
|
|
}
|
|
if privateOnly && !isPrivateIPv4(ip4) {
|
|
continue
|
|
}
|
|
ips = append(ips, ip4.String())
|
|
}
|
|
}
|
|
return ips
|
|
}
|
|
|
|
// PrimaryLANIPv4 returns the first private IPv4 address, or empty string if none.
|
|
func PrimaryLANIPv4() string {
|
|
ips := LocalIPv4s(true)
|
|
if len(ips) > 0 {
|
|
return ips[0]
|
|
}
|
|
// Fallback to any IPv4 if no private address was found
|
|
ips = LocalIPv4s(false)
|
|
if len(ips) > 0 {
|
|
return ips[0]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// ComposeLANURL constructs an http URL using the primary LAN IPv4 and the port from addr
|
|
// when addr binds all interfaces (0.0.0.0 or ::) or empty host. Otherwise returns http://addr as-is.
|
|
func ComposeLANURL(addr string) string {
|
|
host, port, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
// If split fails (e.g., missing port), just return as http URL
|
|
return "http://" + addr
|
|
}
|
|
h := strings.TrimSpace(host)
|
|
if h == "" || h == "0.0.0.0" || h == "::" || h == "[::]" {
|
|
lan := PrimaryLANIPv4()
|
|
if lan != "" {
|
|
return "http://" + lan + ":" + port
|
|
}
|
|
}
|
|
// Not a wildcard bind or no LAN IP found; return the original
|
|
if strings.Contains(h, ":") && !strings.HasPrefix(h, "[") {
|
|
// IPv6 literal without brackets; add them
|
|
return "http://[" + h + "]:" + port
|
|
}
|
|
return "http://" + h + ":" + port
|
|
}
|
|
|