bf4c256c72
Merge in DNS/adguard-home from 2295-dhcp-windows to master Updates #2295. Squashed commit of the following: commit 3b00a90c3d9bc33e9af478e4062c0f938d4f327d Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Mon Nov 16 16:45:43 2020 +0300 all: use the 501 handlers instead of the real ones, revert other changes commit 0a3b37736a21abd6181e0d28c32069e8d7a576d0 Merge: 45feba7556358240e9
Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Mon Nov 16 15:59:15 2020 +0300 Merge branch 'master' into 2295-dhcp-windows and update commit 45feba755dde37e43cc8075b896e1576157341e6 Merge: cd987d8bca19523b25
Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Mon Nov 16 15:51:16 2020 +0300 Merge branch 'master' into 2295-dhcp-windows commit cd987d8bc2cd524b7454d9037b595069714645f9 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Nov 13 15:55:23 2020 +0300 all: improve tests and refactor dhcp checking code even more commit 3aad675443f325b5909523bcc1c987aa04ac61d9 Merge: 70c477e6109196118e
Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Nov 13 14:44:43 2020 +0300 Merge branch 'master' into 2295-dhcp-windows commit 70c477e61cdc1237603918f1c44470c1549f1136 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Nov 13 14:34:06 2020 +0300 home: fix dhcpd test on windows commit e59597d783fb9304e63f94eee2b5a5d67a5b2169 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Nov 13 13:38:25 2020 +0300 all: mention the feature in the changelog commit 5555c8d881b1c20b5b0a0cb096a17cf56e209c06 Merge: c3b6a5a93e802e6645
Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Nov 13 13:35:35 2020 +0300 Merge branch 'master' into 2295-dhcp-windows commit c3b6a5a930693090838eb1ef9f75a09b5b223ba6 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Thu Nov 12 20:37:09 2020 +0300 util: fix comment commit ed92dfdb5d3a6c4ba5d032cbe781e7fd87882813 Author: ArtemBaskal <asbaskal@miem.hse.ru> Date: Thu Nov 12 20:24:14 2020 +0300 Adapt client commit e6f0494c20a4ad5388492af9091568eea5c6e2d6 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Thu Nov 12 13:35:25 2020 +0300 return 501 when we don't support features
313 lines
7.3 KiB
Go
313 lines
7.3 KiB
Go
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
|
|
}
|