Skip to content

[Bugfix] Check if port is used in publish flag [duplicate of #2190] #4097

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions cmd/nerdctl/container/container_run_network_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,61 @@ func TestUniqueHostPortAssignement(t *testing.T) {
}
}

func TestHostPortAlreadyInUse(t *testing.T) {
testCases := []struct {
hostPort string
containerPort string
}{
{
hostPort: "5000",
containerPort: "80/tcp",
},
{
hostPort: "5000",
containerPort: "80/tcp",
},
{
hostPort: "5000",
containerPort: "80/udp",
},
{
hostPort: "5000",
containerPort: "80/sctp",
},
}

tID := testutil.Identifier(t)

for i, tc := range testCases {
tc := tc
tcName := fmt.Sprintf("%+v", tc)
t.Run(tcName, func(t *testing.T) {
if strings.Contains(tc.containerPort, "sctp") && rootlessutil.IsRootless() {
t.Skip("sctp is not supported in rootless mode")
}
testContainerName1 := fmt.Sprintf("%s-%d-1", tID, i)
testContainerName2 := fmt.Sprintf("%s-%d-2", tID, i)
base := testutil.NewBase(t)
t.Cleanup(func() {
base.Cmd("rm", "-f", testContainerName1, testContainerName2).AssertOK()
})
pFlag := fmt.Sprintf("%s:%s", tc.hostPort, tc.containerPort)
cmd1 := base.Cmd("run", "-d",
"--name", testContainerName1, "-p",
pFlag,
testutil.NginxAlpineImage)

cmd2 := base.Cmd("run", "-d",
"--name", testContainerName2, "-p",
pFlag,
testutil.NginxAlpineImage)

cmd1.AssertOK()
cmd2.AssertFail()
})
}
}

func TestRunPort(t *testing.T) {
baseTestRunPort(t, testutil.NginxAlpineImage, testutil.NginxAlpineIndexHTMLSnippet, true)
}
Expand Down
62 changes: 38 additions & 24 deletions pkg/portutil/port_allocate_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,56 @@ func filter(ss []procnet.NetworkDetail, filterFunc func(detail procnet.NetworkDe
}

func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, error) {
netprocData, err := procnet.ReadStatsFileData(protocol)
usedPort, err := getUsedPorts(ip, protocol)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
usedPort, err := getUsedPorts(ip, protocol)
usedPorts, err := getUsedPorts(ip, protocol)

if err != nil {
return 0, 0, err
}
netprocItems := procnet.Parse(netprocData)

start := uint64(allocateStart)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to make things simpler please declare allocateStart and allocateEnd with uint64 type

if count > uint64(allocateEnd-allocateStart+1) {
return 0, 0, fmt.Errorf("can not allocate %d ports", count)
}
for start < allocateEnd {
needReturn := true
for i := start; i < start+count; i++ {
if _, ok := usedPort[i]; ok {
needReturn = false
break
}
}
if needReturn {
allocateStart = int(start + count)
return start, start + count - 1, nil
}
start += count
}
return 0, 0, fmt.Errorf("there is not enough %d free ports", count)
}

func getUsedPorts(ip string, protocol string) (map[uint64]bool, error) {
netprocItems := []procnet.NetworkDetail{}

if protocol == "tcp" || protocol == "udp" {
netprocData, err := procnet.ReadStatsFileData(protocol)
if err != nil {
return nil, err
}
netprocItems = append(netprocItems, procnet.Parse(netprocData)...)
}

// In some circumstances, when we bind address like "0.0.0.0:80", we will get the formation of ":::80" in /proc/net/tcp6.
// So we need some trick to process this situation.
if protocol == "tcp" {
tempTCPV6Data, err := procnet.ReadStatsFileData("tcp6")
if err != nil {
return 0, 0, err
return nil, err
}
netprocItems = append(netprocItems, procnet.Parse(tempTCPV6Data)...)
}
if protocol == "udp" {
tempUDPV6Data, err := procnet.ReadStatsFileData("udp6")
if err != nil {
return 0, 0, err
return nil, err
}
netprocItems = append(netprocItems, procnet.Parse(tempUDPV6Data)...)
}
Expand All @@ -78,31 +110,13 @@ func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, err

ipTableItems, err := iptable.ReadIPTables("nat")
if err != nil {
return 0, 0, err
return nil, err
}
destinationPorts := iptable.ParseIPTableRules(ipTableItems)

for _, port := range destinationPorts {
usedPort[port] = true
}

start := uint64(allocateStart)
if count > uint64(allocateEnd-allocateStart+1) {
return 0, 0, fmt.Errorf("can not allocate %d ports", count)
}
for start < allocateEnd {
needReturn := true
for i := start; i < start+count; i++ {
if _, ok := usedPort[i]; ok {
needReturn = false
break
}
}
if needReturn {
allocateStart = int(start + count)
return start, start + count - 1, nil
}
start += count
}
return 0, 0, fmt.Errorf("there is not enough %d free ports", count)
return usedPort, nil
}
4 changes: 4 additions & 0 deletions pkg/portutil/port_allocate_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ import "fmt"
func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, error) {
return 0, 0, fmt.Errorf("auto port allocate are not support Non-Linux platform yet")
}

func getUsedPorts(ip string, protocol string) (map[uint64]bool, error) {
return nil, nil
}
10 changes: 10 additions & 0 deletions pkg/portutil/portutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func ParseFlagP(s string) ([]cni.PortMapping, error) {
case 2:
proto = strings.ToLower(splitBySlash[1])
switch proto {
// sctp is not a supported protocol
case "tcp", "udp", "sctp":
default:
return nil, fmt.Errorf("invalid protocol %q", splitBySlash[1])
Expand Down Expand Up @@ -101,6 +102,15 @@ func ParseFlagP(s string) ([]cni.PortMapping, error) {
if err != nil {
return nil, fmt.Errorf("invalid hostPort: %s", hostPort)
}
usedPorts, err := getUsedPorts(ip, proto)
if err != nil {
return nil, err
}
for i := startHostPort; i <= endHostPort; i++ {
if usedPorts[i] {
return nil, fmt.Errorf("bind for %s:%d failed: port is already allocated", ip, i)
}
}
}
if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
if endPort != startPort {
Expand Down
Loading