AdGuardHome/internal/dhcpd/options_unix.go

206 lines
5.8 KiB
Go
Raw Normal View History

//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package dhcpd
import (
"encoding/hex"
"fmt"
"net"
"strconv"
"strings"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/insomniacslk/dhcp/dhcpv4"
)
// The aliases for DHCP option types available for explicit declaration.
const (
typHex = "hex"
typIP = "ip"
typIPs = "ips"
typText = "text"
typDel = "del"
)
// parseDHCPOptionHex parses a DHCP option as a hex-encoded string.
func parseDHCPOptionHex(s string) (val dhcpv4.OptionValue, err error) {
var data []byte
data, err = hex.DecodeString(s)
if err != nil {
return nil, fmt.Errorf("decoding hex: %w", err)
}
return dhcpv4.OptionGeneric{Data: data}, nil
}
// parseDHCPOptionIP parses a DHCP option as a single IP address.
func parseDHCPOptionIP(s string) (val dhcpv4.OptionValue, err error) {
var ip net.IP
// All DHCPv4 options require IPv4, so don't put the 16-byte version.
// Otherwise, the clients will receive weird data that looks like four
// IPv4 addresses.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/2688.
if ip, err = netutil.ParseIPv4(s); err != nil {
return nil, err
}
return dhcpv4.IP(ip), nil
}
// parseDHCPOptionIPs parses a DHCP option as a comma-separates list of IP
// addresses.
func parseDHCPOptionIPs(s string) (val dhcpv4.OptionValue, err error) {
var ips dhcpv4.IPs
var ip net.IP
for i, ipStr := range strings.Split(s, ",") {
// See notes in the ipDHCPOptionParserHandler.
if ip, err = netutil.ParseIPv4(ipStr); err != nil {
return nil, fmt.Errorf("parsing ip at index %d: %w", i, err)
}
ips = append(ips, ip)
}
return ips, nil
}
// parseDHCPOptionText parses a DHCP option as a simple UTF-8 encoded
// text.
func parseDHCPOptionText(s string) (val dhcpv4.OptionValue) {
return dhcpv4.OptionGeneric{Data: []byte(s)}
}
// parseDHCPOptionVal parses a DHCP option value considering typ. For the del
// option the value string is ignored. The examples of possible value pairs:
//
// - hex 736f636b733a2f2f70726f78792e6578616d706c652e6f7267
// - ip 192.168.1.1
// - ips 192.168.1.1,192.168.1.2
// - text http://192.168.1.1/wpad.dat
// - del
func parseDHCPOptionVal(typ, valStr string) (val dhcpv4.OptionValue, err error) {
switch typ {
case typHex:
val, err = parseDHCPOptionHex(valStr)
case typIP:
val, err = parseDHCPOptionIP(valStr)
case typIPs:
val, err = parseDHCPOptionIPs(valStr)
case typText:
val = parseDHCPOptionText(valStr)
case typDel:
val = dhcpv4.OptionGeneric{Data: nil}
default:
err = fmt.Errorf("unknown option type %q", typ)
}
return val, err
}
// parseDHCPOption parses an option. See the documentation of
// parseDHCPOptionVal for more info.
func parseDHCPOption(s string) (opt dhcpv4.Option, err error) {
defer func() { err = errors.Annotate(err, "invalid option string %q: %w", s) }()
s = strings.TrimSpace(s)
parts := strings.SplitN(s, " ", 3)
var valStr string
if pl := len(parts); pl < 3 {
if pl < 2 || parts[1] != typDel {
return opt, errors.Error("bad option format")
}
} else {
valStr = parts[2]
}
var code64 uint64
code64, err = strconv.ParseUint(parts[0], 10, 8)
if err != nil {
return opt, fmt.Errorf("parsing option code: %w", err)
}
val, err := parseDHCPOptionVal(parts[1], valStr)
if err != nil {
// Don't wrap an error since it's informative enough as is and there
// also the deferred annotation.
return opt, err
}
return dhcpv4.Option{
Code: dhcpv4.GenericOptionCode(code64),
Value: val,
}, nil
}
// prepareOptions builds the set of DHCP options according to host requirements
// document and values from conf.
func prepareOptions(conf V4ServerConf) (opts dhcpv4.Options) {
// Set default values for host configuration parameters listed in Appendix
// A of RFC-2131. Those parameters, if requested by client, should be
// returned with values defined by Host Requirements Document.
//
// See https://datatracker.ietf.org/doc/html/rfc2131#appendix-A.
//
// See also https://datatracker.ietf.org/doc/html/rfc1122,
// https://datatracker.ietf.org/doc/html/rfc1123, and
// https://datatracker.ietf.org/doc/html/rfc2132.
opts = dhcpv4.OptionsFromList(
// IP-Layer Per Host
dhcpv4.OptGeneric(dhcpv4.OptionNonLocalSourceRouting, []byte{0}),
// Set the current recommended default time to live for the
// Internet Protocol which is 64, see
// https://datatracker.ietf.org/doc/html/rfc1700.
dhcpv4.OptGeneric(dhcpv4.OptionDefaultIPTTL, []byte{0x40}),
dhcpv4.OptGeneric(dhcpv4.OptionPerformMaskDiscovery, []byte{0}),
dhcpv4.OptGeneric(dhcpv4.OptionMaskSupplier, []byte{0}),
dhcpv4.OptGeneric(dhcpv4.OptionPerformRouterDiscovery, []byte{1}),
// The all-routers address is preferred wherever possible, see
// https://datatracker.ietf.org/doc/html/rfc1256#section-5.1.
dhcpv4.Option{
Code: dhcpv4.OptionRouterSolicitationAddress,
Value: dhcpv4.IP(netutil.IPv4allrouter()),
},
dhcpv4.OptBroadcastAddress(netutil.IPv4bcast()),
// Link-Layer Per Interface
dhcpv4.OptGeneric(dhcpv4.OptionTrailerEncapsulation, []byte{0}),
dhcpv4.OptGeneric(dhcpv4.OptionEthernetEncapsulation, []byte{0}),
// TCP Per Host
dhcpv4.Option{
Code: dhcpv4.OptionTCPKeepaliveInterval,
Value: dhcpv4.Duration(0),
},
dhcpv4.OptGeneric(dhcpv4.OptionTCPKeepaliveGarbage, []byte{0}),
// Values From Configuration
dhcpv4.OptRouter(conf.subnet.IP),
dhcpv4.OptSubnetMask(conf.subnet.Mask),
)
// Set values for explicitly configured options.
for i, o := range conf.Options {
opt, err := parseDHCPOption(o)
if err != nil {
log.Error("dhcpv4: bad option string at index %d: %s", i, err)
continue
}
opts.Update(opt)
}
return opts
}