|
4 | 4 | "bytes" |
5 | 5 | "context" |
6 | 6 | "encoding/json" |
| 7 | + "errors" |
7 | 8 | "fmt" |
8 | 9 | "io" |
9 | 10 | "net" |
@@ -139,20 +140,13 @@ func TestProto(t *testing.T, proto string, d port.ParentDriver) { |
139 | 140 | func testProtoWithPID(t *testing.T, proto string, d port.ParentDriver, childPID int) { |
140 | 141 | ensureDeps(t, "nsenter", "ip", "nc") |
141 | 142 | // [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) |
155 | 148 | } |
| 149 | + pairs[childPort] = parentPort |
156 | 150 | } |
157 | 151 |
|
158 | 152 | t.Logf("namespace pid: %d", childPID) |
@@ -222,16 +216,42 @@ func testProtoRoutine(t *testing.T, proto string, d port.ParentDriver, childPID, |
222 | 216 | panic(err) |
223 | 217 | } |
224 | 218 | 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 | + } |
| 245 | + } |
| 246 | + if portStatus == nil { |
| 247 | + panic("AddPort never succeeded") |
234 | 248 | } |
| 249 | + defer func(id int) { |
| 250 | + if err := d.RemovePort(context.TODO(), id); err != nil { |
| 251 | + panic(err) |
| 252 | + } |
| 253 | + }(portStatus.ID) |
| 254 | + |
235 | 255 | t.Logf("opened port: %+v", portStatus) |
236 | 256 | if proto == "udp" || proto == "udp4" { |
237 | 257 | // Dial does not return an error for UDP even if the port is not exposed yet |
@@ -308,3 +328,38 @@ func (w *tLogWriter) Write(p []byte) (int, error) { |
308 | 328 | w.t.Logf("%s: %s", w.s, strings.TrimSuffix(string(p), "\n")) |
309 | 329 | return len(p), nil |
310 | 330 | } |
| 331 | + |
| 332 | +func allocateAvailablePort(proto string) (int, error) { |
| 333 | + const loopback = "127.0.0.1:0" |
| 334 | + switch proto { |
| 335 | + case "tcp", "tcp4": |
| 336 | + ln, err := net.Listen(proto, loopback) |
| 337 | + if err != nil { |
| 338 | + return 0, err |
| 339 | + } |
| 340 | + defer ln.Close() |
| 341 | + return ln.Addr().(*net.TCPAddr).Port, nil |
| 342 | + case "udp", "udp4": |
| 343 | + addr, err := net.ResolveUDPAddr(proto, loopback) |
| 344 | + if err != nil { |
| 345 | + return 0, err |
| 346 | + } |
| 347 | + conn, err := net.ListenUDP(proto, addr) |
| 348 | + if err != nil { |
| 349 | + return 0, err |
| 350 | + } |
| 351 | + defer conn.Close() |
| 352 | + return conn.LocalAddr().(*net.UDPAddr).Port, nil |
| 353 | + default: |
| 354 | + return 0, fmt.Errorf("unsupported proto %q", proto) |
| 355 | + } |
| 356 | +} |
| 357 | + |
| 358 | +func isAddrInUse(err error) bool { |
| 359 | + if errors.Is(err, syscall.EADDRINUSE) { |
| 360 | + return true |
| 361 | + } |
| 362 | + msg := err.Error() |
| 363 | + return strings.Contains(msg, "address already in use") || |
| 364 | + strings.Contains(msg, "port is busy") |
| 365 | +} |
0 commit comments