package dhcpd import ( "encoding/hex" "fmt" "net" "strconv" "strings" "github.com/AdguardTeam/golibs/errors" ) // hexDHCPOptionParserHandler parses a DHCP option as a hex-encoded string. // For example: // // 252 hex 736f636b733a2f2f70726f78792e6578616d706c652e6f7267 // func hexDHCPOptionParserHandler(s string) (data []byte, err error) { data, err = hex.DecodeString(s) if err != nil { return nil, fmt.Errorf("decoding hex: %w", err) } return data, nil } // ipDHCPOptionParserHandler parses a DHCP option as a single IP address. // For example: // // 6 ip 192.168.1.1 // func ipDHCPOptionParserHandler(s string) (data []byte, err error) { ip := net.ParseIP(s) if ip == nil { return nil, errors.Error("invalid ip") } // Most DHCP options require IPv4, so do not put the 16-byte // version if we can. Otherwise, the clients will receive weird // data that looks like four IPv4 addresses. // // See https://github.com/AdguardTeam/AdGuardHome/issues/2688. if ip4 := ip.To4(); ip4 != nil { data = ip4 } else { data = ip } return data, nil } // textDHCPOptionParserHandler parses a DHCP option as a simple UTF-8 encoded // text. For example: // // 252 text http://192.168.1.1/wpad.dat // func ipsDHCPOptionParserHandler(s string) (data []byte, err error) { ipStrs := strings.Split(s, ",") for i, ipStr := range ipStrs { var ipData []byte ipData, err = ipDHCPOptionParserHandler(ipStr) if err != nil { return nil, fmt.Errorf("parsing ip at index %d: %w", i, err) } data = append(data, ipData...) } return data, nil } // ipsDHCPOptionParserHandler parses a DHCP option as a comma-separates list of // IP addresses. For example: // // 6 ips 192.168.1.1,192.168.1.2 // func textDHCPOptionParserHandler(s string) (data []byte, err error) { return []byte(s), nil } // dhcpOptionParserHandler is a parser for a single dhcp option type. type dhcpOptionParserHandler func(s string) (data []byte, err error) // dhcpOptionParser parses DHCP options. type dhcpOptionParser struct { handlers map[string]dhcpOptionParserHandler } // newDHCPOptionParser returns a new dhcpOptionParser. func newDHCPOptionParser() (p *dhcpOptionParser) { return &dhcpOptionParser{ handlers: map[string]dhcpOptionParserHandler{ "hex": hexDHCPOptionParserHandler, "ip": ipDHCPOptionParserHandler, "ips": ipsDHCPOptionParserHandler, "text": textDHCPOptionParserHandler, }, } } // parse parses an option. See the handlers' documentation for more info. func (p *dhcpOptionParser) parse(s string) (code uint8, data []byte, err error) { defer func() { err = errors.Annotate(err, "invalid option string %q: %w", s) }() s = strings.TrimSpace(s) parts := strings.SplitN(s, " ", 3) if len(parts) < 3 { return 0, nil, errors.Error("need at least three fields") } codeStr := parts[0] typ := parts[1] val := parts[2] var code64 uint64 code64, err = strconv.ParseUint(codeStr, 10, 8) if err != nil { return 0, nil, fmt.Errorf("parsing option code: %w", err) } code = uint8(code64) h, ok := p.handlers[typ] if !ok { return 0, nil, fmt.Errorf("unknown option type %q", typ) } data, err = h(val) if err != nil { return 0, nil, err } return uint8(code), data, nil }