this repo has no description
at master 371 lines 9.7 kB view raw
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}