android でLAN内のNTPサーバーに時刻を合わせる

# はじめに

時計大事ですよね。  
私の持っているandroidはWifi専用運用のため、時刻を合わせることができず、困っておりました。  
SIMがささっていれば、携帯基地局から時刻を取得することができるようなのですが、SIMすらないので、簡単に時刻がズレてしまいます。  
root化するという手もあるかもしれませんが、あまりやりたくはないです。  
これを何とかしようとGoogleさんに色々が聞いてみましたが、かなりハマったので、記録しておこうと思います。

# 環境

* Pixel 5a（SIMフリー版）
* Android 12
* SIMカードなし・Wifi接続のみ
* LAN外のNTPサーバーは参照不可

# DNSサーバーをLAN内にたてる方法（失敗）

Googleで「android ntp time sync」とかで調べると、どうも簡単には時刻同期できないということがすぐに分かりました。
もう少し調べてみると、情報としては古いですが、Android 4.1以降では、2.android.pool.ntp.orgにNTPサーバーがハードコーディングされているという情報も出てきました。なるほど、それでは、LAN外のNTPサーバーを参照できない当方の環境では時刻同期しないわけです（と、この時は一度は納得する）。  
そこで、下記ページでは、DNSサーバーをLAN内に立てて、androidが参照するNTPのアドレスをLAN内のNTPサーバーに向かわせるように上書きするという方法をとっていました。  
[https://syn.cocolog-nifty.com/access/2014/10/udpandroid-8ea5.html](https://syn.cocolog-nifty.com/access/2014/10/udpandroid-8ea5.html)  
少々トリッキーですが、私もこの作戦をやってみます（この後失敗します）。

DNSサーバーとしては、適当なUbuntu serverを用意して、Bind9を使ってサーバーを立てました。  
環境としては、下記になります。

* LAN: 192.168.8.0/24
* DNS（Ubuntu 20.04）: 192.168.8.1
* android（Android12）: 192.168.8.2
* NTP：192.168.8.3

まずは、BINDをインストールします。

```
sudo apt install bind9 bind9utils
```

インストールはとても簡単です。  
さて、今回は、特定のURLだけLAN内のNTPサーバーに向かわせて、それ以外は外部のDNSサーバーにフォワードさせるという設定になります。これを実現するには、Response Policy Zone (RPZ)という機能を使うようです。以下、その設定を行っていきます。

まず、以下のような設定ファイルに修正します。

```/etc/bind/named.conf.options
acl mynetwork {
    192.168.8.0/24;
};

options {
        directory "/var/cache/bind";

        // If there is a firewall between you and nameservers you want
        // to talk to, you may need to fix the firewall to allow multiple
        // ports to talk.  See http://www.kb.cert.org/vuls/id/800113

        // If your ISP provided one or more IP addresses for stable
        // nameservers, you probably want to use them as forwarders.
        // Uncomment the following block, and insert the addresses replacing
        // the all-0's placeholder.

        forwarders {
                8.8.8.8;
        };
        forward only;
        allow-recursion { localhost; mynetwork; };

        //========================================================================
        // If BIND logs error messages about the root key being expired,
        // you will need to update your keys.  See https://www.isc.org/bind-keys
        //========================================================================
        dnssec-validation no;

        allow-query {localhost; mynetwork; };
        listen-on port 53 {127.0.0.1; 192.168.8.1; };
        listen-on-v6 { any; };

        response-policy { zone "rpz.zone"; };
};
```

設定ファイルにある`acl`は、単なる設定ファイル中でのネットワーク名をaliasするもののようです。  
ポイントは、forwardersとresponse-policyです。forwardersに適当な外部のDNSサーバーを設定しておくことで、今回たてるDNSサーバーで名前解決できないときにそちらを参照してくれます。また、response-policyは、BIND9でのRPZ機能のことで、ここで`rpz.zone`という名前のゾーンの設定に従って、ドメイン名を上書きします。  
allow-query は問い合わせを受け付ける範囲を記述します。セキュリティリスクを抑えるため、LAN内のみ許可にした方が良いと思います。

次に、rpz.zoneを以下のファイルに定義します。

```/etc/bind/named.conf.local
zone "rpz.zone" {
     type master;
     file "/etc/bind/rpz.zone";
};
```

そして、rpz.zoneにドメイン書き換えのレコードを書いていきます。  
ひとまず、androidが参照していそうなNTPのドメイン名を思いつく限り列挙して、全てLAN内のNTPサーバー（192.168.8.3）に向かわせます。

```/etc/bind/rpz.zone
$TTL 86400
@   IN  SOA     localhost. root.localhost. (
        2022090900  ;Serial
        3600        ;Refresh
        1800        ;Retry
        604800      ;Expire
        86400       ;Minimum TTL
)

@                      IN   NS      localhost.
android.pool.ntp.org   IN   A       192.168.8.3
*.android.pool.ntp.org IN   A       192.168.8.3
pool.ntp.org           IN   A       192.168.8.3
*.pool.ntp.org         IN   A       192.168.8.3
time.google.com        IN   A       192.168.8.3
*.time.google.com      IN   A       192.168.8.3
time.android.com       IN   A       192.168.8.3
*.time.android.com     IN   A       192.168.8.3
```

念のため、下記の設定ファイルで`/etc/bind/named.conf.local`を読み込んでいるかどうか確かめておきます。

```/etc/bind/named.conf
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.default-zones";
```

一通りの設定が完了したら、bind9を再起動します。

```
sudo systemctl restart named
```

なお、設定ファイルの記入ミス等は`named-checkconf`コマンドや`named-checkzone`コマンドで確認できます。

うまくDNSが設定できたかどうかを確かめるために、適当なPC（192.168.8.4）からDNSサーバー（192.168.8.1）にntpサーバーのドメインのみ上書きできているかどうか確かめてみます。

```
host time.android.com
# Using domain server:
# Name: 192.168.8.1
# Address: 192.168.8.1#53
# Aliases:
# 
# time.android.com has address 192.168.8.3

host google.com
# Using domain server:
# Name: 192.168.8.1
# Address: 192.168.8.1#53
# Aliases:
# 
# google.com has address 172.217.161.78
# google.com has IPv6 address 2404:6800:4004:80b::200e
# google.com mail is handled by 10 smtp.google.com.
```

良さそうですね。

さて、これにてうまくいくと思って意気揚々とandroidでWifi接続の設定にあるDNSに今回つくったサーバー（192.168.8.1）を指定して、androidを再起動したり色々とやってみますが、一向に時刻が治りません。おかしいです。  
任意のNTPサーバーの時刻を参照するアプリ（NTP Time）とかをインストールしてみると、きちんと時刻表示されるのにです。androidの`日付と時刻`の設定で、「日時を自動的に設定」の項目もONにしています。とほほです。

参考ページ  
* [https://www.server-world.info/query?os=Ubuntu_20.04&p=dns&f=1](https://www.server-world.info/query?os=Ubuntu_20.04&p=dns&f=1)
* [https://kuroneko666.hatenadiary.jp/entry/20150411/1428731971](https://kuroneko666.hatenadiary.jp/entry/20150411/1428731971)

# Android studioを使って本体のNTPサーバーを指定する（成功）

困り果てて、さらにGoogleさんに質問しまくります。すると、下記ページにandroid studio を使ってうまくいった？ような記述を発見します。

[https://gist.github.com/xujiaao/63cb3bbea9fe22e79206e5eb7ba82d0e](https://gist.github.com/xujiaao/63cb3bbea9fe22e79206e5eb7ba82d0e)

早速試してみます。

まずは、android studio をインストールします（私は元々インストールしてあるのでスキップ）。

そして、適当な空のプロジェクトを作成し、androidをワイヤレスデバックでPCに接続します（USBデバックでも良いです）。やり方は、googleに聞けばたくさん出てくるので、省略します。

次に、SDK Platform-Toolsを下記よりダウンロードして、適当な場所に展開します。
[https://developer.android.com/studio/releases/platform-tools?hl=ja](https://developer.android.com/studio/releases/platform-tools?hl=ja)

android studioでpower shellを開き、展開したPlatform-Toolsの場所に移動します。

```
$ cd C:\Users\hoge\Downloads\platform-tools_r33.0.3-windows\platform-tools
```

次は、接続されているデバイスのシリアルナンバーを確認します。

```
$ .\adb.exe devices
List of devices attached
adb-xxxxxxxxxxxxxxxxxx._adb-tls-connect._tcp device
```

あとは、次のコマンドでandroidのシェルを開きます。

```
$ .\adb.exe -s adb-xxxxxxxxxxxxxxxxxx._adb-tls-connect._tcp shell
```

ここで、今の時刻同期に関する設定を確認してみます。

```
$ dumpsys time_detector
TimeDetectorStrategy:
  mLastAutoSystemClockTimeSet=null
  mEnvironment.isAutoTimeDetectionEnabled()=true
  mEnvironment.elapsedRealtimeMillis()=224743
  mEnvironment.systemClockMillis()=1662689471787
  mEnvironment.systemClockUpdateThresholdMillis()=2000
  mEnvironment.autoTimeLowerBound()=2022-05-25T22:13:51Z(1653516831000)
  mEnvironment.autoOriginPriorities()=[network,telephony]
  Time change log:
  Telephony suggestion history:
    {Empty}
  Network suggestion history:
    {Empty}
  Gnss suggestion history:
    {Empty}
  External suggestion history:
    {Empty}
```

[androidのドキュメント](https://source.android.com/devices/tech/connect/time-source?authuser=1&hl=ja)の解説曰く、`mEnvironment.autoOriginPriorities()=[network,telephony]`となっているのは、NTPがNITZよりも優先されているという意味のようでうです。それにもかかわらず、`Network suggestion history`は空になっており、まったく時刻同期できていないことが分かります。なお、SIMがないので、`Telephony suggestion history`は当然空になります。  
となると、気になるのは、今なんのNTPサーバーを参照しているのか、ということになります。調べてみると、

```
$ settings get global ntp_server
null
```

なんと！参照先のNTPサーバーがそもそも存在していません。驚きましたが、これでは何やっても時刻同期しないのは当たり前です。  
ここで、LAN内のNTPサーバーを指定してあげます。

```
$ settings put global ntp_server 192.168.8.1
$ settings get global ntp_server
192.168.8.1
```

これにて、あっさり時計が合いました。めでたし。

参考ページ  
* [https://source.android.com/devices/tech/connect/time-source?authuser=1&hl=ja](https://source.android.com/devices/tech/connect/time-source?authuser=1&hl=ja)
* [https://developer.android.com/studio/command-line/adb?hl=ja](https://developer.android.com/studio/command-line/adb?hl=ja)
* [https://gist.github.com/xujiaao/63cb3bbea9fe22e79206e5eb7ba82d0e](https://gist.github.com/xujiaao/63cb3bbea9fe22e79206e5eb7ba82d0e)
* [https://techacademy.jp/magazine/2989](https://techacademy.jp/magazine/2989)


# 余談

ところで、今回の問題解決に至る前に不思議な現象も確認できました。  
きちんとSIMをさしてあるスマホのデザリングにWifi接続すると、なぜか時刻同期に成功するのです。NTPサーバーは何も参照していないはずにもかかわらずです。謎です。