# @private module EventMachine # @private module Protocols # Basic SOCKS v5 client implementation # # Use as you would any regular connection: # # class MyConn < EM::P::Socks5 # def post_init # send_data("sup") # end # # def receive_data(data) # send_data("you said: #{data}") # end # end # # EM.connect socks_host, socks_port, MyConn, host, port # # @private class Socks5 < Connection def initialize(host, port) @host = host @port = port @socks_error_code = nil @buffer = '' @socks_state = :method_negotiation @socks_methods = [0] # TODO: other authentication methods setup_methods end def setup_methods class << self def post_init; socks_post_init; end def receive_data(*a); socks_receive_data(*a); end end end def restore_methods class << self remove_method :post_init remove_method :receive_data end end def socks_post_init packet = [5, @socks_methods.size].pack('CC') + @socks_methods.pack('C*') send_data(packet) end def socks_receive_data(data) @buffer << data if @socks_state == :method_negotiation return if @buffer.size < 2 header_resp = @buffer.slice! 0, 2 _, method_code = header_resp.unpack("cc") if @socks_methods.include?(method_code) @socks_state = :connecting packet = [5, 1, 0].pack("C*") if @host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # IPv4 packet << [1, $1.to_i, $2.to_i, $3.to_i, $4.to_i].pack("C*") elsif @host.include?(":") # IPv6 l, r = if @host =~ /^(.*)::(.*)$/ [$1,$2].map {|i| i.split ":"} else [@host.split(":"),[]] end dec_groups = (l + Array.new(8-l.size-r.size, '0') + r).map {|i| i.hex} packet << ([4] + dec_groups).pack("Cn8") else # Domain packet << [3, @host.length, @host].pack("CCA*") end packet << [@port].pack("n") send_data packet else @socks_state = :invalid @socks_error_code = method_code close_connection return end elsif @socks_state == :connecting return if @buffer.size < 4 header_resp = @buffer.slice! 0, 4 _, response_code, _, address_type = header_resp.unpack("C*") if response_code == 0 case address_type when 1 @buffer.slice! 0, 4 when 3 len = @buffer.slice! 0, 1 @buffer.slice! 0, len.unpack("C").first when 4 @buffer.slice! 0, 16 else @socks_state = :invalid @socks_error_code = address_type close_connection return end @buffer.slice! 0, 2 @socks_state = :connected restore_methods post_init receive_data(@buffer) unless @buffer.empty? else @socks_state = :invalid @socks_error_code = response_code close_connection return end end end end end end