POSTS

LANcache on Ubuntu Linux for PS4 and PlayStation Network (PSN)

Update 2018: This is now probably outdated and it never really worked 100% anyways. It is now possible to store games on external media. So, I’m not using LANcache anymore.

Update: Multiplay LANcache on Github

I recently picked up a PS4 and having been out of the gaming scene for a few years was a bit shocked to see the size of game downloads and updates off the PSN Store. When I got back home with the brand new PS4 I just wanted to pop in the free Destiny disc and get to playing, but ended up waiting 2 hours for the 2.5GB update to finish before I could actually start. Battlefield 4 was a whole different story at a 36GB download which took almost 2 days to retrieve.

The PS4 I got has a 500GB HDD on it and considering the size of these games and the free monthly games available with a PlayStation Plus subscription I expect to have to upgrade the HDD at some point in the not so distant future. Luckily for me the PS4 HDD is user upgradeable. Unluckily for me it’ll take me quite a few days, if not weeks, to redownload all the games. I’d rather use one of the few 1TB External USB HDDs I’ve got laying around as backup storage for the downloaded files. Just one problem… there’s no way to save the game files from the PS4 to external or network storage.

The Solution: LANcache

The guys over at Multiplay Labs developed LANcache configuration for Nginx 1.6.0+ leveraging its caching capability to store game and application downloads to a local server for various gaming services such as Steam, Riot, Blizzard, Hirez, Origin, and Sony PlayStation Network (PSN). Their primary goal is to speed up game downloads at LAN parties while preventing the upstream internet connection from getting choked.

Now, I already have Nginx running on my home Ubuntu Linux server which is running multiple services and providing reverse proxy functionality for multiple HTPC-related applications. I didn’t want to have to set up another instance of Nginx for LANcache, or shoehorn my existing complex configuration in to the LANcache configuration. I’ve got multiple site configuration files in my sites-enabled directory (Ubuntu).

LANcache is designed as a multipurpose solution handling several services. I only needed to handle Sony PlayStation Network. So, the best option for me was to make another site configuration file to provide same functionality as LANcache.

The components involved in getting this working are

  • Nginx 1.6.0+ (installed via PPA)
  • /etc/hosts file
  • BIND DNS Server
  • Secondary IP address on server interface for use by Nginx and BIND

The process of communication and file retrieval works as such:

  1. DNS server on the PS4 is set as IP for the local server so that BIND is used for all DNS resolutions.
  2. PS4 requests the file using a full URL.
  3. The domain in the URL is resolved by BIND providing a spoofed IP address which points to Nginx.
  4. Nginx retrieves the files relaying it to the PS4 as well as storing it in its local cache. The PS4 might show failed download if it initiates multiple requests for a multi-part download, but it’ll succeed once file has been stored in cache.
  5. If same file is requested again Nginx serves it directly from local cache.

The /etc/hosts file isn’t mentioned in the above list because it is used only to simplify the Nginx configuration file by not having to hard-code IP addresses in there. Host names can be used in the Nginx configuration file and the appropriate entry in /etc/hosts will provide the IP address.

I seriously recommend checking out the LANcache page for the full details.

The Configuration

The below are snippets of configuration you will require to get the system working properly. I don’t want to go over how to install and set up Nginx or BIND. There are numerous articles out there that describe how to do that. But, I will say this… it would help immensely if you understood how exactly HTTP and DNS work.

/etc/hosts

# LAN router running recursive DNS service
# used by nginx to lookup real IP address of remote server
172.16.1.1        upstream-dns-1

# IP Address for LANcache Nginx server{} instance
# This is a secondary IP address on my server's LAN interface
172.16.1.3        lancache-sony

/etc/nginx/sites-available

log_format
lancache_log_format '$remote_addr - $remote_user [$time_local]
"$request" $status $body_bytes_sent "$http_referer"
"$http_user_agent" "$upstream_cache_status" "$host"
"$http_range"';

proxy_cache_path /srv/lancache/cache/installs levels=2:2
keys_zone=installs:500m inactive=1825d max_size=972800m
loader_files=1000 loader_sleep=50ms loader_threshold=300ms;
proxy_cache_path /srv/lancache/cache/other levels=2:2
keys_zone=other:100m inactive=72h max_size=10240m;
proxy_temp_path /srv/lancache/cache/tmp;

server {
        listen lancache-sony;
        server_name sony _;
        resolver upstream-dns-1;

        sendfile off;
        tcp_nopush off;
        tcp_nodelay off;

        access_log /srv/lancache/logs/access.log lancache_log_format;
        error_log /srv/lancache/logs/error.log;

        proxy_cache_key "$server_name$uri";

        location / {
                proxy_cache installs;
                proxy_next_upstream error timeout http_404;
                proxy_pass http://$host$request_uri;
                proxy_redirect off;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                add_header X-Upstream-Status $upstream_status;
                add_header X-Upstream-Response-Time $upstream_response_time;
                add_header X-Upstream-Cache-Status $upstream_cache_status;
                proxy_ignore_client_abort on;
                proxy_cache_lock on;
                proxy_cache_lock_timeout 72h;
   
                # required for nginx 1.7.8
                proxy_cache_lock_age;
                #proxy_cache_lock_age 72h;
                proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
                proxy_cache_valid 200 900d;
                proxy_cache_valid 301 302 0;
                proxy_cache_revalidate on;
                proxy_cache_bypass $arg_nocache;
                proxy_max_temp_file_size 40960m;
        }
}

/etc/bind/zones.lancache

zone "gs2.sonycoment.loris-e.llnwd.net" { type master; file "/etc/bind/db.lancache"; };
zone "gs2.ww.prod.dl.playstation.net" { type master; file "/etc/bind/db.lancache"; };

/etc/bind/db.lancache

$TTL 604800
@       IN      SOA     localhost. root.localhost. ( 1 604800 86400 2419200 604800 )
@       IN      NS      localhost.
@       3600    IN A    172.16.1.3
*       3600    IN A    172.16.1.3

CDNs and Domain Names

Also, depending on your geographical location and closest available CDN the domain name to which the file download requests are sent may be different and you will have to adjust the configuration to match those domains. For me the download file domain is gs2.ww.prod.dl.playstation.net which sometimes gets redirected to some.subdomain.gs2.sonycoment.loris-e.llnwd.net which is why I have the entries I do. You could find out your file request domains by either performing a packet capture or using a local proxy server.

Challenge I faced

The only problem I came across while setting this up was a corrupted file retrieved from the CDN. The PS4 would cancel the transfer at some point while transferring from cache with error CE-36244-9 i.e. “Downloaded data may have been corrupted during the download process”. At first I thought that Nginx wasn’t transferring the file properly and spent way too long trying to troubleshoot that. I had redownloaded the file and matched it against my cached file successfully. It was only when I checked the JSON digest for the game download files that I saw the hash value in there didn’t match my file. Downloading from another CDN fixed that problem.

Now, I’ve got almost 150GB of game package files in my cache which is wonderfully staisfying requests from the PS4. I tested this by deleting all the games from my PS4 and redownloading them which transferred at rates of about 350Mbps over the 1Gbps LAN. I suspect the bottleneck is the PS4 HDD writing at about 30-40MB/s which is expected.