class EventMachine::Protocols::Socks5

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

Public Class Methods

new(host, port) click to toggle source
# File lib/blather/core_ext/eventmachine.rb, line 23
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
post_init() click to toggle source
# File lib/blather/core_ext/eventmachine.rb, line 35
def post_init; socks_post_init; end
receive_data(*a) click to toggle source
# File lib/blather/core_ext/eventmachine.rb, line 36
def receive_data(*a); socks_receive_data(*a); end

Public Instance Methods

restore_methods() click to toggle source
# File lib/blather/core_ext/eventmachine.rb, line 40
def restore_methods
  class << self
    remove_method :post_init
    remove_method :receive_data
  end
end
setup_methods() click to toggle source
# File lib/blather/core_ext/eventmachine.rb, line 33
def setup_methods
  class << self
    def post_init; socks_post_init; end
    def receive_data(*a); socks_receive_data(*a); end
  end
end
socks_post_init() click to toggle source
# File lib/blather/core_ext/eventmachine.rb, line 47
def socks_post_init
  packet = [5, @socks_methods.size].pack('CC') + @socks_methods.pack('C*')
  send_data(packet)
end
socks_receive_data(data) click to toggle source
# File lib/blather/core_ext/eventmachine.rb, line 52
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