Module: Sparoid

Extended by:
Sparoid
Included in:
Sparoid, Instance
Defined in:
lib/sparoid.rb,
lib/sparoid/cli.rb,
lib/sparoid/version.rb

Overview

Single Packet Authorisation client

Defined Under Namespace

Modules: CLI Classes: Error, Instance, ResolvError

Constant Summary collapse

SPAROID_CACHE_PATH =
ENV.fetch("SPAROID_CACHE_PATH", "/tmp/.sparoid_public_ip")
URLS =
[
  "ipv6.icanhazip.com",
  "ipv4.icanhazip.com"
].freeze
GOOGLE_DNS_V6 =
["2001:4860:4860::8888", 53].freeze
VERSION =
"2.2.0"

Instance Method Summary collapse

Instance Method Details

#auth(key, hmac_key, host, port, open_for_ip: nil) ⇒ Object

Send an authorization packet

Raises:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/sparoid.rb', line 23

def auth(key, hmac_key, host, port, open_for_ip: nil)
  addrs = resolve_ip_addresses(host, port)
  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

  successful_addrs
end

#fdpass(addrs, port, connect_timeout: 10) ⇒ Object

Connect to a TCP server and pass the FD to the parent



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/sparoid.rb', line 58

def fdpass(addrs, port, connect_timeout: 10) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
  # try connect to all IPs; skip addrs that fail synchronously (e.g. no route)
  viable_addrs = []
  sockets = addrs.filter_map do |addr|
    s = Socket.new(addr.afamily, Socket::SOCK_STREAM)
    s.connect_nonblock(Socket.sockaddr_in(port, addr.ip_address), exception: false)
    viable_addrs << addr
    s
  rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ECONNREFUSED => e
    warn "Sparoid: skip #{addr.ip_address}: #{e.message}"
    s&.close
    nil
  end
  # wait for any socket to be connected
  until sockets.empty?
    _, writeable, errors = IO.select(nil, sockets, nil, connect_timeout) || break
    errors.each { |s| sockets.delete(s) }
    writeable.each do |s|
      idx = sockets.index(s)
      sockets.delete_at(idx) # don't retry this socket again
      addr = viable_addrs.delete_at(idx) # find the IP for the socket
      begin
        s.connect_nonblock(Socket.sockaddr_in(port, addr.ip_address)) # check for errors
      rescue Errno::EISCONN
        # already connected, continue
      rescue SystemCallError
        next # skip connection errors, hopefully at least one succeeds
      end
      # pass the connected FD to the parent process over STDOUT
      Socket.for_fd(1).sendmsg "\0", 0, nil, Socket::AncillaryData.unix_rights(s)
      exit 0 # exit as fast as possible so that other sockets don't connect
    end
  end
  exit 1 # all connections failed
end

#keygenObject

Generate new aes and hmac keys, print to stdout



49
50
51
52
53
54
55
# File 'lib/sparoid.rb', line 49

def keygen
  cipher = OpenSSL::Cipher.new("aes-256-cbc")
  key = cipher.random_key.unpack1("H*")
  hmac_key = OpenSSL::Random.random_bytes(32).unpack1("H*")
  puts "key = #{key}"
  puts "hmac-key = #{hmac_key}"
end