Skip to content

Commit 99644ef

Browse files
authored
Merge pull request #183 from datum-cloud/fix/issue-160
fix: validate SRv6Locator format in parseConf
2 parents 7183e49 + ff5e639 commit 99644ef

2 files changed

Lines changed: 148 additions & 0 deletions

File tree

internal/cni/cni_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,90 @@ func TestParseConf(t *testing.T) {
209209
wantVPC: "Abc123XYZ",
210210
wantIfType: interfaceTypeVeth,
211211
},
212+
{
213+
name: "valid srv6_locator with /48 prefix",
214+
input: fmt.Sprintf(
215+
`{"cniVersion":"1.0.0","name":"test",`+
216+
`"type":"galactic-cni","vpc":"%s",`+
217+
`"vpcattachment":"%s","srv6_locator":"2001:db8::/48"}`,
218+
testVPC, testAttachment,
219+
),
220+
wantVPC: testVPC,
221+
wantIfType: interfaceTypeVeth,
222+
},
223+
{
224+
name: "valid srv6_locator with /64 prefix",
225+
input: fmt.Sprintf(
226+
`{"cniVersion":"1.0.0","name":"test",`+
227+
`"type":"galactic-cni","vpc":"%s",`+
228+
`"vpcattachment":"%s","srv6_locator":"fd00:1:2::/64"}`,
229+
testVPC, testAttachment,
230+
),
231+
wantVPC: testVPC,
232+
wantIfType: interfaceTypeVeth,
233+
},
234+
{
235+
name: "valid srv6_locator empty is allowed",
236+
input: fmt.Sprintf(
237+
`{"cniVersion":"1.0.0","name":"test",`+
238+
`"type":"galactic-cni","vpc":"%s",`+
239+
`"vpcattachment":"%s","srv6_locator":""}`,
240+
testVPC, testAttachment,
241+
),
242+
wantVPC: testVPC,
243+
wantIfType: interfaceTypeVeth,
244+
},
245+
{
246+
name: "srv6_locator missing is allowed",
247+
input: fmt.Sprintf(
248+
`{"cniVersion":"1.0.0","name":"test",`+
249+
`"type":"galactic-cni","vpc":"%s",`+
250+
`"vpcattachment":"%s"}`,
251+
testVPC, testAttachment,
252+
),
253+
wantVPC: testVPC,
254+
wantIfType: interfaceTypeVeth,
255+
},
256+
{
257+
name: "srv6_locator invalid mask /65 too long",
258+
input: fmt.Sprintf(
259+
`{"cniVersion":"1.0.0","name":"test",`+
260+
`"type":"galactic-cni","vpc":"%s",`+
261+
`"vpcattachment":"%s","srv6_locator":"fd00:1:2::/65"}`,
262+
testVPC, testAttachment,
263+
),
264+
wantErr: srv6LocatorErrMsg,
265+
},
266+
{
267+
name: "srv6_locator invalid IPv4 address",
268+
input: fmt.Sprintf(
269+
`{"cniVersion":"1.0.0","name":"test",`+
270+
`"type":"galactic-cni","vpc":"%s",`+
271+
`"vpcattachment":"%s","srv6_locator":"192.168.1.0/24"}`,
272+
testVPC, testAttachment,
273+
),
274+
wantErr: srv6LocatorErrMsg,
275+
},
276+
{
277+
name: "srv6_locator not a valid CIDR",
278+
input: fmt.Sprintf(
279+
`{"cniVersion":"1.0.0","name":"test",`+
280+
`"type":"galactic-cni","vpc":"%s",`+
281+
`"vpcattachment":"%s","srv6_locator":"not-a-cidr"}`,
282+
testVPC, testAttachment,
283+
),
284+
wantErr: srv6LocatorErrMsg,
285+
},
286+
{
287+
name: "srv6_locator plain IPv6 without mask",
288+
input: fmt.Sprintf(
289+
`{"cniVersion":"1.0.0","name":"test",`+
290+
`"type":"galactic-cni","vpc":"%s",`+
291+
`"vpcattachment":"%s","srv6_locator":"2001:db8::1"}`,
292+
testVPC, testAttachment,
293+
),
294+
wantErr: srv6LocatorErrMsg,
295+
},
212296
}
213297

214298
for _, tt := range tests {
@@ -272,6 +356,40 @@ func TestIsValidBase62(t *testing.T) {
272356
}
273357
}
274358

359+
// ---- isValidSRv6Locator --------------------------------------------------
360+
361+
func TestIsValidSRv6Locator(t *testing.T) {
362+
tests := []struct {
363+
name string
364+
input string
365+
want bool
366+
}{
367+
{"empty allowed", "", true},
368+
{"valid /48", "2001:db8::/48", true},
369+
{"valid /64", "fd00:1:2::/64", true},
370+
{"valid /0", "::/0", true},
371+
{"valid /63", "fd00::/63", true},
372+
{"invalid /65 too long", "fd00::/65", false},
373+
{"invalid /128", "fd00::1/128", false},
374+
{"invalid IPv4", "192.168.1.0/24", false},
375+
{"invalid IPv4 with IPv6-looking mask", "10.0.0.0/8", false},
376+
{"not a CIDR", "not-a-cidr", false},
377+
{"plain IPv6 no mask", "2001:db8::1", false},
378+
{"plain IPv4 no mask", "192.168.1.1", false},
379+
{"valid with host bits set", "2001:db8:1234::/48", true},
380+
{"all zeros valid", "::/0", true},
381+
}
382+
383+
for _, tt := range tests {
384+
t.Run(tt.name, func(t *testing.T) {
385+
got := isValidSRv6Locator(tt.input)
386+
if got != tt.want {
387+
t.Errorf("isValidSRv6Locator(%q) = %v, want %v", tt.input, got, tt.want)
388+
}
389+
})
390+
}
391+
}
392+
275393
func TestSanitizeForError(t *testing.T) {
276394
printable := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*()"
277395
tests := []struct {

internal/cni/config.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ import (
88
"encoding/json"
99
"errors"
1010
"fmt"
11+
"net"
1112
)
1213

1314
const sanitizeForErrorBinary = "<binary>"
1415

16+
// srv6LocatorErrMsg is the error message prefix for invalid SRv6 locator values.
17+
const srv6LocatorErrMsg = "invalid srv6_locator"
18+
1519
// isValidBase62 reports whether s contains only valid base62 characters
1620
// ([0-9a-zA-Z]) and is non-empty. VPC and VPCAttachment identifiers are
1721
// base62-encoded and used throughout the ADD path (interface naming,
@@ -29,6 +33,25 @@ func isValidBase62(s string) bool {
2933
return true
3034
}
3135

36+
// isValidSRv6Locator reports whether s is a valid IPv6 CIDR prefix suitable
37+
// for SRv6 SID encoding. The locator must be non-empty, parseable as an IPv6
38+
// prefix with a mask length of 64 or less (leaving at least 64 bits of
39+
// address space for embedding VPC/VPCAttachment identifiers).
40+
func isValidSRv6Locator(s string) bool {
41+
if s == "" {
42+
return true // empty locator is valid — setupSRv6Ingress is a no-op
43+
}
44+
_, ipnet, err := net.ParseCIDR(s)
45+
if err != nil {
46+
return false
47+
}
48+
if ipnet.IP.To4() != nil {
49+
return false // must be IPv6
50+
}
51+
maskLen, _ := ipnet.Mask.Size()
52+
return maskLen <= 64
53+
}
54+
3255
// parseConf unmarshals the CNI configuration from stdin data and validates
3356
// the interface type and base62-encoded identifier fields. Returns an error
3457
// if the config is malformed, interface_type is unsupported, or VPC/
@@ -53,6 +76,13 @@ func parseConf(data []byte) (*PluginConf, error) {
5376
}
5477
return nil, fmt.Errorf("invalid base62 value for field 'vpcattachment': %q", sanitizeForError(conf.VPCAttachment))
5578
}
79+
if !isValidSRv6Locator(conf.SRv6Locator) {
80+
return nil, fmt.Errorf(
81+
"%s %q: must be a valid "+
82+
"IPv6 CIDR prefix with mask length <= 64",
83+
srv6LocatorErrMsg, sanitizeForError(conf.SRv6Locator),
84+
)
85+
}
5686
if conf.InterfaceType == "" {
5787
conf.InterfaceType = interfaceTypeVeth
5888
}

0 commit comments

Comments
 (0)