Skip to content

Commit 5dd8e44

Browse files
committed
Streamline netutilsv2 IP family functions, add a few more
- Squash the String functions (eg, IsIPv4String, IsDualStackCIDRStrings) into their corresponding non-string versions (eg, IsIPv4, IsDualStackCIDRs). The new versions use generics to support both types. - Add IsDualStackPair and IsDualStackCIDRPair - Add OtherIPFamily
1 parent 466995d commit 5dd8e44

File tree

2 files changed

+148
-149
lines changed

2 files changed

+148
-149
lines changed

net/v2/ipfamily.go

Lines changed: 104 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,54 @@ const (
3535
IPFamilyUnknown IPFamily = ""
3636
)
3737

38-
// IsDualStackIPs returns true if:
39-
// - all elements of ips are valid
40-
// - at least one IP from each family (v4 and v6) is present
41-
func IsDualStackIPs(ips []net.IP) bool {
38+
type ipOrString interface {
39+
net.IP | string
40+
}
41+
42+
type cidrOrString interface {
43+
*net.IPNet | string
44+
}
45+
46+
// IPFamilyOf returns the IP family of val (or IPFamilyUnknown if val is nil or invalid).
47+
// IPv6-encoded IPv4 addresses (e.g., "::ffff:1.2.3.4") are considered IPv4. val can be a
48+
// net.IP or a string containing a single IP address.
49+
//
50+
// Note that "k8s.io/utils/net/v2".IPFamily intentionally has identical values to
51+
// "k8s.io/api/core/v1".IPFamily and "k8s.io/discovery/v1".AddressType, so you can cast
52+
// the return value of this function to those types.
53+
func IPFamilyOf[T ipOrString](val T) IPFamily {
54+
switch typedVal := interface{}(val).(type) {
55+
case net.IP:
56+
switch {
57+
case typedVal.To4() != nil:
58+
return IPv4
59+
case typedVal.To16() != nil:
60+
return IPv6
61+
}
62+
case string:
63+
return IPFamilyOf(ParseIPSloppy(typedVal))
64+
}
65+
66+
return IPFamilyUnknown
67+
}
68+
69+
// IsIPv4 returns true if IPFamilyOf(val) is IPv4 (and false if it is IPv6 or invalid).
70+
func IsIPv4[T ipOrString](val T) bool {
71+
return IPFamilyOf(val) == IPv4
72+
}
73+
74+
// IsIPv6 returns true if IPFamilyOf(val) is IPv6 (and false if it is IPv4 or invalid).
75+
func IsIPv6[T ipOrString](val T) bool {
76+
return IPFamilyOf(val) == IPv6
77+
}
78+
79+
// IsDualStack returns true if vals contains at least one IPv4 address and at least one
80+
// IPv6 address (and no invalid values).
81+
func IsDualStack[T ipOrString](vals []T) bool {
4282
v4Found := false
4383
v6Found := false
44-
for _, ip := range ips {
45-
switch IPFamilyOf(ip) {
84+
for _, val := range vals {
85+
switch IPFamilyOf(val) {
4686
case IPv4:
4787
v4Found = true
4888
case IPv6:
@@ -55,29 +95,56 @@ func IsDualStackIPs(ips []net.IP) bool {
5595
return (v4Found && v6Found)
5696
}
5797

58-
// IsDualStackIPStrings returns true if:
59-
// - all elements of ips can be parsed as IPs
60-
// - at least one IP from each family (v4 and v6) is present
61-
func IsDualStackIPStrings(ips []string) bool {
62-
parsedIPs := make([]net.IP, 0, len(ips))
63-
for _, ip := range ips {
64-
parsedIP := ParseIPSloppy(ip)
65-
if parsedIP == nil {
66-
return false
98+
// IsDualStackPair returns true if vals contains exactly 1 IPv4 address and 1 IPv6 address
99+
// (in either order).
100+
func IsDualStackPair[T ipOrString](vals []T) bool {
101+
return len(vals) == 2 && IsDualStack(vals)
102+
}
103+
104+
// IPFamilyOfCIDR returns the IP family of val (or IPFamilyUnknown if val is nil or
105+
// invalid). IPv6-encoded IPv4 addresses (e.g., "::ffff:1.2.3.0/120") are considered IPv4.
106+
// val can be a *net.IPNet or a string containing a single CIDR value.
107+
//
108+
// Note that "k8s.io/utils/net/v2".IPFamily intentionally has identical values to
109+
// "k8s.io/api/core/v1".IPFamily and "k8s.io/discovery/v1".AddressType, so you can cast
110+
// the return value of this function to those types.
111+
func IPFamilyOfCIDR[T cidrOrString](val T) IPFamily {
112+
switch typedVal := interface{}(val).(type) {
113+
case *net.IPNet:
114+
if typedVal != nil {
115+
family := IPFamilyOf(typedVal.IP)
116+
// An IPv6 CIDR must have a 128-bit mask. An IPv4 CIDR must have a
117+
// 32- or 128-bit mask. (Any other mask length is invalid.)
118+
_, masklen := typedVal.Mask.Size()
119+
if masklen == 128 || (family == IPv4 && masklen == 32) {
120+
return family
121+
}
67122
}
68-
parsedIPs = append(parsedIPs, parsedIP)
123+
case string:
124+
parsedIP, _, _ := ParseCIDRSloppy(typedVal)
125+
return IPFamilyOf(parsedIP)
69126
}
70-
return IsDualStackIPs(parsedIPs)
127+
128+
return IPFamilyUnknown
129+
}
130+
131+
// IsIPv4CIDR returns true if IPFamilyOfCIDR(val) is IPv4 (and false if it is IPv6 or invalid).
132+
func IsIPv4CIDR[T cidrOrString](val T) bool {
133+
return IPFamilyOfCIDR(val) == IPv4
71134
}
72135

73-
// IsDualStackCIDRs returns true if:
74-
// - all elements of cidrs are non-nil
75-
// - at least one CIDR from each family (v4 and v6) is present
76-
func IsDualStackCIDRs(cidrs []*net.IPNet) bool {
136+
// IsIPv6CIDR returns true if IPFamilyOfCIDR(val) is IPv6 (and false if it is IPv4 or invalid).
137+
func IsIPv6CIDR[T cidrOrString](val T) bool {
138+
return IPFamilyOfCIDR(val) == IPv6
139+
}
140+
141+
// IsDualStackCIDRs returns true if vals contains at least one IPv4 CIDR value and at
142+
// least one IPv6 CIDR value (and no invalid values).
143+
func IsDualStackCIDRs[T cidrOrString](vals []T) bool {
77144
v4Found := false
78145
v6Found := false
79-
for _, cidr := range cidrs {
80-
switch IPFamilyOfCIDR(cidr) {
146+
for _, val := range vals {
147+
switch IPFamilyOfCIDR(val) {
81148
case IPv4:
82149
v4Found = true
83150
case IPv6:
@@ -90,101 +157,24 @@ func IsDualStackCIDRs(cidrs []*net.IPNet) bool {
90157
return (v4Found && v6Found)
91158
}
92159

93-
// IsDualStackCIDRStrings returns if
94-
// - all elements of cidrs can be parsed as CIDRs
95-
// - at least one CIDR from each family (v4 and v6) is present
96-
func IsDualStackCIDRStrings(cidrs []string) bool {
97-
parsedCIDRs, err := ParseCIDRs(cidrs)
98-
if err != nil {
99-
return false
100-
}
101-
return IsDualStackCIDRs(parsedCIDRs)
160+
// IsDualStackCIDRPair returns true if vals contains exactly 1 IPv4 CIDR value and 1 IPv6
161+
// CIDR value (in either order).
162+
func IsDualStackCIDRPair[T cidrOrString](vals []T) bool {
163+
return len(vals) == 2 && IsDualStackCIDRs(vals)
102164
}
103165

104-
// IPFamilyOf returns the IP family of ip, or IPFamilyUnknown if it is invalid.
105-
func IPFamilyOf(ip net.IP) IPFamily {
106-
switch {
107-
case ip.To4() != nil:
108-
return IPv4
109-
case ip.To16() != nil:
166+
// OtherIPFamily returns the other IP family from ipFamily.
167+
//
168+
// Note that "k8s.io/utils/net/v2".IPFamily intentionally has identical values to
169+
// "k8s.io/api/core/v1".IPFamily and "k8s.io/discovery/v1".AddressType, so you can cast
170+
// the input/output values of this function between these types.
171+
func OtherIPFamily(ipFamily IPFamily) IPFamily {
172+
switch ipFamily {
173+
case IPv4:
110174
return IPv6
175+
case IPv6:
176+
return IPv4
111177
default:
112178
return IPFamilyUnknown
113179
}
114180
}
115-
116-
// IPFamilyOfString returns the IP family of ip, or IPFamilyUnknown if ip cannot
117-
// be parsed as an IP.
118-
func IPFamilyOfString(ip string) IPFamily {
119-
return IPFamilyOf(ParseIPSloppy(ip))
120-
}
121-
122-
// IPFamilyOfCIDR returns the IP family of cidr.
123-
func IPFamilyOfCIDR(cidr *net.IPNet) IPFamily {
124-
if cidr != nil {
125-
family := IPFamilyOf(cidr.IP)
126-
// An IPv6 CIDR must have a 128-bit mask. An IPv4 CIDR must have a
127-
// 32- or 128-bit mask. (Any other mask length is invalid.)
128-
_, masklen := cidr.Mask.Size()
129-
if masklen == 128 || (family == IPv4 && masklen == 32) {
130-
return family
131-
}
132-
}
133-
return IPFamilyUnknown
134-
}
135-
136-
// IPFamilyOfCIDRString returns the IP family of cidr.
137-
func IPFamilyOfCIDRString(cidr string) IPFamily {
138-
ip, _, _ := ParseCIDRSloppy(cidr)
139-
return IPFamilyOf(ip)
140-
}
141-
142-
// IsIPv6 returns true if netIP is IPv6 (and false if it is IPv4, nil, or invalid).
143-
func IsIPv6(netIP net.IP) bool {
144-
return IPFamilyOf(netIP) == IPv6
145-
}
146-
147-
// IsIPv6String returns true if ip contains a single IPv6 address and nothing else. It
148-
// returns false if ip is an empty string, an IPv4 address, or anything else that is not a
149-
// single IPv6 address.
150-
func IsIPv6String(ip string) bool {
151-
return IPFamilyOfString(ip) == IPv6
152-
}
153-
154-
// IsIPv6CIDR returns true if a cidr is a valid IPv6 CIDR. It returns false if cidr is
155-
// nil or an IPv4 CIDR. Its behavior is not defined if cidr is invalid.
156-
func IsIPv6CIDR(cidr *net.IPNet) bool {
157-
return IPFamilyOfCIDR(cidr) == IPv6
158-
}
159-
160-
// IsIPv6CIDRString returns true if cidr contains a single IPv6 CIDR and nothing else. It
161-
// returns false if cidr is an empty string, an IPv4 CIDR, or anything else that is not a
162-
// single valid IPv6 CIDR.
163-
func IsIPv6CIDRString(cidr string) bool {
164-
return IPFamilyOfCIDRString(cidr) == IPv6
165-
}
166-
167-
// IsIPv4 returns true if netIP is IPv4 (and false if it is IPv6, nil, or invalid).
168-
func IsIPv4(netIP net.IP) bool {
169-
return IPFamilyOf(netIP) == IPv4
170-
}
171-
172-
// IsIPv4String returns true if ip contains a single IPv4 address and nothing else. It
173-
// returns false if ip is an empty string, an IPv6 address, or anything else that is not a
174-
// single IPv4 address.
175-
func IsIPv4String(ip string) bool {
176-
return IPFamilyOfString(ip) == IPv4
177-
}
178-
179-
// IsIPv4CIDR returns true if cidr is a valid IPv4 CIDR. It returns false if cidr is nil
180-
// or an IPv6 CIDR. Its behavior is not defined if cidr is invalid.
181-
func IsIPv4CIDR(cidr *net.IPNet) bool {
182-
return IPFamilyOfCIDR(cidr) == IPv4
183-
}
184-
185-
// IsIPv4CIDRString returns true if cidr contains a single IPv4 CIDR and nothing else. It
186-
// returns false if cidr is an empty string, an IPv6 CIDR, or anything else that is not a
187-
// single valid IPv4 CIDR.
188-
func IsIPv4CIDRString(cidr string) bool {
189-
return IPFamilyOfCIDRString(cidr) == IPv4
190-
}

0 commit comments

Comments
 (0)