Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Negashev committed Aug 25, 2015
0 parents commit ff90b70
Show file tree
Hide file tree
Showing 5 changed files with 324 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.DS_Store
.idea
16 changes: 16 additions & 0 deletions Dockerfile
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
54 changes: 54 additions & 0 deletions README.md
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)
34 changes: 34 additions & 0 deletions haproxy.cfg.erb
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 %>
218 changes: 218 additions & 0 deletions start.rb
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

0 comments on commit ff90b70

Please sign in to comment.