github.com/containerd/nerdctl@v1.7.7/pkg/portutil/port_allocate_linux.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package portutil
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"github.com/containerd/nerdctl/pkg/portutil/iptable"
    23  	"github.com/containerd/nerdctl/pkg/portutil/procnet"
    24  )
    25  
    26  const (
    27  	// This port range is compatible with Docker, FYI https://github.com/moby/moby/blob/eb9e42a09ee123af1d95bf7d46dd738258fa2109/libnetwork/portallocator/portallocator_unix.go#L7-L12
    28  	allocateEnd = 60999
    29  )
    30  
    31  var (
    32  	allocateStart = 49153
    33  )
    34  
    35  func filter(ss []procnet.NetworkDetail, filterFunc func(detail procnet.NetworkDetail) bool) (ret []procnet.NetworkDetail) {
    36  	for _, s := range ss {
    37  		if filterFunc(s) {
    38  			ret = append(ret, s)
    39  		}
    40  	}
    41  	return
    42  }
    43  
    44  func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, error) {
    45  	netprocData, err := procnet.ReadStatsFileData(protocol)
    46  	if err != nil {
    47  		return 0, 0, err
    48  	}
    49  	netprocItems := procnet.Parse(netprocData)
    50  	// In some circumstances, when we bind address like "0.0.0.0:80", we will get the formation of ":::80" in /proc/net/tcp6.
    51  	// So we need some trick to process this situation.
    52  	if protocol == "tcp" {
    53  		tempTCPV6Data, err := procnet.ReadStatsFileData("tcp6")
    54  		if err != nil {
    55  			return 0, 0, err
    56  		}
    57  		netprocItems = append(netprocItems, procnet.Parse(tempTCPV6Data)...)
    58  	}
    59  	if protocol == "udp" {
    60  		tempUDPV6Data, err := procnet.ReadStatsFileData("udp6")
    61  		if err != nil {
    62  			return 0, 0, err
    63  		}
    64  		netprocItems = append(netprocItems, procnet.Parse(tempUDPV6Data)...)
    65  	}
    66  	if ip != "" {
    67  		netprocItems = filter(netprocItems, func(s procnet.NetworkDetail) bool {
    68  			// In some circumstances, when we bind address like "0.0.0.0:80", we will get the formation of ":::80" in /proc/net/tcp6.
    69  			// So we need some trick to process this situation.
    70  			return s.LocalIP.String() == "::" || s.LocalIP.String() == ip
    71  		})
    72  	}
    73  
    74  	usedPort := make(map[uint64]bool)
    75  	for _, value := range netprocItems {
    76  		usedPort[value.LocalPort] = true
    77  	}
    78  
    79  	ipTableItems, err := iptable.ReadIPTables("nat")
    80  	if err != nil {
    81  		return 0, 0, err
    82  	}
    83  	destinationPorts := iptable.ParseIPTableRules(ipTableItems)
    84  
    85  	for _, port := range destinationPorts {
    86  		usedPort[port] = true
    87  	}
    88  
    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)
   108  }