@@ -25,11 +25,14 @@ import (
2525
2626const (
2727 // 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
28+ allocateEnd = uint64 (60999 )
29+
30+ tcpTimeWait = 6 //TIME_WAIT state is represented by the value 6 in /proc/net/tcp
31+ tcpCloseWait = 8 //CLOSE_WAIT state is represented by the value 8 in /proc/net/tcp
2932)
3033
3134var (
32- allocateStart = 49153
35+ allocateStart = uint64 ( 49153 )
3336)
3437
3538func filter (ss []procnet.NetworkDetail , filterFunc func (detail procnet.NetworkDetail ) bool ) (ret []procnet.NetworkDetail ) {
@@ -42,24 +45,56 @@ func filter(ss []procnet.NetworkDetail, filterFunc func(detail procnet.NetworkDe
4245}
4346
4447func portAllocate (protocol string , ip string , count uint64 ) (uint64 , uint64 , error ) {
45- netprocData , err := procnet . ReadStatsFileData ( protocol )
48+ usedPorts , err := getUsedPorts ( ip , protocol )
4649 if err != nil {
4750 return 0 , 0 , err
4851 }
49- netprocItems := procnet .Parse (netprocData )
52+
53+ start := allocateStart
54+ if count > allocateEnd - allocateStart + 1 {
55+ return 0 , 0 , fmt .Errorf ("can not allocate %d ports" , count )
56+ }
57+ for start < allocateEnd {
58+ needReturn := true
59+ for i := start ; i < start + count ; i ++ {
60+ if _ , ok := usedPorts [i ]; ok {
61+ needReturn = false
62+ break
63+ }
64+ }
65+ if needReturn {
66+ allocateStart = start + count
67+ return start , start + count - 1 , nil
68+ }
69+ start += count
70+ }
71+ return 0 , 0 , fmt .Errorf ("there is not enough %d free ports" , count )
72+ }
73+
74+ func getUsedPorts (ip string , protocol string ) (map [uint64 ]bool , error ) {
75+ netprocItems := []procnet.NetworkDetail {}
76+
77+ if protocol == "tcp" || protocol == "udp" {
78+ netprocData , err := procnet .ReadStatsFileData (protocol )
79+ if err != nil {
80+ return nil , err
81+ }
82+ netprocItems = append (netprocItems , procnet .Parse (netprocData )... )
83+ }
84+
5085 // In some circumstances, when we bind address like "0.0.0.0:80", we will get the formation of ":::80" in /proc/net/tcp6.
5186 // So we need some trick to process this situation.
5287 if protocol == "tcp" {
5388 tempTCPV6Data , err := procnet .ReadStatsFileData ("tcp6" )
5489 if err != nil {
55- return 0 , 0 , err
90+ return nil , err
5691 }
5792 netprocItems = append (netprocItems , procnet .Parse (tempTCPV6Data )... )
5893 }
5994 if protocol == "udp" {
6095 tempUDPV6Data , err := procnet .ReadStatsFileData ("udp6" )
6196 if err != nil {
62- return 0 , 0 , err
97+ return nil , err
6398 }
6499 netprocItems = append (netprocItems , procnet .Parse (tempUDPV6Data )... )
65100 }
@@ -73,36 +108,26 @@ func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, err
73108
74109 usedPort := make (map [uint64 ]bool )
75110 for _ , value := range netprocItems {
111+ // Skip ports in TIME_WAIT or CLOSE_WAIT state
112+ if protocol == "tcp" && (value .State == tcpTimeWait || value .State == tcpCloseWait ) {
113+ // In rootless mode, Rootlesskit creates extra socket connections to proxy traffic from the host network namespace
114+ // to the container namespace. Proxy TCP connections can remain in TIME_WAIT state for 10-20 seconds even when the
115+ // container is stopped/removed, which is standard TCP behavior. These ports are actually available for allocation
116+ // despite appearing in /proc/net/tcp.
117+ continue
118+ }
76119 usedPort [value .LocalPort ] = true
77120 }
78121
79122 ipTableItems , err := iptable .ReadIPTables ("nat" )
80123 if err != nil {
81- return 0 , 0 , err
124+ return nil , err
82125 }
83126 destinationPorts := iptable .ParseIPTableRules (ipTableItems )
84127
85128 for _ , port := range destinationPorts {
86129 usedPort [port ] = true
87130 }
88131
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 )
132+ return usedPort , nil
108133}
0 commit comments