AdguardTeam/AdGuardHome · error

decoding hex: %w

Error message

decoding hex: %w

What it means

Returned by parseDHCPOptionHex when a DHCP option declared with type hex fails hex.DecodeString. Only characters 0-9, a-f, A-F in even count are accepted. This occurs while parsing the custom DHCP options list in the config.

Source

Thrown at internal/dhcpd/options_unix.go:40

// TODO(e.burkov):  Add an option for classless routes.
const (
	typDel  = "del"
	typBool = "bool"
	typDur  = "dur"
	typHex  = "hex"
	typIP   = "ip"
	typIPs  = "ips"
	typText = "text"
	typU8   = "u8"
	typU16  = "u16"
)

// 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

View on GitHub (pinned to b41aefbe51)

Solutions

  1. Strip any 0x prefix and colons/spaces, keep only 0-9a-f, ensure even length
  2. Validate with a quick check: echo -n 'VALUE' | wc -c (must be even) and echo 'VALUE' | grep -E '^[0-9a-fA-F]+$'
  3. Use lowercase or uppercase consistently; both are accepted

Example fix

// before
"options":[{"code":66,"type":"hex","value":"0x68656c6c6f"}]
// -> decoding hex: encoding/hex: invalid byte: U+0078 'x'

// after
"options":[{"code":66,"type":"hex","value":"68656c6c6f"}]
Defensive patterns

Strategy: validation

Validate before calling

func hexOK(s string) bool {
	if len(s)%2 != 0 { return false }
	_, err := hex.DecodeString(strings.TrimPrefix(strings.TrimPrefix(s, "0x"), "0X"))
	return err == nil
}

Try / catch

if _, err := hex.DecodeString(s); err != nil {
    s = strings.TrimPrefix(s, "0x") // retry after stripping common prefix
    // or reject the config before submission
}

Prevention

When it happens

Trigger: Setting an option like 'hex:myhexvalue' where the value contains non-hex characters, an odd number of digits, or a 0x prefix ('0xdeadbeef' is rejected).

Common situations: Copying option values from vendor docs that include 0x prefixes or spaces; deleting one hex digit by accident; pasting values with colons (AA:BB style).

Related errors


AI-assisted analysis of AdguardTeam/AdGuardHome@b41aefbe51 (2026-08-27). Data as JSON: /api/errors/89a2f79d5b371f9b. Report an issue: GitHub.