-
Notifications
You must be signed in to change notification settings - Fork 10
/
ipaddr.go
146 lines (127 loc) · 4.03 KB
/
ipaddr.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package types
import (
"encoding/json"
"fmt"
"hash/fnv"
"net/netip"
"strings"
"github.com/cedar-policy/cedar-go/internal"
)
var errIP = internal.ErrIP
// An IPAddr is value that represents an IP address. It can be either IPv4 or IPv6.
// The value can represent an individual address or a range of addresses.
type IPAddr netip.Prefix
// ParseIPAddr takes a string representation of an IP address and converts it into an IPAddr type.
func ParseIPAddr(s string) (IPAddr, error) {
// We disallow IPv4-mapped IPv6 addresses in dotted notation because Cedar does.
if strings.Count(s, ":") >= 2 && strings.Count(s, ".") >= 2 {
return IPAddr{}, fmt.Errorf("%w: cannot parse IPv4 addresses embedded in IPv6 addresses", errIP)
} else if net, err := netip.ParsePrefix(s); err == nil {
return IPAddr(net), nil
} else if addr, err := netip.ParseAddr(s); err == nil {
return IPAddr(netip.PrefixFrom(addr, addr.BitLen())), nil
} else {
return IPAddr{}, fmt.Errorf("%w: error parsing IP address %s", errIP, s)
}
}
func (a IPAddr) Equal(bi Value) bool {
b, ok := bi.(IPAddr)
return ok && a == b
}
// MarshalCedar produces a valid MarshalCedar language representation of the IPAddr, e.g. `ip("127.0.0.1")`.
func (v IPAddr) MarshalCedar() []byte { return []byte(`ip("` + v.String() + `")`) }
// String produces a string representation of the IPAddr, e.g. `127.0.0.1`.
func (v IPAddr) String() string {
if v.Prefix().Bits() == v.Addr().BitLen() {
return v.Addr().String()
}
return v.Prefix().String()
}
func (v IPAddr) Prefix() netip.Prefix {
return netip.Prefix(v)
}
func (v IPAddr) IsIPv4() bool {
return v.Addr().Is4()
}
func (v IPAddr) IsIPv6() bool {
return v.Addr().Is6()
}
func (v IPAddr) IsLoopback() bool {
// This comment is in the Cedar Rust implementation:
//
// Loopback addresses are "127.0.0.0/8" for IpV4 and "::1" for IpV6
//
// Unlike the implementation of `is_multicast`, we don't need to test prefix
//
// The reason for IpV6 is obvious: There's only one loopback address
//
// The reason for IpV4 is that provided the truncated ip address is a
// loopback address, its prefix cannot be less than 8 because
// otherwise its more significant byte cannot be 127
return v.Prefix().Masked().Addr().IsLoopback()
}
func (v IPAddr) Addr() netip.Addr {
return netip.Prefix(v).Addr()
}
func (v IPAddr) IsMulticast() bool {
// This comment is in the Cedar Rust implementation:
//
// Multicast addresses are "224.0.0.0/4" for IpV4 and "ff00::/8" for
// IpV6
//
// If an IpNet's addresses are multicast addresses, calling
// `is_in_range()` over it and its associated net above should
// evaluate to true
//
// The implementation uses the property that if `ip1/prefix1` is in
// range `ip2/prefix2`, then `ip1` is in `ip2/prefix2` and `prefix1 >=
// prefix2`
var min_prefix_len int
if v.IsIPv4() {
min_prefix_len = 4
} else {
min_prefix_len = 8
}
return v.Addr().IsMulticast() && v.Prefix().Bits() >= min_prefix_len
}
func (c IPAddr) Contains(o IPAddr) bool {
return c.Prefix().Contains(o.Addr()) && c.Prefix().Bits() <= o.Prefix().Bits()
}
// UnmarshalJSON implements encoding/json.Unmarshaler for IPAddr
//
// It is capable of unmarshaling 3 different representations supported by Cedar
// - { "__extn": { "fn": "ip", "arg": "12.34.56.78" }}
// - { "fn": "ip", "arg": "12.34.56.78" }
// - "12.34.56.78"
func (v *IPAddr) UnmarshalJSON(b []byte) error {
vv, err := unmarshalExtensionValue(b, "ip", ParseIPAddr)
if err != nil {
return err
}
*v = vv
return nil
}
// MarshalJSON marshals the IPAddr into JSON using the explicit form.
func (v IPAddr) MarshalJSON() ([]byte, error) {
if v.Prefix().Bits() == v.Prefix().Addr().BitLen() {
return json.Marshal(extValueJSON{
Extn: &extn{
Fn: "ip",
Arg: v.Addr().String(),
},
})
}
return json.Marshal(extValueJSON{
Extn: &extn{
Fn: "ip",
Arg: v.String(),
},
})
}
func (v IPAddr) hash() uint64 {
// MarshalBinary() cannot actually fail
bytes, _ := netip.Prefix(v).MarshalBinary()
h := fnv.New64()
_, _ = h.Write(bytes)
return h.Sum64()
}