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 }