-
Notifications
You must be signed in to change notification settings - Fork 277
/
preferencer.go
117 lines (104 loc) · 3.27 KB
/
preferencer.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
package endpoint
import (
"context"
"net"
"github.com/hashicorp/boundary/internal/errors"
)
type preferencer struct {
matchers []matcher
}
// NewPreferencer builds up a preferencer with a set of preference options. This
// can then be used with Choose to select preferences from among a set of IP
// addresses and DNS names.
//
// Supported options: WithPreferenceOrder
func NewPreferencer(ctx context.Context, opt ...Option) (*preferencer, error) {
const op = "endpoint.NewPreferencer"
opts, err := getOpts(opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter))
}
pref := &preferencer{
matchers: opts.withMatchers,
}
return pref, nil
}
// Choose takes in IP addresses and/or DNS names and chooses an endpoint from
// among them, picking one at random if there are no preferences supplied. If
// preferences are specified but none match, the empty string is returned.
// However, if no IP addresses or DNS names are supplied, an error is returned.
//
// Supported options: WithIpAddrs, WithDnsNames
func (p *preferencer) Choose(ctx context.Context, opt ...Option) (string, error) {
const op = "endpoint.(preferencer).Choose"
opts, err := getOpts(opt...)
if err != nil {
return "", errors.Wrap(ctx, err, op)
}
if len(opts.withIpAddrs)+len(opts.withDnsNames) == 0 {
return "", errors.New(ctx, errors.InvalidParameter, op, "no ip addresses or dns names passed in")
}
switch len(p.matchers) {
case 0:
// We have no matchers, so pick a private address if we have one (since
// those are most likely to be what the user is trying to gain access to
// via a worker). If we don't have one, pick any IP address, as that's
// also more likely desired than a DNS name. Otherwise, pick a DNS name.
// IPv6 follows same rules as 4, but least preferenced.
nonPrivateIp4s := make([]string, 0, len(opts.withIpAddrs))
nonPrivateIp6s := make([]string, 0, len(opts.withIpAddrs))
privateIp6s := make([]string, 0, len(opts.withIpAddrs))
for _, ipStr := range opts.withIpAddrs {
ipVal := net.ParseIP(ipStr)
if ipVal != nil {
switch ipVal.To4() {
case nil: // it's v6
if ipVal.IsPrivate() {
privateIp6s = append(privateIp6s, ipStr)
} else {
nonPrivateIp6s = append(nonPrivateIp6s, ipStr)
}
default:
if ipVal.IsPrivate() {
// private IPv4 is most highly preferenced, so return it directly
return ipStr, nil
}
nonPrivateIp4s = append(nonPrivateIp4s, ipStr)
}
}
}
switch {
case len(nonPrivateIp4s) > 0:
return nonPrivateIp4s[0], nil
case len(opts.withDnsNames) > 0:
return opts.withDnsNames[0], nil
case len(privateIp6s) > 0:
return privateIp6s[0], nil
case len(nonPrivateIp6s) > 0:
return nonPrivateIp6s[0], nil
default:
// We literally have nothing if we get here, so return nothing
return "", nil
}
default:
for _, m := range p.matchers {
switch m.(type) {
case dnsMatcher:
for _, name := range opts.withDnsNames {
if m.Match(name) {
return name, nil
}
}
case cidrMatcher:
for _, addr := range opts.withIpAddrs {
if m.Match(addr) {
return addr, nil
}
}
}
}
// Nothing matched. Don't treat it as an error, let the calling function
// simply ignore the empty result.
return "", nil
}
}