+(dhcpd): added static IP for MacOS
This commit is contained in:
59
util/helpers.go
Normal file
59
util/helpers.go
Normal 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
14
util/helpers_test.go
Normal 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
194
util/network_utils.go
Normal 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
|
||||
}
|
||||
24
util/network_utils_test.go
Normal file
24
util/network_utils_test.go
Normal 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
27
util/os_freebsd.go
Normal 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
27
util/os_unix.go
Normal 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
28
util/os_windows.go
Normal 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
18
util/syslog_others.go
Normal 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
39
util/syslog_windows.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user