Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lookup public IP using HTTP #14

Merged
merged 2 commits into from
Jul 24, 2024
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
12 changes: 8 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
name: Ruby

on: [push,pull_request]
on:
push:
branches:
- main
pull_request:

jobs:
build:
strategy:
fail-fast: false
matrix:
ruby: [ 2.7, '3.0', 3.1, ruby-head ]
ruby: [ 2.7, 3.1, 3.2, 3.3, ruby-head ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- run: bundle install
bundler-cache: true
- run: bundle exec rake
22 changes: 16 additions & 6 deletions lib/sparoid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,21 @@ def write_cache
end
end

def public_ip
Resolv::DNS.open(nameserver: ["208.67.222.222", "208.67.220.220"]) do |dns|
dns.getresource("myip.opendns.com", Resolv::DNS::Resource::IN::A).address
def public_ip(host = "checkip.amazonaws.com", port = 80) # rubocop:disable Metrics/MethodLength
Socket.tcp(host, port, connect_timeout: 3) do |sock|
sock.sync = true
sock.print "GET / HTTP/1.1\r\nHost: #{host}\r\nConnection: close\r\n\r\n"
status = sock.readline(chomp: true)
raise(ResolvError, "#{host}:#{port} response: #{status}") unless status.start_with? "HTTP/1.1 200 "

content_length = 0
until (header = sock.readline(chomp: true)).empty?
if (m = header.match(/^Content-Length: (\d+)/))
content_length = m[1].to_i
end
end
ip = sock.read(content_length).chomp
Resolv::IPv4.create ip
end
end

Expand All @@ -172,9 +184,7 @@ class ResolvError < Error; end
class Instance
include Sparoid

private

def public_ip
def public_ip(*args)
@public_ip ||= super
end

Expand Down
35 changes: 25 additions & 10 deletions test/sparoid_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,26 +61,41 @@ def test_it_sends_message_with_empty_cache_file
end
end

def test_it_resolves_public_ip_only_once_per_instance
dns = Minitest::Mock.new
dns.expect :getresource, Resolv::IPv4.create("1.1.1.1"), ["myip.opendns.com", Resolv::DNS::Resource::IN::A]
Resolv::DNS.stub(:open, ->(_, &blk) { blk.call dns }) do
s = Sparoid::Instance.new
2.times do
s.send(:public_ip)
end
def test_it_resolves_public_ip_only_once_per_instance # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
server = TCPServer.new "127.0.0.1", 0
host = server.addr[3]
port = server.addr[1]
Thread.new do
client = server.accept
client_ip = client.addr[3]
assert_equal "GET / HTTP/1.1", client.readline(chomp: true)
assert_match "Host: ", client.readline(chomp: true)
assert_equal "Connection: close", client.readline(chomp: true)

client.print "HTTP/1.1 200 OK\r\n"
client.print "Content-Length: #{client_ip.bytesize}\r\n"
client.print "\r\n"
client.print client_ip
client.close
server.close
end

s = Sparoid::Instance.new
2.times do
ip = s.public_ip host, port
assert_equal Resolv::IPv4.create("127.0.0.1"), ip
end
dns.verify
end

def test_it_raises_resolve_error_on_dns_socket_error
key = "0000000000000000000000000000000000000000000000000000000000000000"
hmac_key = "0000000000000000000000000000000000000000000000000000000000000000"
open_for_ip = Resolv::IPv4.create("1.1.1.1")
error = ->(*_) { raise SocketError, "getaddrinfo: Name or service not known" }

Addrinfo.stub(:getaddrinfo, error) do
assert_raises(Sparoid::ResolvError) do
Sparoid::Instance.new.auth(key, hmac_key, "127.0.0.1", 1337)
Sparoid::Instance.new.auth(key, hmac_key, "127.0.0.1", 1337, open_for_ip: open_for_ip)
end
end
end
Expand Down