Skip to content

Commit 4e10732

Browse files
vsiravarswagatbora90
authored andcommitted
Check if port is used in publish flag
Signed-off-by: Vishwas Siravara <vsiravara@gmail.com>
1 parent 546e476 commit 4e10732

File tree

4 files changed

+107
-24
lines changed

4 files changed

+107
-24
lines changed

cmd/nerdctl/container/container_run_network_linux_test.go

+55
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,61 @@ func TestUniqueHostPortAssignement(t *testing.T) {
350350
}
351351
}
352352

353+
func TestHostPortAlreadyInUse(t *testing.T) {
354+
testCases := []struct {
355+
hostPort string
356+
containerPort string
357+
}{
358+
{
359+
hostPort: "5000",
360+
containerPort: "80/tcp",
361+
},
362+
{
363+
hostPort: "5000",
364+
containerPort: "80/tcp",
365+
},
366+
{
367+
hostPort: "5000",
368+
containerPort: "80/udp",
369+
},
370+
{
371+
hostPort: "5000",
372+
containerPort: "80/sctp",
373+
},
374+
}
375+
376+
tID := testutil.Identifier(t)
377+
378+
for i, tc := range testCases {
379+
tc := tc
380+
tcName := fmt.Sprintf("%+v", tc)
381+
t.Run(tcName, func(t *testing.T) {
382+
if strings.Contains(tc.containerPort, "sctp") && rootlessutil.IsRootless() {
383+
t.Skip("sctp is not supported in rootless mode")
384+
}
385+
testContainerName1 := fmt.Sprintf("%s-%d-1", tID, i)
386+
testContainerName2 := fmt.Sprintf("%s-%d-2", tID, i)
387+
base := testutil.NewBase(t)
388+
t.Cleanup(func() {
389+
base.Cmd("rm", "-f", testContainerName1, testContainerName2).AssertOk()
390+
}
391+
pFlag := fmt.Sprintf("%s:%s", tc.hostPort, tc.containerPort)
392+
cmd1 := base.Cmd("run", "-d",
393+
"--name", testContainerName1, "-p",
394+
pFlag,
395+
testutil.NginxAlpineImage)
396+
397+
cmd2 := base.Cmd("run", "-d",
398+
"--name", testContainerName2, "-p",
399+
pFlag,
400+
testutil.NginxAlpineImage)
401+
402+
cmd1.AssertOK()
403+
cmd2.AssertFail()
404+
})
405+
}
406+
}
407+
353408
func TestRunPort(t *testing.T) {
354409
baseTestRunPort(t, testutil.NginxAlpineImage, testutil.NginxAlpineIndexHTMLSnippet, true)
355410
}

pkg/portutil/port_allocate_linux.go

+38-24
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,56 @@ func filter(ss []procnet.NetworkDetail, filterFunc func(detail procnet.NetworkDe
4242
}
4343

4444
func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, error) {
45-
netprocData, err := procnet.ReadStatsFileData(protocol)
45+
usedPort, err := getUsedPorts(ip, protocol)
4646
if err != nil {
4747
return 0, 0, err
4848
}
49-
netprocItems := procnet.Parse(netprocData)
49+
50+
start := uint64(allocateStart)
51+
if count > uint64(allocateEnd-allocateStart+1) {
52+
return 0, 0, fmt.Errorf("can not allocate %d ports", count)
53+
}
54+
for start < allocateEnd {
55+
needReturn := true
56+
for i := start; i < start+count; i++ {
57+
if _, ok := usedPort[i]; ok {
58+
needReturn = false
59+
break
60+
}
61+
}
62+
if needReturn {
63+
allocateStart = int(start + count)
64+
return start, start + count - 1, nil
65+
}
66+
start += count
67+
}
68+
return 0, 0, fmt.Errorf("there is not enough %d free ports", count)
69+
}
70+
71+
func getUsedPorts(ip string, protocol string) (map[uint64]bool, error) {
72+
netprocItems := []procnet.NetworkDetail{}
73+
74+
if protocol == "tcp" || protocol == "udp" {
75+
netprocData, err := procnet.ReadStatsFileData(protocol)
76+
if err != nil {
77+
return nil, err
78+
}
79+
netprocItems = append(netprocItems, procnet.Parse(netprocData)...)
80+
}
81+
5082
// In some circumstances, when we bind address like "0.0.0.0:80", we will get the formation of ":::80" in /proc/net/tcp6.
5183
// So we need some trick to process this situation.
5284
if protocol == "tcp" {
5385
tempTCPV6Data, err := procnet.ReadStatsFileData("tcp6")
5486
if err != nil {
55-
return 0, 0, err
87+
return nil, err
5688
}
5789
netprocItems = append(netprocItems, procnet.Parse(tempTCPV6Data)...)
5890
}
5991
if protocol == "udp" {
6092
tempUDPV6Data, err := procnet.ReadStatsFileData("udp6")
6193
if err != nil {
62-
return 0, 0, err
94+
return nil, err
6395
}
6496
netprocItems = append(netprocItems, procnet.Parse(tempUDPV6Data)...)
6597
}
@@ -78,31 +110,13 @@ func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, err
78110

79111
ipTableItems, err := iptable.ReadIPTables("nat")
80112
if err != nil {
81-
return 0, 0, err
113+
return nil, err
82114
}
83115
destinationPorts := iptable.ParseIPTableRules(ipTableItems)
84116

85117
for _, port := range destinationPorts {
86118
usedPort[port] = true
87119
}
88120

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

pkg/portutil/port_allocate_other.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
@@ -59,6 +59,7 @@ func ParseFlagP(s string) ([]cni.PortMapping, error) {
5959
case 2:
6060
proto = strings.ToLower(splitBySlash[1])
6161
switch proto {
62+
// sctp is not a supported protocol
6263
case "tcp", "udp", "sctp":
6364
default:
6465
return nil, fmt.Errorf("invalid protocol %q", splitBySlash[1])
@@ -101,6 +102,15 @@ func ParseFlagP(s string) ([]cni.PortMapping, error) {
101102
if err != nil {
102103
return nil, fmt.Errorf("invalid hostPort: %s", hostPort)
103104
}
105+
usedPorts, err := getUsedPorts(ip, proto)
106+
if err != nil {
107+
return nil, err
108+
}
109+
for i := startHostPort; i <= endHostPort; i++ {
110+
if usedPorts[i] {
111+
return nil, fmt.Errorf("bind for %s:%d failed: port is already allocated", ip, i)
112+
}
113+
}
104114
}
105115
if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
106116
if endPort != startPort {

0 commit comments

Comments
 (0)