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

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

View File

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