+(dhcpd): added static IP for MacOS

This commit is contained in:
Andrey Meshkov
2020-02-13 14:14:30 +03:00
parent 7afa16fbe7
commit c27852537d
26 changed files with 589 additions and 406 deletions

59
util/helpers.go Normal file
View File

@@ -0,0 +1,59 @@
package util
import (
"fmt"
"os"
"os/exec"
"path"
"runtime"
"strings"
)
// ---------------------
// general helpers
// ---------------------
// fileExists returns TRUE if file exists
func FileExists(fn string) bool {
_, err := os.Stat(fn)
if err != nil {
return false
}
return true
}
// runCommand runs shell command
func RunCommand(command string, arguments ...string) (int, string, error) {
cmd := exec.Command(command, arguments...)
out, err := cmd.Output()
if err != nil {
return 1, "", fmt.Errorf("exec.Command(%s) failed: %s", command, err)
}
return cmd.ProcessState.ExitCode(), string(out), nil
}
// ---------------------
// debug logging helpers
// ---------------------
func FuncName() string {
pc := make([]uintptr, 10) // at least 1 entry needed
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
return path.Base(f.Name())
}
// SplitNext - split string by a byte and return the first chunk
// Whitespace is trimmed
func SplitNext(str *string, splitBy byte) string {
i := strings.IndexByte(*str, splitBy)
s := ""
if i != -1 {
s = (*str)[0:i]
*str = (*str)[i+1:]
} else {
s = *str
*str = ""
}
return strings.TrimSpace(s)
}

14
util/helpers_test.go Normal file
View File

@@ -0,0 +1,14 @@
package util
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSplitNext(t *testing.T) {
s := " a,b , c "
assert.True(t, SplitNext(&s, ',') == "a")
assert.True(t, SplitNext(&s, ',') == "b")
assert.True(t, SplitNext(&s, ',') == "c" && len(s) == 0)
}

194
util/network_utils.go Normal file
View File

@@ -0,0 +1,194 @@
package util
import (
"errors"
"fmt"
"net"
"os"
"runtime"
"strconv"
"syscall"
"time"
"github.com/AdguardTeam/golibs/log"
"github.com/joomcode/errorx"
)
// NetInterface represents a list of network interfaces
type NetInterface struct {
Name string // Network interface name
MTU int // MTU
HardwareAddr string // Hardware address
Addresses []string // Array with the network interface addresses
Subnets []string // Array with CIDR addresses of this network interface
Flags string // Network interface flags (up, broadcast, etc)
}
// GetValidNetInterfaces returns interfaces that are eligible for DNS and/or DHCP
// invalid interface is a ppp interface or the one that doesn't allow broadcasts
func GetValidNetInterfaces() ([]net.Interface, error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, fmt.Errorf("Couldn't get list of interfaces: %s", err)
}
netIfaces := []net.Interface{}
for i := range ifaces {
if ifaces[i].Flags&net.FlagPointToPoint != 0 {
// this interface is ppp, we're not interested in this one
continue
}
iface := ifaces[i]
netIfaces = append(netIfaces, iface)
}
return netIfaces, nil
}
// 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 := 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()
}
// Collect network interface addresses
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
}
// ignore IPv6
if ipNet.IP.To4() == nil {
continue
}
netIface.Addresses = append(netIface.Addresses, ipNet.IP.String())
netIface.Subnets = append(netIface.Subnets, ipNet.String())
}
// Discard interfaces with no addresses
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 ""
}
// Get IP address with netmask for the specified interface
// Returns an empty string if it fails to find it
func GetSubnet(ifaceName string) string {
netIfaces, err := GetValidNetInterfacesForWeb()
if err != nil {
log.Error("Could not get network interfaces info: %v", err)
return ""
}
for _, netIface := range netIfaces {
if netIface.Name == ifaceName && len(netIface.Subnets) > 0 {
return netIface.Subnets[0]
}
}
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
}

View File

@@ -0,0 +1,24 @@
package util
import (
"log"
"testing"
)
func TestGetValidNetInterfacesForWeb(t *testing.T) {
ifaces, err := GetValidNetInterfacesForWeb()
if err != nil {
t.Fatalf("Cannot get net interfaces: %s", err)
}
if len(ifaces) == 0 {
t.Fatalf("No net interfaces found")
}
for _, iface := range ifaces {
if len(iface.Addresses) == 0 {
t.Fatalf("No addresses found for %s", iface.Name)
}
log.Printf("%v", iface)
}
}

27
util/os_freebsd.go Normal file
View File

@@ -0,0 +1,27 @@
// +build freebsd
package util
import (
"os"
"syscall"
"github.com/AdguardTeam/golibs/log"
)
// Set user-specified limit of how many fd's we can use
// https://github.com/AdguardTeam/AdGuardHome/issues/659
func SetRlimit(val uint) {
var rlim syscall.Rlimit
rlim.Max = int64(val)
rlim.Cur = int64(val)
err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim)
if err != nil {
log.Error("Setrlimit() failed: %v", err)
}
}
// Check if the current user has root (administrator) rights
func HaveAdminRights() (bool, error) {
return os.Getuid() == 0, nil
}

27
util/os_unix.go Normal file
View File

@@ -0,0 +1,27 @@
// +build aix darwin dragonfly linux netbsd openbsd solaris
package util
import (
"os"
"syscall"
"github.com/AdguardTeam/golibs/log"
)
// Set user-specified limit of how many fd's we can use
// https://github.com/AdguardTeam/AdGuardHome/issues/659
func SetRlimit(val uint) {
var rlim syscall.Rlimit
rlim.Max = uint64(val)
rlim.Cur = uint64(val)
err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim)
if err != nil {
log.Error("Setrlimit() failed: %v", err)
}
}
// Check if the current user has root (administrator) rights
func HaveAdminRights() (bool, error) {
return os.Getuid() == 0, nil
}

28
util/os_windows.go Normal file
View File

@@ -0,0 +1,28 @@
package util
import "golang.org/x/sys/windows"
// Set user-specified limit of how many fd's we can use
func SetRlimit(val uint) {
}
func HaveAdminRights() (bool, error) {
var token windows.Token
h, _ := windows.GetCurrentProcess()
err := windows.OpenProcessToken(h, windows.TOKEN_QUERY, &token)
if err != nil {
return false, err
}
info := make([]byte, 4)
var returnedLen uint32
err = windows.GetTokenInformation(token, windows.TokenElevation, &info[0], uint32(len(info)), &returnedLen)
token.Close()
if err != nil {
return false, err
}
if info[0] == 0 {
return false, nil
}
return true, nil
}

18
util/syslog_others.go Normal file
View File

@@ -0,0 +1,18 @@
// +build !windows,!nacl,!plan9
package util
import (
"log"
"log/syslog"
)
// ConfigureSyslog reroutes standard logger output to syslog
func ConfigureSyslog(serviceName string) error {
w, err := syslog.New(syslog.LOG_NOTICE|syslog.LOG_USER, serviceName)
if err != nil {
return err
}
log.SetOutput(w)
return nil
}

39
util/syslog_windows.go Normal file
View File

@@ -0,0 +1,39 @@
package util
import (
"log"
"strings"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc/eventlog"
)
type eventLogWriter struct {
el *eventlog.Log
}
// Write sends a log message to the Event Log.
func (w *eventLogWriter) Write(b []byte) (int, error) {
return len(b), w.el.Info(1, string(b))
}
func ConfigureSyslog(serviceName string) error {
// Note that the eventlog src is the same as the service name
// Otherwise, we will get "the description for event id cannot be found" warning in every log record
// Continue if we receive "registry key already exists" or if we get
// ERROR_ACCESS_DENIED so that we can log without administrative permissions
// for pre-existing eventlog sources.
if err := eventlog.InstallAsEventCreate(serviceName, eventlog.Info|eventlog.Warning|eventlog.Error); err != nil {
if !strings.Contains(err.Error(), "registry key already exists") && err != windows.ERROR_ACCESS_DENIED {
return err
}
}
el, err := eventlog.Open(serviceName)
if err != nil {
return err
}
log.SetOutput(&eventLogWriter{el: el})
return nil
}