| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- // Package network provides network-related utilities for LocalGo
- package network
- import (
- "errors"
- "fmt"
- "net"
- "strings"
- )
- // systemInterface is a network interface paired with its IPv4 addresses.
- // It exists so interface enumeration can be performed without netlink, which
- // the Android/Termux sandbox blocks (net.Interfaces() returns EPERM there).
- type systemInterface struct {
- net.Interface
- addrs []*net.IPNet
- }
- // systemInterfaces returns all system interfaces with their IPv4 addresses.
- // It uses netlink-based enumeration (net.Interfaces); on android it
- // transparently falls back to ioctl-based enumeration when netlink is
- // unavailable (see platformInterfaceFallback).
- func systemInterfaces() ([]systemInterface, error) {
- ifs, err := net.Interfaces()
- if err != nil {
- return platformInterfaceFallback(err)
- }
- out := make([]systemInterface, 0, len(ifs))
- for _, i := range ifs {
- addrs, err := i.Addrs()
- if err != nil {
- continue
- }
- si := systemInterface{Interface: i}
- for _, a := range addrs {
- if ipn, ok := a.(*net.IPNet); ok && ipn.IP.To4() != nil {
- si.addrs = append(si.addrs, ipn)
- }
- }
- out = append(out, si)
- }
- return out, nil
- }
- // systemInterfaceByName returns the interface with the given name, or an error.
- func systemInterfaceByName(name string) (*systemInterface, error) {
- ifs, err := systemInterfaces()
- if err != nil {
- return nil, err
- }
- for i := range ifs {
- if ifs[i].Name == name {
- return &ifs[i], nil
- }
- }
- return nil, fmt.Errorf("interface %q not found", name)
- }
- // EnumerateInterfaces returns all system interfaces. Unlike net.Interfaces,
- // this works even where netlink is blocked (e.g. Android/Termux).
- func EnumerateInterfaces() ([]net.Interface, error) {
- ifs, err := systemInterfaces()
- if err != nil {
- return nil, err
- }
- out := make([]net.Interface, 0, len(ifs))
- for _, i := range ifs {
- out = append(out, i.Interface)
- }
- return out, nil
- }
- // InterfaceByName returns the named system interface. Unlike net.InterfaceByName,
- // this works even where netlink is blocked (e.g. Android/Termux).
- func InterfaceByName(name string) (*net.Interface, error) {
- si, err := systemInterfaceByName(name)
- if err != nil {
- return nil, err
- }
- iface := si.Interface
- return &iface, nil
- }
- // GetLocalIP returns the primary non-loopback IP address of the machine
- func GetLocalIP() (string, error) {
- ifs, err := systemInterfaces()
- if err != nil {
- return "", err
- }
- for _, i := range ifs {
- if (i.Flags&net.FlagUp) == 0 || (i.Flags&net.FlagLoopback) != 0 {
- continue
- }
- for _, ipn := range i.addrs {
- if !ipn.IP.IsLoopback() {
- return ipn.IP.String(), nil
- }
- }
- }
- return "", errors.New("no suitable local IP address found")
- }
- // GetLocalIPAddresses returns a list of local IP addresses for all non-loopback interfaces
- func GetLocalIPAddresses() ([]net.IP, error) {
- var ips []net.IP
- ifs, err := systemInterfaces()
- if err != nil {
- return nil, err
- }
- for _, i := range ifs {
- // Skip down and loopback interfaces
- if (i.Flags&net.FlagUp) == 0 || (i.Flags&net.FlagLoopback) != 0 {
- continue
- }
- for _, ipn := range i.addrs {
- ips = append(ips, ipn.IP)
- }
- }
- return ips, nil
- }
- // GetInterfaceIPs returns all IP addresses for a specific network interface
- func GetInterfaceIPs(interfaceName string) ([]string, error) {
- iface, err := systemInterfaceByName(interfaceName)
- if err != nil {
- return nil, err
- }
- var ips []string
- for _, ipn := range iface.addrs {
- ips = append(ips, ipn.IP.String())
- }
- return ips, nil
- }
- // FormatAddress formats an IP address and port into a URL
- func FormatAddress(ip net.IP, port int, https bool) string {
- protocol := "http"
- if https {
- protocol = "https"
- }
- ipStr := ip.String()
- // Check if it's an IPv6 address
- if strings.Contains(ipStr, ":") {
- return fmt.Sprintf("%s://[%s]:%d", protocol, ipStr, port)
- }
- return fmt.Sprintf("%s://%s:%d", protocol, ipStr, port)
- }
- // GetPreferredOutboundIP gets the preferred IP address for outbound connections
- func GetPreferredOutboundIP() (net.IP, error) {
- // Connect to a public IP (no actual connection is made)
- conn, err := net.Dial("udp", "8.8.8.8:80")
- if err != nil {
- return nil, fmt.Errorf("failed to determine preferred outbound IP: %w", err)
- }
- defer conn.Close()
- // Get the local address
- localAddr := conn.LocalAddr().(*net.UDPAddr)
- return localAddr.IP, nil
- }
- // ParseCIDRRange parses a CIDR notation (e.g. "192.168.1.0/24") and returns
- // all usable host IPs in that range (network and broadcast addresses excluded).
- func ParseCIDRRange(cidr string) ([]net.IP, error) {
- _, ipnet, err := net.ParseCIDR(cidr)
- if err != nil {
- return nil, fmt.Errorf("invalid CIDR %q: %w", cidr, err)
- }
- ip4 := ipnet.IP.To4()
- if ip4 == nil {
- return nil, fmt.Errorf("CIDR %q is not an IPv4 range", cidr)
- }
- ones, bits := ipnet.Mask.Size()
- hostBits := bits - ones
- if hostBits < 2 || hostBits > 30 {
- return nil, fmt.Errorf("CIDR %q prefix length must be /8–/30", cidr)
- }
- base := uint32(ip4[0])<<24 | uint32(ip4[1])<<16 | uint32(ip4[2])<<8 | uint32(ip4[3])
- totalHosts := (1 << hostBits) - 2
- var ips []net.IP
- for i := 1; i <= totalHosts; i++ {
- addr := base + uint32(i)
- ips = append(ips, net.IPv4(byte(addr>>24), byte(addr>>16), byte(addr>>8), byte(addr)))
- }
- return ips, nil
- }
- // GetSubnetIPs returns all IP addresses in the same /24 subnet as the given IP
- func GetSubnetIPs(ip net.IP) []net.IP {
- ip4 := ip.To4()
- if ip4 == nil {
- return nil
- }
- var ips []net.IP
- // Assume /24 subnet for local discovery (most common)
- // Iterate 1-254
- for i := 1; i <= 254; i++ {
- // Create a copy of the base IP
- newIP := make(net.IP, len(ip4))
- copy(newIP, ip4)
- newIP[3] = byte(i)
- // Skip if it matches the original IP (optional, but scan logic might want to include self for testing)
- // Keeping self allows "finding" the local node which confirms scan is running.
- ips = append(ips, newIP)
- }
- return ips
- }
- // GetInterfaceIPNet returns the IPv4 network (IP + subnet mask) for the named interface.
- // Returns nil if the interface has no IPv4 address.
- func GetInterfaceIPNet(ifaceName string) (*net.IPNet, error) {
- iface, err := systemInterfaceByName(ifaceName)
- if err != nil {
- return nil, fmt.Errorf("interface %q: %w", ifaceName, err)
- }
- if len(iface.addrs) > 0 {
- return iface.addrs[0], nil
- }
- return nil, fmt.Errorf("interface %q has no IPv4 address", ifaceName)
- }
- // GetUsableSubnetIPsFromIP returns all usable host IPs in the subnet of the
- // interface that owns the given IP, respecting its actual netmask. Falls back
- // to a flat /24 scan if the interface cannot be determined.
- func GetUsableSubnetIPsFromIP(ip net.IP) ([]net.IP, error) {
- ipStr := ip.String()
- ifs, err := systemInterfaces()
- if err != nil {
- return GetSubnetIPs(ip), nil
- }
- for _, i := range ifs {
- if (i.Flags&net.FlagUp) == 0 || (i.Flags&net.FlagLoopback) != 0 {
- continue
- }
- for _, ipn := range i.addrs {
- if ipn.IP.String() == ipStr {
- return GetUsableSubnetIPs(i.Name)
- }
- }
- }
- return GetSubnetIPs(ip), nil
- }
- // GetUsableSubnetIPs returns all usable host IPs in the subnet of the named
- // interface, respecting its actual netmask. Subnets with more than 1022 hosts
- // (larger than /22) are capped at /22 to keep scanning practical. Network and
- // broadcast addresses are excluded.
- func GetUsableSubnetIPs(ifaceName string) ([]net.IP, error) {
- ipnet, err := GetInterfaceIPNet(ifaceName)
- if err != nil {
- return nil, err
- }
- ip4 := ipnet.IP.To4()
- if ip4 == nil {
- return nil, fmt.Errorf("interface %q has no IPv4 address", ifaceName)
- }
- ones, bits := ipnet.Mask.Size()
- hostBits := bits - ones
- // Cap at /22 for practical scanning (max 1022 usable hosts)
- effectiveMask := ipnet.Mask
- if hostBits > 10 {
- effectiveMask = net.CIDRMask(22, bits)
- hostBits = bits - 22
- }
- if hostBits < 2 {
- return nil, fmt.Errorf("interface %q subnet prefix /%d is too small for scanning", ifaceName, bits-hostBits)
- }
- maskBits := []byte{effectiveMask[0], effectiveMask[1], effectiveMask[2], effectiveMask[3]}
- base := uint32(ip4[0])<<24 | uint32(ip4[1])<<16 | uint32(ip4[2])<<8 | uint32(ip4[3])
- base &= uint32(maskBits[0])<<24 | uint32(maskBits[1])<<16 | uint32(maskBits[2])<<8 | uint32(maskBits[3])
- totalHosts := (1 << hostBits) - 2
- var ips []net.IP
- for i := 1; i <= totalHosts; i++ {
- addr := base + uint32(i)
- ips = append(ips, net.IPv4(byte(addr>>24), byte(addr>>16), byte(addr>>8), byte(addr)))
- }
- return ips, nil
- }
|