cloudflare/cloudflared · error

expect IPv6, but %s is IPv4

Error message

expect IPv6, but %s is IPv4

What it means

determineICMPv6Src resolves the local IPv6 source address (and zone) used for ICMPv6 echo traffic. When a user-supplied source address parses as IPv4 instead of IPv6, `addr.Is6()` fails and this error is returned. The address and its IPv6 zone are both required to bind ICMPv6 sockets correctly.

Source

Thrown at cmd/cloudflared/tunnel/configuration.go:425

	}
	return addr, nil
}

type interfaceIP struct {
	name string
	ip   net.IP
}

func determineICMPv6Src(userDefinedSrc string, logger *zerolog.Logger, ipv4Src netip.Addr) (addr netip.Addr, zone string, err error) {
	if userDefinedSrc != "" {
		addr, err := netip.ParseAddr(userDefinedSrc)
		if err != nil {
			return netip.Addr{}, "", err
		}
		if addr.Is6() {
			return addr, addr.Zone(), nil
		}
		return netip.Addr{}, "", fmt.Errorf("expect IPv6, but %s is IPv4", userDefinedSrc)
	}

	// Loop through all the interfaces, the preference is
	// 1. The interface where ipv4Src is in
	// 2. Interface with IPv6 address
	// 3. Unspecified interface

	interfaces, err := net.Interfaces()
	if err != nil {
		return netip.IPv6Unspecified(), "", nil
	}

	interfacesWithIPv6 := make([]interfaceIP, 0)
	for _, interf := range interfaces {
		interfaceAddrs, err := interf.Addrs()
		if err != nil {
			continue
		}

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Set the ICMPv6 source to a valid IPv6 address, including a zone if link-local (e.g. fe80::1%eth0)
  2. Remove the explicit source so cloudflared discovers an IPv6 address automatically
  3. If you intended IPv4, use the ICMPv4 source field instead

Example fix

// before
$ cloudflared tunnel run --icmpv6-src 192.168.0.1

// after
$ cloudflared tunnel run --icmpv6-src fe80::1%eth0
Defensive patterns

Strategy: validation

Validate before calling

addr, err := netip.ParseAddr(src)
if err != nil || !addr.Is6() {
    return fmt.Errorf("--icmpv6-src must be an IPv6 address, got %q", src)
}
if addr.IsLinkLocalUnicast() && addr.Zone() == "" {
    return fmt.Errorf("link-local IPv6 source %q requires a zone, e.g. %q", src, src+"%eth0")
}

Type guard

func isIPv6Literal(s string) bool { a, err := netip.ParseAddr(s); return err == nil && a.Is6() }

Try / catch

if _, _, err := determineICMPv6Src(c); err != nil {
    logger.Error().Err(err).Msg("invalid ICMPv6 source")
    os.Exit(1)
}

Prevention

When it happens

Trigger: Setting the ICMPv6 source (e.g. `--icmpv6-src`) to an IPv4 literal like `192.168.0.1`, so `addr.Is6()` returns false after parsing the user-defined address.

Common situations: Copy-paste of the ICMPv4 source into the v6 flag; stale config files written before moving to dual-stack; automation templates parameterized with an IPv4 default.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/11a4a1d77c7051b3. Report an issue: GitHub.