@@ -25,11 +25,14 @@ import (
25
25
26
26
const (
27
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
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
29
32
)
30
33
31
34
var (
32
- allocateStart = 49153
35
+ allocateStart = uint64 ( 49153 )
33
36
)
34
37
35
38
func 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
42
45
}
43
46
44
47
func portAllocate (protocol string , ip string , count uint64 ) (uint64 , uint64 , error ) {
45
- netprocData , err := procnet . ReadStatsFileData ( protocol )
48
+ usedPorts , err := getUsedPorts ( ip , protocol )
46
49
if err != nil {
47
50
return 0 , 0 , err
48
51
}
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
+
50
85
// 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
86
// So we need some trick to process this situation.
52
87
if protocol == "tcp" {
53
88
tempTCPV6Data , err := procnet .ReadStatsFileData ("tcp6" )
54
89
if err != nil {
55
- return 0 , 0 , err
90
+ return nil , err
56
91
}
57
92
netprocItems = append (netprocItems , procnet .Parse (tempTCPV6Data )... )
58
93
}
59
94
if protocol == "udp" {
60
95
tempUDPV6Data , err := procnet .ReadStatsFileData ("udp6" )
61
96
if err != nil {
62
- return 0 , 0 , err
97
+ return nil , err
63
98
}
64
99
netprocItems = append (netprocItems , procnet .Parse (tempUDPV6Data )... )
65
100
}
@@ -73,36 +108,26 @@ func portAllocate(protocol string, ip string, count uint64) (uint64, uint64, err
73
108
74
109
usedPort := make (map [uint64 ]bool )
75
110
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
+ }
76
119
usedPort [value .LocalPort ] = true
77
120
}
78
121
79
122
ipTableItems , err := iptable .ReadIPTables ("nat" )
80
123
if err != nil {
81
- return 0 , 0 , err
124
+ return nil , err
82
125
}
83
126
destinationPorts := iptable .ParseIPTableRules (ipTableItems )
84
127
85
128
for _ , port := range destinationPorts {
86
129
usedPort [port ] = true
87
130
}
88
131
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
108
133
}
0 commit comments