{"record":{"id":"3f8652f9cf341c41","repo":"AdguardTeam/AdGuardHome","slug":"dhcpv4-or-dhcpv6-configuration-must-be-complete","errorCode":null,"errorMessage":"dhcpv4 or dhcpv6 configuration must be complete","messagePattern":"dhcpv4 or dhcpv6 configuration must be complete","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/dhcpd/http_unix.go","lineNumber":310,"sourceCode":"\n\treturn srv6, enabled, err\n}\n\n// createServers returns DHCPv4 and DHCPv6 servers created from the provided\n// configuration conf.\nfunc (s *server) createServers(conf *dhcpServerConfigJSON) (srv4, srv6 DHCPServer, err error) {\n\tsrv4, v4Enabled, err := s.handleDHCPSetConfigV4(conf)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"bad dhcpv4 configuration: %w\", err)\n\t}\n\n\tsrv6, v6Enabled, err := s.handleDHCPSetConfigV6(conf)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"bad dhcpv6 configuration: %w\", err)\n\t}\n\n\tif conf.Enabled == aghalg.NBTrue && !v4Enabled && !v6Enabled {\n\t\treturn nil, nil, fmt.Errorf(\"dhcpv4 or dhcpv6 configuration must be complete\")\n\t}\n\n\treturn srv4, srv6, nil\n}\n\n// handleDHCPSetConfig is the handler for the POST /control/dhcp/set_config\n// HTTP API.\nfunc (s *server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\tl := s.conf.Logger\n\n\tconf := &dhcpServerConfigJSON{}\n\tconf.Enabled = aghalg.BoolToNullBool(s.conf.Enabled)\n\tconf.InterfaceName = s.conf.InterfaceName\n\n\terr := json.NewDecoder(r.Body).Decode(conf)\n\tif err != nil {\n\t\taghhttp.ErrorAndLog(","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/AdguardTeam/AdGuardHome/blob/b41aefbe51c8dde65e2c50f093996afa0502edf9/internal/dhcpd/http_unix.go#L292-L328","documentation":"Returned by createServers when the config has Enabled set to true (aghalg.NBTrue) but neither the DHCPv4 nor the DHCPv6 sub-configuration turned out to be complete/enabled. It prevents starting a DHCP 'server' that would actually serve nothing on either protocol.","triggerScenarios":"POSTing {\"enabled\": true} with both v4 and v6 blocks disabled, empty, or lacking the required fields (e.g. no range set), so v4Enabled and v6Enabled both come back false.","commonSituations":"Flipping the master Enabled toggle in the UI before filling in either protocol's settings; disabling v4 while forgetting to complete the v6 block; partial configs left over from migrations.","solutions":["Fully specify at least one protocol block (v4 or v6): interface, range start/end, gateway/subnet as required","Check the per-protocol enabled flags inside v4/v6 blocks are not false while expecting the server to serve them","Re-read the config with GET /control/dhcp/config to see which block is incomplete, then fix and re-POST"],"exampleFix":"// before\n{\"enabled\":true,\"v4\":{},\"v6\":{}}\n// -> 400 dhcpv4 or dhcpv6 configuration must be complete\n\n// after\n{\"enabled\":true,\"v4\":{\"enabled\":true,\"interface_name\":\"eth0\",\"range_start\":\"192.168.1.50\",\"range_end\":\"192.168.1.100\",\"subnet_mask\":\"255.255.255.0\",\"gateway_ip\":\"192.168.1.1\"},\"v6\":{}}","handlingStrategy":"validation","validationCode":"func configComplete(c Config) bool {\n\treturn c.V4.enabledAndFilled() || c.V6.enabledAndFilled()\n\t// i.e. at least one block has interface + valid range + required IPs\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always fill at least one protocol block completely before setting enabled=true","Use the UI wizard flow which enforces per-protocol completeness","GET the current config and diff before POSTing to spot empty blocks"],"tags":["dhcp","validation","configuration","adguard-home"],"backgroundTag":"incomplete-configuration","analyzedSha":"b41aefbe51c8dde65e2c50f093996afa0502edf9","analyzedAt":"2026-08-27T04:57:55.097Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}