Skip to content

Commit

Permalink
upgrades a lot of stuff, new functions migrated from cjdns-tool, vers…
Browse files Browse the repository at this point in the history
…ion bump
  • Loading branch information
kris kechagia committed May 5, 2012
1 parent 5398a1d commit e18b9e8
Show file tree
Hide file tree
Showing 7 changed files with 457 additions and 141 deletions.
143 changes: 3 additions & 140 deletions lib/cjdns-lib.rb
Original file line number Diff line number Diff line change
@@ -1,144 +1,7 @@
require 'rubygems'
require 'bencode'
require 'socket'
require 'digest'
require 'cjdns-lib/interface'
require 'cjdns-lib/routing_table'
require 'cjdns-lib/hypedns'
require 'cjdns-lib/version'

module Cjdns
class Lib

def initialize(options = {})
options = { 'host' => 'localhost', 'port' => 11234, 'password' => nil, 'debug' => false }.merge options

@password = options['password']
@debug = options['debug']

puts "connecting to #{options['host']}:#{options['port']}" if @debug
@socket = TCPSocket.open(options['host'], options['port'])
raise "#{host}:#{port} doesn't appear to be a cjdns socket" unless ping_self
end

def ping_self
return false unless auth_send('ping')['q'] == 'pong'
true
end

def memory
return auth_send('memory')['bytes']
end

def ping_node(path, timeout = 5000)
response = auth_send('RouterModule_pingNode', { 'path' => path, 'timeout' => timeout } )
return response['ms'] if response['result'] == 'pong'
false
end

def dump_table
page = 0
routing_table = []
begin
response = auth_send('NodeStore_dumpTable', 'page' => page)

# add received routing table
routing_table = routing_table + response['routingTable']

# if 'more' is set, there's more data to come, request next page
page += 1
end while response['more']

routing_table
end

def ping_switch(path, data = 'x', timeout = 5000)
response = auth_send('SwitchPinger_ping', { 'path' => path,
'data' => data,
'timeout' => timeout } )

return response['ms'] if response['result'] == 'pong'
false
end

def lookup(address)
return auth_send('RouterModule_lookup', { 'address' => address } )
end

def authorized_passwords_add(password, auth_type = 1)
return auth_send('AuthorizedPasswords_add', { 'password' => password,
'authType' => auth_type } )
end

def authorized_passwords_flush
return auth_send('AuthorizedPasswords_flush')
end

def scramble_keys(xor_value)
return auth_send('UDPInterface_scrambleKeys', { 'xorValue' => xor_value } )
end

def begin_connection(public_key, address, password = nil)
return auth_send('UDPInterface_beginConnection', { 'publicKey' => public_key,
'address' => address,
'password' => password } )
end


private

def auth_send(funcname, args = nil)
txid = rand(36**8).to_s(36)

# setup authenticated request if password given
if @password
cookie = get_cookie

request = {
'q' => 'auth',
'aq' => funcname,
'hash' => Digest::SHA256.hexdigest(@password + cookie),
'cookie' => cookie,
'txid' => txid
}

request['args'] = args if args
request['hash'] = Digest::SHA256.hexdigest(request.bencode)

# if no password is given, try request without auth
else
request = { 'q' => funcname, 'txid' => txid }
request['args'] = args if args
end

response = send request
raise 'wrong txid in reply' if response['txid'] and response['txid'] != txid
response
end

def get_cookie
txid = rand(36**8).to_s(36)
response = send('q' => 'cookie', 'txid' => txid)
raise 'wrong txid in reply' if response['txid'] and response['txid'] != txid
response['cookie']
end

def send(request)
# clear socket
puts "flushing socket" if @debug
@socket.flush

puts "sending request: #{request.inspect}" if @debug
response = ''
@socket.puts request.bencode

while r = @socket.recv(1024)
response << r
break if r.length < 1024
end

puts "bencoded reply: #{response.inspect}" if @debug
response = response.bdecode

puts "bdecoded reply: #{response.inspect}" if @debug
response
end
end
end
117 changes: 117 additions & 0 deletions lib/cjdns-lib/host.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
require 'socket'

module CJDNS
class Host
attr_reader :ip, :cjdns

# @param [String] ip
# @param [Cjdns::Interface] cjdns
# @param [Hash] options options for CJDNS::Interface
def initialize(ip, cjdns = nil, options = {})
@ip = ip

# connect to cjdns socket, unless given
cjdns = CJDNS::Interface.new(options) unless cjdns
@cjdns = cjdns
end

# TCP pings host (port 7)
#
# @param [Int] timeout
# @return [Hash] { 'time' => [Int] response_time }
# @return [Boolean] false if host is not responding
def ping_tcp(timeout = 5)
start = Time.new

begin
s = connect(7, timeout)
return false unless s
s.close
rescue Errno::ECONNREFUSED
# connection refused means host is alive
rescue Errno::EHOSTUNREACH, Errno::ETIMEDOUT
return false
end

return { 'time' => (Time.new - start) * 1000 }
end

# HTTP ping (port 80)
#
# @param [Int] timeout
# @return [Hash] { 'time' => [Int] response_time, 'title' => [String] html_title (if found) }
# @return [Boolean] false if host is not replying to http
def ping_http(timeout = 5)
response = {}
start = Time.new

begin
s = connect(80, timeout)
return false unless s

s.write "GET / HTTP/1.1\r\nHost: [#{@ip}]\r\nConnection: close\r\n\r\n"

s.read.each_line do |line|
line.force_encoding 'utf-8' unless RUBY_VERSION < '1.9'
if md = (/<title>\s*(.*)\s*<\/title>/iu).match(line)
response['title'] = md[1]
end
end

s.close
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::EPIPE, Errno::EINVAL
return false
end

response['time'] = (Time.new - start) * 1000
return response
end

# cjdns internal ping
#
# @param [Int] timeout
# @return [Hash] { 'time' => [Int] response_time }
# @return [Boolean] false if host is not replying
### TODO
def ping_cjdns(timeout = 1)
time = @cjdns.ping_node(@ip, timeout * 1000)
return false unless time
return { 'time' => time }
end


private

# use nonblocking socket to connect to port, respecting timeout
#
# @param [Int] port
# @param [Int] timeout
# @return [Socket] on success, nil on failure
def connect(port, timeout = 5)
s = Socket.open(Socket::AF_INET6, Socket::SOCK_STREAM, 0)

begin
s.connect_nonblock(Socket.sockaddr_in(port, @ip))
rescue Errno::EINPROGRESS
# block until the socket is ready, then try again
IO.select([s], [s], [s], timeout)

begin
s.connect_nonblock(Socket.sockaddr_in(port, @ip))
rescue Errno::EISCONN
# already connected, do nothing
rescue Errno::EINPROGRESS, Errno::EALREADY
# connection still in progress, this means we timed out given
# our IO.select has returned.
s.close
return nil
rescue Errno::EINVAL, Errno::EACCES
# invalid argument errors or permission denied errors
# rise once in a while on the secoond connect, ignore
end
end
s
end

end
end
60 changes: 60 additions & 0 deletions lib/cjdns-lib/hypedns.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require 'rubygems'
require 'resolv'
require 'ipaddress'

module CJDNS
class HypeDNS

# @param [String] nameserver (either ip, or 'via_internet' / 'via_cjdns' to use default)
def initialize(nameserver = 'via_cjdns')
nameserver = '216.150.225.240' if nameserver == 'via_internet'
nameserver = 'fc5d:baa5:61fc:6ffd:9554:67f0:e290:7535' if nameserver == 'via_cjdns'

@hypedns = Resolv::DNS.new(:nameserver => nameserver)
end

# get AAAA (ipv6) record of host
#
# @param [String] host
# @return [String] ip, nil on failure
def aaaa(host)
return nil if @disabled

begin
@hypedns.getresource(host, Resolv::DNS::Resource::IN::AAAA).address.to_s.downcase
rescue Resolv::ResolvError
return nil
end
end

# get PTR record for ip
#
# @param [String] ip
# @return [String] host, nil on failure
def ptr(ip)
return nil if @disabled

begin
return @hypedns.getname(ip).to_s
rescue Resolv::ResolvError
return nil
end
end

# resolv host unless it already a valid ipv6 address
#
# @param [String] host
# @return [String] ip|host, nil on failure or if host = nil
def aaaa_unless_ip(host = nil)
return nil unless host

if IPAddress.valid_ipv6? host
return host
else
return aaaa host
end
end

end
end

Loading

0 comments on commit e18b9e8

Please sign in to comment.