{"id":18489698,"url":"https://github.com/abicky/socks_handler","last_synced_at":"2025-05-13T23:34:47.776Z","repository":{"id":191010742,"uuid":"683669709","full_name":"abicky/socks_handler","owner":"abicky","description":"A flexible socksifier for sockets created by TCPSocket.new, Socket.tcp or UDPSocket.new","archived":false,"fork":false,"pushed_at":"2023-08-30T09:37:41.000Z","size":30,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-24T12:04:18.874Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/abicky.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-27T10:26:46.000Z","updated_at":"2023-08-30T09:00:59.000Z","dependencies_parsed_at":"2024-11-06T12:58:25.981Z","dependency_job_id":"f74e372a-68ae-4ed4-8224-7aff531e481d","html_url":"https://github.com/abicky/socks_handler","commit_stats":null,"previous_names":["abicky/socks_handler"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abicky%2Fsocks_handler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abicky%2Fsocks_handler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abicky%2Fsocks_handler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abicky%2Fsocks_handler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/abicky","download_url":"https://codeload.github.com/abicky/socks_handler/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254043217,"owners_count":22004912,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T12:57:35.464Z","updated_at":"2025-05-13T23:34:47.658Z","avatar_url":"https://github.com/abicky.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SocksHandler\n\nSocksHandler is a flexible socksifier for sockets created by `TCPSocket.new`, `Socket.tcp`, or `UDPSocket.new` that solves the following issues:\n\n* `SOCKSSocket` is not easy to use\n    - 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.\n* Famous socksifiers such as [socksify](https://www.inet.no/dante/doc/1.3.x/socksify.1.html) and [proxychains4](https://github.com/rofl0r/proxychains-ng) don't support rules using domain names\n    - Besides, they don't work on macOS if Ruby is managed by rbenv maybe due to SIP (System Integrity Protection)\n\nFor more details, see the section \"Related Work.\"\n\n## Installation\n\nInstall the gem and add to the application's Gemfile by executing:\n\n    $ bundle add socks_handler\n\nIf bundler is not being used to manage dependencies, install the gem by executing:\n\n    $ gem install socks_handler\n\n## Usage\n\n### Socksify TCP Connections\n\nAssuming 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:\n\n```yaml\nversion: \"2.4\"\nservices:\n  sockd:\n    image: wernight/dante\n    ports:\n      - 1080:1080\n\n  nginx:\n    image: nginx\n```\n\nHere is an example to create a socket that can access the host \"nginx\" from the Docker host:\n\n```ruby\nrequire \"socks_handler\"\n\nsocket = TCPSocket.new(\"127.0.0.1\", 1080) # or Socket.tcp(\"127.0.0.1\", 1080)\nSocksHandler::TCP.establish_connection(socket, \"nginx\", 80)\n\nsocket.write(\u003c\u003c~REQUEST.gsub(\"\\n\", \"\\r\\n\"))\n  HEAD / HTTP/1.1\n  Host: nginx\n\nREQUEST\nputs socket.gets #=\u003e HTTP/1.1 200 OK\n```\n\nIf you want to access the host through the SOCKS server implicitly, you can use `SocksHandler.socksify` as follows:\n\n```ruby\nrequire \"socks_handler\"\n\nSocksHandler::TCP.socksify([\n  SocksHandler::ProxyAccessRule.new(\n    host_patterns: [\"nginx\"],\n    socks_server: \"127.0.0.1:1080\",\n  )\n])\n\nsocket = TCPSocket.new(\"nginx\", 80)\nsocket.write(\u003c\u003c~REQUEST.gsub(\"\\n\", \"\\r\\n\"))\n  HEAD / HTTP/1.1\n  Host: nginx\n\nREQUEST\nputs socket.gets #=\u003e HTTP/1.1 200 OK\n```\n\nWith `SocksHandler::TCP.socksify`, other methods using `TCPSocket.new` or `Socket.tcp` also access the remote host through the SOCKS server:\n\n```ruby\nrequire \"net/http\"\nrequire \"socks_handler\"\n\nSocksHandler::TCP.socksify([\n  SocksHandler::ProxyAccessRule.new(\n    host_patterns: [\"nginx\"],\n    socks_server: \"127.0.0.1:1080\",\n  )\n])\n\nNet::HTTP.start(\"nginx\", 80) do |http|\n  pp http.head(\"/\") #=\u003e #\u003cNet::HTTPOK 200 OK readbody=true\u003e\nend\n```\n\nFor more details, see the document of `SocksHandler::TCP.socksify`:\n\n```\n$ ri SocksHandler::TCP.socksify\n```\n\n### Socksify UDP Connections\n\nAssuming 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:\n\n```yaml\nversion: \"2.4\"\nservices:\n  sockd:\n    image: wernight/dante\n    ports:\n      - 1080:1080\n      - 1024-1030:1024-1030/udp\n    sysctls:\n      net.ipv4.ip_local_port_range: \"1024 1030\"\n\n  echo:\n    image: abicky/ncat:latest\n    command: -e /bin/cat -kul 7\n    init: true\n```\n\nHere is an example to create a socket that can access the host \"nginx\" from the Docker host:\n\n```ruby\nrequire \"socks_handler\"\n\ntcp_socket = TCPSocket.new(\"127.0.0.1\", 1080) # or Socket.tcp(\"127.0.0.1\", 1080)\nudp_socket = SocksHandler::UDP.associate_udp(tcp_socket, \"0.0.0.0\", 0)\n\nudp_socket.send(\"hello\", 0, \"echo\", 7)\nputs udp_socket.gets #=\u003e hello\n```\n\n\n## Limitation\n\nAs `SocksHandler` only socksifies TCP connections created by `TCPSocket.new` or `Socket.tcp`, it doesn't socksify connections created by native extensions.\n\nFor example, assuming that a SOCKS server that can access the host \"mysql\" is listening on 127.0.0.1:1080 as follows:\n\n```yaml\nversion: \"2.4\"\nservices:\n  sockd:\n    image: wernight/dante\n\n  mysql:\n    image: mysql:8\n    environment:\n      MYSQL_ROOT_PASSWORD: password\n```\n\nThe following code raises `Mysql2::Error::ConnectionError` because the gem \"mysql2\" tries to connect to the server via a native extension:\n\n```ruby\nrequire \"mysql2\"\n\nSocksHandler.socksify([\n  SocksHandler::ProxyAccessRule.new(\n    host_patterns: [\"mysql\"],\n    socks_server: \"127.0.0.1:1080\",\n  )\n])\n\nclient = Mysql2::Client.new(\n  host: \"mysql\",\n  port: 3306,\n  username: \"root\",\n  password: \"password\",\n)\nclient.ping\n#=\u003e Unknown MySQL server host 'mysql' (8) (Mysql2::Error::ConnectionError)\n```\n\n## Related Work\n\n### Gems\n\nThe following projects provide similar gems:\n\n* [ruby-proxifier](https://github.com/samuelkadolph/ruby-proxifier)\n    - This project seems to no longer be maintained.\n* [socksify-ruby](https://github.com/astro/socksify-ruby)\n\n### SOCKSSocket\n\nOn macOS, you can build ruby with `SOCKSSocket` as follows:\n\n```\n$ brew install dante bison\n$ git clone https://github.com/ruby/ruby.git\n$ cd ruby\n$ git checkout v3_2_2\n$ ./configure --enable-socks\n$ PATH=\"/usr/local/opt/bison/bin:$PATH\" make -j$(nproc) install\n$ cat \u003c\u003cEOF \u003e/etc/socks.conf\nroute {\n  from: 0.0.0.0/0 to: 0.0.0.0/0 via: 127.0.0.1 port = 1080\n  proxyprotocol: socks_v5\n  method: none\n}\nEOF\n```\n\nHere is example code to access nginx launched using docker-compose.yml in the section \"Usage\":\n\n```ruby\nrequire \"socket\"\n\nip = `docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker compose ps -q nginx)`.chomp\n[ip, \"nginx\"].each do |host|\n  puts \"Send an HTTP request to #{host}\"\n  socket = SOCKSSocket.new(host, 80)\n  socket.write(\u003c\u003c~REQUEST.gsub(\"\\n\", \"\\r\\n\"))\n    HEAD / HTTP/1.1\n    Host: nginx\n\n  REQUEST\n  puts \"Received: #{socket.gets}\"\nend\n```\n\nAs you can see below, we cannot use the domain name \"nginx\" since socket classes, including `SOCKSSocket`, call `getaddrinfo` at initialization:\n\n```\n$ /usr/local/bin/ruby /path/to/code.rb\nSend an HTTP request to 192.168.160.4\nReceived: HTTP/1.1 200 OK\nSend an HTTP request to nginx\ncode.rb:6:in `initialize': getaddrinfo: nodename nor servname provided, or not known (SocketError)\n        from code.rb:6:in `new'\n        from code.rb:6:in `block in \u003cmain\u003e'\n        from code.rb:4:in `each'\n        from code.rb:4:in `\u003cmain\u003e'\n```\n\n### ProxyChains-NG\n\n[ProxyChains-NG](https://github.com/rofl0r/proxychains-ng) is a socksifier that works well even on macOS.\n\nOn macOS, you can install it as follows:\n\n```\n$ brew install proxychains-ng\n```\n\nThen edit `/usr/local/etc/proxychains.conf` to use the socks server listening on 127.0.0.1:1080:\n\n```\n[ProxyList]\nsocks5 127.0.0.1 1080\n```\n\nProxyChains-NG can socksify even connections created by native extensions. Here is example code to demonstrate it:\n\n```ruby\nrequire \"net/http\"\nrequire \"mysql2\"\n\nNet::HTTP.start(\"nginx\", 80) do |http|\n  pp http.head(\"/\")\nend\n\nclient = Mysql2::Client.new(\n  host: \"mysql\",\n  port: 3306,\n  username: \"root\",\n  password: \"password\",\n)\nputs client.ping\n```\n\nAs you can see below, the program can access containers though the socks server:\n\n```\n$ proxychains4 /usr/local/bin/ruby /path/to/code.rb\n[proxychains] config file found: /usr/local/etc/proxychains.conf\n[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.16/lib/libproxychains4.dylib\n[proxychains] DLL init: proxychains-ng 4.16\n[proxychains] Strict chain  ...  127.0.0.1:1080  ...  nginx:80  ...  OK\n#\u003cNet::HTTPOK 200 OK readbody=true\u003e\n[proxychains] Strict chain  ...  127.0.0.1:1080  ...  mysql:3306  ...  OK\ntrue\n```\n\nHowever, it doesn't work if Ruby is managed by rbenv:\n\n```\n$ rbenv local\n3.2.2\n$ proxychains4 ruby /path/to/code.rb\n[proxychains] config file found: /usr/local/etc/proxychains.conf\n[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.16/lib/libproxychains4.dylib\n/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)\n-- snip --\n```\n\nMaybe the reason is that macOS doesn't allow any system binaries to preload libraries and rbenv uses `/usr/bin/env`:\n\n```\n$ proxychains4 env /usr/local/bin/ruby /path/to/code.rb\n[proxychains] config file found: /usr/local/etc/proxychains.conf\n[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.16/lib/libproxychains4.dylib\n/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)\n-- snip --\n```\n\nAlthough ProxyChains-NG works well in almost all cases, it cannot use domain names to determine whether to access them through a socks proxy.\n\n\n## Development\n\nAfter 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.\n\nTo 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](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/abicky/socks_handler.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabicky%2Fsocks_handler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabicky%2Fsocks_handler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabicky%2Fsocks_handler/lists"}