Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: net/netip: add UnmappedEquals(netip.AddrPort) to netip.AddrPort #54366

Open
database64128 opened this issue Aug 10, 2022 · 3 comments
Labels
Milestone

Comments

@database64128
Copy link
Contributor

In configuration and other places, IPv4 addresses are usually specified unmapped, like 1.1.1.1:53. When dealing with ReadFromUDPAddrPort and ReadMsgUDPAddrPort, the returned netip.AddrPort is likely to be an IPv4-mapped IPv6 address, like [::ffff:1.1.1.1]:53, depending on how the UDP socket was opened.

To check whether these 2 addresses point to the same endpoint, we would have to do something like:

// AddrPortMappedEqual returns whether the two addresses point to the same endpoint.
// An IPv4 address and an IPv4-mapped IPv6 address pointing to the same endpoint are considered equal.
// For example, 1.1.1.1:53 and [::ffff:1.1.1.1]:53 are considered equal.
func AddrPortMappedEqual(l, r netip.AddrPort) bool {
	if l == r {
		return true
	}
	return l.Port() == r.Port() && l.Addr().Unmap() == r.Addr().Unmap()
}

It'd be nice if we have a method on netip.AddrPort to directly compare the unexported addr field in netip.Addr. I propose we add something like:

func (p AddrPort) UnmappedEquals(p1 netip.AddrPort) bool {
	return p.port == p1.port && p.ip.addr == p1.ip.addr
}

The method can also be called MappedEquals if #54365 is accepted.

/cc @bradfitz

@gopherbot gopherbot added this to the Proposal milestone Aug 10, 2022
@bradfitz
Copy link
Contributor

I don't think we want to add API for this. We're trying to fix the real problem (ReadMsgUDPAddrPort etc returning bad netip.Addr/AddrPort values) instead in #54234

@database64128
Copy link
Contributor Author

I wouldn't call this "the real problem" or "bad values". It is natural for ReadMsgUDPAddrPort calls on IPv6 UDP sockets to return IPv4-mapped IPv6 addresses, because that's how the underlying socket works. A method like UnmappedEquals allows us to conveniently compare the returned address without changing the address family.

@database64128
Copy link
Contributor Author

The following benchmark uses AddrPortMappedEqualUnsafe to simulate the proposed UnmappedEquals.

// AddrPortMappedEqual returns whether the two addresses point to the same endpoint.
// An IPv4 address and an IPv4-mapped IPv6 address pointing to the same endpoint are considered equal.
// For example, 1.1.1.1:53 and [::ffff:1.1.1.1]:53 are considered equal.
func AddrPortMappedEqual(l, r netip.AddrPort) bool {
	if l == r {
		return true
	}
	return l.Port() == r.Port() && l.Addr().Unmap() == r.Addr().Unmap()
}

type addrPortHeader struct {
	ip   [16]byte
	z    unsafe.Pointer
	port uint16
}

func AddrPortMappedEqualUnsafe(l, r netip.AddrPort) bool {
	lp := (*addrPortHeader)(unsafe.Pointer(&l))
	rp := (*addrPortHeader)(unsafe.Pointer(&r))
	return lp.ip == rp.ip && lp.port == rp.port
}
var (
	addrPort4    = netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 1080)
	addrPort4in6 = netip.AddrPortFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1}), 1080)
)

func BenchmarkAddrPortMappedEqual(b *testing.B) {
	b.Run("Equal", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			AddrPortMappedEqual(addrPort4, addrPort4)
		}
	})

	b.Run("NotEqual", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			AddrPortMappedEqual(addrPort4, addrPort4in6)
		}
	})
}

func BenchmarkAddrPortMappedEqualUnsafe(b *testing.B) {
	b.Run("Equal", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			AddrPortMappedEqualUnsafe(addrPort4, addrPort4)
		}
	})

	b.Run("NotEqual", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			AddrPortMappedEqualUnsafe(addrPort4, addrPort4in6)
		}
	})
}
BenchmarkAddrPortMappedEqual/Equal-4   	490546808	         2.446 ns/op	       0 B/op	       0 allocs/op
BenchmarkAddrPortMappedEqual/NotEqual-4         	250462476	         4.730 ns/op	       0 B/op	       0 allocs/op
BenchmarkAddrPortMappedEqualUnsafe/Equal-4      	1000000000	         0.4085 ns/op	       0 B/op	       0 allocs/op
BenchmarkAddrPortMappedEqualUnsafe/NotEqual-4   	1000000000	         0.3842 ns/op	       0 B/op	       0 allocs/op

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Incoming
Development

No branches or pull requests

3 participants