En esta sección, explicaré cómo crear un generador de proxies Tor e implementar una funcionalidad de aplicación web para realizar un seguimiento de los proxies y gestionarlos.

Los temas de esta sección incluyen lo siguiente:

  1. Generador de proxies Tor
  2. Generación automatizada de proxies Tor
  3. Creación de proxies
  4. Comprobación del estado de los proxies
  5. Eliminación de proxies
  6. Modelos de base de datos
  7. Creación del backend
  8. Creación del frontend
  9. Pruebas

Generador de proxies Tor

El siguiente repositorio de GitHub contiene el código para un generador de proxies Tor:

https://github.com/0xHamy/tor_proxy_gen

Para cada proxy, necesitarás un contenedor Docker independiente, o al menos eso es lo que yo hago. También puedes comprar cientos de proxies por poco dinero, pero no está de más aprender a configurarlos tú mismo.

Si miras el archivo docker-compose.yaml en ese repositorio, verás que estamos enrutando todo el tráfico a través del puerto 9050 dentro del docker a la red del host a través del puerto 9050:

services:
  tor:
    build:
      context: .
      dockerfile: Dockerfile
    image: tor-proxy
    ports:
      - "9050:9050"
    container_name: tor-proxy
    restart: unless-stopped

Si desea acceder al proxy a través de un puerto diferente al de su red host, puede cambiar los puertos de la siguiente manera:

3531:9050

El primer puerto es el que se abre en su sistema host y el segundo es el que se ejecuta en Docker.

El archivo torrc utiliza el puerto 9050 para el servicio tor:

SocksPort 0.0.0.0:9050
Log notice stdout

Dockerfile está ejecutando una versión reducida del sistema operativo Debian para ejecutar el proxy Tor:

# Use a lightweight Debian-based image
FROM debian:bullseye-slim

# Install Tor
RUN apt-get update && \
    apt-get install -y tor && \
    rm -rf /var/lib/apt/lists/*

# Copy custom Tor configuration
COPY torrc /etc/tor/torrc

# Expose the Tor SOCKS5 proxy port
EXPOSE 9050

# Run Tor as the main process
CMD ["tor", "-f", "/etc/tor/torrc"]

Cuando se trata de crear una aplicación que genere múltiples proxies Tor, no podemos depender únicamente del puerto 9050, ya que se utiliza con frecuencia y no podemos utilizarlo si ya está en uso por otra aplicación. Por este motivo, vamos a crear un script en Python eligiendo un puerto aleatorio entre 40,000 y 60,000. Solo hay 65,535 puertos en cualquier ordenador, por lo que hemos elegido ese rango de puertos específico porque, por lo que he visto, no se suele utilizar y, al menos en Ubuntu, no requiere permisos de root para su uso.


Generación automatizada de proxies Tor

Dado que generamos proxies Tor utilizando Docker, necesitamos implementar tres funcionalidades básicas:

  1. Una función para crear un contenedor Docker para ejecutar un proxy Tor
  2. Una función para comprobar el estado de un contenedor Docker y verificar si el proxy está activo
  3. Una función para apagar y eliminar un contenedor Docker

Los archivos relevantes se encuentran en app/services/*.py. Los archivos específicos que utilizaremos son:

  1. tor_proxy_gen.py: se encarga de la creación de contenedores Docker para proxies Tor
  2. container_status.py: comprueba el estado de los contenedores Docker
  3. rm_container.py: gestiona la eliminación de contenedores Docker

Creación de proxies

El script tor_proxy_gen.py automatiza la creación de contenedores Docker que ejecutan proxies Tor, proporcionando un proxy SOCKS5 para el acceso anónimo a la red. A continuación se ofrece una explicación concisa de sus componentes y funcionalidad.

Propósito:

  • Crea e inicia un contenedor Docker que ejecuta un proxy Tor, asigna un puerto aleatorio, recupera la IP del contenedor y la IP del nodo de salida de Tor, y devuelve los detalles como un diccionario listo para JSON.

Variables globales:

  • PORT_MIN, PORT_MAX:
    • Propósito: Define el rango (40001-60001) para seleccionar puertos de host aleatorios.
    • Detalles: Garantiza la asignación de puertos únicos para cada contenedor.
  • SOCKS_PORT_IN_CONTAINER:
    • Propósito: Establece el puerto SOCKS de Tor dentro del contenedor (9050).
    • Detalles: Puerto estándar para el proxy SOCKS5 de Tor.
  • MAX_PORT_ATTEMPTS:
    • Finalidad: Limita los intentos de encontrar un puerto libre (100).
    • Detalles: Evita bucles infinitos en la selección de puertos.
  • WAIT_MAX_SECONDS, WAIT_STEP_SECONDS:
    • Finalidad: Controla el tiempo de espera (60 s) y el intervalo de sondeo (2 s) para la disponibilidad del puerto.
    • Detalles: Se utiliza cuando se espera a que se abra el puerto del contenedor.
  • DOCKERFILE, TORRC, COMPOSE_TEMPLATE:
    • Finalidad: Define los archivos de configuración de Docker como cadenas.
    • Detalles: DOCKERFILE configura una imagen basada en Debian con Tor; TORRC configura el puerto SOCKS de Tor; COMPOSE_TEMPLATE define la configuración de Docker Compose para el contenedor.

Funciones:

  • die:

    • Propósito: Registra un mensaje de error y sale del programa.
    • Parámetros clave: msg (mensaje de error), code (código de salida, por defecto 1).
    • Devuelve: Ninguno (sale del programa).
    • Detalles: Envía el error a stderr y lo registra mediante logging.
  • cmd_exists:

    • Propósito: Comprueba si un ejecutable (por ejemplo, docker) está disponible en $PATH.
    • Parámetros clave: executable (nombre del comando).
    • Devuelve: Booleano (True si se encuentra).
    • Detalles: Utiliza shutil.which y registra el resultado.
  • docker_compose_available:

    • Propósito: Comprueba si Docker Compose está disponible.
    • Parámetros clave: Ninguno.
    • Devuelve: Booleano (True si docker compose version tiene éxito).
    • Detalles: ejecuta un comando para comprobar Docker Compose y registra la salida o los errores.
  • is_port_free:

    • Finalidad: comprueba si un puerto TCP en 127.0.0.1 está libre.
    • Parámetros clave: port (número de puerto).
    • Devuelve: Booleano (True si el puerto está libre).
    • Detalles: Intenta conectarse al puerto utilizando un socket con un tiempo de espera de 0,5 segundos.
  • random_free_port:

    • Propósito: Encuentra un puerto aleatorio sin usar en el rango definido.
    • Parámetros clave: Ninguno.
    • Devuelve: entero (número de puerto libre).
    • Detalles: intenta hasta MAX_PORT_ATTEMPTS puertos aleatorios; llama a die si ninguno está libre.
  • random_container_name:

    • Propósito: genera un nombre de contenedor único.
    • Parámetros clave: ninguno.
    • Devuelve: Cadena (por ejemplo, torproxy_abcdef).
    • Detalles: Combina torproxy_ con seis letras minúsculas aleatorias.
  • wait_for_port:

    • Propósito: Espera hasta que se abre un puerto en un host o se agota el tiempo de espera.
    • Parámetros clave: host (IP), port (número de puerto), timeout (segundos).
    • Devuelve: Ninguno (genera un RuntimeError si se agota el tiempo de espera).
    • Detalles: Sondea cada WAIT_STEP_SECONDS hasta que se utiliza el puerto o expira el timeout.
  • fetch_tor_exit_ip:

    • Propósito: Recupera la IP del nodo de salida de Tor a través de servicios externos.
    • Parámetros clave: host_port (puerto del proxy), timeout (tiempo de espera de la solicitud, por defecto 15 s).
    • Devuelve: Cadena (IP del nodo de salida).
    • Detalles: Consulta servicios de eco de IP (por ejemplo, checkip.amazonaws.com) a través del proxy SOCKS5; genera un RuntimeError si todos fallan.
  • create_and_start_proxy:

    • Propósito: Crea e inicia un contenedor de proxy Tor, devolviendo sus detalles.
    • Parámetros clave: Ninguno.
    • Devuelve: Diccionario con container_name, container_ip (con puerto), tor_exit_node y timestamp.
    • Detalles:
      • Comprueba la disponibilidad de Docker y Compose; sale si no están disponibles.
      • Genera un nombre y un puerto aleatorios para el contenedor.
      • Crea un directorio temporal con Dockerfile, torrc y compose.yaml.
      • Ejecuta docker compose up --build -d para iniciar el contenedor.
      • Espera a que se abra el puerto, recupera la IP del contenedor mediante docker inspect y obtiene la IP de salida de Tor.
      • Limpia el contenedor en caso de fallo utilizando docker rm -f.

En mi máquina, Docker requiere sudo para obtener permisos de root. Al ejecutar tor_proxy_gen.py con sudo python3 tor_proxy_gen.py, se utiliza el intérprete de Python del sistema, que omite el entorno virtual que contiene las dependencias del proyecto tornet_scraper.

En su lugar, ejecute el script de la siguiente manera para utilizar el intérprete de Python del entorno virtual:

-> % sudo /home/hamy/tornet_scraper/venv/bin/python3 app/services/tor_proxy_gen.py                      
[sudo] password for hamy: 
{
  "container_name": "torproxy_tucgye",
  "proxy_ip_docker": "192.168.128.2:45364",
  "proxy_ip_exit_node": "185.193.52.180",
  "timestamp": 1752431067
}

Esto garantiza que el script utilice el intérprete Python del entorno virtual tornet_scraper con todas las dependencias necesarias instaladas.

Comprobación del estado del proxy

El script container_status.py proporciona funciones útiles para comprobar el estado de los contenedores Docker. A continuación se ofrece una explicación concisa de sus funciones.

  1. cmd_exists:

    • Propósito: comprueba si un ejecutable especificado está disponible en el $PATH del sistema.
    • Parámetros clave:
      • executable: nombre de cadena del comando (por ejemplo, docker).
    • Devuelve: Booleano (True si se encuentra el ejecutable, False en caso contrario).
    • Detalles: Utiliza shutil.which para verificar si el ejecutable existe y es accesible en el $PATH del sistema.
  2. container_running:

    • Propósito: Determina si un contenedor Docker se está ejecutando.
    • Parámetros clave:
      • name: Nombre de cadena del contenedor Docker.
    • Devuelve: Booleano (True si el contenedor existe y se está ejecutando, False en caso contrario).
    • Detalles: ejecuta sudo docker inspect -f {{.State.Running}} <name> para comprobar el estado de ejecución del contenedor. Devuelve True si la salida es «true», False si el comando falla (por ejemplo, el contenedor no existe) o el estado no es de ejecución. Suprime stderr con DEVNULL para evitar la salida de errores.

Eliminación de proxies

El script rm_container.py proporciona una función para eliminar de forma forzada un contenedor Docker. A continuación se ofrece una explicación concisa de su única función.

  1. delete_container:

    • Propósito: Elimina un contenedor Docker especificado utilizando sudo docker rm -f.
    • Parámetros clave:
    • name: nombre de cadena del contenedor Docker que se va a eliminar.
    • Devuelve: booleano (True si la eliminación se realiza correctamente, False si falla).
    • Detalles: ejecuta sudo docker rm -f <nombre> para eliminar de forma forzada el contenedor, suprimiendo tanto stdout como stderr con DEVNULL. Devuelve True si el comando se ejecuta correctamente, o False si se produce un CalledProcessError (por ejemplo, el contenedor no existe o hay problemas de permisos).

Modelos de base de datos

Así es como se ven los modelos de tu base de datos:

class Proxy(Base):
    __tablename__ = "proxies"

    id = Column(Integer, primary_key=True, index=True)
    container_name = Column(String, unique=True, index=True)
    container_ip = Column(String)
    tor_exit_node = Column(String)
    timestamp = Column(DateTime, default=datetime.utcnow)
    running = Column(Boolean, default=True)

Para algunos de vosotros, datetime.utcnow puede parecer obsoleto, pero no tenéis que preocuparos por eso.


Creación del backend

Los módulos de servicios realizan todo el trabajo pesado, pero aún necesitamos un enrutador backend para llamar a esas funciones y gestionar las solicitudes del frontend. El backend para la generación de proxies se encuentra dentro de app/routes/proxy_gen.py.

El script proxy_gen.py define las rutas FastAPI para crear, eliminar y listar contenedores proxy Tor, interactuando con la base de datos y los servicios externos. A continuación se ofrece una explicación concisa de sus componentes y funciones.

Variables globales

  1. logger:
    • Finalidad: configura el registro para la depuración y el seguimiento de errores.
    • Detalles: utiliza el módulo logging con el nivel INFO para registrar operaciones y errores.
  2. proxy_gen_router:
    • Propósito: Enrutador FastAPI para puntos finales relacionados con el proxy.
    • Detalles: Configurado con el prefijo /api/proxy-gen y las etiquetas [«API», «Proxy Generator»] para su organización.

Funciones

  1. create_proxy:

    • Propósito: Crea un nuevo contenedor de proxy Tor y almacena sus detalles en la base de datos.
    • Parámetros clave:
      • request: Objeto Request de FastAPI para la gestión de sesiones.
      • db: Session de SQLAlchemy para operaciones de base de datos (a través de Depends(get_db)).
    • Devuelve: JSONResponse con el estado de éxito, el mensaje y los detalles del proxy (nombre del contenedor, IP, nodo de salida de Tor, marca de tiempo, estado de ejecución).
    • Detalles: Llama a create_and_start_proxy (desde tor_proxy_gen.py) para iniciar un contenedor, crea una instancia del modelo Proxy, la guarda en la base de datos y devuelve una respuesta JSON. Genera una excepción HTTPException (500) en caso de error y registra el problema.
  2. delete_proxy:

    • Propósito: Elimina un contenedor proxy Tor especificado y su registro en la base de datos.
    • Parámetros clave:
      • container_name: Nombre de cadena del contenedor que se va a eliminar.
      • request: Objeto Request de FastAPI para mensajes flash basados en sesiones.
      • db: SQLAlchemy Session para operaciones de base de datos.
    • Devuelve: JSONResponse con el estado de éxito y un mensaje si la eliminación se ha realizado correctamente.
    • Detalles: Consulta la tabla Proxy en busca del contenedor; si lo encuentra, llama a delete_container (desde rm_container.py) para eliminarlo. Elimina el registro de la base de datos si se realiza correctamente y añade un mensaje flash de éxito a la sesión. Genera una excepción HTTPException (404 si no se encuentra, 500 si se produce un error) y añade mensajes flash de error.
  3. list_proxies:

    • Propósito: Recupera una lista de todos los proxies con el estado de ejecución actualizado.
    • Parámetros clave:
      • db: Session de SQLAlchemy para operaciones de base de datos.
    • Devuelve: JSONResponse con una lista de proxies, cada uno con el nombre del contenedor, la IP, el nodo de salida de Tor, la marca de tiempo y el estado de ejecución.
    • Detalles: Consulta todos los registros Proxy, comprueba el estado de ejecución de cada contenedor utilizando container_running (de container_status.py), actualiza el campo running en la base de datos si es necesario y devuelve la lista en formato JSON. Genera una excepción HTTPException (500) en caso de error y registra el problema.

Creación del frontend

El código de la plantilla se encuentra en app/templates/proxy_gen.html.

La plantilla proxy_gen.html amplía base.html para proporcionar una interfaz de usuario para gestionar los contenedores proxy Tor en la aplicación tornet_scraper, interactuando con el backend a través de llamadas API. A continuación se ofrece una explicación concisa de sus funciones principales y su interacción con el backend.

  1. Herencia de plantillas:

    • Propósito: Aprovecha el diseño de base.html para lograr una estructura coherente.
    • Interacción con el backend: Hereda la barra de navegación y el manejo de mensajes flash de base.html. El {% block title %} establece el título de la página como «Proxy Generator», y {% block content %} define la funcionalidad específica de la página. Los mensajes flash del backend (almacenados en la sesión) se muestran en el contenedor heredado.
  2. Creación de proxy:

    • Propósito: Inicia y confirma la creación de un nuevo contenedor de proxy Tor.
    • Interacción con el backend:
      • El botón «Crear nuevo proxy» activa la función JavaScript createProxy(), que abre un modal de confirmación.
      • El botón «Continuar» del modal llama a confirmCreateProxy(), que envía una solicitud AJAX POST a /api/proxy-gen/create (gestionada por proxy_gen.py::create_proxy).
      • El backend crea un contenedor Docker (a través de tor_proxy_gen.py), guarda los detalles del proxy (nombre del contenedor, IP, nodo de salida de Tor, marca de tiempo, estado de ejecución) en la tabla Proxy de la base de datos y devuelve una respuesta JSON.
      • Si se realiza correctamente, la tabla se actualiza a través de updateProxyTable(). Los errores activan los registros de la consola y vuelven a habilitar el botón.
  3. Visualización y actualizaciones de la tabla de proxies:

    • Propósito: muestra una lista dinámica de proxies con actualizaciones de estado en tiempo real.
    • Interacción con el backend:
      • Al cargar la página, Jinja2 renderiza los datos iniciales del proxy (proxies de main.py::proxy_gen) en una tabla con columnas para el nombre del contenedor, la IP, el nodo de salida de Tor, la marca de tiempo y el estado.
      • La función updateProxyTable() se ejecuta cada 10 segundos (a través de setInterval) y al crear o eliminar, envía una solicitud AJAX GET a /api/proxy-gen/list (gestionada por proxy_gen.py::list_proxies).
      • El backend consulta la tabla Proxy, comprueba el estado de ejecución de cada contenedor (a través de container_status.py::container_running), actualiza la base de datos si es necesario y devuelve una lista JSON de proxies.
      • La tabla se borra y se vuelve a rellenar con los datos más recientes, mostrando el estado como «En ejecución» o «No en ejecución» con insignias estilizadas.
  4. Eliminación de proxies:

    • Objetivo: permite a los usuarios eliminar un contenedor proxy.
    • Interacción con el backend:
      • Cada fila de la tabla tiene un botón «Eliminar» que llama a openDeleteModal(containerName) para abrir un modal de confirmación con el nombre del contenedor almacenado en un campo oculto.
      • El botón «Eliminar» del modal activa confirmDeleteProxy(), que envía una solicitud AJAX DELETE a /api/proxy-gen/delete/{container_name} (gestionada por proxy_gen.py::delete_proxy).
      • El backend verifica que el proxy existe en la tabla Proxy, elimina el contenedor (a través de rm_container.py::delete_container), elimina el registro de la base de datos y añade un mensaje flash de éxito a la sesión.
      • Si se realiza correctamente, se muestra un mensaje flash de éxito (a través de showFlashMessage) y la tabla se actualiza. Los errores activan un mensaje flash de error.

Pruebas

Ejecuta la aplicación web:

sudo /home/hamy/tornet_scraper/venv/bin/python3 -m uvicorn app.main:app --reload

Abre la aplicación web:

http://127.0.0.1:8000/proxy-gen

Crea un proxy:

Página del proxy de Tornet Scraper