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.1.2"

Instance Method Summary collapse

Instance Method Details

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

Send an authorization packet



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/sparoid.rb', line 23

def auth(key, hmac_key, host, port, open_for_ip: nil)
  addrs = resolve_ip_addresses(host, port)
  addrs.each do |addr|
    messages = generate_messages(open_for_ip)
    data = messages.map do |message|
      prefix_hmac(hmac_key, encrypt(key, message))
    end
    sendmsg(addr, data)
  end

  # 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
end

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

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



51
52
53
54
55
56
57
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
# File 'lib/sparoid.rb', line 51

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



42
43
44
45
46
47
48
# File 'lib/sparoid.rb', line 42

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