forked from zet4/alpine-tor
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ff90b70
Showing
5 changed files
with
324 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.DS_Store | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
FROM alpine | ||
|
||
RUN apk add tor --update-cache --repository http://dl-4.alpinelinux.org/alpine/edge/testing/ --allow-untrusted haproxy ruby | ||
|
||
RUN apk --update add --virtual build-dependencies ruby-bundler ruby-dev ruby-nokogiri \ | ||
&& gem install socksify \ | ||
&& apk del build-dependencies \ | ||
&& rm -rf /var/cache/apk/* | ||
|
||
|
||
ADD haproxy.cfg.erb /usr/local/etc/haproxy.cfg.erb | ||
|
||
ADD start.rb /usr/local/bin/start.rb | ||
RUN chmod +x /usr/local/bin/start.rb | ||
|
||
CMD ruby /usr/local/bin/start.rb |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
docker-haproxy-tor | ||
================== | ||
|
||
``` | ||
Docker Container | ||
------------------------------------- | ||
<-> Tor Proxy 1 | ||
Client <----> HAproxy <-> Tor Proxy 2 | ||
<-> Tor Proxy n | ||
``` | ||
|
||
Parents | ||
------- | ||
|
||
* [marcelmaatkamp/docker-alpine-tor](https://github.com/marcelmaatkamp/docker-alpine-tor) | ||
* [mattes/rotating-proxy](https://github.com/mattes/rotating-proxy) | ||
|
||
__Why:__ Lots of IP addresses. One single endpoint for your client. | ||
Load-balancing by HAproxy. | ||
|
||
Usage | ||
----- | ||
|
||
```bash | ||
# build docker container | ||
docker build -t negash/docker-haproxy-tor:latest . | ||
|
||
# ... or pull docker container | ||
docker pull negash/docker-haproxy-tor:latest | ||
|
||
# start docker container | ||
docker run -d -p 5566:5566 -p 2090:2090 -e tors=25 negash/docker-haproxy-tor | ||
|
||
# test with ... | ||
curl --socks5 192.168.99.100:5566 http://echoip.com | ||
|
||
# monitor | ||
# auth login:admin | ||
# auth pass:admin | ||
http://192.168.99.100:2090 | ||
|
||
# start docket container with new auth | ||
docker run -d -p 5566:5566 -p 2090:2090 -e login=MySecureLogin -e pass=MySecurePassword negash/docker-haproxy-tor | ||
|
||
``` | ||
|
||
|
||
|
||
Further Readings | ||
---------------- | ||
|
||
* [Tor Manual](https://www.torproject.org/docs/tor-manual.html.en) | ||
* [Tor Control](https://www.thesprawl.org/research/tor-control-protocol/) | ||
* [HAProxy Manual](http://cbonte.github.io/haproxy-dconv/index.html) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
global | ||
daemon | ||
user root | ||
group root | ||
pidfile <%= pid_file %> | ||
|
||
defaults | ||
mode http | ||
maxconn 50000 | ||
timeout client 3600s | ||
timeout connect 1s | ||
timeout queue 5s | ||
timeout server 3600s | ||
|
||
|
||
listen stats | ||
bind *:<%= stats %> | ||
mode http | ||
stats enable | ||
stats uri / | ||
stats auth <%= login %>:<%= pass %> | ||
|
||
|
||
listen TOR-in | ||
bind *:<%= port %> | ||
mode tcp | ||
default_backend TOR | ||
balance roundrobin | ||
|
||
backend TOR | ||
mode tcp | ||
<% backends.each do |b| %> | ||
server <%= b[:addr] %>:<%= b[:port] %> <%= b[:addr] %>:<%= b[:port] %> check | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
#!/usr/bin/env ruby | ||
require 'erb' | ||
require 'socksify/http' | ||
require 'logger' | ||
|
||
$logger = Logger.new(STDOUT, ENV['DEBUG'] ? Logger::DEBUG : Logger::INFO) | ||
|
||
module Service | ||
class Base | ||
attr_reader :port | ||
|
||
def initialize(port) | ||
@port = port | ||
end | ||
|
||
def service_name | ||
self.class.name.downcase.split('::').last | ||
end | ||
|
||
def start | ||
ensure_directories | ||
$logger.info "starting #{service_name} on port #{port}" | ||
end | ||
|
||
def ensure_directories | ||
%w{lib run log}.each do |dir| | ||
path = "/var/#{dir}/#{service_name}" | ||
Dir.mkdir(path) unless Dir.exists?(path) | ||
end | ||
end | ||
|
||
def data_directory | ||
"/var/lib/#{service_name}" | ||
end | ||
|
||
def pid_file | ||
"/var/run/#{service_name}/#{port}.pid" | ||
end | ||
|
||
def executable | ||
self.class.which(service_name) | ||
end | ||
|
||
def stop | ||
$logger.info "stopping #{service_name} on port #{port}" | ||
if File.exists?(pid_file) | ||
pid = File.read(pid_file).strip | ||
begin | ||
self.class.kill(pid.to_i) | ||
rescue => e | ||
$logger.warn "couldn't kill #{service_name} on port #{port}: #{e.message}" | ||
end | ||
else | ||
$logger.info "#{service_name} on port #{port} was not running" | ||
end | ||
end | ||
|
||
def self.kill(pid, signal='SIGINT') | ||
Process.kill(signal, pid) | ||
end | ||
|
||
def self.fire_and_forget(*args) | ||
$logger.debug "running: #{args.join(' ')}" | ||
pid = Process.fork | ||
if pid.nil? then | ||
# In child | ||
exec args.join(" ") | ||
else | ||
# In parent | ||
Process.detach(pid) | ||
end | ||
end | ||
|
||
def self.which(executable) | ||
path = `which #{executable}`.strip | ||
if path == "" | ||
return nil | ||
else | ||
return path | ||
end | ||
end | ||
end | ||
|
||
|
||
class Tor < Base | ||
def data_directory | ||
"#{super}/#{port}" | ||
end | ||
|
||
def start | ||
super | ||
self.class.fire_and_forget(executable, | ||
"--SocksPort #{port}", | ||
"--NewCircuitPeriod 120", | ||
"--DataDirectory #{data_directory}", | ||
"--PidFile #{pid_file}", | ||
"--Log \"warn syslog\"", | ||
'--RunAsDaemon 1', | ||
"| logger -t 'tor' 2>&1") | ||
end | ||
end | ||
|
||
class Proxy | ||
attr_reader :id | ||
attr_reader :tor | ||
|
||
def initialize(id) | ||
@id = id | ||
@tor = Tor.new(tor_port) | ||
end | ||
|
||
def start | ||
$logger.info "starting proxy id #{id}" | ||
@tor.start | ||
end | ||
|
||
def stop | ||
$logger.info "stopping proxy id #{id}" | ||
@tor.stop | ||
end | ||
|
||
def restart | ||
stop | ||
sleep 5 | ||
start | ||
end | ||
|
||
def tor_port | ||
10000 + id | ||
end | ||
|
||
alias_method :port, :tor_port | ||
|
||
def test_url | ||
ENV['test_url'] || 'http://echoip.com/' | ||
end | ||
|
||
def working? | ||
uri = URI.parse(test_url) | ||
Net::HTTP.SOCKSProxy('127.0.0.1', port).start(uri.host, uri.port) do |http| | ||
http.get(uri.path).code=='200' | ||
end | ||
rescue | ||
false | ||
end | ||
end | ||
|
||
class Haproxy < Base | ||
attr_reader :backends | ||
attr_reader :stats | ||
attr_reader :login | ||
attr_reader :pass | ||
|
||
def initialize() | ||
@config_erb_path = "/usr/local/etc/haproxy.cfg.erb" | ||
@config_path = "/usr/local/etc/haproxy.cfg" | ||
@backends = [] | ||
@stats = ENV['stats'] || 2090 | ||
@login = ENV['login'] || 'admin' | ||
@pass = ENV['pass'] || 'admin' | ||
@port = ENV['port'] || 5566 | ||
end | ||
|
||
def start | ||
super | ||
compile_config | ||
self.class.fire_and_forget(executable, | ||
"-f #{@config_path}", | ||
"| logger 2>&1") | ||
end | ||
|
||
def soft_reload | ||
self.class.fire_and_forget(executable, | ||
"-f #{@config_path}", | ||
"-p #{pid_file}", | ||
"-sf #{File.read(pid_file)}", | ||
"| logger 2>&1") | ||
end | ||
|
||
def add_backend(backend) | ||
@backends << {:name => 'tor', :addr => '127.0.0.1', :port => backend.port} | ||
end | ||
|
||
private | ||
def compile_config | ||
File.write(@config_path, ERB.new(File.read(@config_erb_path)).result(binding)) | ||
end | ||
end | ||
end | ||
|
||
|
||
haproxy = Service::Haproxy.new | ||
proxies = [] | ||
|
||
tor_instances = ENV['tors'] || 20 | ||
tor_instances.to_i.times.each do |id| | ||
proxy = Service::Proxy.new(id) | ||
haproxy.add_backend(proxy) | ||
proxy.start | ||
proxies << proxy | ||
end | ||
|
||
haproxy.start | ||
|
||
sleep 60 | ||
|
||
loop do | ||
$logger.info "testing proxies" | ||
proxies.each do |proxy| | ||
$logger.info "testing proxy #{proxy.id} (port #{proxy.port})" | ||
proxy.restart unless proxy.working? | ||
$logger.info "sleeping for #{tor_instances} seconds" | ||
sleep tor_instances | ||
end | ||
|
||
$logger.info "sleeping for 60 seconds" | ||
sleep 60 | ||
end |