AdGuardHome/internal/aghnet/net_linux.go
Eugene Burkov c70f941bf8 Pull request: 2846 cover aghnet vol.4
Merge in DNS/adguard-home from 2846-cover-aghnet-vol.4 to master

Updates #2846.

Squashed commit of the following:

commit 576ef857628a403ce1478c10a4aad23985c09613
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 31 19:38:57 2022 +0300

    aghnet: imp code

commit 5b4b17ff52867aaab2c9d30a0fc7fc2fe31ff4d5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 31 14:58:34 2022 +0300

    aghnet: imp coverage
2022-03-31 19:56:50 +03:00

188 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build linux
// +build linux
package aghnet
import (
"bufio"
"fmt"
"io"
"net"
"os"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/google/renameio/maybe"
"golang.org/x/sys/unix"
)
// dhcpсdConf is the name of /etc/dhcpcd.conf file in the root filesystem.
const dhcpcdConf = "etc/dhcpcd.conf"
func canBindPrivilegedPorts() (can bool, err error) {
cnbs, err := unix.PrctlRetInt(
unix.PR_CAP_AMBIENT,
unix.PR_CAP_AMBIENT_IS_SET,
unix.CAP_NET_BIND_SERVICE,
0,
0,
)
// Don't check the error because it's always nil on Linux.
adm, _ := aghos.HaveAdminRights()
return cnbs == 1 || adm, err
}
// dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to
// have a static IP.
func (n interfaceName) dhcpcdStaticConfig(r io.Reader) (subsources []string, cont bool, err error) {
s := bufio.NewScanner(r)
if !findIfaceLine(s, string(n)) {
return nil, true, s.Err()
}
for s.Scan() {
line := strings.TrimSpace(s.Text())
fields := strings.Fields(line)
if len(fields) >= 2 &&
fields[0] == "static" &&
strings.HasPrefix(fields[1], "ip_address=") {
return nil, false, s.Err()
}
if len(fields) > 0 && fields[0] == "interface" {
// Another interface found.
break
}
}
return nil, true, s.Err()
}
// ifacesStaticConfig checks if the interface is configured by any file of
// /etc/network/interfaces format to have a static IP.
func (n interfaceName) ifacesStaticConfig(r io.Reader) (sub []string, cont bool, err error) {
s := bufio.NewScanner(r)
for s.Scan() {
line := strings.TrimSpace(s.Text())
if len(line) == 0 || line[0] == '#' {
continue
}
// TODO(e.burkov): As man page interfaces(5) says, a line may be
// extended across multiple lines by making the last character a
// backslash. Provide extended lines support.
fields := strings.Fields(line)
fieldsNum := len(fields)
// 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 fieldsNum >= 4 &&
fields[0] == "iface" && fields[1] == string(n) && fields[3] == "static" {
return nil, false, nil
}
if fieldsNum >= 2 && fields[0] == "source" {
sub = append(sub, fields[1])
}
}
return sub, true, s.Err()
}
func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
// TODO(a.garipov): Currently, this function returns the first definitive
// result. So if /etc/dhcpcd.conf has and /etc/network/interfaces has no
// static IP configuration, it will return true. Perhaps this is not the
// most desirable behavior.
iface := interfaceName(ifaceName)
for _, pair := range [...]struct {
aghos.FileWalker
filename string
}{{
FileWalker: iface.dhcpcdStaticConfig,
filename: dhcpcdConf,
}, {
FileWalker: iface.ifacesStaticConfig,
filename: "etc/network/interfaces",
}} {
has, err = pair.Walk(rootDirFS, pair.filename)
if err != nil {
return false, err
} else if has {
return true, nil
}
}
return false, ErrNoStaticIPInfo
}
// findIfaceLine scans s until it finds the line that declares an interface with
// the given name. If findIfaceLine can't find the line, it returns false.
func findIfaceLine(s *bufio.Scanner, name string) (ok bool) {
for s.Scan() {
line := strings.TrimSpace(s.Text())
fields := strings.Fields(line)
if len(fields) == 2 && fields[0] == "interface" && fields[1] == name {
return true
}
}
return false
}
// ifaceSetStaticIP configures the system to retain its current IP on the
// interface through dhcpcd.conf.
func ifaceSetStaticIP(ifaceName string) (err error) {
ipNet := GetSubnet(ifaceName)
if ipNet.IP == nil {
return errors.Error("can't get IP address")
}
body, err := os.ReadFile(dhcpcdConf)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
gatewayIP := GatewayIP(ifaceName)
add := dhcpcdConfIface(ifaceName, ipNet, gatewayIP)
body = append(body, []byte(add)...)
err = maybe.WriteFile(dhcpcdConf, body, 0o644)
if err != nil {
return fmt.Errorf("writing conf: %w", err)
}
return nil
}
// dhcpcdConfIface returns configuration lines for the dhcpdc.conf files that
// configure the interface to have a static IP.
func dhcpcdConfIface(ifaceName string, ipNet *net.IPNet, gwIP net.IP) (conf string) {
b := &strings.Builder{}
stringutil.WriteToBuilder(
b,
"\n# ",
ifaceName,
" added by AdGuard Home.\ninterface ",
ifaceName,
"\nstatic ip_address=",
ipNet.String(),
"\n",
)
if gwIP != nil {
stringutil.WriteToBuilder(b, "static routers=", gwIP.String(), "\n")
}
stringutil.WriteToBuilder(b, "static domain_name_servers=", ipNet.IP.String(), "\n\n")
return b.String()
}