package portallocator import ( "errors" "net" "testing" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) func TestRequestNewPort(t *testing.T) { p := newInstance() port, err := p.RequestPort(net.IPv4zero, "tcp", 0) if err != nil { t.Fatal(err) } if expected := p.begin; port != expected { t.Fatalf("Expected port %d got %d", expected, port) } } func TestRequestSpecificPort(t *testing.T) { p := newInstance() port, err := p.RequestPort(net.IPv4zero, "tcp", 5000) if err != nil { t.Fatal(err) } if port != 5000 { t.Fatalf("Expected port 5000 got %d", port) } } func TestReleasePort(t *testing.T) { p := newInstance() port, err := p.RequestPort(net.IPv4zero, "tcp", 5000) if err != nil { t.Fatal(err) } if port != 5000 { t.Fatalf("Expected port 5000 got %d", port) } p.ReleasePort(net.IPv4zero, "tcp", 5000) } func TestReuseReleasedPort(t *testing.T) { p := newInstance() port, err := p.RequestPort(net.IPv4zero, "tcp", 5000) if err != nil { t.Fatal(err) } if port != 5000 { t.Fatalf("Expected port 5000 got %d", port) } p.ReleasePort(net.IPv4zero, "tcp", 5000) port, err = p.RequestPort(net.IPv4zero, "tcp", 5000) if err != nil { t.Fatal(err) } if port != 5000 { t.Fatalf("Expected port 5000 got %d", port) } } func TestReleaseUnreadledPort(t *testing.T) { p := newInstance() port, err := p.RequestPort(net.IPv4zero, "tcp", 5000) if err != nil { t.Fatal(err) } if port != 5000 { t.Fatalf("Expected port 5000 got %d", port) } _, err = p.RequestPort(net.IPv4zero, "tcp", 5000) var expectedErrType alreadyAllocatedErr if !errors.As(err, &expectedErrType) { t.Fatalf("Expected port allocation error got %s", err) } } func TestUnknowProtocol(t *testing.T) { p := newInstance() if _, err := p.RequestPort(net.IPv4zero, "tcpp", 0); !errors.Is(err, errUnknownProtocol) { t.Fatalf("Expected error %s got %s", errUnknownProtocol, err) } } func TestAllocateAllPorts(t *testing.T) { p := newInstance() for i := 0; i <= p.end-p.begin; i++ { port, err := p.RequestPort(net.IPv4zero, "tcp", 0) if err != nil { t.Fatal(err) } if expected := p.begin + i; port != expected { t.Fatalf("Expected port %d got %d", expected, port) } } if _, err := p.RequestPort(net.IPv4zero, "tcp", 0); !errors.Is(err, errAllPortsAllocated) { t.Fatalf("Expected error %s got %s", errAllPortsAllocated, err) } _, err := p.RequestPort(net.IPv4zero, "udp", 0) if err != nil { t.Fatal(err) } // release a port in the middle and ensure we get another tcp port port := p.begin + 5 p.ReleasePort(net.IPv4zero, "tcp", port) newPort, err := p.RequestPort(net.IPv4zero, "tcp", 0) if err != nil { t.Fatal(err) } if newPort != port { t.Fatalf("Expected port %d got %d", port, newPort) } // now pm.last == newPort, release it so that it's the only free port of // the range, and ensure we get it back p.ReleasePort(net.IPv4zero, "tcp", newPort) port, err = p.RequestPort(net.IPv4zero, "tcp", 0) if err != nil { t.Fatal(err) } if newPort != port { t.Fatalf("Expected port %d got %d", newPort, port) } } func BenchmarkAllocatePorts(b *testing.B) { p := newInstance() for n := 0; n < b.N; n++ { for i := 0; i <= p.end-p.begin; i++ { port, err := p.RequestPort(net.IPv4zero, "tcp", 0) if err != nil { b.Fatal(err) } if expected := p.begin + i; port != expected { b.Fatalf("Expected port %d got %d", expected, port) } } p.ReleaseAll() } } func TestPortAllocation(t *testing.T) { p := newInstance() ip := net.ParseIP("192.168.0.1") ip2 := net.ParseIP("192.168.0.2") if port, err := p.RequestPort(ip, "tcp", 80); err != nil { t.Fatal(err) } else if port != 80 { t.Fatalf("Acquire(80) should return 80, not %d", port) } port, err := p.RequestPort(ip, "tcp", 0) if err != nil { t.Fatal(err) } if port <= 0 { t.Fatalf("Acquire(0) should return a non-zero port") } if _, err := p.RequestPort(ip, "tcp", port); err == nil { t.Fatalf("Acquiring a port already in use should return an error") } if newPort, err := p.RequestPort(ip, "tcp", 0); err != nil { t.Fatal(err) } else if newPort == port { t.Fatalf("Acquire(0) allocated the same port twice: %d", port) } if _, err := p.RequestPort(ip, "tcp", 80); err == nil { t.Fatalf("Acquiring a port already in use should return an error") } if _, err := p.RequestPort(ip2, "tcp", 80); err != nil { t.Fatalf("It should be possible to allocate the same port on a different interface") } if _, err := p.RequestPort(ip2, "tcp", 80); err == nil { t.Fatalf("Acquiring a port already in use should return an error") } p.ReleasePort(ip, "tcp", 80) if _, err := p.RequestPort(ip, "tcp", 80); err != nil { t.Fatal(err) } port, err = p.RequestPort(ip, "tcp", 0) if err != nil { t.Fatal(err) } port2, err := p.RequestPort(ip, "tcp", port+1) if err != nil { t.Fatal(err) } port3, err := p.RequestPort(ip, "tcp", 0) if err != nil { t.Fatal(err) } if port3 == port2 { t.Fatal("Requesting a dynamic port should never allocate a used port") } } func TestPortAllocationWithCustomRange(t *testing.T) { p := newInstance() start, end := 8081, 8082 specificPort := 8000 // get an ephemeral port. port1, err := p.RequestPortInRange(net.IPv4zero, "tcp", 0, 0) if err != nil { t.Fatal(err) } // request invalid ranges if _, err := p.RequestPortInRange(net.IPv4zero, "tcp", 0, end); err == nil { t.Fatalf("Expected error for invalid range %d-%d", 0, end) } if _, err := p.RequestPortInRange(net.IPv4zero, "tcp", start, 0); err == nil { t.Fatalf("Expected error for invalid range %d-%d", 0, end) } if _, err := p.RequestPortInRange(net.IPv4zero, "tcp", 8081, 8080); err == nil { t.Fatalf("Expected error for invalid range %d-%d", 0, end) } // request a single port port, err := p.RequestPortInRange(net.IPv4zero, "tcp", specificPort, specificPort) if err != nil { t.Fatal(err) } if port != specificPort { t.Fatalf("Expected port %d, got %d", specificPort, port) } // get a port from the range port2, err := p.RequestPortInRange(net.IPv4zero, "tcp", start, end) if err != nil { t.Fatal(err) } if port2 < start || port2 > end { t.Fatalf("Expected a port between %d and %d, got %d", start, end, port2) } // get another ephemeral port (should be > port1) port3, err := p.RequestPortInRange(net.IPv4zero, "tcp", 0, 0) if err != nil { t.Fatal(err) } if port3 < port1 { t.Fatalf("Expected new port > %d in the ephemeral range, got %d", port1, port3) } // get another (and in this case the only other) port from the range port4, err := p.RequestPortInRange(net.IPv4zero, "tcp", start, end) if err != nil { t.Fatal(err) } if port4 < start || port4 > end { t.Fatalf("Expected a port between %d and %d, got %d", start, end, port4) } if port4 == port2 { t.Fatal("Allocated the same port from a custom range") } // request 3rd port from the range of 2 if _, err := p.RequestPortInRange(net.IPv4zero, "tcp", start, end); !errors.Is(err, errAllPortsAllocated) { t.Fatalf("Expected error %s got %s", errAllPortsAllocated, err) } } func TestNoDuplicateBPR(t *testing.T) { p := newInstance() if port, err := p.RequestPort(net.IPv4zero, "tcp", p.begin); err != nil { t.Fatal(err) } else if port != p.begin { t.Fatalf("Expected port %d got %d", p.begin, port) } if port, err := p.RequestPort(net.IPv4zero, "tcp", 0); err != nil { t.Fatal(err) } else if port == p.begin { t.Fatalf("Acquire(0) allocated the same port twice: %d", port) } } func TestRequestPortForMultipleIPs(t *testing.T) { p := newInstance() addrs := []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::")} // Default port range. port, err := p.RequestPortsInRange(addrs, "tcp", 0, 0) assert.Check(t, err) assert.Check(t, is.Equal(port, p.begin)) // Single-port range. port, err = p.RequestPortsInRange(addrs, "tcp", 10000, 10000) assert.Check(t, err) assert.Check(t, is.Equal(port, 10000)) // Same single-port range, expect an error. _, err = p.RequestPortsInRange(addrs, "tcp", 10000, 10000) assert.Check(t, is.ErrorContains(err, "port is already allocated")) // Release the port from one address. p.ReleasePort(addrs[0], "tcp", 10000) // Same single-port range, still expect an error because the port's still held // for the second address. _, err = p.RequestPortsInRange(addrs, "tcp", 10000, 10000) assert.Check(t, is.Error(err, "Bind for :::10000 failed: port is already allocated")) // Release the port from the other address. p.ReleasePort(addrs[1], "tcp", 10000) // Should now be able to re-allocate the port. port, err = p.RequestPortsInRange(addrs, "tcp", 10000, 10000) assert.Check(t, err) assert.Check(t, is.Equal(port, 10000)) // Multi-port range. for i := 20000; i < 20004; i += 1 { port, err = p.RequestPortsInRange(addrs, "tcp", 20000, 20004) assert.Check(t, err) assert.Check(t, is.Equal(port, i)) } } func TestMixUnspecAndSpecificAddrs(t *testing.T) { p := newInstance() port, err := p.RequestPort(net.IPv4(127, 0, 0, 1), "udp", 0) assert.Check(t, err) assert.Check(t, is.Equal(port, p.begin)) port, err = p.RequestPort(net.IPv4zero, "udp", 0) assert.Check(t, err) assert.Check(t, is.Equal(port, p.begin+1)) }