Skip to content

Commit 6dbac87

Browse files
committed
Add used port check for publish flag
Signed-off-by: Vishwas Siravara <vsiravara@gmail.com>
1 parent 1e25ba7 commit 6dbac87

File tree

4 files changed

+87
-22
lines changed

4 files changed

+87
-22
lines changed

cmd/nerdctl/container_run_network_linux_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,46 @@ 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",
313+
},
314+
}
315+
316+
tID := testutil.Identifier(t)
317+
318+
for i, tc := range testCases {
319+
i := i
320+
tc := tc
321+
tcName := fmt.Sprintf("%+v", tc)
322+
t.Run(tcName, func(t *testing.T) {
323+
testContainerName1 := fmt.Sprintf("%s-%d-1", tID, i)
324+
testContainerName2 := fmt.Sprintf("%s-%d-2", tID, i)
325+
base := testutil.NewBase(t)
326+
defer base.Cmd("rm", "-f", testContainerName1, testContainerName2).Run()
327+
328+
pFlag := fmt.Sprintf("%s:%s", tc.hostPort, tc.containerPort)
329+
cmd1 := base.Cmd("run", "-d",
330+
"--name", testContainerName1, "-p",
331+
pFlag,
332+
testutil.NginxAlpineImage)
333+
334+
cmd2 := base.Cmd("run", "-d",
335+
"--name", testContainerName2, "-p",
336+
pFlag,
337+
testutil.NginxAlpineImage)
338+
339+
cmd1.AssertOK()
340+
cmd2.AssertFail()
341+
})
342+
}
343+
}
344+
305345
func TestRunPort(t *testing.T) {
306346
baseTestRunPort(t, testutil.NginxAlpineImage, testutil.NginxAlpineIndexHTMLSnippet, true)
307347
}

pkg/portutil/port_allocate_linux.go

+33-22
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,55 @@ 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
}
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+
netprocData, err := procnet.ReadStatsFileData(protocol)
70+
if err != nil {
71+
return nil, err
72+
}
4673
netprocItems := procnet.Parse(netprocData)
4774
// In some circumstances, when we bind address like "0.0.0.0:80", we will get the formation of ":::80" in /proc/net/tcp6.
4875
// So we need some trick to process this situation.
4976
if protocol == "tcp" {
5077
tempTCPV6Data, err := procnet.ReadStatsFileData("tcp6")
5178
if err != nil {
52-
return 0, 0, err
79+
return nil, err
5380
}
5481
netprocItems = append(netprocItems, procnet.Parse(tempTCPV6Data)...)
5582
}
5683
if protocol == "udp" {
5784
tempUDPV6Data, err := procnet.ReadStatsFileData("udp6")
5885
if err != nil {
59-
return 0, 0, err
86+
return nil, err
6087
}
6188
netprocItems = append(netprocItems, procnet.Parse(tempUDPV6Data)...)
6289
}
90+
6391
if ip != "" {
6492
netprocItems = filter(netprocItems, func(s procnet.NetworkDetail) bool {
6593
// 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 +103,13 @@ func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, err
75103

76104
ipTableItems, err := iptable.ReadIPTables("nat")
77105
if err != nil {
78-
return 0, 0, err
106+
return nil, err
79107
}
80108
destinationPorts := iptable.ParseIPTableRules(ipTableItems)
81109

82110
for _, port := range destinationPorts {
83111
usedPort[port] = true
84112
}
85113

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)
114+
return usedPort, nil
104115
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,17 @@ func ParseFlagP(s string) ([]gocni.PortMapping, error) {
100100
if err != nil {
101101
return nil, fmt.Errorf("invalid hostPort: %s", hostPort)
102102
}
103+
usedPorts, err := getUsedPorts(ip, "tcp")
104+
if err != nil {
105+
return nil, err
106+
}
107+
for i := startHostPort; i <= endHostPort; i++ {
108+
if usedPorts[i] {
109+
return nil, fmt.Errorf("ports are not available: exposing port %s %d in use", proto, i)
110+
}
111+
}
103112
}
113+
104114
if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
105115
if endPort != startPort {
106116
return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)

0 commit comments

Comments
 (0)