-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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 netip.Addr.Map #54365
Comments
I agree it's cumbersome, but is it really inefficient? Got numbers? But cumbersome-but-possible is usually a reasonable compromise for use cases that are very rare. It's often better than adding API surface. |
One could also argue that the existing And its use cases are not rare at all. I first adopted Since you opened #45886, you might be interested to see that the relay routines in swgp-go for Linux also take advantage of the |
I also have a code base where I reduce code by handling everything as IPv6. I don't have a single use of |
type addrPortHeader struct {
ip [16]byte
z unsafe.Pointer
port uint16
}
// AddrPortv4Mappedv6 converts an IPv4 address to an IPv4-mapped IPv6 address.
// This function does nothing if addrPort is an IPv6 address.
func AddrPortv4Mappedv6(addrPort netip.AddrPort) netip.AddrPort {
if addrPort.Addr().Is4() {
addr6 := addrPort.Addr().As16()
ip := netip.AddrFrom16(addr6)
port := addrPort.Port()
return netip.AddrPortFrom(ip, port)
}
return addrPort
}
func AddrPortv4Mappedv6Unsafe(addrPort netip.AddrPort) netip.AddrPort {
if addrPort.Addr().Is4() {
app := (*addrPortHeader)(unsafe.Pointer(&addrPort))
app.z = unsafe.Add(app.z, 3*unsafe.Sizeof(uintptr(0)))
}
return addrPort
} 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 BenchmarkAddrPortv4Mappedv6(b *testing.B) {
b.Run("Is4", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AddrPortv4Mappedv6(addrPort4)
}
})
b.Run("Is4In6", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AddrPortv4Mappedv6(addrPort4in6)
}
})
}
func BenchmarkAddrPortv4Mappedv6Unsafe(b *testing.B) {
b.Run("Is4", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AddrPortv4Mappedv6Unsafe(addrPort4)
}
})
b.Run("Is4In6", func(b *testing.B) {
for i := 0; i < b.N; i++ {
AddrPortv4Mappedv6Unsafe(addrPort4in6)
}
})
}
func TestAddrPortv4Mappedv6Unsafe(t *testing.T) {
app := (*addrPortHeader)(unsafe.Pointer(&addrPort4))
t.Logf("addrPort4.z: %p", app.z)
app = (*addrPortHeader)(unsafe.Pointer(&addrPort4in6))
t.Logf("addrPort4in6.z: %p", app.z)
if ap := AddrPortv4Mappedv6Unsafe(addrPort4); ap != addrPort4in6 {
t.Errorf("AddrPortv4Mappedv6Unsafe(%s) returned %s, expected %s.", addrPort4, ap, addrPort4in6)
}
if ap := AddrPortv4Mappedv6Unsafe(addrPort4in6); ap != addrPort4in6 {
t.Errorf("AddrPortv4Mappedv6Unsafe(%s) returned %s, expected %s.", addrPort4in6, ap, addrPort4in6)
}
}
|
@database64128 @vincentbernat Hi all, this is a duplicate of my earlier proposal #51833 (but I don't mean we should close it---see below). Perhaps you will find interesting information there. My proposal was rejected because I could not present evidence of wide usage back then. I, too, have a codebase that could benefit from adding this function, though the benefit is small in my case. In any case, the Go team indicated they could revisit it when there's more information. 🙂 |
How can we gather statistics about the use of As for the name, I would prefer |
Thanks for your investigation. I agree with everything you said except the naming---assuming that
In my opinion, the |
Found a corner case that could support the inclusion of zero := netip.Addr{}
netip.AddrFrom16(zero.As16()) // becomes :: while the proposed function will keep zero values as zeros. So, the longer code is not a full replacement. |
BenchmarkAddrPortv4Mappedv6/Is4 shows that maybe there's an optimization available in that case. The optimization should not be to use unsafe. |
This proposal has been added to the active column of the proposals project |
Based on the discussion above, this proposal seems like a likely decline. |
Map()
to netip.Addr
@rsc Thanks for the update, but I'm a bit confused. It seems most comments in this PR are supportive. What does "the discussion above" refer to? PS: Personally I am not super insistent, but I cannot speak for others. |
It sounds like the main objection is performance, which we can address without new API. Do I have that right? I see the comment about turning the zero Addr into a non-zero Addr (specifically, the non-zero Addr representing "::") but I'm not sure that's too compelling. |
It is unclear how the performance problem can be handled without a new API. In #54365 (comment), we see that there is a performance hit by using the existing API (unless using unsafe), doesn't we? Maybe you would prefer a benchmark of new API vs existing API without unsafe? |
@vincentbernat We'd be happy to see CLs that optimize this case without using unsafe. |
Do you mean a compiler optimization? This is out of my league. But I can provide a benchmark of
Source
package main
import "testing"
// Copy/paste from netip
type uint128 struct {
hi uint64
lo uint64
}
type Addr struct {
addr uint128
z *internValue
}
var (
z0 = (*internValue)(nil)
z4 = new(internValue)
z6noz = new(internValue)
)
type internValue struct {
_ [0]func()
cmpVal any
resurrected bool
}
func AddrFrom16(addr [16]byte) Addr {
return Addr{
addr: uint128{
beUint64(addr[:8]),
beUint64(addr[8:]),
},
z: z6noz,
}
}
func (ip Addr) As16() (a16 [16]byte) {
bePutUint64(a16[:8], ip.addr.hi)
bePutUint64(a16[8:], ip.addr.lo)
return a16
}
func AddrFrom4(addr [4]byte) Addr {
return Addr{
addr: uint128{0, 0xffff00000000 | uint64(addr[0])<<24 | uint64(addr[1])<<16 | uint64(addr[2])<<8 | uint64(addr[3])},
z: z4,
}
}
func (ip Addr) Is4() bool {
return ip.z == z4
}
func bePutUint64(b []byte, v uint64) {
_ = b[7] // early bounds check to guarantee safety of writes below
b[0] = byte(v >> 56)
b[1] = byte(v >> 48)
b[2] = byte(v >> 40)
b[3] = byte(v >> 32)
b[4] = byte(v >> 24)
b[5] = byte(v >> 16)
b[6] = byte(v >> 8)
b[7] = byte(v)
}
func beUint64(b []byte) uint64 {
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
}
// Add Map()
func (ip Addr) Map() Addr {
if ip.Is4() {
ip.z = z6noz
}
return ip
}
// Benchmark
var (
addr4 = AddrFrom4([4]byte{127, 0, 0, 1})
addr4in6 = AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1})
)
func BenchmarkNoMap(b *testing.B) {
b.Run("Is4", func(b *testing.B) {
var z Addr
for i := 0; i < b.N; i++ {
z = AddrFrom16(addr4.As16())
}
if z != addr4in6 {
b.Fatal("NO")
}
})
b.Run("Is4In6", func(b *testing.B) {
var z Addr
for i := 0; i < b.N; i++ {
z = AddrFrom16(addr4in6.As16())
}
if z != addr4in6 {
b.Fatal("NO")
}
})
}
func BenchmarkMap(b *testing.B) {
b.Run("Is4", func(b *testing.B) {
var z Addr
for i := 0; i < b.N; i++ {
z = addr4.Map()
}
if z != addr4in6 {
b.Fatal("NO")
}
})
b.Run("Is4In6", func(b *testing.B) {
var z Addr
for i := 0; i < b.N; i++ {
z = addr4in6.Map()
}
if z != addr4in6 {
b.Fatal("NO")
}
})
} |
No change in consensus, so declined. |
netip.Addr
hasUnmap()
to convert an IPv4-mapped IPv6 address to an IPv4 address:To convert an IPv4 address to an IPv4-mapped IPv6 address, however, can only be achieved by
netip.AddrFrom16(addr.As16())
, which is rather cumbersome and inefficient. I propose we also add a correspondingMap()
method:/cc @bradfitz
The text was updated successfully, but these errors were encountered: