Skip to content

Commit b76d3b7

Browse files
committed
libpod: bind both IPv4 and IPv6 addresses with dual-stack wildcard
Currently podman only binds IPv4 wildcard address. But since golang net.Listen("tcp",) listens on all IPv4 and IPv6 addresses, we (intentionally) handle both 4 and 6 here. But the downside was that you could still explicitly request an IPv6 (::) wildcard along with a dual-stack("") wildcard causing netavark to leak nftable rules. This change handles both IPv4(0.0.0.0) and IPv6(::) when no address is passed, and throws EADDRINUSE if an explicit IPv4 or IPv6 is requested on top of dual-stack wildcard. Signed-off-by: Danish Prakash <[email protected]>
1 parent 386c8f3 commit b76d3b7

File tree

2 files changed

+71
-20
lines changed

2 files changed

+71
-20
lines changed

libpod/oci_util.go

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,15 @@ func bindPorts(ports []types.PortMapping) ([]*os.File, error) {
3232
var files []*os.File
3333
sctpWarning := true
3434
for _, port := range ports {
35-
isV6 := net.ParseIP(port.HostIP).To4() == nil
36-
if port.HostIP == "" {
37-
isV6 = false
38-
}
3935
protocols := strings.SplitSeq(port.Protocol, ",")
4036
for protocol := range protocols {
4137
for i := uint16(0); i < port.Range; i++ {
38+
isV6 := false
39+
if port.HostIP != "" {
40+
isV6 = net.ParseIP(port.HostIP).To4() == nil
41+
}
4242
f, err := bindPort(protocol, port.HostIP, port.HostPort+i, isV6, &sctpWarning)
4343
if err != nil {
44-
// close all open ports in case of early error so we do not
45-
// rely garbage collector to close them
4644
for _, f := range files {
4745
f.Close()
4846
}
@@ -65,19 +63,23 @@ func bindPort(protocol, hostIP string, port uint16, isV6 bool, sctpWarning *bool
6563
addr *net.UDPAddr
6664
err error
6765
)
68-
if isV6 {
69-
addr, err = net.ResolveUDPAddr("udp6", fmt.Sprintf("[%s]:%d", hostIP, port))
66+
67+
proto := "udp"
68+
if hostIP != "" {
69+
if isV6 {
70+
proto = "udp6"
71+
addr, err = net.ResolveUDPAddr(proto, fmt.Sprintf("[%s]:%d", hostIP, port))
72+
} else {
73+
proto = "udp4"
74+
addr, err = net.ResolveUDPAddr(proto, fmt.Sprintf("%s:%d", hostIP, port))
75+
}
7076
} else {
71-
addr, err = net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", hostIP, port))
77+
addr, err = net.ResolveUDPAddr(proto, fmt.Sprintf(":%d", port))
7278
}
7379
if err != nil {
7480
return nil, fmt.Errorf("cannot resolve the UDP address: %w", err)
7581
}
7682

77-
proto := "udp4"
78-
if isV6 {
79-
proto = "udp6"
80-
}
8183
server, err := net.ListenUDP(proto, addr)
8284
if err != nil {
8385
return nil, fmt.Errorf("cannot listen on the UDP port: %w", err)
@@ -98,19 +100,24 @@ func bindPort(protocol, hostIP string, port uint16, isV6 bool, sctpWarning *bool
98100
addr *net.TCPAddr
99101
err error
100102
)
101-
if isV6 {
102-
addr, err = net.ResolveTCPAddr("tcp6", fmt.Sprintf("[%s]:%d", hostIP, port))
103+
104+
// dual-stack bind by default unless hostIP is specified
105+
proto := "tcp"
106+
if hostIP != "" {
107+
if isV6 {
108+
proto = "tcp6"
109+
addr, err = net.ResolveTCPAddr(proto, fmt.Sprintf("[%s]:%d", hostIP, port))
110+
} else {
111+
proto = "tcp4"
112+
addr, err = net.ResolveTCPAddr(proto, fmt.Sprintf("%s:%d", hostIP, port))
113+
}
103114
} else {
104-
addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", hostIP, port))
115+
addr, err = net.ResolveTCPAddr(proto, fmt.Sprintf(":%d", port))
105116
}
106117
if err != nil {
107118
return nil, fmt.Errorf("cannot resolve the TCP address: %w", err)
108119
}
109120

110-
proto := "tcp4"
111-
if isV6 {
112-
proto = "tcp6"
113-
}
114121
server, err := net.ListenTCP(proto, addr)
115122
if err != nil {
116123
return nil, fmt.Errorf("cannot listen on the TCP port: %w", err)

test/system/500-networking.bats

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,4 +1122,48 @@ EOF
11221122
is "$output" ".*${pasta_iface}.*"
11231123
}
11241124

1125+
# Refer https://github.com/containers/netavark/issues/1338
1126+
# bats test_tags=ci:parallel
1127+
@test "podman run - dual-stack port binding" {
1128+
skip_if_rootless
1129+
1130+
myport=$(random_free_port)
1131+
cname="c-$(safename)"
1132+
teststring=$(random_string 30)
1133+
1134+
run_podman run -d --name $cname -p $myport:$myport \
1135+
$IMAGE nc -l -n -v -p $myport
1136+
cid="$output"
1137+
1138+
wait_for_output "listening on .*:$myport .*" $cid
1139+
1140+
echo "$teststring" > /dev/tcp/127.0.0.1/$myport
1141+
1142+
run_podman logs $cid
1143+
assert "$output" =~ "listening on \[::\]:$myport" "nc listening on IPv6 wildcard (dual-stack)"
1144+
assert "$output" =~ "$teststring" "IPv4 connection received"
1145+
1146+
run_podman rm -f -t0 $cid
1147+
}
1148+
1149+
# Refer https://github.com/containers/netavark/issues/1338
1150+
# bats test_tags=ci:parallel
1151+
@test "podman run - dual-stack conflicts with explicit wildcards" {
1152+
skip_if_rootless "port reservation only works as root"
1153+
1154+
myport=$(random_free_port)
1155+
cname1="c1-$(safename)"
1156+
1157+
run_podman run -d --name $cname1 -p $myport:8080 $IMAGE sleep inf
1158+
cid1="$output"
1159+
1160+
run_podman 126 run --rm -p 0.0.0.0:$myport:8080 $IMAGE true
1161+
assert "$output" =~ "address already in use" "explicit IPv4 wildcard should conflict with dual-stack"
1162+
1163+
run_podman 126 run --rm -p [::]:$myport:8080 $IMAGE true
1164+
assert "$output" =~ "address already in use" "explicit IPv6 wildcard should conflict with dual-stack"
1165+
1166+
run_podman rm -f -t0 $cid1
1167+
}
1168+
11251169
# vim: filetype=sh

0 commit comments

Comments
 (0)