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

  1. Use cidrnetmask only with IPv4 prefixes; for IPv6 use the prefix length directly (/N).
  2. Branch on address family: compute netmask for v4 and use prefixlen for v6.
  3. Validate the prefix is IPv4 (contains only dotted-decimal octets) before calling cidrnetmask.
  4. 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

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


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/c29ae70e1ba84f48. Report an issue: GitHub.