Skip to content

Commit d7ff1e2

Browse files
committed
BUG/MEDIUM: dgram-bind: fix IPv6 address parsing and serialization
1 parent 765d0a5 commit d7ff1e2

File tree

3 files changed

+189
-35
lines changed

3 files changed

+189
-35
lines changed

.aspell.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ allowed:
5151
- dataplanes
5252
- datepicker
5353
- dereference
54+
- dgram
5455
- discoverability
5556
- durations
5657
- dns

configuration/dgram_bind.go

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -188,42 +188,27 @@ func ParseDgramBinds(logForward string, p parser.Parser) (models.DgramBinds, err
188188

189189
func ParseDgramBind(ondiskDgramBind types.DgramBind) *models.DgramBind {
190190
b := &models.DgramBind{}
191-
if strings.HasPrefix(ondiskDgramBind.Path, "/") {
192-
b.Address = ondiskDgramBind.Path
193-
} else {
194-
addSlice := strings.Split(ondiskDgramBind.Path, ":")
195-
switch n := len(addSlice); {
196-
case n == 0:
197-
return nil
198-
case n == 4: // :::443
199-
b.Address = "::"
200-
if addSlice[3] != "" {
201-
p, err := strconv.ParseInt(addSlice[3], 10, 64)
202-
if err == nil {
203-
b.Port = &p
204-
}
205-
}
206-
case n > 1:
207-
b.Address = addSlice[0]
208-
ports := strings.Split(addSlice[1], "-")
209-
210-
// *:<port>
211-
if ports[0] != "" {
212-
port, err := strconv.ParseInt(ports[0], 10, 64)
213-
if err == nil {
214-
b.Port = &port
215-
}
191+
address, port, err := misc.ParseBindAddress(ondiskDgramBind.Path)
192+
if err != nil {
193+
return nil
194+
}
195+
b.Address = address
196+
if port != "" {
197+
ports := strings.Split(port, "-")
198+
// *:<port>
199+
if ports[0] != "" {
200+
p, err := strconv.ParseInt(ports[0], 10, 64)
201+
if err == nil {
202+
b.Port = &p
216203
}
217-
// *:<port-first>-<port-last>
218-
if b.Port != nil && len(ports) == 2 {
219-
portRangeEnd, err := strconv.ParseInt(ports[1], 10, 64)
220-
// Deny inverted interval.
221-
if err == nil && (*b.Port < portRangeEnd) {
222-
b.PortRangeEnd = &portRangeEnd
223-
}
204+
}
205+
// *:<port-first>-<port-last>
206+
if b.Port != nil && len(ports) == 2 {
207+
portRangeEnd, err := strconv.ParseInt(ports[1], 10, 64)
208+
// Deny inverted interval.
209+
if err == nil && (*b.Port < portRangeEnd) {
210+
b.PortRangeEnd = &portRangeEnd
224211
}
225-
case n > 0:
226-
b.Address = addSlice[0]
227212
}
228213
}
229214
for _, p := range ondiskDgramBind.Params {
@@ -255,7 +240,7 @@ func SerializeDgramBind(b models.DgramBind) types.DgramBind {
255240
Params: []params.DgramBindOption{},
256241
}
257242
if b.Port != nil {
258-
dBind.Path = b.Address + ":" + strconv.FormatInt(*b.Port, 10)
243+
dBind.Path = misc.SanitizeIPv6Address(b.Address) + ":" + strconv.FormatInt(*b.Port, 10)
259244
if b.PortRangeEnd != nil {
260245
dBind.Path = dBind.Path + "-" + strconv.FormatInt(*b.PortRangeEnd, 10)
261246
}

configuration/dgram_bind_test.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package configuration
2+
3+
import (
4+
"testing"
5+
6+
"github.com/haproxytech/client-native/v6/config-parser/params"
7+
"github.com/haproxytech/client-native/v6/config-parser/types"
8+
"github.com/haproxytech/client-native/v6/models"
9+
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestParseDgramBindIPv6(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
path string
17+
params []params.DgramBindOption
18+
expectedAddress string
19+
expectedPort *int64
20+
expectedName string
21+
}{
22+
{
23+
name: "bracketed IPv6 with port",
24+
path: "[fd66:c3ec:c7fc::7c]:123",
25+
params: []params.DgramBindOption{&params.BindOptionValue{Name: "name", Value: "ntp6"}},
26+
expectedAddress: "fd66:c3ec:c7fc::7c",
27+
expectedPort: int64P(123),
28+
expectedName: "ntp6",
29+
},
30+
{
31+
name: "bracketed IPv6 with port and no name param",
32+
path: "[2a04:4b07:21dc:218::1]:53",
33+
expectedAddress: "2a04:4b07:21dc:218::1",
34+
expectedPort: int64P(53),
35+
expectedName: "[2a04:4b07:21dc:218::1]:53",
36+
},
37+
{
38+
name: "double colon with port",
39+
path: ":::443",
40+
expectedAddress: "::",
41+
expectedPort: int64P(443),
42+
expectedName: ":::443",
43+
},
44+
{
45+
name: "IPv4 with port",
46+
path: "10.0.0.1:8080",
47+
params: []params.DgramBindOption{&params.BindOptionValue{Name: "name", Value: "test"}},
48+
expectedAddress: "10.0.0.1",
49+
expectedPort: int64P(8080),
50+
expectedName: "test",
51+
},
52+
{
53+
name: "wildcard with port",
54+
path: ":443",
55+
expectedAddress: "",
56+
expectedPort: int64P(443),
57+
expectedName: ":443",
58+
},
59+
{
60+
name: "unix socket path",
61+
path: "/var/run/haproxy.sock",
62+
expectedAddress: "/var/run/haproxy.sock",
63+
expectedPort: nil,
64+
expectedName: "/var/run/haproxy.sock",
65+
},
66+
}
67+
68+
for _, tc := range tests {
69+
t.Run(tc.name, func(t *testing.T) {
70+
ondisk := types.DgramBind{
71+
Path: tc.path,
72+
Params: tc.params,
73+
}
74+
result := ParseDgramBind(ondisk)
75+
require.NotNil(t, result, "ParseDgramBind returned nil for path %q", tc.path)
76+
require.Equal(t, tc.expectedAddress, result.Address, "address mismatch")
77+
if tc.expectedPort != nil {
78+
require.NotNil(t, result.Port, "expected port to be set")
79+
require.Equal(t, *tc.expectedPort, *result.Port, "port mismatch")
80+
} else {
81+
require.Nil(t, result.Port, "expected port to be nil")
82+
}
83+
require.Equal(t, tc.expectedName, result.Name, "name mismatch")
84+
})
85+
}
86+
}
87+
88+
func TestSerializeDgramBindIPv6(t *testing.T) {
89+
tests := []struct {
90+
name string
91+
dgramBind models.DgramBind
92+
expectedPath string
93+
}{
94+
{
95+
name: "IPv6 address with port",
96+
dgramBind: models.DgramBind{
97+
Address: "fd66:c3ec:c7fc::7c",
98+
Port: int64P(123),
99+
Name: "ntp6",
100+
},
101+
expectedPath: "[fd66:c3ec:c7fc::7c]:123",
102+
},
103+
{
104+
name: "IPv6 double colon with port",
105+
dgramBind: models.DgramBind{
106+
Address: "::",
107+
Port: int64P(443),
108+
Name: "test",
109+
},
110+
expectedPath: "[::]:443",
111+
},
112+
{
113+
name: "IPv4 address with port",
114+
dgramBind: models.DgramBind{
115+
Address: "10.0.0.1",
116+
Port: int64P(8080),
117+
Name: "test",
118+
},
119+
expectedPath: "10.0.0.1:8080",
120+
},
121+
{
122+
name: "address only, no port",
123+
dgramBind: models.DgramBind{
124+
Address: "/var/run/haproxy.sock",
125+
Name: "test",
126+
},
127+
expectedPath: "/var/run/haproxy.sock",
128+
},
129+
}
130+
131+
for _, tc := range tests {
132+
t.Run(tc.name, func(t *testing.T) {
133+
result := SerializeDgramBind(tc.dgramBind)
134+
require.Equal(t, tc.expectedPath, result.Path, "serialized path mismatch")
135+
})
136+
}
137+
}
138+
139+
func TestParseDgramBindRoundTrip(t *testing.T) {
140+
tests := []struct {
141+
name string
142+
path string
143+
}{
144+
{"IPv6 bracketed", "[fd66:c3ec:c7fc::7c]:123"},
145+
{"IPv6 full", "[2a04:4b07:21dc:218::1]:53"},
146+
{"IPv6 double colon", "[::]:443"},
147+
{"IPv4", "10.0.0.1:8080"},
148+
{"wildcard", ":443"},
149+
}
150+
151+
for _, tc := range tests {
152+
t.Run(tc.name, func(t *testing.T) {
153+
ondisk := types.DgramBind{
154+
Path: tc.path,
155+
Params: []params.DgramBindOption{&params.BindOptionValue{Name: "name", Value: "roundtrip"}},
156+
}
157+
parsed := ParseDgramBind(ondisk)
158+
require.NotNil(t, parsed, "ParseDgramBind returned nil for path %q", tc.path)
159+
160+
serialized := SerializeDgramBind(*parsed)
161+
require.Equal(t, tc.path, serialized.Path, "round-trip path mismatch")
162+
})
163+
}
164+
}
165+
166+
func int64P(v int64) *int64 {
167+
return &v
168+
}

0 commit comments

Comments
 (0)