Android Networking
Docker bridgeが動いても、Androidのnetd・policy routing・iptables経路の違いで外向き通信やpublished portが失敗する場合があります。このページは実証構成で使った切り分け方法です。
症状
container -> docker bridge ARP は成功
container -> Internet は失敗
または
container は正常
host :3000 は LISTEN
LANから接続すると reset / timeout
Android policy routing
Androidにはnetdが管理する多数の ip rule があり、通常Linuxのように自然にmain tableへ落ちないことがあります。Docker bridgeから来たpacketを明示的に lookup main へ送るruleが必要になるケースがあります。
ip rule
ip route show table main
ip route get 1.1.1.1
echo 1 > /proc/sys/net/ipv4/ip_forward
既存のnetd ruleをflushしてはいけません。追加が必要な場合は、自分で管理するpriority範囲にDocker bridge由来のtrafficだけを対象にしたruleを置きます。priority値はip ruleを見て衝突しない値を選びます。
ip -br link
ip -br addr
docker network inspect bridge --format '{{json .IPAM.Config}}'
ip route get 1.1.1.1
ip route show table all | head -n 120
legacy vs nft
Ubuntu chroot内のDockerがnft系ruleを作っても、Android側の実packet pathがlegacy iptablesを通るとpublished portが機能しないことがあります。その場合はAndroid側legacy PREROUTING / FORWARD に専用chainを置いてDNATします。
iptables --version
iptables -t nat -S
iptables -S FORWARD
command -v iptables-legacy && iptables-legacy --version
command -v iptables-nft && iptables-nft --version
command -v nft && nft list ruleset | head -n 120
iptables --versionの表示だけでは、Android kernel内で実packetがどのrule setを通っているかを完全には断定できません。Dockerが作ったruleとAndroid側chainのpacket counterも確認します。
専用chainを使う例
filter: VYDOCKER_IN / VYDOCKER_OUT / VYDOCKER_FWD
nat: VYDOCKER_PRE / VYDOCKER_POST
INPUT -> VYDOCKER_IN
OUTPUT -> VYDOCKER_OUT
FORWARD -> VYDOCKER_FWD
PREROUTING -> VYDOCKER_PRE
POSTROUTING -> VYDOCKER_POST
chain名は任意です。既存のAndroid/netd chainを直接flushせず、自分で追加したruleだけを更新できるように分離します。再適用時は自分のchainだけをflushして作り直せる形にします。
bridge→WAN
interface名とsubnetは端末・接続方法・Docker networkごとに変わります。まず実値を取得します。
WAN_IF=$(ip route get 1.1.1.1 | awk '/dev/ {for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}')
DOCKER_SUBNET=$(docker network inspect bridge --format '{{(index .IPAM.Config 0).Subnet}}')
echo "WAN=$WAN_IF subnet=$DOCKER_SUBNET"
Android側の実packet pathがlegacy iptablesを通ることを確認した場合の例:
iptables -A VYDOCKER_FWD -i docker0 -o "$WAN_IF" -s "$DOCKER_SUBNET" -j ACCEPT
iptables -A VYDOCKER_FWD -i "$WAN_IF" -o docker0 -d "$DOCKER_SUBNET" \
-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -t nat -A VYDOCKER_POST -s "$DOCKER_SUBNET" -o "$WAN_IF" -j MASQUERADE
ここまでで docker run --rm alpine ping -c 1 1.1.1.1 が通るならL3/NATは成立しています。名前解決は別に wget や nslookup で確認します。
LAN published port
host自身から curl http://127.0.0.1:3000/healthz が通るのにLAN端末からだけ失敗する場合、Vylineではなくhost側のpublished-port経路を調べます。対象containerのIPとbridgeを毎回取得します。
CID=$(docker ps -qf 'name=vyline')
CONTAINER_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CID")
NETWORK_ID=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.NetworkID}}{{end}}' "$CID")
BRIDGE_IF="br-$(printf '%s' "$NETWORK_ID" | cut -c1-12)"
ip -br addr
echo "container=$CONTAINER_IP bridge=$BRIDGE_IF"
LANから入ってくるinterfaceを LAN_IF として確認したうえで:
LAN_IF=wlan0 # 例。実際のinterface名へ置き換える
iptables -t nat -A VYDOCKER_PRE -i "$LAN_IF" -p tcp --dport 3000 \
-j DNAT --to-destination "$CONTAINER_IP:3000"
iptables -A VYDOCKER_FWD -i "$LAN_IF" -o "$BRIDGE_IF" \
-p tcp -d "$CONTAINER_IP" --dport 3000 -j ACCEPT
Wi-Fi、USB tethering、mobile data、VPNでは入力interfaceとdefault-route interfaceが別になることがあります。wlan0は例であり、固定値ではありません。
container再作成への追従
Compose再作成でcontainer IPやbridge名が変わる場合、固定したDNAT/FORWARD ruleは更新が必要です。必要な端末ではDocker network、container、LAN/WAN interfaceを再列挙し、自分の専用chainだけを再同期します。
loop
docker network inspect -> bridge/subnet
docker inspect -> container IP / published ports
LAN/WAN interface/gatewayを再検出
自分で予約したpriority範囲のip ruleを再構築
専用iptables chainを再構築
状態が変わったときだけ更新
数秒ごとのpollingは実装が単純ですが必須ではありません。Docker event、network change、boot時の再生成など、その端末で安定する方法を選びます。
外部アクセス時の任意構成
Cloudflare TunnelはVylineの起動要件ではありません。外部アクセスが必要な場合だけ、cloudflaredをVylineと同じDocker networkへ置き、originをhttp://vyline:3000にすると、Android hostのLAN側published portを経由せずTunnelへ接続できます。LAN内だけで使うなら追加する必要はありません。
最後の確認
docker run --rm alpine ping -c 1 1.1.1.1
docker run --rm alpine sh -c 'wget -qO- https://example.com | head'
curl -v http://127.0.0.1:3000/healthz
ss -lnt | grep ':3000'
iptables -nvL FORWARD
iptables -t nat -nvL PREROUTING
「container→Internet」「host→Vyline」「LAN→host:3000」を別々に確認します。3つを同時に直そうとすると、どの層が壊れているか分からなくなります。