Skip to content

Commit 53a6f7d

Browse files
committed
registry: support ipv6 addresses
Current registry reference use a subset of dns and IPv4 addresses to represent a registry domain. Since registries are mostly compatible with rfc3986, that defines the URI generic syntax, this adds support for IPv6 enclosed in squared brackets based on the mentioned rfc. The regexp is only expanded to match on IPv6 addreses enclosed between square brackets, considering only regular IPv6 addresses represented as compressed or uncompressed, excluding special IPv6 address representations. Signed-off-by: Antonio Ojea <[email protected]>
1 parent 3e4f8a0 commit 53a6f7d

5 files changed

Lines changed: 182 additions & 8 deletions

File tree

reference/normalize_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@ func TestValidateReferenceName(t *testing.T) {
2222
"127.0.0.1:5000/docker/docker",
2323
"127.0.0.1:5000/library/debian",
2424
"127.0.0.1:5000/debian",
25+
"192.168.0.1",
26+
"192.168.0.1:80",
27+
"192.168.0.1:8/debian",
28+
"192.168.0.2:25000/debian",
2529
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
30+
"[fc00::1]:5000/docker",
31+
"[fc00::1]:5000/docker/docker",
32+
"[fc00:1:2:3:4:5:6:7]:5000/library/debian",
2633

2734
// This test case was moved from invalid to valid since it is valid input
2835
// when specified with a hostname, it removes the ambiguity from about
@@ -40,6 +47,11 @@ func TestValidateReferenceName(t *testing.T) {
4047
"docker///docker",
4148
"docker.io/docker/Docker",
4249
"docker.io/docker///docker",
50+
"[fc00::1]",
51+
"[fc00::1]:5000",
52+
"fc00::1:5000/debian",
53+
"[fe80::1%eth0]:5000/debian",
54+
"[2001:db8:3:4::192.0.2.33]:5000/debian",
4355
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
4456
}
4557

reference/reference.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
//
66
// reference := name [ ":" tag ] [ "@" digest ]
77
// name := [domain '/'] path-component ['/' path-component]*
8-
// domain := domain-component ['.' domain-component]* [':' port-number]
8+
// domain := host [':' port-number]
9+
// host := domain-name | IPv4address | \[ IPv6address \] ; rfc3986 appendix-A
10+
// domain-name := domain-component ['.' domain-component]*
911
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
1012
// port-number := /[0-9]+/
1113
// path-component := alpha-numeric [separator alpha-numeric]*

reference/reference_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,101 @@ func TestReferenceParse(t *testing.T) {
171171
repository: "foo/foo_bar.com",
172172
tag: "8080",
173173
},
174+
{
175+
input: "192.168.1.1",
176+
repository: "192.168.1.1",
177+
},
178+
{
179+
input: "192.168.1.1:tag",
180+
repository: "192.168.1.1",
181+
tag: "tag",
182+
},
183+
{
184+
input: "192.168.1.1:5000",
185+
repository: "192.168.1.1",
186+
tag: "5000",
187+
},
188+
{
189+
input: "192.168.1.1/repo",
190+
domain: "192.168.1.1",
191+
repository: "192.168.1.1/repo",
192+
},
193+
{
194+
input: "192.168.1.1:5000/repo",
195+
domain: "192.168.1.1:5000",
196+
repository: "192.168.1.1:5000/repo",
197+
},
198+
{
199+
input: "192.168.1.1:5000/repo:5050",
200+
domain: "192.168.1.1:5000",
201+
repository: "192.168.1.1:5000/repo",
202+
tag: "5050",
203+
},
204+
{
205+
input: "[2001:db8::1]",
206+
err: ErrReferenceInvalidFormat,
207+
},
208+
{
209+
input: "[2001:db8::1]:5000",
210+
err: ErrReferenceInvalidFormat,
211+
},
212+
{
213+
input: "[2001:db8::1]:tag",
214+
err: ErrReferenceInvalidFormat,
215+
},
216+
{
217+
input: "[2001:db8::1]/repo",
218+
domain: "[2001:db8::1]",
219+
repository: "[2001:db8::1]/repo",
220+
},
221+
{
222+
input: "[2001:db8:1:2:3:4:5:6]/repo:tag",
223+
domain: "[2001:db8:1:2:3:4:5:6]",
224+
repository: "[2001:db8:1:2:3:4:5:6]/repo",
225+
tag: "tag",
226+
},
227+
{
228+
input: "[2001:db8::1]:5000/repo",
229+
domain: "[2001:db8::1]:5000",
230+
repository: "[2001:db8::1]:5000/repo",
231+
},
232+
{
233+
input: "[2001:db8::1]:5000/repo:tag",
234+
domain: "[2001:db8::1]:5000",
235+
repository: "[2001:db8::1]:5000/repo",
236+
tag: "tag",
237+
},
238+
{
239+
input: "[2001:db8::1]:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
240+
domain: "[2001:db8::1]:5000",
241+
repository: "[2001:db8::1]:5000/repo",
242+
digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
243+
},
244+
{
245+
input: "[2001:db8::1]:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
246+
domain: "[2001:db8::1]:5000",
247+
repository: "[2001:db8::1]:5000/repo",
248+
tag: "tag",
249+
digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
250+
},
251+
{
252+
input: "[2001:db8::]:5000/repo",
253+
domain: "[2001:db8::]:5000",
254+
repository: "[2001:db8::]:5000/repo",
255+
},
256+
{
257+
input: "[::1]:5000/repo",
258+
domain: "[::1]:5000",
259+
repository: "[::1]:5000/repo",
260+
},
261+
{
262+
input: "[fe80::1%eth0]:5000/repo",
263+
err: ErrReferenceInvalidFormat,
264+
},
265+
{
266+
input: "[fe80::1%@invalidzone]:5000/repo",
267+
err: ErrReferenceInvalidFormat,
268+
},
174269
}
175270
for _, testcase := range referenceTestcases {
176271
failf := func(format string, v ...interface{}) {

reference/regexp.go

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,40 @@ var (
2323
alphaNumeric,
2424
optional(repeated(separator, alphaNumeric)))
2525

26-
// domainComponent restricts the registry domain component of a
27-
// repository name to start with a component as defined by DomainRegexp
28-
// and followed by an optional port.
29-
domainComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`
30-
26+
// domainNameComponent restricts the registry domain component of a
27+
// repository name to start with a component as defined by DomainRegexp.
28+
domainNameComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`
29+
30+
// ipv6address are enclosed between square brackets and may be represented
31+
// in many ways, see rfc5952. Only IPv6 in compressed or uncompressed format
32+
// are allowed, IPv6 zone identifiers (rfc6874) or Special addresses such as
33+
// IPv4-Mapped are deliberately excluded.
34+
ipv6address = expression(
35+
literal(`[`), `(?:[a-fA-F0-9:]+)`, literal(`]`),
36+
)
37+
38+
// domainName defines the structure of potential domain components
39+
// that may be part of image names. This is purposely a subset of what is
40+
// allowed by DNS to ensure backwards compatibility with Docker image
41+
// names. This includes IPv4 addresses on decimal format.
42+
domainName = expression(
43+
domainNameComponent,
44+
optional(repeated(literal(`.`), domainNameComponent)),
45+
)
46+
47+
// host defines the structure of potential domains based on the URI
48+
// Host subcomponent on rfc3986. It may be a subset of DNS domain name,
49+
// or an IPv4 address in decimal format, or an IPv6 address between square
50+
// brackets (excluding zone identifiers as defined by rfc6874 or special
51+
// addresses such as IPv4-Mapped).
52+
host = `(?:` + domainName + `|` + ipv6address + `)`
53+
54+
// allowed by the URI Host subcomponent on rfc3986 to ensure backwards
55+
// compatibility with Docker image names.
3156
domain = expression(
32-
domainComponent,
33-
optional(repeated(literal(`.`), domainComponent)),
57+
host,
3458
optional(literal(`:`), `[0-9]+`))
59+
3560
// DomainRegexp defines the structure of potential domain components
3661
// that may be part of image names. This is purposely a subset of what is
3762
// allowed by DNS to ensure backwards compatibility with Docker image

reference/regexp_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,46 @@ func TestDomainRegexp(t *testing.T) {
115115
input: "Asdf.com", // uppercase character
116116
match: true,
117117
},
118+
{
119+
input: "192.168.1.1:75050", // ipv4
120+
match: true,
121+
},
122+
{
123+
input: "192.168.1.1:750050", // port with more than 5 digits, it will fail on validation
124+
match: true,
125+
},
126+
{
127+
input: "[fd00:1:2::3]:75050", // ipv6 compressed
128+
match: true,
129+
},
130+
{
131+
input: "[fd00:1:2::3]75050", // ipv6 wrong port separator
132+
match: false,
133+
},
134+
{
135+
input: "[fd00:1:2::3]::75050", // ipv6 wrong port separator
136+
match: false,
137+
},
138+
{
139+
input: "[fd00:1:2::3%eth0]:75050", // ipv6 with zone
140+
match: false,
141+
},
142+
{
143+
input: "[fd00123123123]:75050", // ipv6 wrong format, will fail in validation
144+
match: true,
145+
},
146+
{
147+
input: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:75050", // ipv6 long format
148+
match: true,
149+
},
150+
{
151+
input: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:750505", // ipv6 long format and invalid port, it will fail in validation
152+
match: true,
153+
},
154+
{
155+
input: "fd00:1:2::3:75050", // bad ipv6 without square brackets
156+
match: false,
157+
},
118158
}
119159
r := regexp.MustCompile(`^` + DomainRegexp.String() + `$`)
120160
for i := range hostcases {

0 commit comments

Comments
 (0)