Skip to content

Commit 3442f88

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

File tree

4 files changed

+93
-23
lines changed

4 files changed

+93
-23
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

+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

+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, proto)
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("bind for %s:%d failed: port is already allocated", ip, 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)