forked from YutaroHayakawa/go-ra
-
Notifications
You must be signed in to change notification settings - Fork 0
/
config.go
305 lines (247 loc) · 12.4 KB
/
config.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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of go-ra
package ra
import (
"encoding/json"
"errors"
"io"
"net/netip"
"os"
"regexp"
"github.com/creasty/defaults"
"github.com/go-playground/validator/v10"
"gopkg.in/yaml.v3"
)
// Config represents the configuration of the daemon
type Config struct {
// Interface-specific configuration parameters. The Name field must be
// unique within the slice. The slice itself and elements must not be
// nil.
Interfaces []*InterfaceConfig `yaml:"interfaces" json:"interfaces" validate:"unique=Name,dive,required" default:"[]"`
}
// InterfaceConfig represents the interface-specific configuration parameters
type InterfaceConfig struct {
// Required: Network interface name. Must be unique within the configuration.
Name string `yaml:"name" json:"name" validate:"required"`
// Required: Interval between sending unsolicited RA. Must be >= 70 and
// <= 1800000. Default is 600000. The upper bound is chosen to be
// compliant with RFC4861. The lower bound is intentionally chosen to
// be lower than RFC4861 for faster convergence. If you don't wish to
// overwhelm the network, and wish to be compliant with RFC4861, set to
// higher than 3000 as RFC4861 suggests.
RAIntervalMilliseconds int `yaml:"raIntervalMilliseconds" json:"raIntervalMilliseconds" validate:"required,gte=70,lte=1800000" default:"600000"`
// RA header fields
// The default value that should be placed in the Hop Count field of
// the IP header for outgoing IP packets. Must be >= 0 and <= 255.
// Default is 0. If set to zero, it means the reachable time is
// unspecified by this router.
CurrentHopLimit int `yaml:"currentHopLimit" json:"currentHopLimit" validate:"gte=0,lte=255" default:"0"`
// Set M (Managed address configuration) flag. When set, it indicates
// that addresses are available via DHCPv6. Default is false.
Managed bool `yaml:"managed" json:"managed"`
// Set O (Other configuration) flag. When set, it indicates that other
// configuration information is available via DHCPv6. Default is false.
Other bool `yaml:"other" json:"other"`
// Set Prf (Default Router Preference) field. Must be one of "low",
// "medium", or "high". If RouterLifetimeSeconds is 0, it must be set
// to "medium". Default is "medium".
Preference string `yaml:"preference" json:"preference" validate:"eq_if medium RouterLifetimeSeconds 0,oneof=low medium high" default:"medium"`
// The lifetime associated with the default router in seconds. Must be
// >= 0 and <= 65535. Default is 0. The upper bound is chosen to be
// compliant to the RFC8319. If set to zero, the router is not
// considered as a default router.
RouterLifetimeSeconds int `yaml:"routerLifetimeSeconds" json:"routerLifetimeSeconds" validate:"gte=0,lte=65535"`
// The time, in milliseconds, that a node assumes a neighbor is
// reachable after having received a reachability confirmation. Must be
// >= 0 and <= 4294967295. Default is 0. If set to zero, it means the
// reachable time is unspecified by this router.
ReachableTimeMilliseconds int `yaml:"reachableTimeMilliseconds" json:"reachableTimeMilliseconds" validate:"gte=0,lte=4294967295"`
// The time, in milliseconds, between retransmitted Neighbor
// Solicitation messages. Must be >= 0 and <= 4294967295. Default is 0.
// If set to zero, it means the retransmission time is unspecified by
// this router.
RetransmitTimeMilliseconds int `yaml:"retransmitTimeMilliseconds" json:"retransmitTimeMilliseconds" validate:"gte=0,lte=4294967295"`
// The maximum transmission unit (MTU) that should be used for outgoing
// This value specifies the largest packet size, in bytes,
// If set to zero or not specified, MTU opton will not be advertised
MTU int `yaml:"mtu" json:"mtu" validate:"gte=0,lte=4294967295"`
// Prefix-specific configuration parameters. The prefix fields must be
// non-overlapping with each other. The slice itself and elements must
// not be nil.
Prefixes []*PrefixConfig `yaml:"prefixes" json:"prefixes" validate:"non_overlapping_prefix,dive,required" default:"[]"`
// Route-specific configuration parameters. The prefix fields must not
// be the same each other. The slice itself and elements must not be nil.
Routes []*RouteConfig `yaml:"routes" json:"routes" validate:"unique=Prefix,dive,required" default:"[]"`
// RDNSS-specific configuration parameters.
RDNSSes []*RDNSSConfig `yaml:"rdnsses" json:"rdnsses" validate:"dive,required" default:"[]"`
// DNSSL-specific configuration parameters.
DNSSLs []*DNSSLConfig `yaml:"dnssls" json:"dnssls" validate:"dive,required" default:"[]"`
// NAT64 prefix-specific configuration parameters.
NAT64Prefixes []*NAT64PrefixConfig `yaml:"nat64prefixes" json:"nat64prefixes" validate:"dive,required" default:"[]"`
}
// PrefixConfig represents the prefix-specific configuration parameters
type PrefixConfig struct {
// Required: Prefix. Must be a valid IPv6 prefix.
Prefix string `yaml:"prefix" json:"prefix" validate:"required,cidrv6"`
// Set L (On-Link) flag. When set, it indicates that this prefix can be
// used for on-link determination. Default is false.
OnLink bool `yaml:"onLink" json:"onLink"`
// Set A (Autonomous address-configuration) flag. When set, it indicates
// that this prefix can be used for stateless address autoconfiguration.
// Default is false.
Autonomous bool `yaml:"autonomous" json:"autonomous"`
// The valid lifetime of the prefix in seconds. Must be >= 0 and <=
// 4294967295 and must be >= PreferredLifetimeSeconds. Default is
// 2592000 (30 days). If set to 4294967295, it indicates infinity.
ValidLifetimeSeconds *int `yaml:"validLifetimeSeconds" json:"validLifetimeSeconds" validate:"required,gte=0,lte=4294967295" default:"2592000"`
// The preferred lifetime of the prefix in seconds. Must be >= 0 and <=
// 4294967295 and must be <= ValidLifetimeSeconds. Default is 604800 (7
// days). If set to 4294967295, it indicates infinity.
PreferredLifetimeSeconds *int `yaml:"preferredLifetimeSeconds" json:"preferredLifetimeSeconds" validate:"required,gte=0,ltefield=ValidLifetimeSeconds" default:"604800"`
}
// RouteConfig represents the route-specific configuration parameters
type RouteConfig struct {
// Required: Prefix. Must be a valid IPv6 prefix.
Prefix string `yaml:"prefix" json:"prefix" validate:"required,cidrv6"`
// Required: The valid lifetime of the route in seconds. Must be >= 0
// and <= 4294967295. If set to 4294967295, it indicates infinity.
LifetimeSeconds int `yaml:"lifetimeSeconds" json:"lifetimeSeconds" validate:"required,gte=0,lte=4294967295"`
// Set Prf (Route Preference) field. It indicates whether to prefer the
// router associated with this prefix over others, when multiple
// identical prefixes (for different routers) have been received. Must
// be one of "low", "medium", or "high". Default is "medium".
Preference string `yaml:"preference" json:"preference" validate:"oneof=low medium high" default:"medium"`
}
// RDNSSConfig represents the RDNSS-specific configuration parameters
type RDNSSConfig struct {
// Required: The maximum time in seconds over which these RDNSS
// addresses may be used for name resolution.
LifetimeSeconds int `yaml:"lifetimeSeconds" json:"lifetimeSeconds" validate:"required,gte=0,lte=4294967295"`
// Required: The addresses of the RDNSS servers. You must specify at least one address.
Addresses []string `yaml:"addresses" json:"addresses" validate:"required,unique,min=1,dive,ipv6"`
}
// DNSSLConfig represents the DNSSL-specific configuration parameters
type DNSSLConfig struct {
// Required: The maximum time in seconds over which these DNSSL domain
// names may be used for name resolution.
LifetimeSeconds int `yaml:"lifetimeSeconds" json:"lifetimeSeconds" validate:"required,gte=0,lte=4294967295"`
// Required: The domain names to be used for DNS search list. You must specify at least one domain name.
DomainNames []string `yaml:"domainNames" json:"domainNames" validate:"required,unique,min=1,dive,domain"`
}
// NAT64PrefixConfig represents the NAT64 prefix-specific configuration parameters
type NAT64PrefixConfig struct {
// Required: NAT64 prefix. Must be a valid IPv6 prefix.
// Can only be one of /32, /40, /48, /56, /64, or /96.
Prefix string `yaml:"prefix" json:"prefix" validate:"required,cidrv6,invalid_prefix_len"`
// Required: The valid lifetime of the NAT64 prefix in seconds. Must be >= 0
// and <= 65528. If set to 0, it indicates that the prefix should not be used anymore.
// Should not be shorter than Router Lifetime. This lifetime is encoded
// in units of 8-seconds increments as ScaledLifetime.
LifetimeSeconds *int `yaml:"lifetimeSeconds" json:"lifetimeSeconds" validate:"required,gte=0,lte=65528" default:"65528"`
}
// ValidationErrors is a type alias for the validator.ValidationErrors
type ValidationErrors = validator.ValidationErrors
// Regular expression to validate the domain name in DNSSL configuration
var domainRegexp = regexp.MustCompile(`^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$`)
func (c *Config) defaultAndValidate() error {
if err := defaults.Set(c); err != nil {
panic("BUG (Please report 🙏): Defaulting failed: " + err.Error())
}
validate := validator.New(validator.WithRequiredStructEnabled())
// Adhoc custom validator which validates the Prefix fields are non-overlapping with each other.
validate.RegisterValidation("non_overlapping_prefix", func(fl validator.FieldLevel) bool {
prefixes := []netip.Prefix{}
prefixSlice := fl.Field()
for i := 0; i < prefixSlice.Len(); i++ {
prefix := prefixSlice.Index(i).Elem().FieldByName("Prefix")
p, err := netip.ParsePrefix(prefix.String())
if err != nil {
// Just ignore this error here. cidrv6 constraint will catch it later.
continue
}
prefixes = append(prefixes, p)
}
// Check the prefix is not overlapping with each other
for _, p0 := range prefixes {
for _, p1 := range prefixes {
if p0 != p1 && p0.Overlaps(p1) {
return false
}
}
}
return true
})
// Adhoc custom validator which validates the value of this field must
// be medium if RouterLifetimeSeconds is 0.
validate.RegisterValidation("eq_if medium RouterLifetimeSeconds 0", func(fl validator.FieldLevel) bool {
pref := fl.Field().String()
routerLifetimeSeconds := fl.Parent().FieldByName("RouterLifetimeSeconds").Int()
if routerLifetimeSeconds == 0 && pref != "medium" {
return false
}
return true
})
// Adhoc custom validator which validates the string is a valid domain name.
validate.RegisterValidation("domain", func(fl validator.FieldLevel) bool {
dom := fl.Field().String()
return domainRegexp.Match([]byte(dom))
})
// Adhoc custom validator which validates the prefix length must
// be one of /32, /40, /48, /56, /64, or /96.
validate.RegisterValidation("invalid_prefix_len", func(fl validator.FieldLevel) bool {
p := netip.MustParsePrefix(fl.Field().String())
validPrefixLengths := map[int]bool{
32: true,
40: true,
48: true,
56: true,
64: true,
96: true,
}
return validPrefixLengths[p.Bits()]
})
if err := validate.Struct(c); err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
panic("BUG (Please report 🙏): Invalid validation: " + err.Error())
}
var verrs ValidationErrors
if errors.As(err, &verrs) {
return verrs
}
// This is impossible, according to the validator's documentation
// https://pkg.go.dev/github.com/go-playground/validator/v10#hdr-Validation_Functions_Return_Type_error
return err
}
return nil
}
// ParseConfigJSON parses the JSON-encoded configuration from the reader. This
// function doesn't validate the configuration. The configuration is validated
// when you pass it to the Daemon.
func ParseConfigJSON(r io.Reader) (*Config, error) {
var c Config
if err := json.NewDecoder(r).Decode(&c); err != nil {
return nil, err
}
return &c, nil
}
// ParseConfigYAML parses the YAML-encoded configuration from the reader. This
// function doesn't validate the configuration. The configuration is validated
// when you pass it to the Daemon.
func ParseConfigYAML(r io.Reader) (*Config, error) {
var c Config
if err := yaml.NewDecoder(r).Decode(&c); err != nil {
return nil, err
}
return &c, nil
}
// ParseConfigYAMLFile parses the YAML-encoded configuration from the file at
// the given path. This function doesn't validate the configuration. The
// configuration is validated when you pass it to the Daemon.
func ParseConfigYAMLFile(path string) (*Config, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return ParseConfigYAML(f)
}