class Net::SSH::Proxy::SOCKS5

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

Constants

ATYP_DOMAIN

The SOCKS address type for connections via domain name.

ATYP_IPV4

The SOCKS address type for connections via IP address.

CMD_CONNECT

The SOCKS packet type for requesting a proxy connection.

METHOD_NONE

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

METHOD_NO_AUTH

The SOCKS authentication type for requests without authentication

METHOD_PASSWD

The SOCKS authentication type for requests via username/password

SUCCESS

The SOCKS response code for a successful operation.

VERSION

The SOCKS protocol version used by this class

Attributes

options[R]

The map of options given at initialization

proxy_host[R]

The proxy's host name or IP address

proxy_port[R]

The proxy's port number

Public Class Methods

new(proxy_host, proxy_port=1080, options={}) click to toggle source

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.

# File lib/net/ssh/proxy/socks5.rb, line 57
def initialize(proxy_host, proxy_port=1080, options={})
  @proxy_host = proxy_host
  @proxy_port = proxy_port
  @options = options
end

Public Instance Methods

open(host, port, connection_options) click to toggle source

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

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