diff --git a/README.md b/README.md
index 02962645..415f2deb 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ See [HTTPClient](http://www.rubydoc.info/gems/httpclient/frames) for documentati
 ## Features
 
 * methods like GET/HEAD/POST/* via HTTP/1.1.
-* HTTPS(SSL), Cookies, proxy, authentication(Digest, NTLM, Basic), etc.
+* HTTPS(SSL), Cookies, (HTTP, socks[45])proxy, authentication(Digest, NTLM, Basic), etc.
 * asynchronous HTTP request, streaming HTTP request.
 * debug mode CLI.
 * by contrast with net/http in standard distribution;
@@ -24,6 +24,7 @@ See [HTTPClient](http://www.rubydoc.info/gems/httpclient/frames) for documentati
   * extensible with filter interface
   * you don't have to care HTTP/1.1 persistent connection
     (httpclient cares instead of you)
+  * socks proxy
 * Not supported now
   * Cache
   * Rather advanced HTTP/1.1 usage such as Range, deflate, etc.
diff --git a/lib/httpclient.rb b/lib/httpclient.rb
index e1f18647..8afca57c 100644
--- a/lib/httpclient.rb
+++ b/lib/httpclient.rb
@@ -44,9 +44,12 @@
 #     clnt = HTTPClient.new
 #
 # 2. Accessing resources through HTTP proxy.  You can use environment
-#    variable 'http_proxy' or 'HTTP_PROXY' instead.
+#    variable 'http_proxy' or 'HTTP_PROXY' or 'SOCKS_PROXY' instead.
 #
 #     clnt = HTTPClient.new('http://myproxy:8080')
+#     clnt = HTTPClient.new('socks4://myproxy:1080')
+#     clnt = HTTPClient.new('socks5://myproxy:1080')
+#     clnt = HTTPClient.new('socks5://username:password@myproxy:1080')
 #
 # === How to retrieve web resources
 #
@@ -503,8 +506,9 @@ def proxy=(proxy)
       @proxy_auth.reset_challenge
     else
       @proxy = urify(proxy)
-      if @proxy.scheme == nil or @proxy.scheme.downcase != 'http' or
-          @proxy.host == nil or @proxy.port == nil
+      if @proxy.scheme == nil or
+        (@proxy.scheme.downcase != 'http' and !socks?(@proxy)) or
+         @proxy.host == nil or @proxy.port == nil
         raise ArgumentError.new("unsupported proxy #{proxy}")
       end
       @proxy_auth.reset_challenge
@@ -1073,8 +1077,10 @@ def load_environment
       # HTTP_* is used for HTTP header information.  Unlike open-uri, we
       # simply ignore http_proxy in CGI env and use cgi_http_proxy instead.
       self.proxy = getenv('cgi_http_proxy')
-    else
+    elsif ENV.key?('http_proxy')
       self.proxy = getenv('http_proxy')
+    else
+      self.proxy = getenv('socks_proxy')
     end
     # no_proxy
     self.no_proxy = getenv('no_proxy')
diff --git a/lib/httpclient/session.rb b/lib/httpclient/session.rb
index 67e2c3ba..40833849 100644
--- a/lib/httpclient/session.rb
+++ b/lib/httpclient/session.rb
@@ -21,6 +21,7 @@
 require 'httpclient/timeout' # TODO: remove this once we drop 1.8 support
 require 'httpclient/ssl_config'
 require 'httpclient/http'
+require 'httpclient/socks'
 if defined? JRUBY_VERSION
   require 'httpclient/jruby_ssl_socket'
 else
@@ -92,6 +93,8 @@ def inspect # :nodoc:
 
   # Manages sessions for a HTTPClient instance.
   class SessionManager
+    include Util
+
     # Name of this client.  Used for 'User-Agent' header in HTTP request.
     attr_accessor :agent_name
     # Owner of this client.  Used for 'From' header in HTTP request.
@@ -132,6 +135,8 @@ class SessionManager
     def initialize(client)
       @client = client
       @proxy = client.proxy
+      @socks_user = nil
+      @socks_password = nil
 
       @agent_name = nil
       @from = nil
@@ -167,6 +172,10 @@ def proxy=(proxy)
         @proxy = nil
       else
         @proxy = Site.new(proxy)
+        if socks?(proxy)
+          @socks_user = proxy.user
+          @socks_password = proxy.password
+        end
       end
     end
 
@@ -218,6 +227,8 @@ def open(uri, via_proxy = false)
       site = Site.new(uri)
       sess = Session.new(@client, site, @agent_name, @from)
       sess.proxy = via_proxy ? @proxy : nil
+      sess.socks_user = @socks_user
+      sess.socks_password = @socks_password
       sess.socket_sync = @socket_sync
       sess.tcp_keepalive = @tcp_keepalive
       sess.requested_version = @protocol_version if @protocol_version
@@ -448,6 +459,9 @@ class Session
     # Device for dumping log for debugging
     attr_accessor :debug_dev
 
+    attr_accessor :socks_user
+    attr_accessor :socks_password
+
     attr_accessor :connect_timeout
     attr_accessor :connect_retry
     attr_accessor :send_timeout
@@ -469,6 +483,8 @@ def initialize(client, dest, agent_name, from)
       @client = client
       @dest = dest
       @proxy = nil
+      @socks_user = nil
+      @socks_password = nil
       @socket_sync = true
       @tcp_keepalive = false
       @requested_version = nil
@@ -627,6 +643,32 @@ def create_socket(host, port)
       socket
     end
 
+    def via_socks_proxy?
+      @proxy && socks?(@proxy)
+    end
+
+    def via_http_proxy?
+      @proxy && !socks?(@proxy)
+    end
+
+    def create_socks_socket(proxy, dest)
+      if socks4?(proxy)
+        options = {}
+        options = { user: @socks_user } if @socks_user
+        socks_socket = SOCKS4Socket.new(proxy.host, proxy.port, options)
+      elsif socks5?(proxy)
+        options = {}
+        options = { user: @socks_user } if @socks_user
+        options[:password] = @socks_password if @socks_password
+        socks_socket = SOCKS5Socket.new(proxy.host, proxy.port, options)
+      else
+        raise "invalid proxy url #{proxy}"
+      end
+      dest_site = Site.new(dest)
+      opened_socket = socks_socket.open(dest_site.host, dest_site.port, {})
+      opened_socket
+    end
+
     def create_loopback_socket(host, port, str)
       @debug_dev << "! CONNECT TO #{host}:#{port}\n" if @debug_dev
       socket = LoopBackSocket.new(host, port, str)
@@ -751,6 +793,8 @@ def connect
           elsif https?(@dest)
             @socket = SSLSocket.create_socket(self)
             @ssl_peer_cert = @socket.peer_cert
+          elsif socks?(@proxy)
+            @socket = create_socks_socket(@proxy, @dest)
           else
             @socket = create_socket(site.host, site.port)
           end
diff --git a/lib/httpclient/socks.rb b/lib/httpclient/socks.rb
new file mode 100644
index 00000000..a2ad47fe
--- /dev/null
+++ b/lib/httpclient/socks.rb
@@ -0,0 +1,213 @@
+# Copyright (c) 2008 Jamis Buck
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the 'Software'), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+require 'socket'
+require 'resolv'
+require 'ipaddr'
+
+#
+# This implementation was borrowed from Net::SSH::Proxy
+#
+class HTTPClient
+  # An Standard SOCKS error.
+  class SOCKSError < SocketError; end
+
+  # Used for reporting proxy connection errors.
+  class SOCKSConnectError < SOCKSError; end
+
+  # Used when the server doesn't recognize the user's credentials.
+  class SOCKSUnauthorizedError < SOCKSError; end
+
+  # An implementation of a SOCKS4 proxy.
+  class SOCKS4Socket
+    # The SOCKS protocol version used by this class
+    VERSION = 4
+
+    # The packet type for connection requests
+    CONNECT = 1
+
+    # The status code for a successful connection
+    GRANTED = 90
+
+    # The proxy's host name or IP address, as given to the constructor.
+    attr_reader :proxy_host
+
+    # The proxy's port number.
+    attr_reader :proxy_port
+
+    # The additional options that were given to the proxy's constructor.
+    attr_reader :options
+
+    # Create a new proxy connection to the given proxy host and port.
+    # Optionally, a :user key may be given to identify the username
+    # with which to authenticate.
+    def initialize(proxy_host, proxy_port = 1080, options = {})
+      @proxy_host = proxy_host
+      @proxy_port = proxy_port
+      @options = options
+    end
+
+    # Return a new socket connected to the given host and port via the
+    # proxy that was requested when the socket factory was instantiated.
+    def open(host, port, connection_options)
+      socket = Socket.tcp(proxy_host, proxy_port, nil, nil,
+                          connect_timeout: connection_options[:timeout])
+      ip_addr = IPAddr.new(Resolv.getaddress(host))
+
+      packet = [
+        VERSION, CONNECT, port.to_i,
+        ip_addr.to_i, options[:user]
+      ].pack('CCnNZ*')
+      socket.send packet, 0
+
+      _version, status, _port, _ip = socket.recv(8).unpack('CCnN')
+      if status != GRANTED
+        socket.close
+        raise SOCKSConnectError, "error connecting to socks proxy (#{status})"
+      end
+
+      socket
+    end
+  end
+
+  # An implementation of a SOCKS5 proxy.
+  class SOCKS5Socket
+    # The SOCKS protocol version used by this class
+    VERSION = 5
+
+    # The SOCKS authentication type for requests without authentication
+    METHOD_NO_AUTH = 0
+
+    # The SOCKS authentication type for requests via username/password
+    METHOD_PASSWD = 2
+
+    # The SOCKS authentication type for when there are no supported
+    # authentication methods.
+    METHOD_NONE = 0xFF
+
+    # The SOCKS packet type for requesting a proxy connection.
+    CMD_CONNECT = 1
+
+    # The SOCKS address type for connections via IP address.
+    ATYP_IPV4 = 1
+
+    # The SOCKS address type for connections via domain name.
+    ATYP_DOMAIN = 3
+
+    # The SOCKS response code for a successful operation.
+    SUCCESS = 0
+
+    # The proxy's host name or IP address
+    attr_reader :proxy_host
+
+    # The proxy's port number
+    attr_reader :proxy_port
+
+    # The map of options given at initialization
+    attr_reader :options
+
+    # 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.
+    def initialize(proxy_host, proxy_port = 1080, options = {})
+      @proxy_host = proxy_host
+      @proxy_port = proxy_port
+      @options = options
+    end
+
+    # Return a new socket connected to the given host and port via the
+    # proxy that was requested when the socket factory was instantiated.
+    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 SOCKSError, "invalid SOCKS version (#{version})"
+      end
+
+      if method == METHOD_NONE
+        socket.close
+        raise SOCKSError, '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 SOCKSConnectError, 'Illegal response type'
+      end
+      _portnum = socket.recv(2)
+
+      unless reply == SUCCESS
+        socket.close
+        raise SOCKSConnectError, reply.to_s
+      end
+
+      socket
+    end
+
+    private
+
+    # Simple username/password negotiation with the SOCKS5 server.
+    def negotiate_password(socket)
+      packet = [
+        0x01, options[:user].length, options[:user],
+        options[:password].length, options[:password]
+      ].pack('CCA*CA*')
+      socket.send packet, 0
+
+      _version, status = socket.recv(2).unpack('CC')
+
+      return if status == SUCCESS
+      socket.close
+      raise SOCKSUnauthorizedError, 'could not authorize user'
+    end
+  end
+end
diff --git a/lib/httpclient/ssl_socket.rb b/lib/httpclient/ssl_socket.rb
index 14801428..ffa7e4c9 100644
--- a/lib/httpclient/ssl_socket.rb
+++ b/lib/httpclient/ssl_socket.rb
@@ -18,9 +18,14 @@ def self.create_socket(session)
         :debug_dev => session.debug_dev
       }
       site = session.proxy || session.dest
-      socket = session.create_socket(site.host, site.port)
+      if session.via_socks_proxy?
+        socket = session.create_socks_socket(session.proxy, session.dest)
+      else
+        socket = session.create_socket(site.host, site.port)
+      end
+
       begin
-        if session.proxy
+        if session.via_http_proxy?
           session.connect_ssl_proxy(socket, Util.urify(session.dest.to_s))
         end
         new(socket, session.dest, session.ssl_config, opts)
diff --git a/lib/httpclient/util.rb b/lib/httpclient/util.rb
index 6ff38016..64d767cd 100644
--- a/lib/httpclient/util.rb
+++ b/lib/httpclient/util.rb
@@ -216,6 +216,19 @@ def https?(uri)
     def http?(uri)
       uri.scheme && uri.scheme.downcase == 'http'
     end
+
+    def socks?(uri)
+      uri && (socks4?(uri) || socks5?(uri))
+    end
+
+    def socks4?(uri)
+      uri && uri.scheme && uri.scheme.downcase == 'socks4'
+    end
+
+    def socks5?(uri)
+      uri && uri.scheme && uri.scheme.downcase == 'socks5'
+    end
+
   end
 
 
diff --git a/sample/socks.rb b/sample/socks.rb
new file mode 100644
index 00000000..5ebbd467
--- /dev/null
+++ b/sample/socks.rb
@@ -0,0 +1,16 @@
+require 'httpclient'
+
+#  ssh -fN -D 9999 remote_server
+clnt = HTTPClient.new('socks4://localhost:9999')
+
+#clnt = HTTPClient.new('socks5://localhost:9999')
+#clnt = HTTPClient.new('socks5://username:password@localhost:9999')
+
+#ENV['SOCKS_PROXY'] = 'socks5://localhost:9999'
+#clnt = HTTPClient.new
+
+target = 'http://www.example.com/'
+puts clnt.get(target).content
+
+target = 'https://www.google.co.jp/'
+puts clnt.get(target).content
diff --git a/test/helper.rb b/test/helper.rb
index 26bc4f9b..501e55a0 100644
--- a/test/helper.rb
+++ b/test/helper.rb
@@ -10,12 +10,14 @@
 
 require 'httpclient'
 require 'webrick'
+require 'webrick/https'
 require 'webrick/httpproxy.rb'
 require 'logger'
 require 'stringio'
 require 'cgi'
 require 'webrick/httputils'
 
+require File.expand_path('sockssvr', File.dirname(__FILE__))
 
 module Helper
   Port = 17171
@@ -37,6 +39,22 @@ def proxyurl
     "http://localhost:#{proxyport}/"
   end
 
+  def socks4_proxyurl
+    "socks4://username@localhost:#{proxyport}/"
+  end
+
+  def socks5_noauth_proxyurl
+    "socks5://localhost:#{proxyport}/"
+  end
+
+  def socks5_auth_proxyurl
+    "socks5://admin:admin@localhost:#{proxyport}/"
+  end
+
+  def socks5_invalid_user_proxyurl
+    "socks5://invaliduser:pass@localhost:#{proxyport}/"
+  end
+
   def setup
     @logger = Logger.new(STDERR)
     @logger.level = Logger::Severity::FATAL
@@ -92,6 +110,12 @@ def teardown_proxyserver
     #@proxyserver_thread.kill
   end
 
+  def setup_socksproxyserver
+    @proxyserver = SOCKSServer.new(0)
+    @proxyport = @proxyserver.port
+    @proxyserver.start
+  end
+
   def start_server_thread(server)
     t = Thread.new {
       Thread.current.abort_on_exception = true
diff --git a/test/sockssvr.rb b/test/sockssvr.rb
new file mode 100644
index 00000000..f64fabf4
--- /dev/null
+++ b/test/sockssvr.rb
@@ -0,0 +1,374 @@
+require 'logger'
+require 'socket'
+
+TEST_USERNAME = 'admin'.freeze
+TEST_PASSWORD = 'admin'.freeze
+
+#
+# Generic SOCKS[45] server implementation
+#
+class SOCKSServer
+  attr_reader :port
+
+  # protocol version
+  SOCKS_VERSION_4 = 4
+  SOCKS_VERSION_5 = 5
+
+  # auth method
+  SOCKS_NO_AUTH = 0
+  SOCKS_USER_PASS_AUTH = 2
+
+  # auth result
+  SOCKS_AUTH_SUCCESS = 1
+  SOCKS_AUTH_FAILURE = 2
+
+  # host type
+  SOCKS_HOSTTYPE_IPV4 = 1
+  SOCKS_HOSTTYPE_DOMAIN = 3
+
+  # command
+  SOCKS_COMMAND_CONNECT = 1
+
+  # misc
+  SOCKS4_MAX_USERNAME = 255
+
+  class SOCKSProtocolError < StandardError; end
+
+  def initialize(port = 1080)
+    @server = TCPServer.new(port)
+    addr = @server.addr
+    addr.shift
+    @port = addr[0]
+    @logger = Logger.new(STDERR)
+    @logger.level = Logger::WARN
+    @protocol_version = nil
+  end
+
+  def start
+    @logger.info('main thread started!')
+    Thread.start do
+      loop do
+        begin
+          Thread.start(@server.accept) do |client_socket|
+            @logger.info("client from #{client_socket.peeraddr[2]}")
+            begin
+              handle_client(client_socket)
+            rescue SOCKSProtocolError => e
+              @logger.warn(e)
+              send_protocol_error_response(client_socket)
+            rescue RuntimeError => e1
+              @logger.warn(e1)
+            rescue Errno::ECONNRESET => e2
+              @logger.warn("SOCKSServer: Connection Reset by peer")
+            ensure
+              client_socket.close
+            end
+          end
+        rescue RuntimeError, IOError => e2 # @server.accept error
+          @logger.warn(e2) unless e2.instance_of?(IOError)
+          break
+        end
+      end
+    end
+  end
+
+  def shutdown
+    @server.close unless @server.closed?
+    @logger.info('Socks server closed... ')
+  end
+
+  private
+
+  def handle_client(client_socket)
+    socks_version = parse_protocol_version(client_socket)
+    @protocol_version = socks_version
+
+    case @protocol_version
+    when SOCKS_VERSION_4 # not socks4a, but socks4
+      @logger.debug('entering socks4 protocol mode...')
+      session = create_socks4_session(client_socket)
+      client_socket.send([0x00, 0x5a].concat([0xFF] * 6).pack('C*'), 0)
+      forward_packet(session, client_socket)
+    when SOCKS_VERSION_5
+      @logger.debug('entering socks5 protocol mode...')
+      auth_method = get_auth_method(client_socket)
+      client_socket.send([SOCKS_VERSION_5, auth_method].pack('C*'), 0)
+      unless auth_method == SOCKS_NO_AUTH
+        auth_result = authenticate(client_socket)
+        send_auth_response(auth_result, client_socket)
+        return unless auth_result == SOCKS_AUTH_SUCCESS
+      end
+
+      session = create_socks5_session(client_socket)
+      forward_packet(session, client_socket)
+    end
+  end
+
+  def get_username(socket)
+    username = nil
+    if @protocol_version == SOCKS_VERSION_4
+      username = socket.recv(SOCKS4_MAX_USERNAME).unpack('Z*').join('')
+    elsif @protocol_version == SOCKS_VERSION_5
+      username_length = socket.recv(1).unpack('C')[0]
+      username = socket.recv(username_length)
+    end
+    @logger.debug("got username -> #{username}")
+    username
+  end
+
+  def get_password(socket)
+    if @protocol_version == SOCKS_VERSION_4
+      raise SOCKSProtocolError, 'unsupported field -> password'
+    end
+    password_length = socket.recv(1).unpack('C')[0]
+    password = socket.recv(password_length)
+    @logger.debug("got password -> #{password}")
+    password
+  end
+
+  def parse_protocol_version(socket)
+    socks_version = socket.recv(1).unpack('C')[0]
+    if socks_version != SOCKS_VERSION_4 && socks_version != SOCKS_VERSION_5
+      raise "bad socks protocol version -> #{socks_version}"
+    end
+    socks_version
+  end
+
+  def get_auth_method(socket)
+    number_of_methods = socket.recv(1).unpack('C')[0]
+    if number_of_methods < 1
+      raise SOCKSProtocolError,
+            'auth method must be > 0'
+    end
+
+    @logger.debug("select auth method from #{number_of_methods} choice")
+
+    choice_methods = []
+    number_of_methods.times do
+      auth_method = socket.recv(1).unpack('C')[0]
+      # auth_method == 0 -> NO AUTH
+      # auth_method == 2 -> USERNAME:PASSWORD AUTH
+      if [SOCKS_NO_AUTH, SOCKS_USER_PASS_AUTH].include?(auth_method)
+        @logger.debug("auth method get success -> #{auth_method}")
+        choice_methods.push(auth_method)
+      end
+    end
+
+    raise SOCKSProtocolError, 'unsupported auth method' if choice_methods.empty?
+
+    case choice_methods.size
+    when 1
+      @logger.debug("selected auth method -> #{choice_methods[0]}")
+      return choice_methods[0]
+    when 2
+      @logger.debug('selected auth method -> 2')
+      return SOCKS_USER_PASS_AUTH
+    end
+  end
+
+  def authenticate(socket)
+    version = socket.recv(1).unpack('C')[0]
+    if version != 1
+      raise SOCKSProtocolError,
+            "invalid auth version -> #{version} must be 1"
+    end
+
+    username = get_username(socket)
+    password = get_password(socket)
+
+    if username == TEST_USERNAME && password == TEST_PASSWORD
+      @logger.debug('socks5 username:password auth -> success')
+      return SOCKS_AUTH_SUCCESS
+    else
+      @logger.debug('socks5 username:password auth -> fail')
+      return SOCKS_AUTH_FAILURE
+    end
+  end
+
+  def send_auth_response(auth_result, socket)
+    case auth_result
+    when SOCKS_AUTH_SUCCESS
+      socket.send([SOCKS_VERSION_5, 0x00].pack('C*'), 0)
+    when SOCKS_AUTH_FAILURE
+      socket.send([SOCKS_VERSION_5, 0xFF].pack('C*'), 0)
+    end
+  end
+
+  def create_socks4_session(socket)
+    command = get_command(socket)
+    port, raw_port = get_port(socket)
+    host, raw_host = get_ipv4_host(socket)
+    _username = get_username(socket)
+    session = {
+      address_type: SOCKS_HOSTTYPE_IPV4,
+      host: host,
+      port: port,
+      raw_host: raw_host,
+      raw_port: raw_port,
+      command: command
+    }
+    session
+  end
+
+  def create_socks5_session(socket)
+    validate_protocol_version(socket)
+
+    command = get_command(socket)
+    check_reserved_byte(socket)
+    session = get_host_and_port(socket)
+    session[:command] = command
+    session
+  end
+
+  def validate_protocol_version(socket)
+    version = parse_protocol_version(socket)
+    unless @protocol_version == version
+      raise SOCKSProtocolError,
+            "protoversion mismatch #{@protocol_version} != #{version}"
+    end
+    version
+  end
+
+  def get_command(socket)
+    @logger.debug('get connection type')
+    command = socket.recv(1).unpack('C')[0]
+    @logger.debug("command => #{command}")
+    command
+  end
+
+  def check_reserved_byte(socket)
+    # reserved byte
+    reserved_byte = socket.recv(1).unpack('C')[0]
+    unless reserved_byte.zero?
+      raise SOCKSProtocolError,
+            "reserved byte must be zero, but #{reserved_byte}"
+    end
+    @logger.debug("reserved byte => #{reserved_byte}")
+  end
+
+  def get_ipv4_host(socket)
+    raw_host = socket.recv(4).unpack('C*')
+    host = raw_host.join('.')
+    @logger.debug("host => #{host}")
+    if host.empty?
+      raise SOCKSProtocolError,
+            'specified host is invalid!'
+    end
+    [host, raw_host]
+  end
+
+  def get_domain(socket)
+    domain_length = socket.recv(1).unpack('C')[0]
+    if domain_length < 1
+      raise SOCKSProtocolError,
+            'domain length is too short!'
+    end
+
+    host = socket.recv(domain_length)
+    raw_host = host.unpack('C*')
+    [host, raw_host]
+  end
+
+  def get_port(socket)
+    raw_port = socket.recv(2).unpack('C*')
+    port = raw_port[0] << 8 | raw_port[1]
+    [port, raw_port]
+  end
+
+  def get_host_and_port(socket)
+    address_type = socket.recv(1).unpack('C')[0]
+    @logger.debug("address_type #{address_type}")
+
+    case address_type
+    when SOCKS_HOSTTYPE_IPV4
+      @logger.debug('hostname type is IPv4')
+      host, raw_host = get_ipv4_host(socket)
+      port, raw_port = get_port(socket)
+    when SOCKS_HOSTTYPE_DOMAIN
+      @logger.debug('host type is domain name')
+      host, raw_host = get_domain(socket)
+      port, raw_port = get_port(socket)
+    end
+
+    @logger.debug("host => #{host}")
+    @logger.debug("raw host => #{raw_host}")
+    @logger.debug("port => #{port}")
+    @logger.debug("raw port => #{raw_port}")
+
+    session = {
+      address_type: address_type,
+      host: host,
+      port: port,
+      raw_host: raw_host,
+      raw_port: raw_port
+    }
+    session
+  end
+
+  def forward_packet(session, client_socket)
+    command = session[:command]
+    raise 'CONNECT is only implemented' unless command == SOCKS_COMMAND_CONNECT
+
+    client_addr = client_socket.peeraddr[2]
+
+    host = session[:host]
+    port = session[:port]
+
+    @logger.debug("client #{client_addr} connecting to #{host}:#{port} ...")
+
+    dest_socket = TCPSocket.open(host, port)
+    if dest_socket
+      target_addr = dest_socket.peeraddr[2]
+      send_socks5_response(client_socket, 0, session) # success
+
+      loop do # main io loop
+        readables, _, exceptions = IO.select([client_socket, dest_socket])
+        raise exceptions[0] unless exceptions.empty?
+
+        readables.each do |io|
+          if io == client_socket
+            client_msg = client_socket.recv(4096)
+            next if client_msg.length.zero?
+            @logger.debug("recvmsg from client => #{client_msg.length} bytes")
+            @logger.debug("send to dest #{target_addr} => #{client_msg}")
+            dest_socket.sendmsg(client_msg)
+          elsif io == dest_socket
+            dest_msg = dest_socket.recv(4096)
+            next if dest_msg.length.zero?
+            @logger.debug("recvmsg from dest => #{dest_msg.length} bytes")
+            @logger.debug("send to client #{client_addr} => #{dest_msg}")
+            client_socket.sendmsg(dest_msg)
+          end
+        end
+      end
+    else
+      @logger.error("connecting to #{host}:#{port} fail")
+      send_socks5_response(client_socket, 1, session) # error
+    end
+  end
+
+  def send_protocol_error_response(client_socket)
+    if @protocol_version == SOCKS_VERSION_4
+      client_socket.send([0x00, 0x5b].concat([0xff] * 6).pack('C*'), 0)
+    elsif @protocol_version == SOCKS_VERSION_5
+      client_socket.send([SOCKS_VERSION_5, 0xff].pack('C*'), 0)
+    end
+  end
+
+  def send_socks5_response(socket, code, session)
+    return unless @protocol_version == SOCKS_VERSION_5
+
+    host = session[:host]
+    address_type = session[:address_type]
+    raw_host = session[:raw_host]
+    raw_port = session[:raw_port]
+
+    resp = [
+      SOCKS_VERSION_5, code, 0x00
+    ].push(address_type).push(
+      host.length
+    ).concat(raw_host).concat(raw_port)
+    @logger.debug("resp => #{resp.inspect}")
+    socket.send(resp.pack('C*'), 0)
+  end
+end
diff --git a/test/test_httpclient.rb b/test/test_httpclient.rb
index c8e5330c..ba95f477 100644
--- a/test/test_httpclient.rb
+++ b/test/test_httpclient.rb
@@ -28,6 +28,32 @@ def test_initialize
     end
   end
 
+  def test_socks_initialize
+    setup_socksproxyserver
+
+    [
+      socks4_proxyurl,
+      socks5_noauth_proxyurl,
+      socks5_auth_proxyurl
+    ].each do |proxyurl|
+      escape_noproxy do
+        @client = HTTPClient.new(proxyurl)
+        assert_equal(urify(proxyurl), @client.proxy)
+        assert_equal(200, @client.head(serverurl).status)
+      end
+    end
+  end
+
+  def test_socks5_autherror
+    setup_socksproxyserver
+    escape_noproxy do
+      @client = HTTPClient.new(socks5_invalid_user_proxyurl)
+      assert_raises(HTTPClient::SOCKSUnauthorizedError) do
+        @client.get(serverurl)
+      end
+    end
+  end
+
   def test_agent_name
     @client = HTTPClient.new(nil, "agent_name_foo")
     str = ""
@@ -241,7 +267,9 @@ def test_host_header
   def test_proxy_env
     setup_proxyserver
     escape_env do
+      # put HTTP_PROXY env ahead of SOCKS_PROXY
       ENV['http_proxy'] = "http://admin:admin@foo:1234"
+      ENV['socks_proxy'] = 'socks5://admin:admin@foo:1234'
       ENV['NO_PROXY'] = "foobar"
       client = HTTPClient.new
       assert_equal(urify("http://admin:admin@foo:1234"), client.proxy)
@@ -249,6 +277,17 @@ def test_proxy_env
     end
   end
 
+  def test_socks_proxy_env
+    setup_socksproxyserver
+    escape_env do
+      ENV['socks_proxy'] = 'socks5://admin:admin@foo:1234'
+      ENV['NO_PROXY'] = "foobar"
+      client = HTTPClient.new
+      assert_equal(urify("socks5://admin:admin@foo:1234"), client.proxy)
+      assert_equal('foobar', client.no_proxy)
+    end
+  end
+
   def test_proxy_env_cgi
     setup_proxyserver
     escape_env do
@@ -405,6 +444,20 @@ def test_proxy_ssl
     end
   end
 
+  def test_socks_proxy_ssl
+    setup_socksproxyserver
+    setup_sslserver
+    escape_noproxy do
+      curdir = File.dirname(File.expand_path(__FILE__))
+      @client.proxy = socks5_auth_proxyurl
+      @client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
+      assert_equal(
+        'hello',
+        @client.get("https://localhost:#{@serverport}/hello").content
+      )
+    end
+  end
+
   def test_loopback_response
     @client.test_loopback_response << 'message body 1'
     @client.test_loopback_response << 'message body 2'
@@ -1957,6 +2010,38 @@ def setup_server
     @server_thread = start_server_thread(@server)
   end
 
+  def setup_sslserver
+    curdir = File.dirname(File.expand_path(__FILE__))
+    servercert = OpenSSL::X509::Certificate.new(File.open(File.join(curdir, 'server.cert')) { |f|
+      f.read
+    })
+    key = OpenSSL::PKey::RSA.new(File.open(File.join(curdir, 'server.key')) { |f|
+      f.read
+    })
+
+    @server = WEBrick::HTTPServer.new(
+      :BindAddress => "localhost",
+      :Logger => @logger,
+      :Port => 0,
+      :DocumentRoot => curdir,
+      :SSLEnable => true,
+      :SSLCACertificateFile => File.join(curdir, 'ca.cert'),
+      :SSLCertificate => servercert,
+      :SSLPrivateKey => key,
+      :SSLVerifyClient => ::OpenSSL::SSL::VERIFY_NONE,
+      :SSLCertName => nil
+    )
+    @serverport = @server.config[:Port]
+
+    [:hello].each do |sym|
+      @server.mount(
+        "/#{sym}",
+        WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
+      )
+    end
+    @server_thread = start_server_thread(@server)
+  end
+
   def add_query_string(req)
     if req.query_string
       '?' + req.query_string