Sunday, January 11, 2015

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

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.

17 comments:

  1. ^http:\/\/.*\/gs2\/ppkgo\/prod\/([\w_]*)\/.*\/([\w_]*)\/.*\/([\w_]*-[\w_]*-[\w_]*-[\w_]*-[\w_]*[\w_\.]*).*

    I use this to cache the update using store-id and squid, might be useful. I don't really like messing with DNS setting, seems to work perfectly for me.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. } elsif ($X[0] =~ m/^http:\/\/.*\.ps4\.update\.playstation\.net\/update\/ps4\/image\/([\w_]*)\/([\w_]*)\/(.*)$/){
    $out="http://ps4.squid.internal/" . $2 . $3 . $4 ;

    } elsif ($X[0] =~ m/^http:\/\/.*\/gs2\/ppkgo\/prod\/([\w_]*)\/.*\/([\w_]*)\/.*\/([\w_]*-[\w_]*-[\w_]*-[\w_]*-[\w_]*[\w_\.]*).*$/){
    $out="http://ps4.squid.internal/" . $2 . $3 . $4 ;

    ReplyDelete
  4. Never mind it didn't work due the range request, eh..

    ReplyDelete
  5. hello and thank you for this article .
    if i have 2 ps4 device and both of them request an update in one time for first(i mean that file not exist in cache and must be download), what happend? 2 copy of link download togheter or one of them blocked until download complete??? because i have 6 device in a club maybe all of them download an update in one second.
    thank you

    ReplyDelete
    Replies
    1. In that case only one copy of the file will start to download. Other requests will be blocked till the download is complete.

      Delete
  6. Hi, I was wondering if you could help get my lancache working. I have tried a couple of times including re-installing Debian from scratch a couple of times. DNS works, I set DNS on the PS4 console to point to the lancahe server. It resolves and can start downloading games etc. However it seems that the PS4 is downloading directly and the lancache server is not doing the downloads. Any idea what I have missed or what I might be doing wrong?

    ReplyDelete
    Replies
    1. I can try. I'll need to see your DNS and Nginx configs. It might be a bit of back and forth on this and looks like we're in very different time zones, so we should proceed through email. I've posted to Google Plus mentioning you so you should see it there. It includes my email address.

      Delete
  7. Hello, Ali. Hopefully you can point me in the right direction with this.

    I succesfully replicated the configuration and lancache is working, with these two caveats:

    -The download speed seems to be painfully slow
    -It works, but for small files. I tried downloading some themes on my PS4. The first time it took some time, but after deleting the themes & re-downloading them, they were clearly pulled from the cache because the process was almost instant.

    When I tried to download a larger file (I.E.: a patch for Guilty Gear Xrd Revelator - almost 4Gigs), at some point the progress halted and the PS4 informed "Couldn't download the file".

    Any ideas on why this happens?

    ReplyDelete
  8. What you're experiencing as slow download speed and download failed message is due to the difference in behavior in how Nginx and PS4 download files.

    The PS4 downloads file using byte-range requests (byte serving) i.e. it sends multiple requests for the same file but for different parts of the file. This can help with servers or ISPs that restrict download rates of single streams. By using byte serving you could increase the overall rate of transfer as each download stream may be restricted, but by downloading multiple streams simultaneously you're able to compound the download speed.

    For example let's say your ISP restricts each stream to 50KB/s and you're downloading a 1MB file. At that rate it'll take about 20 seconds to download with a single stream. Now, using byte serving and initiating 4 simultaneous downloads you'd have the following scenario:

    Request 1: bytes 1-262,144 @ 50KB/s
    Request 2: bytes 262,145-524,288 @ 50KB/s
    Request 3: bytes 524,289-786,432 @ 50KB/s
    Request 4: bytes 786,433-1,048,576 @ 50KB/s

    Your total download speed has now become 200KB/s and the download is completed in 5 seconds. That's a 75% reduction.

    This not how Nginx in our LANcache downloads the file. Nginx will use a single stream to download the file from byte 1-1,048,576. When Nginx gets the first request regardless of the requested byte range it will start from the beginning of the file, and till this download is complete it will block all further requests.

    The PS4 continues to request byte ranges from Nginx, but as it is still processing the file the range requests are failing. It is only after Nginx downloads the complete file and adds it to the cache that it can properly serve the PS4s byte range requests.

    I usually start the download on the PS4 and will see it show a failure and stop not long after. But, if you check the tmp directory (check nginx configuration file) you'll see that there is a file that's increasing in size as its being downloaded to be added to the cache.

    Hope that helps.

    ReplyDelete
    Replies
    1. I'm facong a weird problem. The PS4 says the downloads are corrupted even when the SHA-1 hash for the files is exactly the one in the json file. This happens with big files (4Gb).

      Smaller files don't have that problem. For example, I downloaded some themes (around 20Mb) and they are installed correctly. I'm scratching my head :(

      Delete
    2. I have also faced this issue. I still haven't found a root cause and resolution yet. It helped me to delete the partial download from the PS4, restart PS4, and then install again. I've done this for a couple of games. There was one game were this didn't work either. I gave up and temporarily deactivated the cache to download it.

      It's been on my list for a long time to figure this out, but the issue is intermittent and I haven't had the time to dedicate to reproducing it. If you manage to figure something out please let me know.

      Delete
    3. I have the same experience with two games: Axiom Verge, which weighs around 300Mb and a patch for SFV, which is a multi-file download (4gb,4gb, 3gb).

      I have to download Tekken7 (40Gb) to 3 PS4s and I really wanted to take advantage of this, damn.

      I'll try with other games to see what happens.
      Thanks for the help

      Delete
    4. Ali, I accidentally found this utility:
      https://psxdownloadhelper.codeplex.com/

      I just downloaded the patch for Street Fighter V without problems using the same cached files... I feel a little guilty because I prefer doing stuff by myself, but it definitively works.

      Delete
  9. Thanks Ali, that was really helpful. The part in your post where you mention it makes a lot of sense now.

    ReplyDelete
  10. Hi Ali,

    I've tried to replicate your setup, modifying to my specific infrastructure (nginx, dns, ...)

    I've run into the same problem with corrupted files and was really hoping you made some progress in the mean time.

    Any help is highly appreciated

    ReplyDelete