Go version

go version go1.24.4 openbsd/amd64

Output of go env in your module/workspace:

AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/jrick/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/jrick/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2338133049=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='openbsd'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/home/jrick/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='openbsd'
GOPATH='/home/jrick/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/jrick/src/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/jrick/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/home/jrick/src/go/pkg/tool/openbsd_amd64'
GOVCS=''
GOVERSION='go1.24.4'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

ParseAddr with the address string "fe80::1234:1%iwx0/104" returns an Addr that strings back to the same input, while I would expect this to fail (as ParseAddr does for inputs with prefixes without zones).

https://go.dev/play/p/sBg1XwXT-q3

This unexpected behavior occurred while attempting to write a single function that handled parsing both plain IPs and CIDR masked IPs, with implicit full masks when the bits were not specified, due to my function first calling netip.ParseAddr and returning without error if that succeeds, before falling back to netip.ParsePrefix (which does error on the very same input: https://go.dev/play/p/pcjj0dEEmhV).

What did you see happen?

No error.

What did you expect to see?

ParseAddr errors if input contains both zone and prefix, as it is documented to error if it includes a prefix (this does work as documented without a zone).

Comment From: jrick

And I realize the /104 is silly, originally before rewriting this test case that was a 10/8 as a 4-in-6, that i slapped a zone onto.

Comment From: gabyhelp

Related Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: jrick

Oh it gets worse: https://go.dev/play/p/FMKe53qtWat

Unlike ParsePrefix which catches the out-of-range prefix, ParseAddr here does not.

Comment From: jrick

The only explanation I can think of is that perhaps slashes in zones are legal, as that is how it is parsed (by evidence of what Zone() on the above returns: https://go.dev/play/p/vc_SbupuOiE).

~Yet, it seems to be parsed as a real prefix at some point, as anything following the slash must be a legal numeric character.~ nope anything goes, I probably edited my ParsePrefix playground link trying this out.

Comment From: jrick

This is unfortunate but probably correct, per rfc6874:

According to IPv6 Scoped Address syntax [RFC4007], a zone identifier is attached to the textual representation of an IPv6 address by concatenating "%" followed by , where is a string identifying the zone of the address. However, the IPv6 Scoped Address Architecture specification gives no precise definition of the character set allowed in . There are no rules or de facto standards for this. For example, the first Ethernet interface in a host might be called %0, %1, %en1, %eth0, or whatever the implementer happened to choose.