Azure VM 上で IP アドレス 168.63.129.16 がどのように検出されるのかを調べてみた

こんにちは。
この記事は、[Microsoft Azure Tech Advent Calendar 2024](https://qiita.com/advent-calendar/2024/microsoft-azure-tech) 12/12 の記事です。

Azure では、IP アドレス 168.63.129.16 は、主に VM Agent と Azure 基盤の通信、正常性プローブへの応答、DNS、DHCP として利用されます。
詳しくは公式ドキュメント [IP アドレス 168.63.129.16 とは](https://learn.microsoft.com/ja-jp/azure/virtual-network/what-is-ip-address-168-63-129-16) に記載されていますが、このドキュメント内で、以下の言及があります。

:::note warn
注意

非仮想ネットワーク シナリオ (クラシック) の場合、168.63.129.16 ではなくプライベート IP アドレスが使用されます。 このプライベート IP アドレスは、DHCP を介して動的に検出されます。 168.63.129.16 に固有のファイアウォール規則は、必要に応じて調整する必要があります。
:::

`このプライベート IP アドレスは、DHCP を介して動的に検出されます。`ということで、168.63.129.16 は、実は動的に検出されています。(既にクラシックは終了したでしょという突っ込みはあると思いますが、そういう仕組みがあるということは変わりないので)今回は、具体的に、どのように検出されているのか、Azure VM (Ubuntu 20.04.6 LTS) を用いて、見ていこうと思います。

# 1. VM Agent (WALinuxAgent) のログ解析
SSH ログイン後、/var/log/waagent.log を見ると、以下のような記録が見つかると思います。
```
2024-**-**T**:39:14.157412Z INFO Daemon WireServer endpoint is not found. Rerun dhcp handler
...
2024-**-**T**:39:14.183438Z INFO Daemon Wire server endpoint:168.63.129.16
```
`WireServer endpoint is not found. Rerun dhcp handler` というところで、DHCP リクエストを送っていることがわかります。ソースコードでいうと[このあたり](https://github.com/Azure/WALinuxAgent/blob/v2.2.46/azurelinuxagent/common/protocol/util.py#L224)です。
DHCP リクエストの後 168.63.129.16 が検出されていることから、ドキュメントの通り、DHCP を介して動的に検出されているようです。では、具体的にはどのようにこの IP アドレスが検出されるのでしょうか。今回は DHCP を解析してみようと思います。

# 2. DHCP パケットを dhcpdump で捉える
tcpdump でパケットを解析してもよいのですが、dhcpdump という便利なツールが利用できるので、今回はそれを使ってみようと思います。Ubuntu の場合 apt でインストールできます。
```bash
# apt install dhcpdump
```
インストールできたら、-i にプライマリのネットワーク インターフェースを指定し実行します。

```bash
# dhcpdump -i eth0
```

DHCP リクエストを流すために、もう一つのターミナルから、WALinuxAgent を再起動して DHCP リクエストを送ってもらいます。WireServerEndpoint ファイルや、DHCP のリース、固定ルートがあるとリクエストを送らないことがあるので、検証のため、それらを消した後に起動します。
```bash
# systemctl stop walinuxagent
# rm -f /var/lib/waagent/WireServerEndpoint
# rm -f /run/systemd/netif/leases/*
# ip route del 168.63.129.16
# systemctl start walinuxagent
```
WALinuxAgent が起動したタイミングで、ログが流れますので、これを解析します。
WALinuxAgent は、REQUEST/ACK をする必要がないので、DISCOVER -> OFFER だけになりますが、ネットワークをリセットした時などには、DORA (DISCOVER, OFFER, REQUEST, ACK) の4つが見えます。

```plantuml
"DHCP Client"->"DHCP Server" : DHCPDISCOVER
"DHCP Server"->"DHCP Client" : DHCPOFFER
"DHCP Client"->"DHCP Server" : DHCPREQUEST
"DHCP Server"->"DHCP Client" : DHCPACK
```

# 3. DHCP を解析する

### DHCPDISCOVER

```
  TIME: 2024-12-12 07:45:38.257
  IP: 10.0.0.4 (60:45:bd:80:70:68) > 255.255.255.255 (ff:ff:ff:ff:ff:ff)
  OP: 1 (BOOTPREQUEST)
 HTYPE: 1 (Ethernet)
 HLEN: 6
 HOPS: 0
   XID: 35aeca9a
   SECS: 0
 FLAGS: 0
CIADDR: 0.0.0.0
YIADDR: 0.0.0.0
SIADDR: 0.0.0.0
GIADDR: 0.0.0.0
CHADDR: 60:45:bd:80:70:68:00:00:00:00:00:00:00:00:00:00
 SNAME: .
 FNAME: .
OPTION:  53 (  1) DHCP message type         1 (DHCPDISCOVER)
---------------------------------------------------------------------------
```

まずは DISCOVER から見ていきます。これは VM からブロードキャストされたパケットです。
非常にシンプルなものなので、特に説明するところがありませんが、OPTION 53 のメッセージタイプが DHCPDISCOVER であることを確認できます。
ネットワークのリセットを行った場合には、OPTION 1 (Subnet mask) や OPTION 3 (Routers) をはじめ、多数の DHCP OPTION がついているパケットを観測できると思います。DHCP Options については、[RFC2132](https://www.rfc-editor.org/rfc/rfc2132.html) を参照すると詳しく書いてあります。

### DHCPOFFER
```
  TIME: 2024-12-12 07:45:38.257
    IP: 168.63.129.16 (12:34:56:78:9a:bc) > 255.255.255.255 (60:45:bd:80:70:68)
    OP: 2 (BOOTPREPLY)
 HTYPE: 1 (Ethernet)
  HLEN: 6
  HOPS: 0
   XID: 35aeca9a
  SECS: 0
 FLAGS: 7f80
CIADDR: 0.0.0.0
YIADDR: 10.0.0.4
SIADDR: 168.63.129.16
GIADDR: 168.63.129.16
CHADDR: 60:45:bd:80:70:68:00:00:00:00:00:00:00:00:00:00
 SNAME: LVL081071715051.
 FNAME: .
OPTION:   1 (  4) Subnet mask               255.255.255.0
OPTION:   3 (  4) Routers                   10.0.0.1
OPTION:   6 (  4) DNS server                168.63.129.16
OPTION:  15 ( 51) Domainname                c5siaacay0xehdh51eakfqc5bf.cx.internal.cloudapp.net
OPTION:  51 (  4) IP address leasetime      -1 ()
OPTION:  53 (  1) DHCP message type         2 (DHCPOFFER)
OPTION:  54 (  4) Server identifier         168.63.129.16
OPTION:  58 (  4) T1                        -1 ()
OPTION:  59 (  4) T2                        -1 ()
OPTION: 245 (  4) ???                       a83f8110         .?..
OPTION: 121 ( 23) Classless Static Route    000a00000120a83f ..... .?
                                            81100a00000120a9 ...... .
                                            fea9fe0a000001   .......
---------------------------------------------------------------------------
```
つぎに、DHCPOFFER です。
まず、168.63.129.16 からの OFFER であることがわかります。すなわち、DHCP Server は 168.63.129.16 であることが確認できました。
つぎに、Option 6 (DNS Server) を見ると、DNS として 168.63.129.16 が使用できることが確認できます。
じゃあ VM Agent が通信する IP アドレスはどれなのかというと、DHCP OPTION 245 で配布されている `a83f8110` です。16進数なので、（一部の特殊能力を持った人を除いて）普通の人間には意味が分かりませんが、8 bit ずつ 10 進数に変換すると、IP アドレスになります。ソースコードでは[このあたり](https://github.com/Azure/WALinuxAgent/blob/v2.2.46/azurelinuxagent/common/dhcp.py#L301)になります。
```
0xa8 -> 168
0x3f -> 63
0x81 -> 129
0x10 -> 16
```

# まとめ
VM Agent が通信する IP アドレスは、DHCP OPTION 245 で配布されていることがわかりました。



---

## DHCP OPTION 121

トピックとは直接関係ありませんが、DHCP OPTION 121 (Classless Static Route) が 16進数だったので、ついでにこれもデコードしてみようと思います。
DHCP OPTION 121 (Classless Static Route) は、DHCPサーバーがクライアントに静的ルートを提供できる機能です。このエンコードされたデータには、宛先ネットワークプレフィックスとそれに対応するゲートウェイが含まれています。
今回は、`000a00000120a83f81100a00000120a9fea9fe0a000001` です。

### デコード手順

DHCPオプション121のデータは、エンコードされたルートのシーケンスです。各ルートエントリは以下の要素で構成されています：

1. **プレフィックス長**（ビット単位、1バイト）
2. **ネットワークプレフィックス**（可変長、プレフィックス長に基づく）
3. **ゲートウェイアドレス**（4バイト）

これらを1つずつ処理します。

#### ルート1

- **プレフィックス長**: `00`  
  これは、ビット長が0のプレフィックス（デフォルトルート `0.0.0.0/0`）を表します。
- **ネットワークプレフィックス**:  
  プレフィックス長が0のため、ネットワークプレフィックスはありません。
- **ゲートウェイ**: `0a000001`  
  ドット10進表記に変換すると `10.0.0.1` になります。

**結果**: デフォルトルート `0.0.0.0/0` はゲートウェイ `10.0.0.1` を経由します。

#### ルート2

- **プレフィックス長**: `20`（16進数、10進数では32）  
  これは、32ビットのプレフィックス長を持つ完全なIPアドレスを表します。
- **ネットワークプレフィックス**: `a83f8110`  
  ドット10進表記に変換すると `168.63.129.16` になります。
- **ゲートウェイ**: `0a000001`  
  ドット10進表記に変換すると `10.0.0.1` になります。

**結果**: `168.63.129.16/32` へのルートは、ゲートウェイ `10.0.0.1` を経由します。

#### ルート3

- **プレフィックス長**: `20`（32ビット、完全なアドレス）  
  上記と同様、/32のルートです。
- **ネットワークプレフィックス**: `a9fea9fe`  
  ドット10進表記に変換すると `169.254.169.254` になります。
- **ゲートウェイ**: `0a000001`  
  ドット10進表記に変換すると `10.0.0.1` になります。

**結果**: `169.254.169.254/32` へのルートは、ゲートウェイ `10.0.0.1` を経由します。

#### デコード結果

デコードした結果、以下のルートが DHCP で配布されていることがわかりました。

1. **デフォルトルート**: `0.0.0.0/0` → `10.0.0.1`  
2. **ルート**: `168.63.129.16/32` → `10.0.0.1`  
3. **ルート**: `169.254.169.254/32` → `10.0.0.1`