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+:

---
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.