lib/pione/relay/relay-socket.rb
module Pione
module Relay
# RelaySocket is connection layer between PIONE client and PIONE relay.
class RelaySocket < DRb::DRbSSLSocket
# AuthError is an error for relay authentication failure.
class AuthError < StandardError; end
# ProxyError is raised when proxy server cannot start.
class ProxyError < StandardError; end
# BadMessage is an error for protocol violation.
class BadMessage < Exception; end
def self.parse_uri(uri)
if uri =~ /^relay:\/\/(.*?)(:(\d+))?(\?(.*))?$/
host = $1
port = $3 ? $3.to_i : Global.relay_port
option = $5
return host, port, option
else
raise DRb::DRbBadScheme.new(uri) unless uri =~ /^relay:/
raise DRb::DRbBadURI.new('can\'t parse uri:' + uri)
end
end
# Opens the socket on pione-client.
def self.open(uri, config)
host, port, option = parse_uri(uri)
host.untaint
port.untaint
# make tcp connection with SSL
soc = TCPSocket.open(host, port)
ssl_conf = DRb::DRbSSLSocket::SSLConfig.new(config)
ssl_conf.setup_ssl_context
ssl = ssl_conf.connect(soc)
if Global.show_communication
puts "you connected relay socket to %s" % uri
end
# auth like HTTP's digest method
begin
Timeout.timeout(Global.relay_client_auth_timeout_sec) do
realm = ssl.gets.chomp
uuid = ssl.gets.chomp
account = Global.relay_account_db[realm] || (raise AuthError.new("unknown realm: %s" % realm))
name = account.name
digest = account.digest
response = "%s:%s" % [name, Digest::SHA512.hexdigest("%s:%s" % [uuid, digest])]
ssl.puts(response)
unless ssl.read(3).chomp == "OK"
raise AuthError.new("authentication failed")
end
end
rescue AuthError => e
raise e
rescue Timeout::Error
raise AuthError.new("authentication timeout")
end
if Global.show_communication
puts "you succeeded relay authentication: %s" % uri
end
# create receiver socket
ReceiverSocket.table["%s:%s" % [host, port]] = ssl
Global.relay_receiver = DRb::DRbServer.new(
"receiver://%s:%s" % [host, port],
Global.relay_tuple_space_server
)
# create an instance
return self.new(uri, ssl, ssl_conf, true)
end
# Opens relay server port for clients.
# @api private
def self.open_server(uri, config)
# parse URI
uri = 'relay://:%s' % Global.relay_port unless uri
host, port, option = parse_uri(uri)
# rebuild URI
if host.size == 0
host = getservername
soc = open_server_inaddr_any(host, port)
else
soc = TCPServer.open(host, port)
end
port = soc.addr[1] if port == 0
new_uri = "relay://#{host}:#{port}"
# prepare SSL
ssl_conf = DRb::DRbSSLSocket::SSLConfig.new(config).tap do |conf|
conf.setup_certificate
conf.setup_ssl_context
end
# create instance
self.new(new_uri, soc, ssl_conf, false)
end
def self.uri_option(uri, config)
host, port, option = parse_uri(uri)
return "relay://#{host}:#{port}", option
end
# Accepts pione-clients on server side.
# @api private
def accept
begin
# accept loop
while true
soc = @socket.accept
break if (@acl ? @acl.allow_socket?(soc) : true)
soc.close
end
if Global.show_communication
puts "someone connected to relay socket..."
end
# build ssl
ssl = @config.accept(soc)
# relay auth like HTTP's digest method
ssl.puts(Global.relay_realm)
uuid = Util::UUID.generate
ssl.puts(uuid)
if msg = ssl.gets
name, digest = msg.chomp.split(":")
unless Global.relay_client_db.auth(uuid, name, digest)
raise AuthError.new(name)
end
ssl.puts "OK"
if Global.show_communication
puts "succeeded authentication for %s" % name
end
# setup transmitter_id
transmitter_id = Util::UUID.generate
# save ssl socket as receiver side with transmitter_id
TransmitterSocket.receiver_socket[transmitter_id] = ssl
# open and save tcp socket with transmitter_id
Global.relay_transmitter_proxy_side_port_range.each do |port|
begin
tcp_socket = TCPServer.new("localhost", port)
TransmitterSocket.proxy_socket[transmitter_id] = tcp_socket
break
rescue
end
end
# create servers
transmitter_server = create_transmitter_server(transmitter_id)
proxy_server = create_proxy_server(transmitter_id)
# start to provide the proxy server
TupleSpaceProvider.instance.add_tuple_space_server(
DRb::DRbObject.new_with_uri(proxy_server.uri)
)
# create instance
self.class.new(uri, ssl, @config, true)
else
raise BadMessage
end
rescue OpenSSL::SSL::SSLError, AuthError, BadMessage => e
soc.close
Log::Debug.communication("relay socket was closed: %s" % e.message)
retry
end
end
# Creates a transmitter server with the relay socket.
# @return [void]
def create_transmitter_server(transmitter_id)
uri = "transmitter://%s" % transmitter_id
server = DRb::DRbServer.new(uri, Trampoline.new(uri, @config))
if Global.show_communication
puts "relay created the transmitter: %s" % server.uri
end
return server
end
# Creates a proxy server for brokers in LAN.
def create_proxy_server(transmitter_id)
transmitter = DRb::DRbObject.new_with_uri("transmitter://%s" % transmitter_id)
Global.relay_proxy_port_range.each do |port|
begin
uri = "druby://localhost:%s" % port
server = DRb::DRbServer.new(uri, transmitter)
if Global.show_communication
puts "relay created the proxy: %s" % server.uri
end
return server
rescue
next
end
end
raise ProxyError.new("You cannot start relay proxy server.")
end
end
# install the protocol
DRb::DRbProtocol.add_protocol(RelaySocket)
end
end