golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
122.72k stars 17.5k forks source link

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

Open database64128 opened 2 years ago

database64128 commented 2 years ago

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

bradfitz commented 2 years ago

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 commented 2 years ago

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 commented 1 year ago

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