Watching Disney+ in Asia on Android TV via Clash on the gateway

I've been watching Marvel movies recently on Disney+. I also have got a 4K UHD HDR monitor and a MiBox at home, which is exactly capable of handling 4K HDR content! So I thought why not give it a try to play Disney+ content on that box since Disney+ has got a native Android TV app.

The issue is that as of now Disney+ only serves Western customers, and I need to do some tricks in order to play content in Taiwan. Two things: TCP proxy and the so-called "Smart DNS". In short terms (and to my best knowledge), Smart DNS is basically a DNS proxy. You just need to resolve Disney+ domains through that proxy in order to bypass the regional restrictions.

There is tremendous amount of such services that offer both IP proxy and DNS proxy at the same time, often known as "airports" or Shadowsocks services in China. You can get them easily on the Internet. I'm not gonna cover that in here. I'd recommend Dler Cloud, though! (yes that's a referral link)

You'll need a Ubuntu machine, either a physical machine or a VM is ok, that will act as an another gateway on your LAN. We'll run https://github.com/Dreamacro/clash on that, which will help us with DNS and IP proxying. It's a high performance proxy server that allows rule-based routing and a lot of proxy protocols.

First of all, install some packages and enable IPv4 and IPv6 forwarding.

apt install -y iptables-persistent net-tools curl wget vim
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.forwarding = 1" >> /etc/sysctl.conf
sysctl -p

Get Clash for your platform. There are pre-built binaries for various platforms at https://github.com/Dreamacro/clash/releases.

wget -O clash.gz https://github.com/Dreamacro/clash/releases/download/v0.20.0/clash-linux-amd64-v0.20.0.gz
gzip -d clash.gz
chmod +x clash
mv clash /usr/bin
setcap cap_net_bind_service=+ep /usr/bin/clash
clash # this will create ~/.config/clash and an example config

Also get the Web Clash controller.

wget https://github.com/Dreamacro/clash-dashboard/archive/gh-pages.zip
unzip gh-pages.zip
mv clash-dashboard-gh-pages/ ~/.config/clash/dashboard

Go to the Clash config directory, and download the so-called "subscription" or a managed configuration file from your service provider.

cd ~/.config/clash
wget -O managed-config.yaml https://dler.cloud/subscribe/xxxxxxx?clash=ss
rm config.yaml # delete example config
cp managed-config.yaml config.yaml

Here is an example config for Disney+:

layout: layouts/post.njk
---
port: 7890 # HTTP proxy port
socks-port: 7891 # SOCKS5 proxy port
redir-port: 7892 # transparent proxy port
allow-lan: true # allow other devices to use Clash
mode: Rule # rule-based routing
log-level: info
external-controller: 0.0.0.0:9090 # for the web service
external-ui: dashboard # the relative path of web controller
experimental:
ignore-resolve-fail: true
dns:
enable: true
ipv6: false
listen: 0.0.0.0:5399
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
nameserver:
- '1.1.1.1'
- '8.8.8.8'
fallback:
- '1.1.1.1'
- '8.8.8.8'
Proxy:
- name: "Azure US"
type: ss
server: 1.2.3.4
port: 1234
cipher: aes-128-gcm
password: hello
udp: true
plugin: obfs
plugin-opts:
mode: tls
host: some.host
Proxy Group:
- name: DisneyPlus
type: select
proxies:
- DIRECT
- Azure US
Rule:
- DOMAIN,cdn.registerdisney.go.com,DisneyPlus
- DOMAIN,cdn.cdn.unid.go.com,DisneyPlus
- DOMAIN-SUFFIX,bamgrid.com,DisneyPlus
- DOMAIN-SUFFIX,braze.com,DisneyPlus
- DOMAIN-SUFFIX,conviva.com,DisneyPlus
- DOMAIN-SUFFIX,disney.demdex.net,DisneyPlus
- DOMAIN-SUFFIX,disneyplus.com,DisneyPlus
- DOMAIN-SUFFIX,disney-plus.net,DisneyPlus
- DOMAIN-SUFFIX,dssott.com,DisneyPlus
- DOMAIN-SUFFIX,execute-api.us-east-1.amazonaws.com,DisneyPlus
- IP-CIDR,10.0.0.0/8,DIRECT
- IP-CIDR,100.64.0.0/10,DIRECT
- IP-CIDR,127.0.0.0/8,DIRECT
- IP-CIDR,172.16.0.0/12,DIRECT
- IP-CIDR,192.168.0.0/16,DIRECT
- MATCH,DIRECT
...

Regarding the DNS part, we're using fake-ip mode here. Say we're connecting to google.com:

In the fake-ip mode, when you ask Clash DNS server for the A record of google.com, it returns a "fake" IP address in the CIDR 198.18.0.0/16, say 198.18.2.100. If a packet later sent to the Clash transparent port is destined for 198.18.2.100, Clash sends the hostname google.com to the remote proxy server, and the real DNS resolution is performed there. This is how we'll do the "Smart DNS".

The rest of the configuration above is pretty self-explanatory. Here's an unofficial English documentation of Clash should you have any issues: https://lancellc.gitbook.io/clash

The last step is setting up iptables rules.

#!/bin/bash

# Set up fake DNS server at 198.19.0.0/24 that redirect all packets
# on port 53 to Clash DNS
iptables -t nat -N clash_dns

iptables -t nat -A PREROUTING -p tcp --dport 53 -d 198.19.0.0/24 -j clash_dns
iptables -t nat -A PREROUTING -p udp --dport 53 -d 198.19.0.0/24 -j clash_dns

iptables -t nat -A clash_dns -p udp --dport 53 -d 198.19.0.0/24 -j DNAT --to-destination 10.0.1.250:5399
iptables -t nat -A clash_dns -p tcp --dport 53 -d 198.19.0.0/24 -j DNAT --to-destination 10.0.1.250:5399

# Also hijack any packets to 8.8.8.8:53 to Clash DNS
# since Chromecast or some Google Apps force using 8.8.8.8 to resolve IPs
iptables -t nat -A clash_dns -p udp --dport 53 -d 8.8.8.8 -j DNAT --to-destination 10.0.1.250:5399
iptables -t nat -A clash_dns -p tcp --dport 53 -d 8.8.8.8 -j DNAT --to-destination 10.0.1.250:5399

# Clash IP proxy
iptables -t nat -N clash

iptables -t nat -A clash -d 0.0.0.0/8 -j RETURN
iptables -t nat -A clash -d 10.0.0.0/8 -j RETURN
iptables -t nat -A clash -d 127.0.0.0/8 -j RETURN
iptables -t nat -A clash -d 169.254.0.0/16 -j RETURN
iptables -t nat -A clash -d 172.16.0.0/12 -j RETURN
iptables -t nat -A clash -d 192.168.0.0/16 -j RETURN
iptables -t nat -A clash -d 224.0.0.0/4 -j RETURN
iptables -t nat -A clash -d 240.0.0.0/4 -j RETURN

iptables -t nat -A clash -p tcp -j REDIRECT --to-ports 7892

iptables -t nat -A PREROUTING -p tcp -j clash

At this point, run clash and our gateway should be ready to go. On your Android TV, set up static IP for Internet access. Set the gateway IP to your ubuntu machine's IP address. For DNS, set 198.19.0.1 for DNS 1, leave DNS 2 empty.

Now open Disney+, you should be able to enjoy the movies! But we're not done yet. We need to make Clash a service. Kill Clash first, and then create the service:

vim /etc/systemd/system/clash.service

For the content:

[Unit]
Description=clashd

[Service]
Type=simple
User=root
ExecStart=/usr/bin/clash -d /root/.config/clash/
Restart=on-failure

[Install]
WantedBy=multi-user.target

Then enable the service, which makes Clash run at startup. And finally launch Clash.

systemctl enable clash.service
systemctl start clash.service

If you want to access the web control interface, go to http://GATEWAY_IP:9090/ui. You can see logs and switch proxies there.