Using PlexConnect with dnsmasq and Ad-free time

My Internet Media Setup

For a long time, I've been living without cable TV. My personal setup has been a Mac Mini for a media server (running Plex Media Server, combined with a jailbroken AppleTV 2 (ATV2) with a native iOS Plex app installed. Frustrated with regularly having to free up disk space, I also recently attached a Drobo 5D to my Mac Mini.

While I could have connected my Mac Mini directly to my TV (as HDMI output), I prefer the experience of the AppleTV and its user interface. I don't want to feel like I'm using a desktop computer; I want the convenience of a TV appliance. Plex's Mac App is great, but not all content I want is available through it. My usual content sources are:

  • Netflix
  • MLB.tv
  • NHL GameCenter Live
  • iTunes

On a Mac, I'd have to be switching between apps and interfaces to access these. On the AppleTV, the experience is seamless.

Once I found out about the jailbreak for the ATV2 and the fact that a Plex app was available, I gave it a try. I ended up loving that setup so much that I've stuck with it for two years now. Here's a sample of what that setup looked like:

Plex ATV2 Native App

(Avoiding) Professional Sports Blackouts

I also had aspirations of setting up a VPN tunnel to allow me to avoid pro sports blackouts. In the US & Canada, we have this awful anti-competitive thing called a Broadcast Blackout where TV broadcasters prevent fans from watching the game on TV (or the internet), even if the fans are willing to pay money for the privilege. Outdated broadcast contracts continue to ruin the fan experience even in 2014, in many cases making it so there is no legitimate way to watch a local or nationally broadcasted game live on the internet instead of cable TV, even if you're already paying for an internet viewing subscription.

The ATV2 jailbreak allowed me to learn quite a bit about how iOS and the sports apps on it worked. That includes what types of outbound traffic they send (mostly HTTP requests, unencrypted in some cases!) and which servers they communicate with.

However, in the end, it turned out to be more practical to run the VPN tunnel on another home server (or router) and use that device as a network gateway for the AppleTV. With this setup, I could easily run packet captures in WireShark and see what traffic was leaving the AppleTV device (and for which IPs). This eventually allowed me to run a somewhat complicated VPN setup that, rather than routing all traffic through the VPN, allowed me to only route traffic from the AppleTV through the VPN if it was destined for certain outbound IPs.

In particular, I was routing HTTP requests used for geolocation & blackout authorizations through the VPN, leaving all other traffic (including video streams) to flow directly through my local high-bandwidth connection. This was a great setup as I used very little VPN bandwidth and the experience felt more or less the same as it did without a VPN in the way. I've still only used 5GB of the 50GB I purchased from PrivateTunnel over two years ago.

In this setup, the jailbreak wasn't actually required for blackout avoidance, but I kept it so I could use Plex on my AppleTV.

A simpler setup for avoiding broadcast blackouts

Not too long ago, I heard about a service called Ad-free time!. It's similar to other DNS-based services that let you avoid internet advertisements or circumvent region-blocking streaming services.

For example, if you're a Netflix US customer, but are traveling in Europe, Ad-free time would let you watch all of the Netflix US content even while you're abroad.

Ad-free time can also make you appear to other streaming services as if you're in another country. If you're a subscriber to MLB.tv, this is actually the only way you can use it to watch Saturday games, other national broadcasts, or playoff games. In other words: if you pay MLB exactly the same amount of money, but happen to live overseas, you get more for your money than somebody in the US. Fuck that.

So anyway, a DNS-based service like Ad-free time! is very simple to setup. Once you've signed up and activated your IP through their website, you just manually set your DNS server address and you're done! You can set this on a single device (such as an AppleTV) or on your home network router (to apply it to your entire network). I've done this just for my AppleTV since I don't want to affect any other network requests, and I really only view region-specific content from that device anyway.

Issues with a Jailbroken AppleTV

Jailbroken AppleTV devices can't be updated normally; when a new ATV update comes out, you have to wait for a jailbreak to be available before. Because recent versions of iOS were a lot harder to jailbreak, the jailbroken version of iOS was lagging over a year behind the official release. That meant that there were a lot of bug fixes and new channels that weren't available on my old device. The jailbroken setup was also slow, crashed often, and had lots of weird issues.

But, because I liked my native Plex client so much, I was unwilling to upgrade to a newer iOS without a jailbreak available.

PlexConnect

Recently, I heard about PlexConnect. PlexConnect works by running an HTTP server on the local network that pretends to be the trailers.apple.com server, as well as a DNS server that overrides responses for the target hostname so that they point at the local PlexConnect HTTP server.

The PlexConnect HTTP server then just serves up XML responses (and some JavaScript) that match the format the AppleTV Trailers app already expects and knows how to parse.

Here's a sample of the sort of XML that's served by PlexConnect:

<?xml version="1.0" encoding="UTF-8"?>
<atv>
  <head>
    <script src="" />
  </head>
  <body>
    <viewWithNavigationBar id="PlexConnect_Navigation" onNavigate="loadMenuPage(event)">
      <navigation>
        <navigationItem id="Library">
          <title></title>
          <url></url>
        </navigationItem>
        <navigationItem id="Channels">
          <title></title>
          <url></url>
        </navigationItem>
        <navigationItem id="SharedLibraries">
          <title></title>
          <url></url>
        </navigationItem>
        <navigationItem id="Settings">
          <title></title>
          <url></url>
        </navigationItem>
      </navigation>
    </viewWithNavigationBar>
  </body>
</atv>

Isn't that awesome?

There's a lot more template examples in the Github repo.

As far as I can tell the documentation on this format is not openly available, but the folks working on PlexConnect have done an awesome job reverse-engineering it. It's amazing what you can do here just by returning some fairly simple XML responses, especially the interactive menus.

Check out this video demo to see how PlexConnect looks and feels:

Installing PlexConnect

Installing PlexConnect was very straightforward. Their install docs are great, so I won't bother repeating those here.

The one caveat for me was that I had to be using a newer version of the AppleTV iOS than I could use with my jailbreak. But that also meant new features, new content, and a more stable AppleTV experience, so it was a no-brainer for me.

Once I had my AppleTV updated to the latest iOS (and no longer jailbroken), I set its DNS server to the IP of my media server, and PlexConnect just worked!

There are some technical details about how this works that will be relevant later in the next section post. PlexConnect is able to trick the AppleTV into thinking that the trailers.apple.com server is actually a local server because it can intercept the DNS lookup for that hostname. Instead of responding with the real DNS records of that hostname, the PlexConnect DNS server just directs the AppleTV to the local server's LAN IP (on which the PlexConnect web server is running). All other DNS lookups are passed on to the "DNS master", which is preconfigured to the Google DNS IP (8.8.8.8) in PlexConnect's config file.

Combining PlexConnect with Ad-free Time!

The difficult thing about this PlexConnect setup is that it also relies on a custom DNS server, and I can't just point my AppleTV at both DNS servers at once. That means I could either use PlexConnect or Ad-free Time, but not both at the same time.

Because this is a common problem amongst people who use the software, PlexConnect provides a simple config setting to override the fallback DNS server:

The docs for this and more PlexConnect advanced config options are in the project's Github wiki.

Just tweak this setting to point to your closest Ad-free Time DNS server and you'll get the best of both worlds.

Keeping the local content you want, even with Ad-free Time

An unfortunate side effect of directing all your DNS lookups through Ad-free Time is that none of your DNS requests actually appear to come from your local network anymore. That's great for avoiding region-specific blackouts, but it also means that you don't appear to be in your own region for local content services. The AppleTV has some local content such as ABC or ESPN where the out-of-the-box experience lets you watch your local affiliate station's broadcasts. If you use Ad-free Time for all DNS queries, you'll probably end up getting a completely different station (i.e. NYC instead of SF). Services like ESPN may not even work at all this way.

What we really want is some more fine-grained control around how our DNS queries are being routed. We want to continue intercepting trailers.apple.com requests with our local server's IP, and we want to send lookups for geo-restricted services through Ad-free Time, but we also want to send DNS lookups for localized content through our regular DNS server.

That sounds complicated, but fortunately a flexible piece of software called dnsmasq lets us do that pretty easily. I won't go through the details of installing dnsmasq as it's system-specific. It's probably quite simple, though. If you're on Mac, you can brew install dnsmasq. On Linux, it's probably available through your package manager.

Figuring out which hostnames to exclude from DNS forwarding

In order to tell dnsmasq which DNS queries we want to exclude from forwarding, we need to provide it a list of hostnames.

While the instructions below are for a Mac OS X system, the process should be very similar for Linux machines, maybe just with different file paths.

First, you'll need to stop the background process (daemon) that you may have started when you installed PlexConnect. Next, start PlexConnect in the foreground:

PlexConnect-0.3 bgentry$ sudo ./PlexConnect.py 
20:43:06 PlexConnect: ***
20:43:06 PlexConnect: PlexConnect
20:43:06 PlexConnect: Press CTRL-C to shut down.
20:43:06 PlexConnect: ***
20:43:06 PlexConnect: started: 20:43:06
20:43:06 PlexConnect: Version: 0.3
20:43:06 PlexConnect: Python: 2.7.5 (default, Aug 25 2013, 00:04:04) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)]
20:43:06 PlexConnect: Host OS: darwin
20:43:06 PlexConnect: IP_self: 192.168.1.10
20:43:06 DNSServer: started: 20:43:06
20:43:06 DNSServer: ***
20:43:06 DNSServer: DNSServer: Serving DNS on 192.168.1.10 port 53.
20:43:06 DNSServer: intercept: ['trailers.apple.com'] => 192.168.1.10
20:43:06 DNSServer: restrain: ['mesu.apple.com', 'appldnld.apple.com', 'appldnld.apple.com.edgesuite.net'] => 127.0.0.1
20:43:06 DNSServer: forward other to higher level DNS: 8.8.8.8
20:43:06 DNSServer: ***
20:43:06 WebServer: started: 20:43:06
20:43:06 WebServer: ***
20:43:06 WebServer: WebServer: Serving HTTP on 192.168.1.10 port 80.
20:43:06 WebServer: ***
20:43:06 WebServer: started: 20:43:06
20:43:06 WebServer: ***
20:43:06 WebServer: WebServer: Serving HTTPS on 192.168.1.10 port 443.
20:43:06 WebServer: ***

Then, on your AppleTV, start using the service that you're trying to determine the hostnames for. Your PlexConnect logs will show a list of DNS queries coming from your device. Here's an example from my AppleTV showing some Apple servers:

20:43:20 DNSServer: DNS request received!
20:43:20 DNSServer: Source: ('192.168.1.156', 58807)
20:43:20 DNSServer: Domain: ld-2.itunes.apple.com
20:43:20 DNSServer: ***forward request
20:43:20 DNSServer: -> DNS response from higher level
20:43:20 DNSServer: DNS request received!
20:43:20 DNSServer: Source: ('192.168.1.156', 53551)
20:43:20 DNSServer: Domain: pd-nk.itunes.apple.com
20:43:20 DNSServer: ***forward request
20:43:20 DNSServer: -> DNS response from higher level
20:43:20 DNSServer: DNS request received!
20:43:20 DNSServer: Source: ('192.168.1.156', 63614)
20:43:20 DNSServer: Domain: play.itunes.apple.com
20:43:20 DNSServer: ***forward request
20:43:20 DNSServer: -> DNS response from higher level
20:43:21 DNSServer: DNS request received!
20:43:21 DNSServer: Source: ('192.168.1.156', 60203)
20:43:21 DNSServer: Domain: radio.itunes.apple.com
20:43:21 DNSServer: ***forward request
20:43:21 DNSServer: -> DNS response from higher level

There might be some hostnames in this list that aren't relevant towards what you're trying to unblock. That's because there are many background processes on an AppleTV that make DNS queries on their own. Just ignore those and find the hostnames that might belong to your service or its CDN.

Once you've got the list of hostnames you need, you can stop the PlexConnect server.

Configuring PlexConnect and dnsmasq to work together

Next, you'll need to modify the PlexConnect config to disable its built-in DNS server:

enable_dnsserver = False

We'll be using dnsmasq for this instead. Once you've made that change, you can start the PlexConnect daemon again:

PlexConnect-0.3 bgentry$ sudo launchctl load /Library/LaunchDaemons/com.plex.plexconnect.bash.plist

Now you'll need to add the hostnames of your service to the dnsmasq config. Because dnsmasq is so flexible, there are many ways you could do this. Mine is configured to forward DNS queries for localized services to my home router (192.168.1.1), and trailers.apple.com queries to my PlexConnect server (192.168.1.10).

Any queries that aren't specifically overridden here will be answered by whatever DNS server this machine is configured to point at (Ad-free Time, in my case). Here's my /usr/local/etc/dnsmasq.conf:

###
# Tells dnsmasq to forward anything with the domain of remote.local to dns server 192.168.1.1
###

# ABC
server=/watchabc.go.com/192.168.1.1
server=/abc.112.2o7.net/192.168.1.1
server=/api.auth.adobe.com/192.168.1.1
server=/cdn.edgedatg.com/192.168.1.1
server=/abc.go.com/192.168.1.1
server=/uplynk.com/192.168.1.1

# ESPN
server=/espn.go.com/192.168.1.1
server=/espn.com/192.168.1.1
server=/espn.112.2o7.net/192.168.1.1

# PlexConnect routing
address=/trailers.apple.com/192.168.1.10
address=/atv.plexconnect/192.168.1.10

# Listen to requests only coming from the local machine
# listen-address=127.0.0.1

# Do not cache anything
# A decent dns server will already cache for your local network
cache-size=0

# No reason to have a 0s TTL on local queries
local-ttl=30

Finally, once you've made those changes to the dnsmasq config, you can start the dnsmasq daemon:

$ sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist

That should be everything!

Summary

This setup gives you:

  • All of AppleTV's features with no jailbreak
  • PlexConnect in all its glory, letting you easily browse and watch media from your local Plex server
  • Unrestricted access to the geo/blackout-restricted services you already pay for, such as Netflix, MLB.tv, NHL GameCenter Live, etc.
  • Regular access to localized ATV content like ABC or ESPN, provided that you have login credentials for these services
  • No VPN required!

I hope this post has been useful and informative. I really love the type of hack that PlexConnect uses: mimicking a device's native protocol to reprogram it to be more powerful. Let's see more of that :)