Skip to content

Commit eded758

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

File tree

4 files changed

+109
-23
lines changed

4 files changed

+109
-23
lines changed

cmd/nerdctl/container_run_network_linux_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,62 @@ 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+
hostPort: "5000",
324+
containerPort: "80/sctp",
325+
},
326+
}
327+
328+
tID := testutil.Identifier(t)
329+
330+
for i, tc := range testCases {
331+
332+
i := i
333+
tc := tc
334+
tcName := fmt.Sprintf("%+v", tc)
335+
t.Run(tcName, func(t *testing.T) {
336+
if strings.Contains(tc.containerPort, "sctp") && rootlessutil.IsRootless() {
337+
t.Skip("sctp is not supported in rootless mode")
338+
}
339+
testContainerName1 := fmt.Sprintf("%s-%d-1", tID, i)
340+
testContainerName2 := fmt.Sprintf("%s-%d-2", tID, i)
341+
base := testutil.NewBase(t)
342+
defer base.Cmd("rm", "-f", testContainerName1, testContainerName2).Run()
343+
344+
pFlag := fmt.Sprintf("%s:%s", tc.hostPort, tc.containerPort)
345+
cmd1 := base.Cmd("run", "-d",
346+
"--name", testContainerName1, "-p",
347+
pFlag,
348+
testutil.NginxAlpineImage)
349+
350+
cmd2 := base.Cmd("run", "-d",
351+
"--name", testContainerName2, "-p",
352+
pFlag,
353+
testutil.NginxAlpineImage)
354+
355+
cmd1.AssertOK()
356+
cmd2.AssertFail()
357+
})
358+
}
359+
}
360+
305361
func TestRunPort(t *testing.T) {
306362
baseTestRunPort(t, testutil.NginxAlpineImage, testutil.NginxAlpineIndexHTMLSnippet, true)
307363
}

pkg/portutil/port_allocate_linux.go

+38-23
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,59 @@ 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)
4343
if err != nil {
4444
return 0, 0, err
4545
}
46-
netprocItems := procnet.Parse(netprocData)
46+
47+
start := uint64(allocateStart)
48+
if count > uint64(allocateEnd-allocateStart+1) {
49+
return 0, 0, fmt.Errorf("can not allocate %d ports", count)
50+
}
51+
for start < allocateEnd {
52+
needReturn := true
53+
for i := start; i < start+count; i++ {
54+
if _, ok := usedPort[i]; ok {
55+
needReturn = false
56+
break
57+
}
58+
}
59+
if needReturn {
60+
return start, start + count - 1, nil
61+
}
62+
start += count
63+
}
64+
return 0, 0, fmt.Errorf("there is not enough %d free ports", count)
65+
}
66+
67+
func getUsedPorts(ip string, protocol string) (map[uint64]bool, error) {
68+
netprocItems := []procnet.NetworkDetail{}
69+
70+
if protocol == "tcp" || protocol == "udp" {
71+
netprocData, err := procnet.ReadStatsFileData(protocol)
72+
if err != nil {
73+
return nil, err
74+
}
75+
netprocItems = append(netprocItems, procnet.Parse(netprocData)...)
76+
}
77+
4778
// In some circumstances, when we bind address like "0.0.0.0:80", we will get the formation of ":::80" in /proc/net/tcp6.
4879
// So we need some trick to process this situation.
4980
if protocol == "tcp" {
5081
tempTCPV6Data, err := procnet.ReadStatsFileData("tcp6")
5182
if err != nil {
52-
return 0, 0, err
83+
return nil, err
5384
}
5485
netprocItems = append(netprocItems, procnet.Parse(tempTCPV6Data)...)
5586
}
5687
if protocol == "udp" {
5788
tempUDPV6Data, err := procnet.ReadStatsFileData("udp6")
5889
if err != nil {
59-
return 0, 0, err
90+
return nil, err
6091
}
6192
netprocItems = append(netprocItems, procnet.Parse(tempUDPV6Data)...)
6293
}
94+
6395
if ip != "" {
6496
netprocItems = filter(netprocItems, func(s procnet.NetworkDetail) bool {
6597
// 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 +107,13 @@ func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, err
75107

76108
ipTableItems, err := iptable.ReadIPTables("nat")
77109
if err != nil {
78-
return 0, 0, err
110+
return nil, err
79111
}
80112
destinationPorts := iptable.ParseIPTableRules(ipTableItems)
81113

82114
for _, port := range destinationPorts {
83115
usedPort[port] = true
84116
}
85117

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)
118+
return usedPort, nil
104119
}

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)