{"record":{"id":"d5e8a4c13c16310f","repo":"grafana/k6","slug":"mixed-ip-range-format-s","errorCode":null,"errorMessage":"mixed IP range format: {s}","messagePattern":"mixed IP range format: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/types/ipblock.go","lineNumber":50,"sourceCode":"\t\treturn ipBlockFromRange(s)\n\tcase strings.Contains(s, \"/\"):\n\t\treturn ipBlockFromCIDR(s)\n\tdefault:\n\t\tif net.ParseIP(s) == nil {\n\t\t\treturn nil, fmt.Errorf(\"%s is not a valid IP, IP range or CIDR\", s)\n\t\t}\n\t\treturn ipBlockFromRange(s + \"-\" + s)\n\t}\n}\n\nfunc ipBlockFromRange(s string) (*ipBlock, error) {\n\tip0Str, ip1Str, _ := strings.Cut(s, \"-\")\n\tip0, ip1 := net.ParseIP(ip0Str), net.ParseIP(ip1Str)\n\tif ip0 == nil || ip1 == nil {\n\t\treturn nil, errors.New(\"wrong IP range format: \" + s)\n\t}\n\tif (ip0.To4() == nil) != (ip1.To4() == nil) { // XOR\n\t\treturn nil, errors.New(\"mixed IP range format: \" + s)\n\t}\n\tblock := ipBlockFromTwoIPs(ip0, ip1)\n\n\tif block.count.Sign() <= 0 {\n\t\treturn nil, errors.New(\"negative IP range: \" + s)\n\t}\n\treturn block, nil\n}\n\nfunc ipBlockFromTwoIPs(ip0, ip1 net.IP) *ipBlock {\n\t// This code doesn't do any checks on the validity of the arguments, that should be\n\t// done before and/or after it is called\n\tvar block ipBlock\n\tblock.firstIP = new(big.Int)\n\tblock.count = new(big.Int)\n\tblock.ipv6 = ip0.To4() == nil\n\tif block.ipv6 {\n\t\tblock.firstIP.SetBytes(ip0.To16())","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/lib/types/ipblock.go#L32-L68","documentation":"Returned by ipBlockFromRange (lib/types/ipblock.go:49-51) when both endpoints of an IP range parse as IPs but one is IPv4 and the other IPv6 ((ip0.To4() == nil) != (ip1.To4() == nil) XOR check). A single ipBlock tracks one address family (block.ipv6 is set from ip0 alone in ipBlockFromTwoIPs), so mixed-family ranges cannot be represented and are rejected.","triggerScenarios":"Calling getIPBlock with something like '::1-192.168.0.10' or '10.0.0.1-ffff::2' — both sides syntactically valid IPs, but of different families.","commonSituations":"Config generation joining a v4 start and v6 end from different variables; dual-stack configs where a template's start/end fields were filled from different address families.","solutions":["Keep both endpoints in the same family, e.g. '::1-::ff' or '10.0.0.1-10.0.0.10'","If both families are needed, define two separate blocks, one per family"],"exampleFix":"// before\nblock, err := getIPBlock(\"10.0.0.1-::ffff\")\n\n// after\nblock, err := getIPBlock(\"10.0.0.1-10.0.0.10\")","handlingStrategy":"validation","validationCode":"// Go: ensure both endpoints are the same address family\nfunc sameFamily(s string) bool {\n    a, b, _ := strings.Cut(s, \"-\")\n    ipa, ipb := net.ParseIP(a), net.ParseIP(b)\n    return ipa != nil && ipb != nil && ((ipa.To4() == nil) == (ipb.To4() == nil))\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not mix IPv4 and IPv6 endpoints in one range; declare one block per family","When templating dual-stack configs, tag start/end fields with the family they came from"],"tags":["config","ipblock","ipv6","validation"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}