Skip to content

Commit 984ffc5

Browse files
committed
Check if port is used in publish flag
Signed-off-by: Vishwas Siravara <vsiravara@gmail.com>
1 parent 1e25ba7 commit 984ffc5

File tree

4 files changed

+147
-23
lines changed

4 files changed

+147
-23
lines changed

cmd/nerdctl/container_run_network_linux_test.go

+93
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,99 @@ func TestUniqueHostPortAssignement(t *testing.T) {
302302
}
303303
}
304304

305+
func TestHostPortAlreadyInUse(t *testing.T) {
306+
testCases := []struct {
307+
hostPort string
308+
containerPort string
309+
}{
310+
{
311+
hostPort: "5000",
312+
containerPort: "80/tcp",
313+
},
314+
{
315+
hostPort: "5000",
316+
containerPort: "80/tcp",
317+
},
318+
{
319+
hostPort: "5000",
320+
containerPort: "80/udp",
321+
},
322+
}
323+
324+
tID := testutil.Identifier(t)
325+
326+
for i, tc := range testCases {
327+
328+
i := i
329+
tc := tc
330+
tcName := fmt.Sprintf("%+v", tc)
331+
t.Run(tcName, func(t *testing.T) {
332+
testContainerName1 := fmt.Sprintf("%s-%d-1", tID, i)
333+
testContainerName2 := fmt.Sprintf("%s-%d-2", tID, i)
334+
base := testutil.NewBase(t)
335+
defer base.Cmd("rm", "-f", testContainerName1, testContainerName2).Run()
336+
337+
pFlag := fmt.Sprintf("%s:%s", tc.hostPort, tc.containerPort)
338+
cmd1 := base.Cmd("run", "-d",
339+
"--name", testContainerName1, "-p",
340+
pFlag,
341+
testutil.NginxAlpineImage)
342+
343+
cmd2 := base.Cmd("run", "-d",
344+
"--name", testContainerName2, "-p",
345+
pFlag,
346+
testutil.NginxAlpineImage)
347+
348+
cmd1.AssertOK()
349+
cmd2.AssertFail()
350+
})
351+
}
352+
}
353+
354+
func TestHostPortAlreadyInUseForSctp(t *testing.T) {
355+
if rootlessutil.IsRootless() {
356+
t.Skip("sctp is not supported rootless mode")
357+
}
358+
testCases := []struct {
359+
hostPort string
360+
containerPort string
361+
}{
362+
{
363+
hostPort: "5000",
364+
containerPort: "80/sctp",
365+
},
366+
}
367+
368+
tID := testutil.Identifier(t)
369+
370+
for i, tc := range testCases {
371+
372+
i := i
373+
tc := tc
374+
tcName := fmt.Sprintf("%+v", tc)
375+
t.Run(tcName, func(t *testing.T) {
376+
testContainerName1 := fmt.Sprintf("%s-%d-1", tID, i)
377+
testContainerName2 := fmt.Sprintf("%s-%d-2", tID, i)
378+
base := testutil.NewBase(t)
379+
defer base.Cmd("rm", "-f", testContainerName1, testContainerName2).Run()
380+
381+
pFlag := fmt.Sprintf("%s:%s", tc.hostPort, tc.containerPort)
382+
cmd1 := base.Cmd("run", "-d",
383+
"--name", testContainerName1, "-p",
384+
pFlag,
385+
testutil.NginxAlpineImage)
386+
387+
cmd2 := base.Cmd("run", "-d",
388+
"--name", testContainerName2, "-p",
389+
pFlag,
390+
testutil.NginxAlpineImage)
391+
392+
cmd1.AssertOK()
393+
cmd2.AssertFail()
394+
})
395+
}
396+
}
397+
305398
func TestRunPort(t *testing.T) {
306399
baseTestRunPort(t, testutil.NginxAlpineImage, testutil.NginxAlpineIndexHTMLSnippet, true)
307400
}

pkg/portutil/port_allocate_linux.go

+39-23
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,60 @@ func filter(ss []procnet.NetworkDetail, filterFunc func(detail procnet.NetworkDe
3939
}
4040

4141
func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, error) {
42-
netprocData, err := procnet.ReadStatsFileData(protocol)
42+
usedPort, err := getUsedPorts(ip, protocol)
43+
4344
if err != nil {
4445
return 0, 0, err
4546
}
46-
netprocItems := procnet.Parse(netprocData)
47+
48+
start := uint64(allocateStart)
49+
if count > uint64(allocateEnd-allocateStart+1) {
50+
return 0, 0, fmt.Errorf("can not allocate %d ports", count)
51+
}
52+
for start < allocateEnd {
53+
needReturn := true
54+
for i := start; i < start+count; i++ {
55+
if _, ok := usedPort[i]; ok {
56+
needReturn = false
57+
break
58+
}
59+
}
60+
if needReturn {
61+
return start, start + count - 1, nil
62+
}
63+
start += count
64+
}
65+
return 0, 0, fmt.Errorf("there is not enough %d free ports", count)
66+
}
67+
68+
func getUsedPorts(ip string, protocol string) (map[uint64]bool, error) {
69+
netprocItems := []procnet.NetworkDetail{}
70+
71+
if protocol == "tcp" || protocol == "udp" {
72+
netprocData, err := procnet.ReadStatsFileData(protocol)
73+
if err != nil {
74+
return nil, err
75+
}
76+
netprocItems = append(netprocItems, procnet.Parse(netprocData)...)
77+
}
78+
4779
// In some circumstances, when we bind address like "0.0.0.0:80", we will get the formation of ":::80" in /proc/net/tcp6.
4880
// So we need some trick to process this situation.
4981
if protocol == "tcp" {
5082
tempTCPV6Data, err := procnet.ReadStatsFileData("tcp6")
5183
if err != nil {
52-
return 0, 0, err
84+
return nil, err
5385
}
5486
netprocItems = append(netprocItems, procnet.Parse(tempTCPV6Data)...)
5587
}
5688
if protocol == "udp" {
5789
tempUDPV6Data, err := procnet.ReadStatsFileData("udp6")
5890
if err != nil {
59-
return 0, 0, err
91+
return nil, err
6092
}
6193
netprocItems = append(netprocItems, procnet.Parse(tempUDPV6Data)...)
6294
}
95+
6396
if ip != "" {
6497
netprocItems = filter(netprocItems, func(s procnet.NetworkDetail) bool {
6598
// In some circumstances, when we bind address like "0.0.0.0:80", we will get the formation of ":::80" in /proc/net/tcp6.
@@ -75,30 +108,13 @@ func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, err
75108

76109
ipTableItems, err := iptable.ReadIPTables("nat")
77110
if err != nil {
78-
return 0, 0, err
111+
return nil, err
79112
}
80113
destinationPorts := iptable.ParseIPTableRules(ipTableItems)
81114

82115
for _, port := range destinationPorts {
83116
usedPort[port] = true
84117
}
85118

86-
start := uint64(allocateStart)
87-
if count > uint64(allocateEnd-allocateStart+1) {
88-
return 0, 0, fmt.Errorf("can not allocate %d ports", count)
89-
}
90-
for start < allocateEnd {
91-
needReturn := true
92-
for i := start; i < start+count; i++ {
93-
if _, ok := usedPort[i]; ok {
94-
needReturn = false
95-
break
96-
}
97-
}
98-
if needReturn {
99-
return start, start + count - 1, nil
100-
}
101-
start += count
102-
}
103-
return 0, 0, fmt.Errorf("there is not enough %d free ports", count)
119+
return usedPort, nil
104120
}

pkg/portutil/port_allocate_others.go

+4
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,7 @@ import "fmt"
2323
func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, error) {
2424
return 0, 0, fmt.Errorf("auto port allocate are not support Non-Linux platform yet")
2525
}
26+
27+
func getUsedPorts(ip string, protocol string) (map[uint64]bool, error) {
28+
return nil, nil
29+
}

pkg/portutil/portutil.go

+11
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func ParseFlagP(s string) ([]gocni.PortMapping, error) {
5858
case 2:
5959
proto = strings.ToLower(splitBySlash[1])
6060
switch proto {
61+
// sctp is not a supported protocol
6162
case "tcp", "udp", "sctp":
6263
default:
6364
return nil, fmt.Errorf("invalid protocol %q", splitBySlash[1])
@@ -100,7 +101,17 @@ func ParseFlagP(s string) ([]gocni.PortMapping, error) {
100101
if err != nil {
101102
return nil, fmt.Errorf("invalid hostPort: %s", hostPort)
102103
}
104+
usedPorts, err := getUsedPorts(ip, proto)
105+
if err != nil {
106+
return nil, err
107+
}
108+
for i := startHostPort; i <= endHostPort; i++ {
109+
if usedPorts[i] {
110+
return nil, fmt.Errorf("bind for %s:%d failed: port is already allocated", ip, i)
111+
}
112+
}
103113
}
114+
104115
if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
105116
if endPort != startPort {
106117
return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)

0 commit comments

Comments
 (0)