Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions lib/sparoid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,27 @@ module Sparoid # rubocop:disable Metrics/ModuleLength
# Send an authorization packet
def auth(key, hmac_key, host, port, open_for_ip: nil)
addrs = resolve_ip_addresses(host, port)
addrs.each do |addr|
errors = []
successful_addrs = addrs.filter_map do |addr|
messages = generate_messages(open_for_ip)
data = messages.map do |message|
prefix_hmac(hmac_key, encrypt(key, message))
end
sendmsg(addr, data)
addr
rescue SystemCallError => e
errors << "#{addr.ip_address}: #{e.message}"
nil
end

raise Error, "Sparoid failed to send to any address for #{host}: #{errors.join("; ")}" if successful_addrs.empty?

# wait some time for the server to actually open the port
# if we don't wait the next SYN package will be dropped
# and it have to be redelivered, adding 1 second delay
sleep 0.02

addrs
successful_addrs
end

# Generate new aes and hmac keys, print to stdout
Expand Down Expand Up @@ -105,11 +112,9 @@ def sendmsg(addr, data)
socket.nonblock = false
data.each do |packet|
socket.sendmsg packet, 0, addr
rescue StandardError => e
warn "Sparoid error: #{e.message}"
end
ensure
socket.close
socket&.close
end

def encrypt(key, data)
Expand Down
39 changes: 39 additions & 0 deletions test/sparoid_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,45 @@ def test_it_raises_resolve_error_on_dns_socket_error
end
end

def test_auth_raises_when_all_addrs_fail
key = "0000000000000000000000000000000000000000000000000000000000000000"
hmac_key = "0000000000000000000000000000000000000000000000000000000000000000"
addrs = [Addrinfo.udp("127.0.0.1", 1337), Addrinfo.udp("::1", 1337)]
fail_send = ->(*_) { raise Errno::EHOSTUNREACH }

Sparoid.stub(:resolve_ip_addresses, addrs) do
Sparoid.stub(:sendmsg, fail_send) do
err = assert_raises(Sparoid::Error) do
Sparoid.auth(key, hmac_key, "example.invalid", 1337, open_for_ip: "127.0.0.1")
end
assert_match(/failed to send to any address/, err.message)
assert_match(/127\.0\.0\.1/, err.message)
assert_match(/::1/, err.message)
end
end
end

def test_auth_returns_only_successful_addrs_on_partial_failure
key = "0000000000000000000000000000000000000000000000000000000000000000"
hmac_key = "0000000000000000000000000000000000000000000000000000000000000000"
unreachable = Addrinfo.udp("::1", 1337)
reachable = Addrinfo.udp("127.0.0.1", 1337)
sendmsg_stub = lambda do |addr, _data|
raise Errno::EHOSTUNREACH if addr == unreachable
end

out, err = capture_io do
Sparoid.stub(:resolve_ip_addresses, [unreachable, reachable]) do
Sparoid.stub(:sendmsg, sendmsg_stub) do
result = Sparoid.auth(key, hmac_key, "example.invalid", 1337, open_for_ip: "127.0.0.1")
assert_equal [reachable], result
end
end
end
assert_empty out
assert_empty err
end

def test_instance_sends_message
key = "0000000000000000000000000000000000000000000000000000000000000000"
hmac_key = "0000000000000000000000000000000000000000000000000000000000000000"
Expand Down
Loading