Offensive

Using redsocks to proxy a docker container traffic

There are multiple cases in which you may end up in need to redirect the traffic of a docker container. A straightforward example is when an application doesn't take as a parameter a proxy, and you don't want to funnel the traffic of the whole host through a proxy.

I came to this solution when I was creating a bot that needed to be scaled and each instance needed to look like coming from another location, using multiple proxies.

To do so, we are going to use redsocks, and iptables.

The principle is simple. We are going to redirect the traffic we want thanks to iptables to redsocks, which will then use our proxy to communicate with the outside world.

Application -> iptables -> redsocks -> outside world

We are going to have four files for it, main.py is here simply for showing a complete working configuration, it can be any program you want to run.

Dockerfile
redsocks.conf
entrypoint.sh
main.py

So we are going to construct a Docker container that will have everything needed. In this case, I use Ubuntu latest image as a base.

Dockerfile

FROM ubuntu:latest
RUN apt update
RUN apt-get install -y redsocks
RUN apt-get install -y iptables
RUN apt-get install -y iptables-persistent
COPY . /code
RUN rm -f /etc/redsocks.conf
RUN ln -s /code/redsocks.conf /etc/redsocks.conf
RUN cp /code/entrypoint.sh /entrypoint.sh
RUN sudo service redsocks restart
USER root
ENTRYPOINT [ "sh", "/entrypoint.sh"]
CMD ["python3", "main.py"]

In this Docker file, we install Redsocks and iptables, and then we load our custom redsocks.conf.

For the sake of ease, in this example, our custom redsocks.conf is in the same folder as the Dockerfile.

In the end, you can see that we are running an entrypoint.sh file. We are going to use it to load the iptables rules that we need.

The configuration of redsocks will look like the following:

redsocks.conf

base {
log_debug = off;
log_info = on;
log = "file:/var/log/redsocks.log";
daemon = on;
user = redsocks;
group = redsocks;
redirector = iptables;
}

redsocks {
local_ip = 127.0.0.1;
local_port = <port_to_contact_redsocks>;
ip = <ip_of_the_proxy>;
port = <port_of_the_proxy>;
login = <proxy_username>;
password = <proxy_password>;
type = socks5;
}

For the demonstration, we are going to use a port_to_contact_redsocks of 12345.

As you can see the configuration is almost self-explanatory. The IP parameter can also be a domain name, redsocks will automatically resolve it.

Redsocks configured we need to create the forwarding rules in iptables.

The tricky part here is to know which ports are going to use your applications and what you want to redirect. In my case, it is simple, as my bots are crawling the web, I want to redirect any traffic that will contact a port 80(HTTP) or 443(HTTPs).

entrypoint.sh

#echo "Restarting redsocks and redirecting traffic via iptables"
/etc/init.d/redsocks restart

iptables -t nat -A OUTPUT  -p tcp --dport 80 -j REDIRECT --to-port <port_to_contact_redsocks>
iptables -t nat -A OUTPUT  -p tcp --dport 443 -j REDIRECT --to-port <port_to_contact_redsocks>

Traffic is now redirected to the port which is the one Redsock is listening on.

Now all traffic sent to port 80 or 443 will go through our proxy.

If you want to change the proxy on the fly you can use python. Here is an example:

import requests
import os
from urllib.parse import parse_qs

# Here we have a pattern that we are going to fill with the information needed.
redsocks_pattern = 'base {\n log_debug = off;\n log_info = on;\n log = "file:/var/log/redsocks.log";\n daemon = on;\n user = redsocks;\n group = redsocks;\n redirector = iptables;\n}\nredsocks {\n local_ip = 127.0.0.1;\n local_port = 12345;\n ip = "%s";\n port = 3129;\n login = "username";\n password = "password";\n type = socks5;\n}\n'


def renew_ip():
    print("Renewing IP")
    # First kill redsocks rules in case you need to query a proxy api that uses IP as identifier (For example DSLroot)
    os.system(
        "sudo iptables -t nat -D OUTPUT  -p tcp --dport 80 -j REDIRECT --to-port 12345"
    )
    os.system(
        "sudo iptables -t nat -D OUTPUT  -p tcp --dport 443 -j REDIRECT --to-port 12345"
    )

    s = requests.Session()

    # Get the IP of the proxy, you can for example use environment variable, here, is code for DSLroot renew.
    l = s.post(
        "http://dashboard.dslroot.com/?app=query&UserID=userID",
        data={"password": "password"},
    )
    r = s.get(
        "http://dashboard.dslroot.com/?app=query&UserID=userID&ADSLID=ADSLID&cmd=Get"
    )
    f = s.get("http://dashboard.dslroot.com/?app=query&UserID=userID&cmd=ChangeIP")


    try:
        ip = parse_qs(f.content)[b"ADSLIP"][0]
    except KeyError:
        ip = parse_qs(f.content)[b"ADSLIP[1]"][0]


    # Add redsocks rules back:
    os.system(
        "sudo iptables -t nat -A OUTPUT  -p tcp --dport 80 -j REDIRECT --to-port 12345"
    )
    os.system(
        "sudo iptables -t nat -A OUTPUT  -p tcp --dport 443 -j REDIRECT --to-port 12345"
    )

    # Now we rewrite the redsocks file and we restart it.
    redsocks_pattern_filled = redsocks_pattern % ip.decode("utf-8")
    with open("/etc/redsocks.conf", "w+") as redsocks_file:
        redsocks_file.write(redsocks_pattern_filled)
    os.system("sudo killall redsocks")
    os.system("sudo redsocks")
    # We display the new ip just to be sure :)
    print(requests.get("https://ifconfig.co/ip").content)

You need to change the part where we get the IP configuration. You can use an environment variable, query a server, rotate with IPs in a file. You are free.

I have searched for quite long good residential proxies, and this solution works for many of them, DSLroot, DSLrental, Luminati, and more.

At this point, you have a configuration that will redirect any outgoing HTTP/HTTPS traffic through a proxy. You can run as many containers as you wish and therefore reduce your infrastructure costs.

If you have any comment or question, please let them below. Don't hesitate to share.

By PXke
the 09/01/2020 tags: Linux, Docker, Redsocks, Proxy, Updated: 09/01/2020