Toxicantidote LANcache with Dnsmasq
Advertisement
Advertisement

LANcache with Dnsmasq

April 2025

Introduction

I've been using the excellent LANcache software to cache a variety of downloaded content used by multiple clients in my network (e.g. Steam, EA, Epic Games, Windows Update). This essentially works by redirecting a list of known CDN hostnames to a web proxy on the local network, so that downloads come over the faster local network instead of the (relatively) slow internet. It's a great way to make the most of a limited internet connection with many users, having been originally developed for LAN parties.

To help the cache function, a Docker image is available from the LANcache website. It is intended to redirect cached domains to the LANcache proxy, and pass through other (non-cache) DNS queries to the upstream DNS server. It works, but I also have Dnsmasq for DHCP and DNSDNS servers I needed to tie it all together with the functionality I desired.

The plan

My idea of fixing this was to consolidate all DNS functions to Dnsmasq. One DNS server, one point of reference. UKLANS maintain a list of CDN hostnames to serve with LANcache on Github which can be used as the basis for a configuration file. The UKLANS cache domains repository also contains a JSON file (cache_domains.json) that provides a name, description and filename that in turn lists all of the hostnames for that CDN provider.

This means Dnsmasq will be responsible for the DNS side and LANcache will be responsible for the web proxy as before. The LANcache DNS docker is removed without removing LANcache itself.

The process is as follows:

  1. Get the cache_domains.json file from the UKLANS repository.
  2. Make a list of filenames referenced in the JSON file.
  3. Request each of those filenames from the UKLANS repository.
  4. For each file, make a list of hostnames.
  5. Write an entry to a Dnsmasq config file that redirects that hostname to the cache server.
  6. Reload dnsmasq to apply the new configuration.

The script

Written in Python 3, this script will perform all of the above steps (except for restarting Dnsmasq). It is inteded to be run in a CRON job periodically. I have the job run daily so that it always has the latest hostnames. You could have it run more or less frequently depending on your preference. This script also depends on the Requests library for Python.

Because this is a quick and dirty script, it does have some limitations:

The cache domain lists contain both full hostnames (e.g. cdn.example.com) and wildcard names (e.g. *.cdn.example.com). Full hostnames can be configured using the address Dnsmasq directive, but wildcard hostnames must use the cname directive. Because of the latter, this script also has the hostname cache redirect to the specified server, and all of the CNAME entries redirect to the cache hostname in turn.

The script source is below:


## LANcache server IP
cache_ip = '192.168.0.1'

## DNSmasq output file (don't overwrite main config)
output_file = '/etc/dnsmasq.lancache'

###
import requests
import os
import datetime

script_path = os.path.abspath(__file__)

with open(output_file, 'w') as out:
    out.write('## This file is automatically generated by ' + script_path + '\n')
    out.write('## Last updated ' + str(datetime.datetime.now()) + '\n')
    out.write('address=/cache/' + cache_ip + '\n')

    repo_info = requests.get('https://raw.githubusercontent.com/uklans/cache-domains/refs/heads/master/cache_domains.json')
    repo_info_json = repo_info.json()
    for domain in repo_info_json['cache_domains']:
        name = str(domain['name'])
        description = str(domain['description'])
        file_list = domain['domain_files']
        dns_list = []
        for fname in file_list:
            if fname == '': continue
            dns_info = requests.get('https://raw.githubusercontent.com/uklans/cache-domains/refs/heads/master/' + fname)
            for line in dns_info.text.split('\n'):
                if line == '': continue
                if line[:1] == '#': continue
                dns_list.append(line)
        
        print('Repository: ' + name)
        print('Description: ' + description)
        print('DNS entries: ' + str(dns_list))
        
        out.write('\n## ' + name + ' - ' + description + '\n')
        for name in dns_list:
            if name[:1] == '*':
                out.write('cname=' + name + ',cache\n')
            else:
                out.write('address=/' + name + '/' + cache_ip + '\n')


As usual, this is also available on my GitHub.

The setup

Dnsmasq needs to be told to look for an additional configuration file. For example, if you name the file dnsmasq.lancache and save it in /etc/ with your main dnsmasq.conf file, you need to add the following to the end of your /etc/dnsmasq.conf file:


conf-file=/etc/dnsmasq.lancache

To schedule this with cron, as root (or sudo) you need to add the following to your crontab (/etc/crontab on most systems) and adjust the timing to suit your needs (below is daily at midnight). This assumes you have the script saved to /root/lancache.py owned by root user and group, with permissions 775::


0 0     * * *   root    python3 /root/lancache.py && service dnsmasq restart

If you need help understanding the timing options in crontab, check out the online CRON tool from Cronitor.