Class: EventMachine::Protocols::Socks5

Inherits:
Connection
  • Object
show all
Defined in:
lib/blather/core_ext/eventmachine.rb

Overview

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

Instance Method Summary collapse

Constructor Details

#initialize(host, port) ⇒ Socks5

Returns a new instance of Socks5.



23
24
25
26
27
28
29
30
31
# 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

Instance Method Details

#restore_methodsObject



40
41
42
43
44
45
# 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_methodsObject



33
34
35
36
37
38
# 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_initObject



47
48
49
50
# 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) ⇒ Object



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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# 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