Skip to content

Commit f3c72e4

Browse files
authored
Raise when sendmsg fails for every resolved address (#23)
Previously sendmsg warned on each failed UDP send and auth always returned the full address list. Hosts with both IPv4 and IPv6 records on networks lacking v6 routing produced noisy 'No route to host' warnings even when the v4 path succeeded. Drop the per-packet warn, rescue SystemCallError per address in auth, and raise Sparoid::Error only when no address accepted the send. Return only the successful addresses so fdpass skips unreachable peers.
1 parent 0509d2d commit f3c72e4

2 files changed

Lines changed: 49 additions & 5 deletions

File tree

lib/sparoid.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,27 @@ module Sparoid # rubocop:disable Metrics/ModuleLength
2222
# Send an authorization packet
2323
def auth(key, hmac_key, host, port, open_for_ip: nil)
2424
addrs = resolve_ip_addresses(host, port)
25-
addrs.each do |addr|
25+
errors = []
26+
successful_addrs = addrs.filter_map do |addr|
2627
messages = generate_messages(open_for_ip)
2728
data = messages.map do |message|
2829
prefix_hmac(hmac_key, encrypt(key, message))
2930
end
3031
sendmsg(addr, data)
32+
addr
33+
rescue SystemCallError => e
34+
errors << "#{addr.ip_address}: #{e.message}"
35+
nil
3136
end
3237

38+
raise Error, "Sparoid failed to send to any address for #{host}: #{errors.join("; ")}" if successful_addrs.empty?
39+
3340
# wait some time for the server to actually open the port
3441
# if we don't wait the next SYN package will be dropped
3542
# and it have to be redelivered, adding 1 second delay
3643
sleep 0.02
3744

38-
addrs
45+
successful_addrs
3946
end
4047

4148
# Generate new aes and hmac keys, print to stdout
@@ -105,11 +112,9 @@ def sendmsg(addr, data)
105112
socket.nonblock = false
106113
data.each do |packet|
107114
socket.sendmsg packet, 0, addr
108-
rescue StandardError => e
109-
warn "Sparoid error: #{e.message}"
110115
end
111116
ensure
112-
socket.close
117+
socket&.close
113118
end
114119

115120
def encrypt(key, data)

test/sparoid_test.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,45 @@ def test_it_raises_resolve_error_on_dns_socket_error
112112
end
113113
end
114114

115+
def test_auth_raises_when_all_addrs_fail
116+
key = "0000000000000000000000000000000000000000000000000000000000000000"
117+
hmac_key = "0000000000000000000000000000000000000000000000000000000000000000"
118+
addrs = [Addrinfo.udp("127.0.0.1", 1337), Addrinfo.udp("::1", 1337)]
119+
fail_send = ->(*_) { raise Errno::EHOSTUNREACH }
120+
121+
Sparoid.stub(:resolve_ip_addresses, addrs) do
122+
Sparoid.stub(:sendmsg, fail_send) do
123+
err = assert_raises(Sparoid::Error) do
124+
Sparoid.auth(key, hmac_key, "example.invalid", 1337, open_for_ip: "127.0.0.1")
125+
end
126+
assert_match(/failed to send to any address/, err.message)
127+
assert_match(/127\.0\.0\.1/, err.message)
128+
assert_match(/::1/, err.message)
129+
end
130+
end
131+
end
132+
133+
def test_auth_returns_only_successful_addrs_on_partial_failure
134+
key = "0000000000000000000000000000000000000000000000000000000000000000"
135+
hmac_key = "0000000000000000000000000000000000000000000000000000000000000000"
136+
unreachable = Addrinfo.udp("::1", 1337)
137+
reachable = Addrinfo.udp("127.0.0.1", 1337)
138+
sendmsg_stub = lambda do |addr, _data|
139+
raise Errno::EHOSTUNREACH if addr == unreachable
140+
end
141+
142+
out, err = capture_io do
143+
Sparoid.stub(:resolve_ip_addresses, [unreachable, reachable]) do
144+
Sparoid.stub(:sendmsg, sendmsg_stub) do
145+
result = Sparoid.auth(key, hmac_key, "example.invalid", 1337, open_for_ip: "127.0.0.1")
146+
assert_equal [reachable], result
147+
end
148+
end
149+
end
150+
assert_empty out
151+
assert_empty err
152+
end
153+
115154
def test_instance_sends_message
116155
key = "0000000000000000000000000000000000000000000000000000000000000000"
117156
hmac_key = "0000000000000000000000000000000000000000000000000000000000000000"

0 commit comments

Comments
 (0)