hashicorp/terraform · error
IPv6 addresses cannot have a netmask: %s
Error message
IPv6 addresses cannot have a netmask: %s
What it means
Returned by the cidrnetmask built-in (CidrNetmaskFunc, internal/lang/funcs/cidr.go:70) when the prefix parsed successfully (so it is not error 955) but network.IP.To4() == nil, i.e. the parsed CIDR is IPv6. cidrnetmask is defined only for IPv4 (dotted-decimal netmask has no meaning for IPv6, which uses prefix-length notation), so an IPv6 input is explicitly rejected even though it is a valid CIDR.
Source
Thrown at internal/lang/funcs/cidr.go:70
// CidrNetmaskFunc contructs a function that converts an IPv4 address prefix given
// in CIDR notation into a subnet mask address.
var CidrNetmaskFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "prefix",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
_, network, err := ipaddr.ParseCIDR(args[0].AsString())
if err != nil {
return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
}
if network.IP.To4() == nil {
return cty.UnknownVal(cty.String), fmt.Errorf("IPv6 addresses cannot have a netmask: %s", args[0].AsString())
}
return cty.StringVal(ipaddr.IP(network.Mask).String()), nil
},
})
// CidrSubnetFunc contructs a function that calculates a subnet address within
// a given IP network address prefix.
var CidrSubnetFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "prefix",
Type: cty.String,
},
{
Name: "newbits",
Type: cty.Number,
},View on GitHub (pinned to c9def3e214)
Solutions
- Use cidrnetmask only with IPv4 prefixes; for IPv6 use the prefix length directly (/N).
- Branch on address family: compute netmask for v4 and use prefixlen for v6.
- Validate the prefix is IPv4 (contains only dotted-decimal octets) before calling cidrnetmask.
- Use the can() / try() functions to gracefully fall back for dual-stack variables.
Example fix
# before (HCL)
mask = cidrnetmask(var.cidr) # var.cidr = "2001:db8::/32" -> IPv6 addresses cannot have a netmask
# after
locals {
is_v6 = can(regex(":", var.cidr))
mask = local.is_v6 ? null : cidrnetmask(var.cidr)
}
# or
mask = can(cidrnetmask(var.cidr)) ? cidrnetmask(var.cidr) : null Defensive patterns
Strategy: type-guard
Validate before calling
# (HCL) branch on address family
locals {
is_v6 = can(regex(":", var.cidr))
mask = local.is_v6 ? null : cidrnetmask(var.cidr)
} Type guard
// (Go) true if the CIDR is IPv4 (safe for cidrnetmask)
func isIPv4CIDR(s string) bool {
_, n, err := net.ParseCIDR(s)
return err == nil && n.IP.To4() != nil
} Try / catch
# (HCL) value = can(cidrnetmask(var.cidr)) ? cidrnetmask(var.cidr) : null
Prevention
- Use cidrnetmask only for IPv4 prefixes.
- For IPv6 use the prefix length directly.
- Detect family (presence of ':') before calling.
- Use can()/try() for dual-stack CIDR variables.
When it happens
Trigger: Calling cidrnetmask("2001:db8::/32") or any IPv6 CIDR; the prefix parses fine but To4() returns nil, triggering the IPv6-specific message. The original string is echoed back so the user sees exactly what they passed.
Common situations: Dual-stack configs where the CIDR variable may resolve to IPv6; copy-pasting an IPv6 prefix into an IPv4-only cidrnetmask call; attempting to compute a netmask for an IPv6 subnet (not supported).
Related errors
- invalid CIDR expression: %s
- lookup() takes two or three arguments, got %d
- lookup failed to find key %s
- empty state name
- Attempted to initialize pluggable state with an empty string
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/c29ae70e1ba84f48.
Report an issue: GitHub.