Pull request: 2302 static ip

Merge in DNS/adguard-home from 2302-static-ip to master

Closes #2302.

Squashed commit of the following:

commit e62b7b033861f1c55f0d06811ca005b3934ddc5b
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 19:38:15 2020 +0300

    all: format changelog

commit 4127aa7630674ddcfe84f712e6c7c8d06b1cab9a
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 19:24:53 2020 +0300

    all: fix changelog typo

commit f8a432056d3682facae0cdec99d7d80a5c2bd9b5
Merge: b809a866e e4383189a
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 19:22:27 2020 +0300

    Merge branch 'master' into 2302-static-ip

commit b809a866e49147354f9c6952b2f958b6b56ad873
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 19:20:05 2020 +0300

    all: log changes

commit 248c35ba411af731d6eae755a901cdbc77980628
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 18:57:15 2020 +0300

    sysutil: use bufio.Scanner

commit 0dc19dd5c232fbe9552a2b0d846e048274d73a74
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 17:26:18 2020 +0300

    sysutil: fix linux tests

commit 91202d6763595cff187040516dd1db10a20762b7
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 17:15:29 2020 +0300

    sysutil: fix linux files

commit 40fbdbb95876322ebaeef1cbdaa8e3299b83ca7e
Merge: d9a43ffb6 9b963fc77
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 16:52:35 2020 +0300

    Merge branch 'master' into 2302-static-ip

commit d9a43ffb68a2ddbbcf185b69fc75aed139cd6919
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 16:49:22 2020 +0300

    sysutil: add main test

commit bfef89186035ab0423d23246d46511584c26534c
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 16:21:59 2020 +0300

    sysutil: improve code quality

commit a5f57a373f736971fdeb0c03371da7c8138b3a82
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Fri Dec 4 14:14:08 2020 +0300

    all: move system functionality from dhcpd package to sysutil.

commit 020b51864f85a39ca80e2b4e06faeb47cf318e11
Merge: 967e111a6 ab8defdb0
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Wed Dec 2 14:53:43 2020 +0300

    Merge branch 'master' into 2302-static-ip

commit 967e111a663c18b5f4a87f3ae38d076f3ab6c200
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Wed Dec 2 13:52:17 2020 +0300

    all: improve code quality
This commit is contained in:
Eugene Burkov 2020-12-07 19:48:24 +03:00
parent e4383189a5
commit 2313eda123
21 changed files with 533 additions and 402 deletions

View File

@ -15,6 +15,8 @@ and this project adheres to
### Added ### Added
- Detecting of network interface configurated to have static IP address via
`/etc/network/interfaces` ([#2302]).
- DNSCrypt protocol support [#1361]. - DNSCrypt protocol support [#1361].
- A 5 second wait period until a DHCP server's network interface gets an IP - A 5 second wait period until a DHCP server's network interface gets an IP
address ([#2304]). address ([#2304]).
@ -22,6 +24,7 @@ and this project adheres to
- HTTP API request body size limit ([#2305]). - HTTP API request body size limit ([#2305]).
[#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361 [#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361
[#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302
[#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 [#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304
[#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 [#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305
[#2337]: https://github.com/AdguardTeam/AdGuardHome/issues/2337 [#2337]: https://github.com/AdguardTeam/AdGuardHome/issues/2337

View File

@ -81,11 +81,6 @@ type ServerInterface interface {
SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT) SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT)
} }
// CheckConfig checks the configuration
func (s *Server) CheckConfig(config ServerConfig) error {
return nil
}
// Create - create object // Create - create object
func Create(config ServerConfig) *Server { func Create(config ServerConfig) *Server {
s := &Server{} s := &Server{}

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
"github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/AdGuardHome/internal/util"
"github.com/AdguardTeam/golibs/jsonutil" "github.com/AdguardTeam/golibs/jsonutil"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
@ -205,9 +206,9 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
s.dbLoad() s.dbLoad()
if s.conf.Enabled { if s.conf.Enabled {
staticIP, err := HasStaticIP(newconfig.InterfaceName) staticIP, err := sysutil.IfaceHasStaticIP(newconfig.InterfaceName)
if !staticIP && err == nil { if !staticIP && err == nil {
err = SetStaticIP(newconfig.InterfaceName) err = sysutil.IfaceSetStaticIP(newconfig.InterfaceName)
if err != nil { if err != nil {
httpError(r, w, http.StatusInternalServerError, "Failed to configure static IP: %s", err) httpError(r, w, http.StatusInternalServerError, "Failed to configure static IP: %s", err)
return return
@ -282,7 +283,7 @@ func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
} }
} }
if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 { if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 {
jsonIface.GatewayIP = getGatewayIP(iface.Name) jsonIface.GatewayIP = sysutil.GatewayIP(iface.Name)
response[iface.Name] = jsonIface response[iface.Name] = jsonIface
} }
} }
@ -319,7 +320,7 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
found4, err4 := CheckIfOtherDHCPServersPresentV4(interfaceName) found4, err4 := CheckIfOtherDHCPServersPresentV4(interfaceName)
staticIP := map[string]interface{}{} staticIP := map[string]interface{}{}
isStaticIP, err := HasStaticIP(interfaceName) isStaticIP, err := sysutil.IfaceHasStaticIP(interfaceName)
staticIPStatus := "yes" staticIPStatus := "yes"
if err != nil { if err != nil {
staticIPStatus = "error" staticIPStatus = "error"

View File

@ -1,312 +0,0 @@
package dhcpd
import (
"errors"
"fmt"
"io/ioutil"
"net"
"os/exec"
"regexp"
"runtime"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/util"
"github.com/AdguardTeam/golibs/file"
"github.com/AdguardTeam/golibs/log"
)
// HasStaticIP check if the network interface has a static IP configured
//
// Supports: Raspbian.
func HasStaticIP(ifaceName string) (bool, error) {
if runtime.GOOS == "linux" {
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
if err != nil {
return false, err
}
return hasStaticIPDhcpcdConf(string(body), ifaceName), nil
}
if runtime.GOOS == "darwin" {
return hasStaticIPDarwin(ifaceName)
}
return false, fmt.Errorf("cannot check if IP is static: not supported on %s", runtime.GOOS)
}
// SetStaticIP sets a static IP for the network interface.
func SetStaticIP(ifaceName string) error {
if runtime.GOOS == "linux" {
return setStaticIPDhcpdConf(ifaceName)
}
if runtime.GOOS == "darwin" {
return setStaticIPDarwin(ifaceName)
}
return fmt.Errorf("cannot set static IP on %s", runtime.GOOS)
}
// for dhcpcd.conf
func hasStaticIPDhcpcdConf(dhcpConf, ifaceName string) bool {
lines := strings.Split(dhcpConf, "\n")
nameLine := fmt.Sprintf("interface %s", ifaceName)
withinInterfaceCtx := false
for _, line := range lines {
line = strings.TrimSpace(line)
if withinInterfaceCtx && len(line) == 0 {
// an empty line resets our state
withinInterfaceCtx = false
}
if len(line) == 0 || line[0] == '#' {
continue
}
line = strings.TrimSpace(line)
if !withinInterfaceCtx {
if line == nameLine {
// we found our interface
withinInterfaceCtx = true
}
} else {
if strings.HasPrefix(line, "interface ") {
// we found another interface - reset our state
withinInterfaceCtx = false
continue
}
if strings.HasPrefix(line, "static ip_address=") {
return true
}
}
}
return false
}
// Get gateway IP address
func getGatewayIP(ifaceName string) string {
cmd := exec.Command("ip", "route", "show", "dev", ifaceName)
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
d, err := cmd.Output()
if err != nil || cmd.ProcessState.ExitCode() != 0 {
return ""
}
fields := strings.Fields(string(d))
if len(fields) < 3 || fields[0] != "default" {
return ""
}
ip := net.ParseIP(fields[2])
if ip == nil {
return ""
}
return fields[2]
}
// setStaticIPDhcpdConf - updates /etc/dhcpd.conf and sets the current IP address to be static
func setStaticIPDhcpdConf(ifaceName string) error {
ip := util.GetSubnet(ifaceName)
if len(ip) == 0 {
return errors.New("can't get IP address")
}
ip4, _, err := net.ParseCIDR(ip)
if err != nil {
return err
}
gatewayIP := getGatewayIP(ifaceName)
add := updateStaticIPDhcpcdConf(ifaceName, ip, gatewayIP, ip4.String())
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
if err != nil {
return err
}
body = append(body, []byte(add)...)
err = file.SafeWrite("/etc/dhcpcd.conf", body)
if err != nil {
return err
}
return nil
}
// updates dhcpd.conf content -- sets static IP address there
// for dhcpcd.conf
func updateStaticIPDhcpcdConf(ifaceName, ip, gatewayIP, dnsIP string) string {
var body []byte
add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n",
ifaceName, ip)
body = append(body, []byte(add)...)
if len(gatewayIP) != 0 {
add = fmt.Sprintf("static routers=%s\n",
gatewayIP)
body = append(body, []byte(add)...)
}
add = fmt.Sprintf("static domain_name_servers=%s\n\n",
dnsIP)
body = append(body, []byte(add)...)
return string(body)
}
// Check if network interface has a static IP configured
// Supports: MacOS.
func hasStaticIPDarwin(ifaceName string) (bool, error) {
portInfo, err := getCurrentHardwarePortInfo(ifaceName)
if err != nil {
return false, err
}
return portInfo.static, nil
}
// setStaticIPDarwin - uses networksetup util to set the current IP address to be static
// Additionally it configures the current DNS servers as well
func setStaticIPDarwin(ifaceName string) error {
portInfo, err := getCurrentHardwarePortInfo(ifaceName)
if err != nil {
return err
}
if portInfo.static {
return errors.New("IP address is already static")
}
dnsAddrs, err := getEtcResolvConfServers()
if err != nil {
return err
}
args := make([]string, 0)
args = append(args, "-setdnsservers", portInfo.name)
args = append(args, dnsAddrs...)
// Setting DNS servers is necessary when configuring a static IP
code, _, err := util.RunCommand("networksetup", args...)
if err != nil {
return err
}
if code != 0 {
return fmt.Errorf("failed to set DNS servers, code=%d", code)
}
// Actually configures hardware port to have static IP
code, _, err = util.RunCommand("networksetup", "-setmanual",
portInfo.name, portInfo.ip, portInfo.subnet, portInfo.gatewayIP)
if err != nil {
return err
}
if code != 0 {
return fmt.Errorf("failed to set DNS servers, code=%d", code)
}
return nil
}
// getCurrentHardwarePortInfo gets information the specified network interface
func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) {
// First of all we should find hardware port name
m := getNetworkSetupHardwareReports()
hardwarePort, ok := m[ifaceName]
if !ok {
return hardwarePortInfo{}, fmt.Errorf("could not find hardware port for %s", ifaceName)
}
return getHardwarePortInfo(hardwarePort)
}
// getNetworkSetupHardwareReports parses the output of the `networksetup -listallhardwareports` command
// it returns a map where the key is the interface name, and the value is the "hardware port"
// returns nil if it fails to parse the output
func getNetworkSetupHardwareReports() map[string]string {
_, out, err := util.RunCommand("networksetup", "-listallhardwareports")
if err != nil {
return nil
}
re, err := regexp.Compile("Hardware Port: (.*?)\nDevice: (.*?)\n")
if err != nil {
return nil
}
m := make(map[string]string)
matches := re.FindAllStringSubmatch(out, -1)
for i := range matches {
port := matches[i][1]
device := matches[i][2]
m[device] = port
}
return m
}
// hardwarePortInfo - information obtained using MacOS networksetup
// about the current state of the internet connection
type hardwarePortInfo struct {
name string
ip string
subnet string
gatewayIP string
static bool
}
func getHardwarePortInfo(hardwarePort string) (hardwarePortInfo, error) {
h := hardwarePortInfo{}
_, out, err := util.RunCommand("networksetup", "-getinfo", hardwarePort)
if err != nil {
return h, err
}
re := regexp.MustCompile("IP address: (.*?)\nSubnet mask: (.*?)\nRouter: (.*?)\n")
match := re.FindStringSubmatch(out)
if len(match) == 0 {
return h, errors.New("could not find hardware port info")
}
h.name = hardwarePort
h.ip = match[1]
h.subnet = match[2]
h.gatewayIP = match[3]
if strings.Index(out, "Manual Configuration") == 0 {
h.static = true
}
return h, nil
}
// Gets a list of nameservers currently configured in the /etc/resolv.conf
func getEtcResolvConfServers() ([]string, error) {
body, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
return nil, err
}
re := regexp.MustCompile("nameserver ([a-zA-Z0-9.:]+)")
matches := re.FindAllStringSubmatch(string(body), -1)
if len(matches) == 0 {
return nil, errors.New("found no DNS servers in /etc/resolv.conf")
}
addrs := make([]string, 0)
for i := range matches {
addrs = append(addrs, matches[i][1])
}
return addrs, nil
}

View File

@ -1,63 +0,0 @@
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package dhcpd
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHasStaticIPDhcpcdConf(t *testing.T) {
dhcpdConf := `#comment
# comment
interface eth0
static ip_address=192.168.0.1/24
# interface wlan0
static ip_address=192.168.1.1/24
# comment
`
assert.True(t, !hasStaticIPDhcpcdConf(dhcpdConf, "wlan0"))
dhcpdConf = `#comment
# comment
interface eth0
static ip_address=192.168.0.1/24
# interface wlan0
static ip_address=192.168.1.1/24
# comment
interface wlan0
# comment
static ip_address=192.168.2.1/24
`
assert.True(t, hasStaticIPDhcpcdConf(dhcpdConf, "wlan0"))
}
func TestSetStaticIPDhcpcdConf(t *testing.T) {
dhcpcdConf := `
interface wlan0
static ip_address=192.168.0.2/24
static routers=192.168.0.1
static domain_name_servers=192.168.0.2
`
s := updateStaticIPDhcpcdConf("wlan0", "192.168.0.2/24", "192.168.0.1", "192.168.0.2")
assert.Equal(t, dhcpcdConf, s)
// without gateway
dhcpcdConf = `
interface wlan0
static ip_address=192.168.0.2/24
static domain_name_servers=192.168.0.2
`
s = updateStaticIPDhcpcdConf("wlan0", "192.168.0.2/24", "", "192.168.0.2")
assert.Equal(t, dhcpcdConf, s)
}

View File

@ -15,7 +15,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/AdGuardHome/internal/util"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/sysutil"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
) )
@ -167,7 +167,7 @@ func handleStaticIP(ip string, set bool) staticIPJSON {
if set { if set {
// Try to set static IP for the specified interface // Try to set static IP for the specified interface
err := dhcpd.SetStaticIP(interfaceName) err := sysutil.IfaceSetStaticIP(interfaceName)
if err != nil { if err != nil {
resp.Static = "error" resp.Static = "error"
resp.Error = err.Error() resp.Error = err.Error()
@ -177,7 +177,7 @@ func handleStaticIP(ip string, set bool) staticIPJSON {
// Fallthrough here even if we set static IP // Fallthrough here even if we set static IP
// Check if we have a static IP and return the details // Check if we have a static IP and return the details
isStaticIP, err := dhcpd.HasStaticIP(interfaceName) isStaticIP, err := sysutil.IfaceHasStaticIP(interfaceName)
if err != nil { if err != nil {
resp.Static = "error" resp.Static = "error"
resp.Error = err.Error() resp.Error = err.Error()

44
internal/sysutil/net.go Normal file
View File

@ -0,0 +1,44 @@
package sysutil
import (
"net"
"os/exec"
"strings"
"github.com/AdguardTeam/golibs/log"
)
// IfaceHasStaticIP checks if interface is configured to have static IP address.
func IfaceHasStaticIP(ifaceName string) (has bool, err error) {
return ifaceHasStaticIP(ifaceName)
}
// IfaceSetStaticIP sets static IP address for network interface.
func IfaceSetStaticIP(ifaceName string) (err error) {
return ifaceSetStaticIP(ifaceName)
}
// GatewayIP returns IP address of interface's gateway.
func GatewayIP(ifaceName string) string {
cmd := exec.Command("ip", "route", "show", "dev", ifaceName)
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
d, err := cmd.Output()
if err != nil || cmd.ProcessState.ExitCode() != 0 {
return ""
}
fields := strings.Fields(string(d))
// The meaningful "ip route" command output should contain the word
// "default" at first field and default gateway IP address at third
// field.
if len(fields) < 3 || fields[0] != "default" {
return ""
}
ip := net.ParseIP(fields[2])
if ip == nil {
return ""
}
return fields[2]
}

View File

@ -0,0 +1,161 @@
// +build darwin
package sysutil
import (
"errors"
"fmt"
"io/ioutil"
"regexp"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/util"
)
// hardwarePortInfo - information obtained using MacOS networksetup
// about the current state of the internet connection
type hardwarePortInfo struct {
name string
ip string
subnet string
gatewayIP string
static bool
}
func ifaceHasStaticIP(ifaceName string) (bool, error) {
portInfo, err := getCurrentHardwarePortInfo(ifaceName)
if err != nil {
return false, err
}
return portInfo.static, nil
}
// getCurrentHardwarePortInfo gets information the specified network interface.
func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) {
// First of all we should find hardware port name
m := getNetworkSetupHardwareReports()
hardwarePort, ok := m[ifaceName]
if !ok {
return hardwarePortInfo{}, fmt.Errorf("could not find hardware port for %s", ifaceName)
}
return getHardwarePortInfo(hardwarePort)
}
// getNetworkSetupHardwareReports parses the output of the `networksetup -listallhardwareports` command
// it returns a map where the key is the interface name, and the value is the "hardware port"
// returns nil if it fails to parse the output
func getNetworkSetupHardwareReports() map[string]string {
_, out, err := util.RunCommand("networksetup", "-listallhardwareports")
if err != nil {
return nil
}
re, err := regexp.Compile("Hardware Port: (.*?)\nDevice: (.*?)\n")
if err != nil {
return nil
}
m := make(map[string]string)
matches := re.FindAllStringSubmatch(out, -1)
for i := range matches {
port := matches[i][1]
device := matches[i][2]
m[device] = port
}
return m
}
func getHardwarePortInfo(hardwarePort string) (hardwarePortInfo, error) {
h := hardwarePortInfo{}
_, out, err := util.RunCommand("networksetup", "-getinfo", hardwarePort)
if err != nil {
return h, err
}
re := regexp.MustCompile("IP address: (.*?)\nSubnet mask: (.*?)\nRouter: (.*?)\n")
match := re.FindStringSubmatch(out)
if len(match) == 0 {
return h, errors.New("could not find hardware port info")
}
h.name = hardwarePort
h.ip = match[1]
h.subnet = match[2]
h.gatewayIP = match[3]
if strings.Index(out, "Manual Configuration") == 0 {
h.static = true
}
return h, nil
}
func ifaceSetStaticIP(ifaceName string) (err error) {
portInfo, err := getCurrentHardwarePortInfo(ifaceName)
if err != nil {
return err
}
if portInfo.static {
return errors.New("IP address is already static")
}
dnsAddrs, err := getEtcResolvConfServers()
if err != nil {
return err
}
args := make([]string, 0)
args = append(args, "-setdnsservers", portInfo.name)
args = append(args, dnsAddrs...)
// Setting DNS servers is necessary when configuring a static IP
code, _, err := util.RunCommand("networksetup", args...)
if err != nil {
return err
}
if code != 0 {
return fmt.Errorf("failed to set DNS servers, code=%d", code)
}
// Actually configures hardware port to have static IP
code, _, err = util.RunCommand("networksetup", "-setmanual",
portInfo.name, portInfo.ip, portInfo.subnet, portInfo.gatewayIP)
if err != nil {
return err
}
if code != 0 {
return fmt.Errorf("failed to set DNS servers, code=%d", code)
}
return nil
}
// getEtcResolvConfServers returns a list of nameservers configured in
// /etc/resolv.conf.
func getEtcResolvConfServers() ([]string, error) {
body, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
return nil, err
}
re := regexp.MustCompile("nameserver ([a-zA-Z0-9.:]+)")
matches := re.FindAllStringSubmatch(string(body), -1)
if len(matches) == 0 {
return nil, errors.New("found no DNS servers in /etc/resolv.conf")
}
addrs := make([]string, 0)
for i := range matches {
addrs = append(addrs, matches[i][1])
}
return addrs, nil
}

View File

@ -0,0 +1,168 @@
// +build linux
package sysutil
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
"github.com/AdguardTeam/AdGuardHome/internal/util"
"github.com/AdguardTeam/golibs/file"
)
// maxConfigFileSize is the maximum length of interfaces configuration file.
const maxConfigFileSize = 1024 * 1024
func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
var f *os.File
for _, check := range []struct {
checker func(io.Reader, string) (bool, error)
filePath string
}{{
checker: dhcpcdStaticConfig,
filePath: "/etc/dhcpcd.conf",
}, {
checker: ifacesStaticConfig,
filePath: "/etc/network/interfaces",
}} {
f, err = os.Open(check.filePath)
if errors.Is(err, os.ErrNotExist) {
continue
}
if err != nil {
return false, err
}
defer f.Close()
fileReadCloser, err := aghio.LimitReadCloser(f, maxConfigFileSize)
if err != nil {
return false, err
}
defer fileReadCloser.Close()
has, err = check.checker(fileReadCloser, ifaceName)
if has || err != nil {
break
}
}
return has, err
}
// dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to
// have a static IP.
func dhcpcdStaticConfig(r io.Reader, ifaceName string) (has bool, err error) {
s := bufio.NewScanner(r)
var withinInterfaceCtx bool
for s.Scan() {
line := strings.TrimSpace(s.Text())
if withinInterfaceCtx && len(line) == 0 {
// An empty line resets our state.
withinInterfaceCtx = false
}
if len(line) == 0 || line[0] == '#' {
continue
}
fields := strings.Fields(line)
if withinInterfaceCtx {
if len(fields) >= 2 && fields[0] == "static" && strings.HasPrefix(fields[1], "ip_address=") {
return true, nil
}
if len(fields) > 0 && fields[0] == "interface" {
// Another interface found.
withinInterfaceCtx = false
}
continue
}
if len(fields) == 2 && fields[0] == "interface" && fields[1] == ifaceName {
// The interface found.
withinInterfaceCtx = true
}
}
return false, s.Err()
}
// ifacesStaticConfig checks if interface is configured by
// /etc/network/interfaces to have a static IP.
func ifacesStaticConfig(r io.Reader, ifaceName string) (has bool, err error) {
s := bufio.NewScanner(r)
for s.Scan() {
line := strings.TrimSpace(s.Text())
if len(line) == 0 || line[0] == '#' {
continue
}
fields := strings.Fields(line)
// Man page interfaces(5) declares that interface definition
// should consist of the key word "iface" followed by interface
// name, and method at fourth field.
if len(fields) >= 4 && fields[0] == "iface" && fields[1] == ifaceName && fields[3] == "static" {
return true, nil
}
}
return false, s.Err()
}
func ifaceSetStaticIP(ifaceName string) (err error) {
ip := util.GetSubnet(ifaceName)
if len(ip) == 0 {
return errors.New("can't get IP address")
}
ip4, _, err := net.ParseCIDR(ip)
if err != nil {
return err
}
gatewayIP := GatewayIP(ifaceName)
add := updateStaticIPdhcpcdConf(ifaceName, ip, gatewayIP, ip4.String())
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
if err != nil {
return err
}
body = append(body, []byte(add)...)
err = file.SafeWrite("/etc/dhcpcd.conf", body)
if err != nil {
return err
}
return nil
}
// updateStaticIPdhcpcdConf sets static IP address for the interface by writing
// into dhcpd.conf.
func updateStaticIPdhcpcdConf(ifaceName, ip, gatewayIP, dnsIP string) string {
var body []byte
add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n",
ifaceName, ip)
body = append(body, []byte(add)...)
if len(gatewayIP) != 0 {
add = fmt.Sprintf("static routers=%s\n",
gatewayIP)
body = append(body, []byte(add)...)
}
add = fmt.Sprintf("static domain_name_servers=%s\n\n",
dnsIP)
body = append(body, []byte(add)...)
return string(body)
}

View File

@ -0,0 +1,109 @@
// +build linux
package sysutil
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
const nl = "\n"
func TestDHCPCDStaticConfig(t *testing.T) {
testCases := []struct {
name string
data []byte
want bool
}{{
name: "has_not",
data: []byte(`#comment` + nl +
`# comment` + nl +
`interface eth0` + nl +
`static ip_address=192.168.0.1/24` + nl +
`# interface wlan0` + nl +
`static ip_address=192.168.1.1/24` + nl +
`# comment` + nl,
),
want: false,
}, {
name: "has",
data: []byte(`#comment` + nl +
`# comment` + nl +
`interface eth0` + nl +
`static ip_address=192.168.0.1/24` + nl +
`# interface wlan0` + nl +
`static ip_address=192.168.1.1/24` + nl +
`# comment` + nl +
`interface wlan0` + nl +
`# comment` + nl +
`static ip_address=192.168.2.1/24` + nl,
),
want: true,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
r := bytes.NewReader(tc.data)
has, err := dhcpcdStaticConfig(r, "wlan0")
assert.Nil(t, err)
assert.Equal(t, tc.want, has)
})
}
}
func TestIfacesStaticConfig(t *testing.T) {
testCases := []struct {
name string
data []byte
want bool
}{{
name: "has_not",
data: []byte(`allow-hotplug enp0s3` + nl +
`#iface enp0s3 inet static` + nl +
`# address 192.168.0.200` + nl +
`# netmask 255.255.255.0` + nl +
`# gateway 192.168.0.1` + nl +
`iface enp0s3 inet dhcp` + nl,
),
want: false,
}, {
name: "has",
data: []byte(`allow-hotplug enp0s3` + nl +
`iface enp0s3 inet static` + nl +
` address 192.168.0.200` + nl +
` netmask 255.255.255.0` + nl +
` gateway 192.168.0.1` + nl +
`#iface enp0s3 inet dhcp` + nl,
),
want: true,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
r := bytes.NewReader(tc.data)
has, err := ifacesStaticConfig(r, "enp0s3")
assert.Nil(t, err)
assert.Equal(t, tc.want, has)
})
}
}
func TestSetStaticIPdhcpcdConf(t *testing.T) {
dhcpcdConf := nl + `interface wlan0` + nl +
`static ip_address=192.168.0.2/24` + nl +
`static routers=192.168.0.1` + nl +
`static domain_name_servers=192.168.0.2` + nl + nl
s := updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", "192.168.0.1", "192.168.0.2")
assert.Equal(t, dhcpcdConf, s)
// without gateway
dhcpcdConf = nl + `interface wlan0` + nl +
`static ip_address=192.168.0.2/24` + nl +
`static domain_name_servers=192.168.0.2` + nl + nl
s = updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", "", "192.168.0.2")
assert.Equal(t, dhcpcdConf, s)
}

View File

@ -0,0 +1,16 @@
// +build !linux,!darwin
package sysutil
import (
"fmt"
"runtime"
)
func ifaceHasStaticIP(string) (bool, error) {
return false, fmt.Errorf("cannot check if IP is static: not supported on %s", runtime.GOOS)
}
func ifaceSetStaticIP(string) error {
return fmt.Errorf("cannot set static IP on %s", runtime.GOOS)
}

View File

@ -24,8 +24,3 @@ func HaveAdminRights() (bool, error) {
func SendProcessSignal(pid int, sig syscall.Signal) error { func SendProcessSignal(pid int, sig syscall.Signal) error {
return sendProcessSignal(pid, sig) return sendProcessSignal(pid, sig)
} }
// ConfigureSyslog reroutes standard logger output to syslog.
func ConfigureSyslog(serviceName string) error {
return configureSyslog(serviceName)
}

View File

@ -0,0 +1,6 @@
package sysutil
// ConfigureSyslog reroutes standard logger output to syslog.
func ConfigureSyslog(serviceName string) error {
return configureSyslog(serviceName)
}

View File

@ -0,0 +1,11 @@
package sysutil
import (
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/testutil"
)
func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}

View File

@ -33,10 +33,7 @@ func GetValidNetInterfaces() ([]net.Interface, error) {
netIfaces := []net.Interface{} netIfaces := []net.Interface{}
for i := range ifaces { netIfaces = append(netIfaces, ifaces...)
iface := ifaces[i]
netIfaces = append(netIfaces, iface)
}
return netIfaces, nil return netIfaces, nil
} }