Skip to content

Commit 6e2efc9

Browse files
committed
Add net/netip conversion functions to netutils
We will need to deal with both net.IP/net.IPNet and netip.Addr/netip.Prefix for a while, so add functions to convert between the types.
1 parent bee57e5 commit 6e2efc9

File tree

3 files changed

+436
-4
lines changed

3 files changed

+436
-4
lines changed

net/v2/convert.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package net
18+
19+
import (
20+
"net"
21+
"net/netip"
22+
)
23+
24+
// AddrFromIP converts a net.IP to a netip.Addr. Given valid input this will always
25+
// succeed; it will return the invalid netip.Addr on nil or garbage input.
26+
//
27+
// Use this rather than netip.AddrFromSlice(), which (despite the claims of its
28+
// documentation) does not always do what you would expect if you pass it a net.IP.
29+
func AddrFromIP(ip net.IP) netip.Addr {
30+
// Naively using netip.AddrFromSlice() gives unexpected results:
31+
//
32+
// ip := net.ParseIP("1.2.3.4")
33+
// addr, _ := netip.AddrFromSlice(ip)
34+
// addr.String() => "::ffff:1.2.3.4"
35+
// addr.Is4() => false
36+
// addr.Is6() => true
37+
//
38+
// This is because net.IP and netip.Addr have different ideas about how to handle
39+
// "IPv4-mapped IPv6" addresses, but netip.AddrFromSlice ignores that fact.
40+
//
41+
// In net.IP, parsing either "1.2.3.4" or "::ffff:1.2.3.4", will give you the
42+
// same result:
43+
//
44+
// ip1 := net.ParseIP("1.2.3.4")
45+
// []byte(ip1) => []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 1, 2, 3, 4}
46+
// ip1.String() => "1.2.3.4"
47+
// ip2 := net.ParseIP("::ffff:1.2.3.4")
48+
// []byte(ip2) => []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 1, 2, 3, 4}
49+
// ip2.String() => "1.2.3.4"
50+
//
51+
// net.IP normally stores IPv4 addresses as 16-byte IPv4-mapped IPv6 addresses,
52+
// but it hides that from the user, and it never stringifies an IPv4 IP to an
53+
// IPv4-mapped IPv6 form, even if that was the format you started with.
54+
//
55+
// net.IP *can* represent IPv4 addresses in a 4-byte format, but this is treated
56+
// as completly equivalent to the 16-byte representation:
57+
//
58+
// ip4 := ip1.To4()
59+
// []byte(ip4) => []byte{1, 2, 3, 4}
60+
// ip4.String() => "1.2.3.4"
61+
// ip1.Equal(ip4) => true
62+
//
63+
// netip.Addr, on the other hand, treats "plain" IPv4 and IPv4-mapped IPv6 as two
64+
// completely separate things:
65+
//
66+
// a1 := netip.MustParseAddr("1.2.3.4")
67+
// a2 := netip.MustParseAddr("::ffff:1.2.3.4")
68+
// a1.String() => "1.2.3.4"
69+
// a2.String() => "::ffff:1.2.3.4"
70+
// a1 == a2 => false
71+
//
72+
// which would be fine, except that netip.AddrFromSlice breaks net.IP's normal
73+
// semantics by converting the 4-byte and 16-byte net.IP forms to different
74+
// netip.Addr values, giving the confusing results above.
75+
//
76+
// In order to correctly convert an IPv4 address from net.IP to netip.Addr, you
77+
// need to either call .To4() on it before converting, or call .Unmap() on it
78+
// after converting. (The latter option is slightly simpler for us here because we
79+
// can just do it unconditionally, since it's a no-op in the IPv6 and invalid
80+
// cases).
81+
82+
addr, _ := netip.AddrFromSlice(ip)
83+
return addr.Unmap()
84+
}
85+
86+
// IPFromAddr converts a netip.Addr to a net.IP. Given valid input this will always
87+
// succeed; it will return nil if addr is the invalid netip.Addr.
88+
func IPFromAddr(addr netip.Addr) net.IP {
89+
// addr.AsSlice() returns:
90+
// - a []byte of length 4 if addr is a normal IPv4 address
91+
// - a []byte of length 16 if addr is an IPv6 address (including IPv4-mapped IPv6)
92+
// - nil if addr is the zero Addr (which is the only other possibility)
93+
//
94+
// Any of those values can be correctly cast directly to a net.IP.
95+
//
96+
// Note that we don't bother to do any "cleanup" here like in the AddrFromIP case,
97+
// so converting a plain IPv4 netip.Addr to net.IP gives a different result than
98+
// converting an IPv4-mapped IPv6 netip.Addr:
99+
//
100+
// ip1 := netutils.IPFromAddr(netip.MustParseAddr("1.2.3.4"))
101+
// []byte(ip1) => []byte{1, 2, 3, 4}
102+
//
103+
// ip2 := netutils.IPFromAddr(netip.MustParseAddr("::ffff:1.2.3.4"))
104+
// []byte(ip2) => []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 1, 2, 3, 4}
105+
//
106+
// However, the net.IP API treats the two values as the same anyway, so it doesn't
107+
// matter.
108+
//
109+
// ip1.String() => "1.2.3.4"
110+
// ip2.String() => "1.2.3.4"
111+
// ip2.Equal(ip1) => true
112+
113+
return net.IP(addr.AsSlice())
114+
}
115+
116+
// PrefixFromIPNet converts a *net.IPNet to a netip.Prefix. Given valid input this will
117+
// always succeed; it will return the invalid netip.Prefix on nil or garbage input.
118+
func PrefixFromIPNet(ipnet *net.IPNet) netip.Prefix {
119+
if ipnet == nil {
120+
return netip.Prefix{}
121+
}
122+
123+
addr := AddrFromIP(ipnet.IP)
124+
if !addr.IsValid() {
125+
return netip.Prefix{}
126+
}
127+
128+
prefixLen, bits := ipnet.Mask.Size()
129+
if prefixLen == 0 && bits == 0 {
130+
// non-CIDR Mask representation; not representible as a netip.Prefix
131+
return netip.Prefix{}
132+
}
133+
if bits == 128 && addr.Is4() && (bits-prefixLen <= 32) {
134+
// In the same way that net.IP allows an IPv4 IP to be either 4 or 16
135+
// bytes (32 or 128 bits), *net.IPNet allows an IPv4 CIDR to have either a
136+
// 32-bit or a 128-bit mask. If the mask is 128 bits, we discard the
137+
// leftmost 96 bits.
138+
prefixLen -= 128 - 32
139+
} else if bits != addr.BitLen() {
140+
// invalid IPv4/IPv6 mix
141+
return netip.Prefix{}
142+
}
143+
144+
return netip.PrefixFrom(addr, prefixLen)
145+
}
146+
147+
// IPNetFromPrefix converts a netip.Prefix to a *net.IPNet. Given valid input this will
148+
// always succeed; it will return nil if prefix is the invalid netip.Prefix or is
149+
// otherwise invalid.
150+
func IPNetFromPrefix(prefix netip.Prefix) *net.IPNet {
151+
addr := prefix.Addr()
152+
bits := prefix.Bits()
153+
if bits == -1 || !addr.IsValid() {
154+
return nil
155+
}
156+
addrLen := addr.BitLen()
157+
158+
// (As with IPFromAddr, a plain IPv4 netip.Prefix and an equivalent IPv4-mapped
159+
// IPv6 netip.Prefix will get converted to distinct *net.IPNet values, but
160+
// *net.IPNet will treat them equivalently.)
161+
162+
return &net.IPNet{
163+
IP: IPFromAddr(addr),
164+
Mask: net.CIDRMask(bits, addrLen),
165+
}
166+
}

0 commit comments

Comments
 (0)