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

Classes: CLI, Error, Instance, ResolvError

Constant Summary collapse

SPAROID_CACHE_PATH =
ENV.fetch("SPAROID_CACHE_PATH", "/tmp/.sparoid_public_ip")
VERSION =
"1.2.0"

Instance Method Summary collapse

Instance Method Details

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

Send an authorization packet



15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/sparoid.rb', line 15

def auth(key, hmac_key, host, port, open_for_ip: cached_public_ip)
  addrs = resolve_ip_addresses(host, port)
  ip = Resolv::IPv4.create(open_for_ip)
  msg = message(ip)
  data = prefix_hmac(hmac_key, encrypt(key, msg))
  sendmsg(addrs, data)

  # 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.map(&:ip_address) # return resolved IP(s)
end

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

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



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/sparoid.rb', line 40

def fdpass(ips, port, connect_timeout: 10) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
  # try connect to all IPs
  sockets = ips.map do |ip|
    Socket.new(Socket::AF_INET, Socket::SOCK_STREAM).tap do |s|
      s.connect_nonblock(Socket.sockaddr_in(port, ip), exception: false)
    end
  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
      ip = ips.delete_at(idx) # find the IP for the socket
      begin
        s.connect_nonblock(Socket.sockaddr_in(port, ip)) # 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



31
32
33
34
35
36
37
# File 'lib/sparoid.rb', line 31

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