@@ -4,8 +4,10 @@ import (
44 "bytes"
55 "context"
66 "encoding/json"
7+ "errors"
78 "fmt"
89 "io"
10+ "math/rand"
911 "net"
1012 "os"
1113 "os/exec"
@@ -25,6 +27,11 @@ const (
2527 reexecKeyQuitFD = "rootlesskit-port-testsuite.quitfd"
2628)
2729
30+ var (
31+ randSrc = rand .New (rand .NewSource (time .Now ().UnixNano ()))
32+ randMu sync.Mutex
33+ )
34+
2835func Main (m * testing.M , cf func () port.ChildDriver ) {
2936 switch mode := os .Getenv (reexecKeyMode ); mode {
3037 case "" :
@@ -139,20 +146,13 @@ func TestProto(t *testing.T, proto string, d port.ParentDriver) {
139146func testProtoWithPID (t * testing.T , proto string , d port.ParentDriver , childPID int ) {
140147 ensureDeps (t , "nsenter" , "ip" , "nc" )
141148 // [child]parent
142- pairs := map [int ]int {
143- // FIXME: flaky
144- 80 : (childPID + 80 ) % 60000 ,
145- 8080 : (childPID + 8080 ) % 60000 ,
146- }
147- if proto == "tcp" {
148- for _ , parentPort := range pairs {
149- var d net.Dialer
150- d .Timeout = 50 * time .Millisecond
151- _ , err := d .Dial (proto , fmt .Sprintf ("127.0.0.1:%d" , parentPort ))
152- if err == nil {
153- t .Fatalf ("port %d is already used?" , parentPort )
154- }
149+ pairs := make (map [int ]int , 2 )
150+ for _ , childPort := range []int {80 , 8080 } {
151+ parentPort , err := allocateAvailablePort (proto )
152+ if err != nil {
153+ t .Fatalf ("failed to allocate parent port for %s: %v" , proto , err )
155154 }
155+ pairs [childPort ] = parentPort
156156 }
157157
158158 t .Logf ("namespace pid: %d" , childPID )
@@ -222,16 +222,42 @@ func testProtoRoutine(t *testing.T, proto string, d port.ParentDriver, childPID,
222222 panic (err )
223223 }
224224 defer cmd .Process .Kill ()
225- portStatus , err := d .AddPort (context .TODO (),
226- port.Spec {
227- Proto : proto ,
228- ParentIP : "127.0.0.1" ,
229- ParentPort : parentP ,
230- ChildPort : childP ,
231- })
232- if err != nil {
233- panic (err )
225+
226+ const maxAttempts = 10
227+ var (
228+ currentParent = parentP
229+ portStatus * port.Status
230+ err error
231+ )
232+ for attempt := 0 ; attempt < maxAttempts ; attempt ++ {
233+ portStatus , err = d .AddPort (context .TODO (),
234+ port.Spec {
235+ Proto : proto ,
236+ ParentIP : "127.0.0.1" ,
237+ ParentPort : currentParent ,
238+ ChildPort : childP ,
239+ })
240+ if err == nil {
241+ parentP = currentParent
242+ break
243+ }
244+ if attempt == maxAttempts - 1 || ! isAddrInUse (err ) {
245+ panic (err )
246+ }
247+ currentParent , err = allocateAvailablePort (proto )
248+ if err != nil {
249+ panic (err )
250+ }
234251 }
252+ if portStatus == nil {
253+ panic ("AddPort never succeeded" )
254+ }
255+ defer func (id int ) {
256+ if err := d .RemovePort (context .TODO (), id ); err != nil {
257+ panic (err )
258+ }
259+ }(portStatus .ID )
260+
235261 t .Logf ("opened port: %+v" , portStatus )
236262 if proto == "udp" || proto == "udp4" {
237263 // Dial does not return an error for UDP even if the port is not exposed yet
@@ -308,3 +334,38 @@ func (w *tLogWriter) Write(p []byte) (int, error) {
308334 w .t .Logf ("%s: %s" , w .s , strings .TrimSuffix (string (p ), "\n " ))
309335 return len (p ), nil
310336}
337+
338+ func allocateAvailablePort (proto string ) (int , error ) {
339+ const loopback = "127.0.0.1:0"
340+ switch proto {
341+ case "tcp" , "tcp4" :
342+ ln , err := net .Listen (proto , loopback )
343+ if err != nil {
344+ return 0 , err
345+ }
346+ defer ln .Close ()
347+ return ln .Addr ().(* net.TCPAddr ).Port , nil
348+ case "udp" , "udp4" :
349+ addr , err := net .ResolveUDPAddr (proto , loopback )
350+ if err != nil {
351+ return 0 , err
352+ }
353+ conn , err := net .ListenUDP (proto , addr )
354+ if err != nil {
355+ return 0 , err
356+ }
357+ defer conn .Close ()
358+ return conn .LocalAddr ().(* net.UDPAddr ).Port , nil
359+ default :
360+ return 0 , fmt .Errorf ("unsupported proto %q" , proto )
361+ }
362+ }
363+
364+ func isAddrInUse (err error ) bool {
365+ if errors .Is (err , syscall .EADDRINUSE ) {
366+ return true
367+ }
368+ msg := err .Error ()
369+ return strings .Contains (msg , "address already in use" ) ||
370+ strings .Contains (msg , "port is busy" )
371+ }
0 commit comments