Class: Net::SSH::Proxy::SOCKS5

Inherits:
Object
  • Object
show all
Defined in:
lib/net/ssh/proxy/socks5.rb

Overview

An implementation of a SOCKS5 proxy. To use it, instantiate it, then pass the instantiated object via the :proxy key to Net::SSH.start:

require 'net/ssh/proxy/socks5'

proxy = Net::SSH::Proxy::SOCKS5.new('proxy.host', proxy_port,
  :user => 'user', :password => "password")
Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
  ...
end

Constant Summary collapse

VERSION =

The SOCKS protocol version used by this class

5
METHOD_NO_AUTH =

The SOCKS authentication type for requests without authentication

0
METHOD_PASSWD =

The SOCKS authentication type for requests via username/password

2
METHOD_NONE =

The SOCKS authentication type for when there are no supported authentication methods.

0xFF
CMD_CONNECT =

The SOCKS packet type for requesting a proxy connection.

1
ATYP_IPV4 =

The SOCKS address type for connections via IP address.

1
ATYP_DOMAIN =

The SOCKS address type for connections via domain name.

3
SUCCESS =

The SOCKS response code for a successful operation.

0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(proxy_host, proxy_port = 1080, options = {}) ⇒ SOCKS5

Create a new proxy connection to the given proxy host and port. Optionally, :user and :password options may be given to identify the username and password with which to authenticate.



55
56
57
58
59
# File 'lib/net/ssh/proxy/socks5.rb', line 55

def initialize(proxy_host, proxy_port = 1080, options = {})
  @proxy_host = proxy_host
  @proxy_port = proxy_port
  @options = options
end

Instance Attribute Details

#optionsObject (readonly)

The map of options given at initialization



50
51
52
# File 'lib/net/ssh/proxy/socks5.rb', line 50

def options
  @options
end

#proxy_hostObject (readonly)

The proxy’s host name or IP address



44
45
46
# File 'lib/net/ssh/proxy/socks5.rb', line 44

def proxy_host
  @proxy_host
end

#proxy_portObject (readonly)

The proxy’s port number



47
48
49
# File 'lib/net/ssh/proxy/socks5.rb', line 47

def proxy_port
  @proxy_port
end

Instance Method Details

#open(host, port, connection_options) ⇒ Object

Return a new socket connected to the given host and port via the proxy that was requested when the socket factory was instantiated.



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
# File 'lib/net/ssh/proxy/socks5.rb', line 63

def open(host, port, connection_options)
  socket = Socket.tcp(proxy_host, proxy_port, nil, nil,
                      connect_timeout: connection_options[:timeout])

  methods = [METHOD_NO_AUTH]
  methods << METHOD_PASSWD if options[:user]

  packet = [VERSION, methods.size, *methods].pack("C*")
  socket.send packet, 0

  version, method = socket.recv(2).unpack("CC")
  if version != VERSION
    socket.close
    raise Net::SSH::Proxy::Error, "invalid SOCKS version (#{version})"
  end

  if method == METHOD_NONE
    socket.close
    raise Net::SSH::Proxy::Error, "no supported authorization methods"
  end

  negotiate_password(socket) if method == METHOD_PASSWD

  packet = [VERSION, CMD_CONNECT, 0].pack("C*")

  if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
    packet << [ATYP_IPV4, $1.to_i, $2.to_i, $3.to_i, $4.to_i].pack("C*")
  else
    packet << [ATYP_DOMAIN, host.length, host].pack("CCA*")
  end

  packet << [port].pack("n")
  socket.send packet, 0

  version, reply, = socket.recv(2).unpack("C*")
  socket.recv(1)
  address_type = socket.recv(1).getbyte(0)
  case address_type
  when 1
    socket.recv(4) # get four bytes for IPv4 address
  when 3
    len = socket.recv(1).getbyte(0)
    hostname = socket.recv(len)
  when 4
    ipv6addr hostname = socket.recv(16)
  else
    socket.close
    raise ConnectError, "Illegal response type"
  end
  portnum = socket.recv(2)

  unless reply == SUCCESS
    socket.close
    raise ConnectError, "#{reply}"
  end

  return socket
end