AdGuardHome/home/network_utils.go

145 lines
3.3 KiB
Go

package home
import (
"errors"
"fmt"
"net"
"os"
"runtime"
"strconv"
"syscall"
"time"
"github.com/AdguardTeam/AdGuardHome/dhcpd"
"github.com/joomcode/errorx"
)
type netInterface struct {
Name string
MTU int
HardwareAddr string
Addresses []string
Flags string
}
// getValidNetInterfacesMap returns interfaces that are eligible for DNS and WEB only
// we do not return link-local addresses here
func getValidNetInterfacesForWeb() ([]netInterface, error) {
ifaces, err := dhcpd.GetValidNetInterfaces()
if err != nil {
return nil, errorx.Decorate(err, "Couldn't get interfaces")
}
if len(ifaces) == 0 {
return nil, errors.New("couldn't find any legible interface")
}
var netInterfaces []netInterface
for _, iface := range ifaces {
addrs, e := iface.Addrs()
if e != nil {
return nil, errorx.Decorate(e, "Failed to get addresses for interface %s", iface.Name)
}
netIface := netInterface{
Name: iface.Name,
MTU: iface.MTU,
HardwareAddr: iface.HardwareAddr.String(),
}
if iface.Flags != 0 {
netIface.Flags = iface.Flags.String()
}
// we don't want link-local addresses in json, so skip them
for _, addr := range addrs {
ipnet, ok := addr.(*net.IPNet)
if !ok {
// not an IPNet, should not happen
return nil, fmt.Errorf("got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr)
}
// ignore link-local
if ipnet.IP.IsLinkLocalUnicast() {
continue
}
netIface.Addresses = append(netIface.Addresses, ipnet.IP.String())
}
if len(netIface.Addresses) != 0 {
netInterfaces = append(netInterfaces, netIface)
}
}
return netInterfaces, nil
}
// Get interface name by its IP address.
func getInterfaceByIP(ip string) string {
ifaces, err := getValidNetInterfacesForWeb()
if err != nil {
return ""
}
for _, iface := range ifaces {
for _, addr := range iface.Addresses {
if ip == addr {
return iface.Name
}
}
}
return ""
}
// checkPortAvailable is not a cheap test to see if the port is bindable, because it's actually doing the bind momentarily
func checkPortAvailable(host string, port int) error {
ln, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return err
}
_ = ln.Close()
// It seems that net.Listener.Close() doesn't close file descriptors right away.
// We wait for some time and hope that this fd will be closed.
time.Sleep(100 * time.Millisecond)
return nil
}
func checkPacketPortAvailable(host string, port int) error {
ln, err := net.ListenPacket("udp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return err
}
_ = ln.Close()
// It seems that net.Listener.Close() doesn't close file descriptors right away.
// We wait for some time and hope that this fd will be closed.
time.Sleep(100 * time.Millisecond)
return err
}
// check if error is "address already in use"
func errorIsAddrInUse(err error) bool {
errOpError, ok := err.(*net.OpError)
if !ok {
return false
}
errSyscallError, ok := errOpError.Err.(*os.SyscallError)
if !ok {
return false
}
errErrno, ok := errSyscallError.Err.(syscall.Errno)
if !ok {
return false
}
if runtime.GOOS == "windows" {
const WSAEADDRINUSE = 10048
return errErrno == WSAEADDRINUSE
}
return errErrno == syscall.EADDRINUSE
}