In contemplating traffic analysis, it occurred to me that having something generating extra legitimate (but irrelevant) traffic might help. To that end, I wrote a short Python script that:

  1. Takes one or more "seed" URLs
  2. Downloads the page(s) at random from the list
  3. Fetches the URLs mentioned on the page, adding them to the list
  4. Pause for a random length of time, then repeat

The script uses the multiprocessing module to run multiple instances of the function that does the above. Each instance roughly simulates a user randomly clicking on links.

The idea is that if there are enough instances running, a traffic analysis attack will become very difficult for the adversary. The downside is that it wastes bandwidth. It can be run with the HTTP_PROXY and HTTPS_PROXY environment variables set to cause the traffic to go through TOR (to obfuscate relay / exit node analysis) or without (to obfuscate ISP level traffic analysis).

I would like to get other opinions on the merits and problems with this idea. The code is as follows:

#!/usr/bin/env python3

import re
import requests
import sys
import random
import argparse
import multiprocessing
import signal
import time
import os

links = list()
link_search = re.compile(r"(?i)(https?://[^><\"\'\s]+)")

def pid_print(msg):
    print(str(os.getpid()) + ": " + msg)

def find_urls(page_text):
    global link_search
    urls = link_search.findall(page_text)
    if urls:
        return urls
        return []

def run_link():
    global links
    global args

    link = links[random.randint(0, len(links) - 1)]

    if args.verbose:
        pid_print(str(len(links)) + " links: navigating to " + link)

        resp = requests.get(link)

        if 200 <= resp.status_code <= 299:
            start_len = len(links)
            links = list(set(links))
            end_len = len(links)

            if end_len > start_len and args.verbose:
                pid_print("Found " + str(end_len - start_len) + " new links")

            while len(links) > 200000:
                del links[random.randint(0, len(links) - 1)]
            if args.verbose:
                pid_print(link + "  - HTTP " + str(resp.status_code))

            if link in links:

    except requests.exceptions.MissingSchema:
        if args.verbose:
            pid_print("  - failed, missing schema")

        if link in links:
            links.append("https://" + link)

    except Exception as ee:
        if args.verbose:
            pid_print("  - failed, " + str(ee))

        if link in links:

def run_loop():
    while True:
        global args

        time.sleep(random.randint(int(args.min_linger), int(args.max_linger)))

def signal_handler(signum, frame):

if __name__ == "__main__":
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    parser = argparse.ArgumentParser()
    parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
    parser.add_argument('-t', '--threads', help='Number of threads (default 4)', default=4)
    parser.add_argument('-m', '--min_linger', help='Minimum number of seconds to wait after page pull', default=2)
    parser.add_argument('-x', '--max_linger', help='Maximum number of seconds to wait after page pull', default=10)
    parser.add_argument('links', nargs='*', help='Seed links')
    args = parser.parse_args()

    if len(args.links) < 1:
        sys.stderr.write("Please supply one or more seed URLs as arguments\n")

    print("Starting with a seed of " + str(len(links)) + " URLs")

    child_list = list()

    for i in range(1, int(args.threads)):
        print("Started child " + str(child_list[-1].pid))

    for t in child_list:


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.