-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathip.go
204 lines (185 loc) · 6.63 KB
/
ip.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Package anonymize provides IP address anonymization tools.
package anonymize
import (
"flag"
"fmt"
"log"
"net"
)
var (
// Netblock causes IPv4 addresses to be anonymized up to the
// /24 level and IPv6 addresses to the /64 level.
Netblock = Method("netblock")
// None performs no anonymization. By creating an anonymizer that performs
// no anonymization, we make it possible to always have the anonymizer code
// path be used, whether anonymization is actually needed or not, preventing
// the creation of hundreds of needless `if shouldAnonymize {...}` code
// blocks.
None = Method("none")
// IPAnonymizationFlag is a flag that determines whether IP anonymization is
// on or off. Its value should be fixed for the duration of a program. This
// library is not guaranteeed to work properly if you keep switching back
// and forth between different anonymization schemes. The default is no
// anonymization.
IPAnonymizationFlag = None
// IgnoredIPs is a set of IPs that should be ignored and not anonymized. By
// default it is the set of local IP addresses. This set should be small, so
// it is represented as an array because net.IP objects can't be used as map
// keys.
//
// If runtime profiling indicates this causes too much of a slowdown in
// practice, then the other design that is "near at hand" with Go primitives
// is to use map[string]struct{} where the string is the IP converted to a
// string. If that design also causes too much of a slowdown in practice,
// then we will have to fall back on our algorithms knowledge and build a
// clever data structure. The 3 main options are:
// 1. a bloom filter in front of a set object,
// 2. a 256-ary tree using each successive byte of the IP for each
// successive level, or
// 3. a map[int32]struct{} for v4 addresses and a
// map[int64](map[int64]struct{}) for v6 addresses.
// Likely the last option will be the fastest; ceteris paribus, native ints
// and language builtins tend to have the best performance.
//
// Big-O analysis doesn't buy us much here. There are a small number of
// local IPs and each one is of length 4 or 16. This means that taking the
// limit as N goes to infinity doesn't tell us much, because all of the
// quantities we might call "N" are 16 or less in all realistic scenarios,
// and 16 is a poor approximation of infinity. Therefore, any claims about
// relative speed of implementation need to be backed up by experimental
// evidence.
IgnoredIPs = []net.IP{}
// An injected log.Fatal to aid in testing.
logFatalf = log.Fatalf
)
// Method is an enum suitable for using as a command-line flag. It
// allows only a finite set of values. We can imagine future anonymization
// techniques based on k-anonymity or that completely blot out the IP. We leave
// room for those implementations here, but do not (yet) implement them.
type Method string
// Get is required for all flag.Flag values.
func (m Method) Get() interface{} {
return m
}
// Set is required for all flag.Flag values.
func (m *Method) Set(s string) error {
switch Method(s) {
case Netblock:
*m = Netblock
case None:
*m = None
default:
return fmt.Errorf("Uknown anonymization method: %q", s)
}
return nil
}
// String is required for all flag.Flag values.
func (m Method) String() string {
return string(m)
}
func init() {
flag.Var(&IPAnonymizationFlag, "anonymize.ip", "Valid values are \"none\" and \"netblock\".")
// Set up the local IP addresses to be ignored by the anonymization system.
// We want to anonymize our users but not ourselves.
localAddrs, err := net.InterfaceAddrs()
if err == nil {
for _, addr := range localAddrs {
if ipnet, ok := addr.(*net.IPNet); ok {
IgnoredIPs = append(IgnoredIPs, ipnet.IP)
}
}
}
}
// IPAnonymizer is the generic interface for all systems that try and ensure IP
// addresses are not human identifiers. It is a problem with many potential
// subtleties, so we permit multiple implementations. We anonymize the address
// in-place. If you don't want the address to be modified, then make a copy
// before you pass it in.
type IPAnonymizer interface {
IP(ip net.IP)
Contains(dst, ip net.IP) bool
}
// New is an IP anonymization factory function that expects you to pass in
// anonymize.IPAnonymizationFlag, which contains the contents of the
// `--anonymize.ip` command-line flag.
//
// If the anonymization method is set to "netblock", then IPv4 addresses will be
// anonymized up to the /24 level and IPv6 addresses to the /64 level. If it is
// set to "none" then no anonymization will be performed. We can imagine future
// anonymization techniques based on k-anonymity or that completely blot out the
// IP. We leave room for those implementations here, but do not (yet) implement
// them.
//
// A program attempting to perform IP anonymization should only ever create one
// IPAnonymizer and use that one anonymizer for all connections. Otherwise, the
// created IPAnonymizer will lack the necessary context to correctly perform
// k-anonymization.
func New(method Method) IPAnonymizer {
switch method {
case None:
return nullIPAnonymizer{}
case Netblock:
return netblockAnonymizer{}
default:
logFatalf("Unknown anonymization method: %q, exiting to avoid accidentally leaking private data", method)
panic("This line should only be reached during testing.")
}
}
// nullIPAnonymizer does nothing.
type nullIPAnonymizer struct{}
func (nullIPAnonymizer) IP(ip net.IP) {}
func (nullIPAnonymizer) Contains(dst, ip net.IP) bool {
return dst.Equal(ip)
}
// netblockIPAnonymizer restricts v4 addresses to a /24 and v6 addresses to a /64
type netblockAnonymizer struct{}
func (netblockAnonymizer) IP(ip net.IP) {
if ip == nil {
return
}
for i := range IgnoredIPs {
if IgnoredIPs[i].Equal(ip) {
return
}
}
if ip.To4() != nil {
// Zero out the last byte. That's ip[3] in the 4-byte v4 representation and ip[15] in the v4-in-v6 representation.
ip[len(ip)-1] = 0
return
}
if ip.To16() != nil {
// Truncate IPv6 addresses to the containing /48
for i := 6; i < 16; i++ {
ip[i] = 0
}
return
}
log.Println("The passed in IP address was neither a v4 nor a v6 address:", ip)
return
}
// Contains determines whether the dst IP (as if netblock anonymized), contains the given dst address.
func (n netblockAnonymizer) Contains(dst, ip net.IP) bool {
if dst == nil || ip == nil {
return false
}
for i := range IgnoredIPs {
if IgnoredIPs[i].Equal(dst) {
return false
}
}
if dst.To4() != nil {
nn := &net.IPNet{
IP: dst,
Mask: net.CIDRMask(24, 32),
}
return nn.Contains(ip)
}
if dst.To16() != nil {
nn := &net.IPNet{
IP: dst,
Mask: net.CIDRMask(48, 128),
}
return nn.Contains(ip)
}
return false
}