44 "bytes"
55 "context"
66 "encoding/json"
7+ "errors"
78 "fmt"
89 "io"
910 "net"
@@ -139,20 +140,13 @@ func TestProto(t *testing.T, proto string, d port.ParentDriver) {
139140func testProtoWithPID (t * testing.T , proto string , d port.ParentDriver , childPID int ) {
140141 ensureDeps (t , "nsenter" , "ip" , "nc" )
141142 // [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- }
143+ pairs := make (map [int ]int , 2 )
144+ for _ , childPort := range []int {80 , 8080 } {
145+ parentPort , err := allocateAvailablePort (proto )
146+ if err != nil {
147+ t .Fatalf ("failed to allocate parent port for %s: %v" , proto , err )
155148 }
149+ pairs [childPort ] = parentPort
156150 }
157151
158152 t .Logf ("namespace pid: %d" , childPID )
@@ -222,16 +216,43 @@ func testProtoRoutine(t *testing.T, proto string, d port.ParentDriver, childPID,
222216 panic (err )
223217 }
224218 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 )
219+
220+ const maxAttempts = 10
221+ var (
222+ currentParent = parentP
223+ portStatus * port.Status
224+ err error
225+ )
226+ for attempt := 0 ; attempt < maxAttempts ; attempt ++ {
227+ portStatus , err = d .AddPort (context .TODO (),
228+ port.Spec {
229+ Proto : proto ,
230+ ParentIP : "127.0.0.1" ,
231+ ParentPort : currentParent ,
232+ ChildPort : childP ,
233+ })
234+ if err == nil {
235+ parentP = currentParent
236+ break
237+ }
238+ if attempt == maxAttempts - 1 || ! isAddrInUse (err ) {
239+ panic (err )
240+ }
241+ currentParent , err = allocateAvailablePort (proto )
242+ if err != nil {
243+ panic (err )
244+ }
234245 }
246+ if portStatus == nil {
247+ panic ("AddPort never succeeded" )
248+ }
249+ defer func (id int ) {
250+ if err := d .RemovePort (context .TODO (), id ); err != nil {
251+ panic (err )
252+ }
253+ t .Logf ("closed port ID %d" , portStatus .ID )
254+ }(portStatus .ID )
255+
235256 t .Logf ("opened port: %+v" , portStatus )
236257 if proto == "udp" || proto == "udp4" {
237258 // Dial does not return an error for UDP even if the port is not exposed yet
@@ -281,10 +302,6 @@ func testProtoRoutine(t *testing.T, proto string, d port.ParentDriver, childPID,
281302 // nc -u does not exit automatically
282303 syscall .Kill (cmd .Process .Pid , syscall .SIGKILL )
283304 }
284- if err := d .RemovePort (context .TODO (), portStatus .ID ); err != nil {
285- panic (err )
286- }
287- t .Logf ("closed port ID %d" , portStatus .ID )
288305}
289306
290307func ensureDeps (t testing.TB , deps ... string ) {
@@ -308,3 +325,38 @@ func (w *tLogWriter) Write(p []byte) (int, error) {
308325 w .t .Logf ("%s: %s" , w .s , strings .TrimSuffix (string (p ), "\n " ))
309326 return len (p ), nil
310327}
328+
329+ func allocateAvailablePort (proto string ) (int , error ) {
330+ const loopback = "127.0.0.1:0"
331+ switch proto {
332+ case "tcp" , "tcp4" :
333+ ln , err := net .Listen (proto , loopback )
334+ if err != nil {
335+ return 0 , err
336+ }
337+ defer ln .Close ()
338+ return ln .Addr ().(* net.TCPAddr ).Port , nil
339+ case "udp" , "udp4" :
340+ addr , err := net .ResolveUDPAddr (proto , loopback )
341+ if err != nil {
342+ return 0 , err
343+ }
344+ conn , err := net .ListenUDP (proto , addr )
345+ if err != nil {
346+ return 0 , err
347+ }
348+ defer conn .Close ()
349+ return conn .LocalAddr ().(* net.UDPAddr ).Port , nil
350+ default :
351+ return 0 , fmt .Errorf ("unsupported proto %q" , proto )
352+ }
353+ }
354+
355+ func isAddrInUse (err error ) bool {
356+ if errors .Is (err , syscall .EADDRINUSE ) {
357+ return true
358+ }
359+ msg := err .Error ()
360+ return strings .Contains (msg , "address already in use" ) ||
361+ strings .Contains (msg , "port is busy" )
362+ }
0 commit comments