1// Copyright 2019 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Package net defines net-related types.
16package net
17
18import (
19 "errors"
20 "fmt"
21 "math/big"
22 "net/netip"
23
24 "cuelang.org/go/cue"
25)
26
27// IP address lengths (bytes).
28const (
29 IPv4len = 4
30 IPv6len = 16
31)
32
33func netGetIP(ip cue.Value) (goip netip.Addr) {
34 switch ip.Kind() {
35 case cue.StringKind:
36 s, err := ip.String()
37 if err != nil {
38 return netip.Addr{}
39 }
40 goip, err := netip.ParseAddr(s)
41 if err != nil {
42 return netip.Addr{}
43 }
44 return goip
45
46 case cue.BytesKind:
47 b, err := ip.Bytes()
48 if err != nil {
49 return netip.Addr{}
50 }
51 goip, err := netip.ParseAddr(string(b))
52 if err != nil {
53 return netip.Addr{}
54 }
55 return goip
56
57 case cue.ListKind:
58 iter, err := ip.List()
59 if err != nil {
60 return netip.Addr{}
61 }
62 var bytes []byte
63 for iter.Next() {
64 v, err := iter.Value().Int64()
65 if err != nil {
66 return netip.Addr{}
67 }
68 if v < 0 || 255 < v {
69 return netip.Addr{}
70 }
71 bytes = append(bytes, byte(v))
72 }
73 goip, ok := netip.AddrFromSlice(bytes)
74 if !ok {
75 return netip.Addr{}
76 }
77 return goip
78
79 default:
80 // TODO: return canonical invalid type.
81 return netip.Addr{}
82 }
83}
84
85func netGetIPCIDR(ip cue.Value) (gonet *netip.Prefix, err error) {
86 switch ip.Kind() {
87 case cue.StringKind:
88 s, err := ip.String()
89 if err != nil {
90 return nil, err
91 }
92 cidr, err := netip.ParsePrefix(s)
93 if err != nil {
94 return nil, err
95 }
96 return &cidr, nil
97
98 case cue.BytesKind:
99 b, err := ip.Bytes()
100 if err != nil {
101 return nil, err
102 }
103 cidr, err := netip.ParsePrefix(string(b))
104 if err != nil {
105 return nil, err
106 }
107 return &cidr, nil
108
109 default:
110 // TODO: return canonical invalid type.
111 return nil, nil
112 }
113}
114
115// ParseIP parses s as an IP address, returning the result.
116// The string s can be in dotted decimal ("192.0.2.1")
117// or IPv6 ("2001:db8::68") form.
118// If s is not a valid textual representation of an IP address,
119// ParseIP returns nil.
120func ParseIP(s string) ([]uint, error) {
121 goip, err := netip.ParseAddr(s)
122 if err != nil {
123 return nil, fmt.Errorf("invalid IP address %q", s)
124 }
125 return netToList(goip.AsSlice()), nil
126}
127
128func netToList(ip []byte) []uint {
129 a := make([]uint, len(ip))
130 for i, p := range ip {
131 a[i] = uint(p)
132 }
133 return a
134}
135
136// IPv4 reports whether ip is a valid IPv4 address.
137//
138// The address may be a string or list of bytes.
139func IPv4(ip cue.Value) bool {
140 // TODO: convert to native CUE.
141 return netGetIP(ip).Is4()
142}
143
144// IPv6 reports whether ip is a valid IPv6 address.
145//
146// The address may be a string or list of bytes.
147func IPv6(ip cue.Value) bool {
148 return netGetIP(ip).Is6()
149}
150
151// IP reports whether ip is a valid IPv4 or IPv6 address.
152//
153// The address may be a string or list of bytes.
154func IP(ip cue.Value) bool {
155 // TODO: convert to native CUE.
156 return netGetIP(ip).IsValid()
157}
158
159// IPCIDR reports whether ip is a valid IPv4 or IPv6 address with CIDR subnet notation.
160//
161// The address may be a string or list of bytes.
162func IPCIDR(ip cue.Value) (bool, error) {
163 _, err := netGetIPCIDR(ip)
164 return err == nil, err
165}
166
167// LoopbackIP reports whether ip is a loopback address.
168func LoopbackIP(ip cue.Value) bool {
169 return netGetIP(ip).IsLoopback()
170}
171
172// MulticastIP reports whether ip is a multicast address.
173func MulticastIP(ip cue.Value) bool {
174 return netGetIP(ip).IsMulticast()
175}
176
177// InterfaceLocalMulticastIP reports whether ip is an interface-local multicast
178// address.
179func InterfaceLocalMulticastIP(ip cue.Value) bool {
180 return netGetIP(ip).IsInterfaceLocalMulticast()
181}
182
183// LinkLocalMulticastIP reports whether ip is a link-local multicast address.
184func LinkLocalMulticastIP(ip cue.Value) bool {
185 return netGetIP(ip).IsLinkLocalMulticast()
186}
187
188// LinkLocalUnicastIP reports whether ip is a link-local unicast address.
189func LinkLocalUnicastIP(ip cue.Value) bool {
190 return netGetIP(ip).IsLinkLocalUnicast()
191}
192
193// GlobalUnicastIP reports whether ip is a global unicast address.
194//
195// The identification of global unicast addresses uses address type
196// identification as defined in RFC 1122, RFC 4632 and RFC 4291 with the
197// exception of IPv4 directed broadcast addresses. It returns true even if ip is
198// in IPv4 private address space or local IPv6 unicast address space.
199func GlobalUnicastIP(ip cue.Value) bool {
200 return netGetIP(ip).IsGlobalUnicast()
201}
202
203// UnspecifiedIP reports whether ip is an unspecified address, either the IPv4
204// address "0.0.0.0" or the IPv6 address "::".
205func UnspecifiedIP(ip cue.Value) bool {
206 return netGetIP(ip).IsUnspecified()
207}
208
209// ToIP4 converts a given IP address, which may be a string or a list, to its
210// 4-byte representation.
211func ToIP4(ip cue.Value) ([]uint, error) {
212 ipdata := netGetIP(ip)
213 if !ipdata.IsValid() {
214 return nil, fmt.Errorf("invalid IP %q", ip)
215 }
216 if !ipdata.Is4() {
217 return nil, fmt.Errorf("cannot convert %q to IPv4", ipdata)
218 }
219 as4 := ipdata.As4()
220 return netToList(as4[:]), nil
221}
222
223// ToIP16 converts a given IP address, which may be a string or a list, to its
224// 16-byte representation.
225func ToIP16(ip cue.Value) ([]uint, error) {
226 ipdata := netGetIP(ip)
227 if !ipdata.IsValid() {
228 return nil, fmt.Errorf("invalid IP %q", ip)
229 }
230 as16 := ipdata.As16()
231 return netToList(as16[:]), nil
232}
233
234// IPString returns the string form of the IP address ip. It returns one of 4 forms:
235//
236// - "<nil>", if ip has length 0
237// - dotted decimal ("192.0.2.1"), if ip is an IPv4 or IP4-mapped IPv6 address
238// - IPv6 ("2001:db8::1"), if ip is a valid IPv6 address
239// - the hexadecimal form of ip, without punctuation, if no other cases apply
240func IPString(ip cue.Value) (string, error) {
241 ipdata := netGetIP(ip)
242 if !ipdata.IsValid() {
243 return "", fmt.Errorf("invalid IP %q", ip)
244 }
245 return ipdata.String(), nil
246}
247
248func netIPAdd(addr netip.Addr, offset *big.Int) (netip.Addr, error) {
249 i := big.NewInt(0).SetBytes(addr.AsSlice())
250 i = i.Add(i, offset)
251
252 if i.Sign() < 0 {
253 return netip.Addr{}, errors.New("IP address arithmetic resulted in out-of-range address (underflow)")
254 }
255
256 b := i.Bytes()
257 size := addr.BitLen() / 8
258
259 if len(b) > size {
260 return netip.Addr{}, errors.New("IP address arithmetic resulted in out-of-range address (overflow)")
261 }
262
263 if len(b) < size {
264 b = append(make([]byte, size-len(b), size), b...)
265 }
266 addr, _ = netip.AddrFromSlice(b)
267 return addr, nil
268}
269
270// AddIP adds a numerical offset to a given IP address.
271// The address can be provided as a string, byte array, or CIDR subnet notation.
272// It returns the resulting IP address or CIDR subnet notation as a string.
273func AddIP(ip cue.Value, offset *big.Int) (string, error) {
274 prefix, err := netGetIPCIDR(ip)
275 if err == nil {
276 addr, err := netIPAdd(prefix.Addr(), offset)
277 if err != nil {
278 return "", err
279 }
280 return netip.PrefixFrom(addr, prefix.Bits()).String(), nil
281 }
282 ipdata := netGetIP(ip)
283 if !ipdata.IsValid() {
284 return "", fmt.Errorf("invalid IP %q", ip)
285 }
286 addr, err := netIPAdd(ipdata, offset)
287 if err != nil {
288 return "", err
289 }
290 return addr.String(), nil
291}
292
293// AddIPCIDR adds a numerical offset to a given CIDR subnet
294// string, returning a CIDR string.
295func AddIPCIDR(ip cue.Value, offset *big.Int) (string, error) {
296 prefix, err := netGetIPCIDR(ip)
297 if err != nil {
298 return "", err
299 }
300 shifted := big.NewInt(0).Lsh(offset, (uint)(prefix.Addr().BitLen()-prefix.Bits()))
301 addr, err := netIPAdd(prefix.Addr(), shifted)
302 if err != nil {
303 return "", err
304 }
305 return netip.PrefixFrom(addr, prefix.Bits()).String(), nil
306}
307
308// ParsedCIDR holds the parsed components of a CIDR notation string.
309type ParsedCIDR struct {
310 PrefixMask string `json:"prefix_mask"`
311 PrefixLen int `json:"prefix_len"`
312 PrefixAddr string `json:"prefix_addr"`
313 // BroadcastAddr is only set for IPv4 CIDRs.
314 BroadcastAddr string `json:"broadcast_addr,omitempty"`
315}
316
317// ParseCIDR parses a CIDR notation string and returns its components:
318// prefix_mask (e.g. "255.255.255.0"), prefix_len (e.g. 24),
319// prefix_addr (e.g. "10.20.30.0"), and broadcast_addr (e.g. "10.20.30.255").
320// broadcast_addr is only set for IPv4 CIDRs.
321func ParseCIDR(s string) (*ParsedCIDR, error) {
322 prefix, err := netip.ParsePrefix(s)
323 if err != nil {
324 return nil, err
325 }
326
327 bits := prefix.Bits()
328 addr := prefix.Addr()
329 maskBytes := make([]byte, addr.BitLen()/8)
330 for i := range bits / 8 {
331 maskBytes[i] = 0xFF
332 }
333 if rem := bits % 8; rem > 0 {
334 maskBytes[bits/8] = ^byte(0xFF >> rem)
335 }
336 netmask, _ := netip.AddrFromSlice(maskBytes)
337
338 networkAddr := prefix.Masked().Addr()
339
340 result := &ParsedCIDR{
341 PrefixMask: netmask.String(),
342 PrefixLen: bits,
343 PrefixAddr: networkAddr.String(),
344 }
345
346 if addr.Is4() {
347 broadcastBytes := networkAddr.AsSlice()
348 for i := range broadcastBytes {
349 broadcastBytes[i] |= ^maskBytes[i]
350 }
351 broadcastAddr, _ := netip.AddrFromSlice(broadcastBytes)
352 result.BroadcastAddr = broadcastAddr.String()
353 }
354
355 return result, nil
356}
357
358// InCIDR reports whether an IP address is contained a CIDR subnet string.
359func InCIDR(ip, cidr cue.Value) (bool, error) {
360 ipAddr := netGetIP(ip)
361 if !ipAddr.IsValid() {
362 return false, fmt.Errorf("invalid IP %q", ip)
363 }
364
365 prefix, err := netGetIPCIDR(cidr)
366 if err != nil {
367 return false, err
368 }
369
370 return prefix.Contains(ipAddr), nil
371}