class RaptorIO::Socket::Comm::SOCKS
Communication through a SOCKS
proxy
@see openssh.org/txt/socks4.protocol @see tools.ietf.org/html/rfc1928
Attributes
socks_comm[RW]
@!attribute socks_comm
The {Comm} used to connect to the SOCKS server @return [Comm]
socks_host[RW]
@!attribute socks_host
The SOCKS server's address @return [String]
socks_port[RW]
@!attribute socks_port
The SOCKS server's port @return [Fixnum]
Public Class Methods
new(options = {})
click to toggle source
@param options [Hash] @option options :socks_host [String,IPAddr] @option options :socks_port [Fixnum] @option options :socks_comm [Comm]
# File lib/raptor-io/socket/comm/socks.rb, line 59 def initialize(options = {}) @socks_host = options[:socks_host] @socks_port = options[:socks_port].to_i @socks_comm = options[:socks_comm] end
Public Instance Methods
create_tcp(options)
click to toggle source
Connect to ‘:peer_host`
@option (see Comm#create_tcp)
@return [Socket::TCP]
@raise [RaptorIO::Socket::Error::ConnectTimeout]
# File lib/raptor-io/socket/comm/socks.rb, line 83 def create_tcp(options) @socks_socket = socks_comm.create_tcp( peer_host: socks_host, peer_port: socks_port ) negotiate_connection(options[:peer_host], options[:peer_port]) if options[:ssl_context] RaptorIO::Socket::TCP::SSL.new(@socks_socket, options) else RaptorIO::Socket::TCP.new(@socks_socket, options) end end
support_ipv6?()
click to toggle source
(see Comm#support_ipv6?)
# File lib/raptor-io/socket/comm/socks.rb, line 66 def support_ipv6? begin tcp = create_tcp("::1", {}) tcp.close true rescue RaptorIO::Error nil end end
Private Instance Methods
handle_reply(reply_pkt)
click to toggle source
# File lib/raptor-io/socket/comm/socks.rb, line 177 def handle_reply(reply_pkt) # [ version ][ reply code ][ reserved ][ atyp ] _, reply, _, type = reply_pkt.unpack("C4") # X'00' succeeded # X'01' general SOCKS server failure # X'02' connection not allowed by ruleset # X'03' Network unreachable # X'04' Host unreachable # X'05' Connection refused # X'06' TTL expired # X'07' Command not supported # X'08' Address type not supported # X'09' to X'FF' unassigned case reply when ReplyCodes::SUCCEEDED # Read in the bind addr. The protocol spec says this is supposed # to be the getsockname(2) address of the sockfd on the server, # which isn't all that useful to begin with. SSH(1) always # populates it with NULL bytes, making it completely pointless. # Read it off the socket and ignore it so it doesn't get in the # way of the proxied traffic. case type when AddressTypes::ATYP_IPv4 @socks_socket.read(4) when AddressTypes::ATYP_IPv6 @socks_socket.read(16) when AddressTypes::ATYP_DOMAINNAME # Pascal string, so read in the length and then read that many len = @socks_socket.read(1).to_i @socks_socket.read(len) end # bind port @socks_socket.read(2) when ReplyCodes::NETUNREACH, ReplyCodes::HOSTUNREACH @socks_socket.close raise RaptorIO::Socket::Error::HostUnreachable when ReplyCodes::CONNREFUSED @socks_socket.close raise RaptorIO::Socket::Error::ConnectionRefused when ReplyCodes::GENERAL_FAILURE, ReplyCodes::NOT_ALLOWED, ReplyCodes::TTL_EXPIRED, ReplyCodes::CMD_NOT_SUPPORTED, ReplyCodes::ATYP_NOT_SUPPORTED # Then this is a kind of failure that doesn't map well to standard # socket errors. Just call it a ConnectionError. @socks_socket.close raise RaptorIO::Socket::Error::ConnectionError else # Then this is an unassigned error code. No idea what it is, so # just call it a ConnectionError @socks_socket.close raise RaptorIO::Socket::Error::ConnectionError end end
negotiate_connection(peer_host, peer_port)
click to toggle source
Attempt to create a connection to ‘peer_host`:`peer_port` via the SOCKS
server at {#socks_host}:{#socks_port}.
@param peer_host [String] An address or hostname @param peer_port [Fixnum] TCP port to connect to
@raise [Error::ConnectionError] When the connection fails
# File lib/raptor-io/socket/comm/socks.rb, line 108 def negotiate_connection(peer_host, peer_port) # From RFC1928: # ``` # o X'00' NO AUTHENTICATION REQUIRED # o X'01' GSSAPI # o X'02' USERNAME/PASSWORD # o X'03' to X'7F' IANA ASSIGNED # o X'80' to X'FE' RESERVED FOR PRIVATE METHODS # o X'FF' NO ACCEPTABLE METHODS # ``` auth_methods = [ 0 ] # [ version ][ N methods ][ methods ... ] v5_pkt = [ 5, auth_methods.count, *auth_methods ].pack("CCC*") @socks_socket.write(v5_pkt) response = @socks_socket.read(2) case response when "\x05\x00".force_encoding('binary') # Then they accepted NO AUTHENTICATION and we can send a connect # request *without* a password request = pack_v5_connect_packet(peer_host, peer_port.to_i) else # Then they didn't like what we had to offer. @socks_socket.close raise RaptorIO::Socket::Error::ConnectionError, "Proxy connection failed" end @socks_socket.write(request) reply_pkt = @socks_socket.read(4) if reply_pkt.nil? # ssh(1) likes to just sever the connection if it can't connect raise RaptorIO::Socket::Error::ConnectionError end handle_reply(reply_pkt) end
pack_v5_connect_packet(peer_host, peer_port)
click to toggle source
# File lib/raptor-io/socket/comm/socks.rb, line 147 def pack_v5_connect_packet(peer_host, peer_port) begin ip = IPAddr.parse(peer_host) rescue ArgumentError type = AddressTypes::ATYP_DOMAINNAME # Packed as a Pascal string packed_addr = [peer_host.length, peer_host].pack("Ca*") else if ip.to_range.count != 1 raise ArgumentError, "Invalid host" end type = if ip.ipv4? AddressTypes::ATYP_IPv4 elsif ip.ipv6? AddressTypes::ATYP_IPv6 end packed_addr = ip.hton end connect_packet = [ 5, # Version 1, # CMD, CONNECT X'01' 0, # reserved type, packed_addr, peer_port ].pack("CCCCa*n") connect_packet end