Skip to content

Commit c3dc166

Browse files
Backport(v1.19): test: fix unused ports outside the OS dynamic port range (#5451) (#5460)
**Which issue(s) this PR fixes**: Backport #5451 Fixes # **What this PR does / why we need it**: On Windows, Hyper-V/WinNAT/HNS take their reservations from the dynamic port range, which is 49152-65535 as observed on GitHub Actions runners, and a reservation can appear between the check and the actual bind. When searching for unused ports, this PR will avoid this range to prevent following error. ``` 3) Error: test: send_with_time_as_integer(ForwardOutputTest): Errno::EACCES: Permission denied - bind(2) for 127.0.0.1:58707 C:/hostedtoolcache/windows/Ruby/4.0.5/arm64/lib/ruby/4.0.0/socket.rb:183:in 'Socket#bind' C:/hostedtoolcache/windows/Ruby/4.0.5/arm64/lib/ruby/4.0.0/socket.rb:183:in 'Addrinfo#bind' C:/a/fluentd/fluentd/lib/fluent/plugin_helper/server.rb:402:in 'Fluent::PluginHelper::Server#server_create_udp_socket' C:/a/fluentd/fluentd/lib/fluent/plugin_helper/server.rb:178:in 'Fluent::PluginHelper::Server#server_create' C:/a/fluentd/fluentd/lib/fluent/plugin/in_forward.rb:192:in 'Fluent::Plugin::ForwardInput#start' C:/a/fluentd/fluentd/lib/fluent/test/driver/base.rb:120:in 'Fluent::Test::Driver::Base#instance_start' C:/a/fluentd/fluentd/lib/fluent/test/driver/base.rb:78:in 'Fluent::Test::Driver::Base#run' C:/a/fluentd/fluentd/lib/fluent/test/driver/base_owner.rb:130:in 'Fluent::Test::Driver::BaseOwner#run' C:/a/fluentd/fluentd/test/plugin/test_out_forward.rb:516:in 'block (2 levels) in <class:ForwardOutputTest>' C:/hostedtoolcache/windows/Ruby/4.0.5/arm64/lib/ruby/gems/4.0.0/gems/test-unit-rr-1.0.5/lib/test/unit/rr.rb:98:in 'Test::Unit::RR::Adapter#assert_rr' C:/a/fluentd/fluentd/test/plugin/test_out_forward.rb:515:in 'block in <class:ForwardOutputTest>' ``` https://github.com/fluent/fluentd/actions/runs/29884012909/job/88810722450#step:6:5337 Related to #5434 **Docs Changes**: N/A **Release Note**: N/A Signed-off-by: Shizuo Fujita <fujita@clear-code.com> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Shizuo Fujita <fujita@clear-code.com>
1 parent 82f26a9 commit c3dc166

2 files changed

Lines changed: 18 additions & 8 deletions

File tree

test/helper.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,16 @@ def unused_port(num = 1, protocol:, bind: "0.0.0.0")
8989
# roughly 100 ports and multiple ranges may coexist, so use a sufficiently
9090
# wide candidate range to reduce the probability that all candidates are
9191
# excluded.
92+
# Those reservations are taken from the dynamic port range (49152-65535 on
93+
# Windows, observed on GitHub Actions runners), and a port can become
94+
# reserved even between the checks below and the actual bind, which then
95+
# fails with EACCES. So keep the candidate range outside the dynamic port
96+
# range of every platform (Linux uses 32768-60999) to avoid competing with
97+
# the OS for ports in the first place.
9298
# About dynamic excluded port ranges, see:
9399
# > netsh interface ipv4 show excludedportrange protocol=tcp
94-
# > netsh interface ipv4 show excludedportrange protocol=ucp
95-
PORT_RANGE_TCP_UDP = (55000..65000)
100+
# > netsh interface ipv4 show excludedportrange protocol=udp
101+
PORT_RANGE_TCP_UDP = (20000..30000)
96102

97103
def unused_port_tcp_udp(num = 1, retries: 1000)
98104
raise "not support num > 1" if num > 1
@@ -106,9 +112,13 @@ def unused_port_tcp_udp(num = 1, retries: 1000)
106112
raise "can't find unused port"
107113
end
108114

115+
# Bind the loopback address that the tests using unused_port(protocol: :all)
116+
# actually bind, so that a successful check means the same bind can succeed.
117+
BIND_ADDRESS_TCP_UDP = "127.0.0.1"
118+
109119
def port_bindable_udp?(port)
110120
u = UDPSocket.new(::Socket::AF_INET)
111-
u.bind("0.0.0.0", port)
121+
u.bind(BIND_ADDRESS_TCP_UDP, port)
112122
true
113123
rescue SystemCallError
114124
false
@@ -117,7 +127,7 @@ def port_bindable_udp?(port)
117127
end
118128

119129
def port_bindable_tcp?(port)
120-
TCPServer.open("0.0.0.0", port).close
130+
TCPServer.open(BIND_ADDRESS_TCP_UDP, port).close
121131
true
122132
rescue SystemCallError
123133
false

test/test_unused_port.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ class UnusedPortTest < Test::Unit::TestCase
55
port = unused_port(protocol: :all)
66
assert_kind_of(Integer, port)
77
assert_include(PORT_RANGE_TCP_UDP, port)
8-
tcp = TCPServer.open("0.0.0.0", port)
8+
tcp = TCPServer.open(BIND_ADDRESS_TCP_UDP, port)
99
tcp.close
1010
udp = UDPSocket.new(::Socket::AF_INET)
11-
udp.bind("0.0.0.0", port)
11+
udp.bind(BIND_ADDRESS_TCP_UDP, port)
1212
udp.close
1313
end
1414

@@ -20,7 +20,7 @@ class UnusedPortTest < Test::Unit::TestCase
2020

2121
sub_test_case "port_bindable_tcp?" do
2222
test "returns false while the port is held and true after release" do
23-
held = TCPServer.open("0.0.0.0", 0)
23+
held = TCPServer.open(BIND_ADDRESS_TCP_UDP, 0)
2424
port = held.addr[1]
2525
assert_false(port_bindable_tcp?(port))
2626
held.close
@@ -31,7 +31,7 @@ class UnusedPortTest < Test::Unit::TestCase
3131
sub_test_case "port_bindable_udp?" do
3232
test "returns false while the port is held and true after release" do
3333
held = UDPSocket.new(::Socket::AF_INET)
34-
held.bind("0.0.0.0", 0)
34+
held.bind(BIND_ADDRESS_TCP_UDP, 0)
3535
port = held.addr[1]
3636
assert_false(port_bindable_udp?(port))
3737
held.close

0 commit comments

Comments
 (0)