Skip to content

Commit c669818

Browse files
committed
chore: add timeout and ip tune up
1 parent 3bbc416 commit c669818

2 files changed

Lines changed: 62 additions & 3 deletions

File tree

lib/appium_lib_core/driver.rb

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
require 'uri'
1616
require 'socket'
1717
require 'ipaddr'
18+
require 'timeout'
1819

1920
module Appium
2021
# The struct for 'location'
@@ -115,6 +116,7 @@ def valid?
115116

116117
# Do not allow loopback, link-local, unspecified and multicast addresses for
117118
# direct connect since they are not accessible from outside of the server.
119+
DNS_RESOLVE_TIMEOUT_SECONDS = 30
118120
LOOPBACK_RANGES = [IPAddr.new('127.0.0.0/8'), IPAddr.new('::1/128')].freeze
119121
LINK_LOCAL_RANGES = [IPAddr.new('169.254.0.0/16'), IPAddr.new('fe80::/10')].freeze
120122
UNSPECIFIED_RANGES = [IPAddr.new('0.0.0.0/32'), IPAddr.new('::/128')].freeze
@@ -128,13 +130,27 @@ def valid?
128130

129131
def resolve_addresses(host)
130132
normalized_host = host.to_s.delete_prefix('[').delete_suffix(']')
131-
Socket.getaddrinfo(normalized_host, nil).map { |entry| entry[3] }.uniq
133+
# If the host is already an IP literal, skip DNS resolution to avoid blocking calls
134+
return [normalized_host] if ip_literal?(normalized_host)
135+
136+
Timeout.timeout(DNS_RESOLVE_TIMEOUT_SECONDS) do
137+
Socket.getaddrinfo(normalized_host, nil).map { |entry| entry[3] }.uniq
138+
end
139+
rescue Timeout::Error
140+
::Appium::Logger.warn("DNS resolution for '#{host}' timed out after #{DNS_RESOLVE_TIMEOUT_SECONDS}s")
141+
[]
132142
rescue SocketError => e
133-
error_message = "Failed to resolve host '#{host}' for direct connect: #{e.message}"
134-
::Appium::Logger.warn(error_message)
143+
::Appium::Logger.warn("Failed to resolve host '#{host}' for direct connect: #{e.message}")
135144
[]
136145
end
137146

147+
def ip_literal?(host)
148+
IPAddr.new(host)
149+
true
150+
rescue IPAddr::InvalidAddressError
151+
false
152+
end
153+
138154
def disallowed?(ip)
139155
address = IPAddr.new ip
140156
DISALLOWED_RANGES.any? { |range| range.include? address }

test/unit/driver_test.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ def test_default_timeout_for_http_client_with_direct_appium_prefix
199199
appActivity: 'io.appium.android.apis.ApiDemos',
200200
someCapability: 'some_capability',
201201
'appium:directConnectProtocol' => 'https',
202+
# Not the best, but to include tests with host name
202203
'appium:directConnectHost' => 'appium.io',
203204
'appium:directConnectPort' => '8888',
204205
'appium:directConnectPath' => '/wd/hub'
@@ -331,6 +332,48 @@ def test_direct_connect_loopback_host_falls_back_to_original_url
331332
assert_not_requested(:post, 'http://localhost:8888/wd/hub/session/1234567890/timeouts')
332333
end
333334

335+
def test_direct_connect_dns_timeout_falls_back_to_original_url
336+
response = {
337+
value: {
338+
sessionId: '1234567890',
339+
capabilities: {
340+
platformName: :android,
341+
automationName: ENV['APPIUM_DRIVER'] || 'uiautomator2',
342+
app: 'test/functional/app/ApiDemos-debug.apk',
343+
platformVersion: '7.1.1',
344+
deviceName: 'Android Emulator',
345+
appPackage: 'io.appium.android.apis',
346+
appActivity: 'io.appium.android.apis.ApiDemos',
347+
someCapability: 'some_capability',
348+
directConnectProtocol: 'http',
349+
directConnectHost: 'slow.example.internal',
350+
directConnectPort: '8888',
351+
directConnectPath: '/wd/hub'
352+
}
353+
}
354+
}.to_json
355+
356+
stub_request(:post, 'http://127.0.0.1:4723/session')
357+
.to_return(headers: HEADER, status: 200, body: response)
358+
359+
stub_request(:post, 'http://127.0.0.1:4723/session/1234567890/timeouts')
360+
.with(body: { implicit: 30_000 }.to_json)
361+
.to_return(headers: HEADER, status: 200, body: { value: nil }.to_json)
362+
363+
Socket.stub(:getaddrinfo, ->(*) { raise Timeout::Error }) do
364+
core = ::Appium::Core.for(Caps.android_direct)
365+
driver = core.start_driver
366+
367+
uri = driver.send(:bridge).http.send(:server_url)
368+
assert_equal 'http', uri.scheme
369+
assert_equal '127.0.0.1', uri.host
370+
assert_equal 4723, uri.port
371+
372+
assert_requested(:post, 'http://127.0.0.1:4723/session', times: 1)
373+
assert_not_requested(:post, 'http://slow.example.internal:8888/wd/hub/session/1234567890/timeouts')
374+
end
375+
end
376+
334377
def test_default_timeout_for_http_client_with_direct_no_path
335378
android_mock_create_session_w3c_direct_no_path = lambda do |core|
336379
response = {

0 commit comments

Comments
 (0)