Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

socks_handler

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

socks_handler

  • 0.1.0
  • Rubygems
  • Socket score

Version published
Maintainers
1
Created
Source

SocksHandler

SocksHandler is a flexible socksifier for sockets created by TCPSocket.new, Socket.tcp, or UDPSocket.new that solves the following issues:

  • SOCKSSocket is not easy to use
    • It is unavailable unless ruby is built with --enable-socks, and even if it is available, we cannot use domain names that the network where the program runs cannot resolve since socket classes, including SOCKSSocket, call getaddrinfo at initialization.
  • Famous socksifiers such as socksify and proxychains4 don't support rules using domain names
    • Besides, they don't work on macOS if Ruby is managed by rbenv maybe due to SIP (System Integrity Protection)

For more details, see the section "Related Work."

Installation

Install the gem and add to the application's Gemfile by executing:

$ bundle add socks_handler

If bundler is not being used to manage dependencies, install the gem by executing:

$ gem install socks_handler

Usage

Socksify TCP Connections

Assuming that a SOCKS server that can access the host "nginx" is listening on 127.0.0.1:1080. You can prepare such an environment with the following docker-compose.yml:

version: "2.4"
services:
  sockd:
    image: wernight/dante
    ports:
      - 1080:1080

  nginx:
    image: nginx

Here is an example to create a socket that can access the host "nginx" from the Docker host:

require "socks_handler"

socket = TCPSocket.new("127.0.0.1", 1080) # or Socket.tcp("127.0.0.1", 1080)
SocksHandler::TCP.establish_connection(socket, "nginx", 80)

socket.write(<<~REQUEST.gsub("\n", "\r\n"))
  HEAD / HTTP/1.1
  Host: nginx

REQUEST
puts socket.gets #=> HTTP/1.1 200 OK

If you want to access the host through the SOCKS server implicitly, you can use SocksHandler.socksify as follows:

require "socks_handler"

SocksHandler::TCP.socksify([
  SocksHandler::ProxyAccessRule.new(
    host_patterns: ["nginx"],
    socks_server: "127.0.0.1:1080",
  )
])

socket = TCPSocket.new("nginx", 80)
socket.write(<<~REQUEST.gsub("\n", "\r\n"))
  HEAD / HTTP/1.1
  Host: nginx

REQUEST
puts socket.gets #=> HTTP/1.1 200 OK

With SocksHandler::TCP.socksify, other methods using TCPSocket.new or Socket.tcp also access the remote host through the SOCKS server:

require "net/http"
require "socks_handler"

SocksHandler::TCP.socksify([
  SocksHandler::ProxyAccessRule.new(
    host_patterns: ["nginx"],
    socks_server: "127.0.0.1:1080",
  )
])

Net::HTTP.start("nginx", 80) do |http|
  pp http.head("/") #=> #<Net::HTTPOK 200 OK readbody=true>
end

For more details, see the document of SocksHandler::TCP.socksify:

$ ri SocksHandler::TCP.socksify

Socksify UDP Connections

Assuming that a SOCKS server that can access the host "echo", which is a UDP echo server, is listening on 127.0.0.1:1080. You can prepare such an environment with the following docker-compose.yml:

version: "2.4"
services:
  sockd:
    image: wernight/dante
    ports:
      - 1080:1080
      - 1024-1030:1024-1030/udp
    sysctls:
      net.ipv4.ip_local_port_range: "1024 1030"

  echo:
    image: abicky/ncat:latest
    command: -e /bin/cat -kul 7
    init: true

Here is an example to create a socket that can access the host "nginx" from the Docker host:

require "socks_handler"

tcp_socket = TCPSocket.new("127.0.0.1", 1080) # or Socket.tcp("127.0.0.1", 1080)
udp_socket = SocksHandler::UDP.associate_udp(tcp_socket, "0.0.0.0", 0)

udp_socket.send("hello", 0, "echo", 7)
puts udp_socket.gets #=> hello

Limitation

As SocksHandler only socksifies TCP connections created by TCPSocket.new or Socket.tcp, it doesn't socksify connections created by native extensions.

For example, assuming that a SOCKS server that can access the host "mysql" is listening on 127.0.0.1:1080 as follows:

version: "2.4"
services:
  sockd:
    image: wernight/dante

  mysql:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: password

The following code raises Mysql2::Error::ConnectionError because the gem "mysql2" tries to connect to the server via a native extension:

require "mysql2"

SocksHandler.socksify([
  SocksHandler::ProxyAccessRule.new(
    host_patterns: ["mysql"],
    socks_server: "127.0.0.1:1080",
  )
])

client = Mysql2::Client.new(
  host: "mysql",
  port: 3306,
  username: "root",
  password: "password",
)
client.ping
#=> Unknown MySQL server host 'mysql' (8) (Mysql2::Error::ConnectionError)

Gems

The following projects provide similar gems:

SOCKSSocket

On macOS, you can build ruby with SOCKSSocket as follows:

$ brew install dante bison
$ git clone https://github.com/ruby/ruby.git
$ cd ruby
$ git checkout v3_2_2
$ ./configure --enable-socks
$ PATH="/usr/local/opt/bison/bin:$PATH" make -j$(nproc) install
$ cat <<EOF >/etc/socks.conf
route {
  from: 0.0.0.0/0 to: 0.0.0.0/0 via: 127.0.0.1 port = 1080
  proxyprotocol: socks_v5
  method: none
}
EOF

Here is example code to access nginx launched using docker-compose.yml in the section "Usage":

require "socket"

ip = `docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker compose ps -q nginx)`.chomp
[ip, "nginx"].each do |host|
  puts "Send an HTTP request to #{host}"
  socket = SOCKSSocket.new(host, 80)
  socket.write(<<~REQUEST.gsub("\n", "\r\n"))
    HEAD / HTTP/1.1
    Host: nginx

  REQUEST
  puts "Received: #{socket.gets}"
end

As you can see below, we cannot use the domain name "nginx" since socket classes, including SOCKSSocket, call getaddrinfo at initialization:

$ /usr/local/bin/ruby /path/to/code.rb
Send an HTTP request to 192.168.160.4
Received: HTTP/1.1 200 OK
Send an HTTP request to nginx
code.rb:6:in `initialize': getaddrinfo: nodename nor servname provided, or not known (SocketError)
        from code.rb:6:in `new'
        from code.rb:6:in `block in <main>'
        from code.rb:4:in `each'
        from code.rb:4:in `<main>'

ProxyChains-NG

ProxyChains-NG is a socksifier that works well even on macOS.

On macOS, you can install it as follows:

$ brew install proxychains-ng

Then edit /usr/local/etc/proxychains.conf to use the socks server listening on 127.0.0.1:1080:

[ProxyList]
socks5 127.0.0.1 1080

ProxyChains-NG can socksify even connections created by native extensions. Here is example code to demonstrate it:

require "net/http"
require "mysql2"

Net::HTTP.start("nginx", 80) do |http|
  pp http.head("/")
end

client = Mysql2::Client.new(
  host: "mysql",
  port: 3306,
  username: "root",
  password: "password",
)
puts client.ping

As you can see below, the program can access containers though the socks server:

$ proxychains4 /usr/local/bin/ruby /path/to/code.rb
[proxychains] config file found: /usr/local/etc/proxychains.conf
[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.16/lib/libproxychains4.dylib
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  nginx:80  ...  OK
#<Net::HTTPOK 200 OK readbody=true>
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  mysql:3306  ...  OK
true

However, it doesn't work if Ruby is managed by rbenv:

$ rbenv local
3.2.2
$ proxychains4 ruby /path/to/code.rb
[proxychains] config file found: /usr/local/etc/proxychains.conf
[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.16/lib/libproxychains4.dylib
/Users/arabiki/.anyenv/envs/rbenv/versions/3.2.2/lib/ruby/3.2.0/net/http.rb:1271:in `initialize': Failed to open TCP connection to nginx:80 (getaddrinfo: nodename nor servname provided, or not known) (SocketError)
-- snip --

Maybe the reason is that macOS doesn't allow any system binaries to preload libraries and rbenv uses /usr/bin/env:

$ proxychains4 env /usr/local/bin/ruby /path/to/code.rb
[proxychains] config file found: /usr/local/etc/proxychains.conf
[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.16/lib/libproxychains4.dylib
/usr/local/lib/ruby/3.2.0/net/http.rb:1271:in `initialize': Failed to open TCP connection to nginx:80 (getaddrinfo: nodename nor servname provided, or not known) (SocketError)
-- snip --

Although ProxyChains-NG works well in almost all cases, it cannot use domain names to determine whether to access them through a socks proxy.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/abicky/socks_handler.

License

The gem is available as open source under the terms of the MIT License.

FAQs

Package last updated on 30 Aug 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc