From: "Jørgen P. Tjernø" Date: 2006-10-05T08:41:33+09:00 Subject: Small SOCKSv4-implementation - comments / feedback appreciated After having struggeled with libsock/ruby-sockets+socks, etc, and not getting any way, I decided to try another approach. The whole ruby-builting-socks / libsock idea is to make it as transparent / global as possible, seemingly never meant to be controlled directly by the application. So, I implemented my own little TCPSocket-"wrapper"-class, which just speaks the one command from SOCKSv4 and which only understands the one reply. After that, it lets go of the socket. (SOCKS protocol information found on http://en.wikipedia.org/wiki/SOCKS) Then I just overrode Net::FTP#open_socket, so if ENV['SOCKS_SERVER'] is set, it uses this my little class to set up a socks-connection. This can of course be used for anything, not just Net::FTP - any other Net::-module, or a custom creation of yours. Net::FTP just happens to be what I wanted it for. ;-) Now, I'm curious what you think of the code as a whole (coding style, language use, approach, how to "hook into" Net::FTP, etc), and if you have any suggestions. I'm thinking I should release this as public domain, so anyone else in my situation won't have to bark up all the wrong trees for as long as I did. ;-) Thanks in advance! Kindest regards, J�rgen P. Tjern�. * code follows: require 'socket' require 'resolv' require 'net/ftp' class SocksProxying REQUEST_GRANTED = 0x5A REQUEST_FAILED = 0x5B REQUEST_FAILED_NOIDENT = 0x5C REQUEST_FAILED_IDENTINVALID = 0x5D SOCKS_VERSION_4 = 0x04 SOCKS_CMD_CONNECTION = 0x01 SOCKS_CMD_BINDING = 0x02 SOCKS_REPLY_LENGTH = 8 def initialize @socket = nil end def connect_via(proxy, proxy_port = 1080) @socket = TCPSocket.open(proxy, proxy_port) end def connect(host, port, user = ENV['USER']) if @socket.nil? raise "Error: Must connect_via first, then connect." end ip = Resolv.getaddress(host).split(/\./).collect {|part| part.to_i} port = port.to_i data = [SOCKS_VERSION_4, SOCKS_CMD_CONNECTION, port, ip, user].flatten.pack('CCnC4a' + (user.length + 1).to_s) @socket.write data dud, statuscode, port, ip = @socket.recv(SOCKS_REPLY_LENGTH).unpack('CCnC4') if statuscode != REQUEST_GRANTED if statuscode == REQUEST_FAILED raise "Error: SOCKS-access not granted by SOCKS-server." elsif statuscode == REQUEST_FAILED_NOIDENT raise "Error: SOCKS-connection failed; server could not reach our ident server." elsif statuscode == REQUEST_FAILED_IDENTINVALID raise "Error: SOCKS-connection failed; server could not match our ident reply to the one sent." else raise "Error: SOCKS-connection failed; unknown error-code." end end @socket end end # Here is where we override Net::FTP's internal socket creation. class Net::FTP def open_socket(host, port) if ENV["SOCKS_SERVER"] @passive = true proxy_host, proxy_port = ENV["SOCKS_SERVER"].split /:/ sp = SocksProxying.new sp.connect_via(proxy_host, proxy_port) return sp.connect(host, port) else return TCPSocket.open(host, port) end end end