Kea DHCPでAct-Sby(hot-standby)のHA構成を組む

# ゴール
Kea DHCPを用いてAct-Sbyのhot-standby High-Availability構成を組み，動作させることができること．

# About Kea DHCP
[Kea DHCP](https://www.isc.org/kea/)とは，ISC DHCPの後継として開発されているオープンソースのDHCPサーバプログラムである． MPL2.0ライセンスのもと[ISCのGitlabで公開](https://gitlab.isc.org/isc-projects/kea)されている．ISC DHCPとの違いは下記のように書かれている．

```
1. モジュラーコンポーネントデザインとHooksモジュールによる拡張
2. REST APIによるオンラインRe-configuration
3. 既存のシステムとのインテグレーション可能なデザイン
```

1つ目に述べられているhooksモジュールについては，ユーザ自身でこのhooksモジュールを書くことが可能であることを示している．これによりKea DHCPの独自のモジュールを実装し，より柔軟なサービス提供ができるように考慮されている．今回対象とするHA構成についても，このhooksモジュールとして実装されているHA機能を提供するライブラリをincludeして利用する形で実現する．

2つ目に述べられているREST APIによるconfigurationについては，このKea DHCPには`kea-ctrl-agent`が内包されており，このagentがREST APIの仲介者となり`kea-dhcp4`プロセス(DHCPv4)や`kea-dhcp6`プロセス(DHCPv6)に対して各種設定のgetやset等の様々な操作をすることが可能である．またKea DHCPはconfigを変更した際にrestartが極力必要とならないよう設計されている．多くのconfiguratinにおいては，configのreloadを行うことでconfigの適用を行うことができる．


最後に，既存システムとのインテグレーションについて述べられているが，これはどうもKea DHCPのconfigurationを，configファイルだけでなくDatabaseを用いることでも行うことができる点を指しているようである．これにより，実行環境とconfigurationを分離することができることがこの3つ目の要旨であるようだ．たしかに，実行環境とconfigの環境が異なり，さらにDBを用いて管理をすることができるのは一部のIT管理者にとっては有効かもしれない．

# Kea DHCPのHA構成とISC DHCPのFailover
かつてのISC-DHCPではHA機能は提供されておらず，Failover機能のみ提供されているものであった．しかしながらKea DHCPでは`release 1.4.0`よりHA構成が適用可能となった．リリースノートからも分かる通り，1.4.0は2018年6月にリリースされたものであり，まだ1年少々しか経過していない．そのためまだ機能が十分でない部分等も多くあると推察できる．1.4.0のリリース当初はこのHA機能はPremium featureとして提供されるようであったが，現在は通常のhooks libraryとして提供されている．


ISC-DHCPのFailoverとKea DHCPのHA構成については下記で述べられている．  
[Kea High Availability vs ISC DHCP Failover - Kea DHCP](https://kb.isc.org/docs/aa-01617)  
比較してみると，ISCのFailoverがDHCPv4のみに適用可能だったのに対し，Keaではv4とv6の両方に対して適用可能となっている．他には，ISCでは2つのサーバでの冗長構成のみしか組むことができなかったが，Keaでは2つのactiveサーバと無限のバックアップサーバを用いることが可能である．他にもリースDBのレプリケーション等がKeaでは実施可能であったり，ISC DHCPではOMAPIであったが，Keaではより一般的となっているREST APIによるconfigurationを採用するなど，多くの点でKea DHCPはISC DHCPをfollowする形となっているが，一部機能においてはISCよりデグレしている点が見受けられる．たとえばLoadBalancingだと，ISCではFlexible LBが可能であったが，Keaでは50/50のLBモードしか現状選択することができない．いずれもRFC3074に準拠しているようだ．また，Lazy lease updates(MCLT)の項目についてはISC DHCPでは可能であったがKeaではこれを許さず，リースアップデートが完全に完了してからでなければclientへresponseを送らないなど，より厳密となっている．このLazy lease updates(MCLT)については特にHA構成を組んだ場合のパフォーマンスに大きく影響することとなる．

# Kea DHCPでHA構成を組む
## Introduction
では早速Kea DHCPを用いたHA構成を組んでいく．
今回はKea DHCP verion 1.5.0を利用する．基本的にはマニュアルを熟読の上設定を行なっていくこととなる．v1.5.0では下記を参照することとなるだろう．  
[Kea Administrator Reference Manual](https://downloads.isc.org/isc/kea/1.5.0/doc/kea-guide.html)  
HA構成を組む上ではこのマニュアルのうち `15.4.8. ha: High Availability` をrefer するとよい．
また，HA構成において，HA構成を組んだホスト間のリースアップデート等は`kea-ctrl-agent`を介したREST API経由で実施されることとなる．そのため，Kea DHCPのREST API，kea-ctrl-agentについての理解も深めておくとより理解が深まると思う．

## Build and Configuration
今回はdockerを用いて検証環境を構成してHA構成を組むことを試みる．  
下記のdocker file とdocker-compose.ymlを用いてkea-dhcp2台をデプロイし，その間を`kea-mng`ネットワークで繋いでおく．`docker compose up`する前に，`docker network create kea-mng` をしておく必要がある．これはHA構成を組むにあたり`heartbeat`や`lease-update`をREST APIで実施するためのmanagement network相当のものである．L3疎通性さえあれば問題はない．

```dockerfile
FROM ubuntu:latest

ENV KEA_DHCP_VERSION=1.5.0
ENV LOG4_CPLUS_VERSION=1.2.1

RUN apt-get update && apt-get install -y iputils-ping net-tools traceroute nmap tcpdump iproute2 python3 vim  jq curl automake libtool pkg-config build-essential ccache libboost-dev libboost-system-dev liblog4cplus-dev libssl-dev && \
    curl -SL https://downloads.isc.org/isc/kea/${KEA_DHCP_VERSION}/kea-${KEA_DHCP_VERSION}.tar.gz | tar -xzC /root && \
    cd /root/kea-${KEA_DHCP_VERSION} && \
    ./configure --enable-shell && \
    make -s -j$(nproc) && \
    make install && \
    ldconfig
```

```
version: '3'
services:
  kea_1:
    image: kea:1.5.0
    container_name: kea_1
    privileged: true
    networks:
      - kea-mng
    tty: true

  kea_2:
    image: kea:1.5.0
    container_name: kea_2
    privileged: true
    networks:
      - kea-mng
    tty: true

networks:
    kea-mng:
        external: true
```


環境が構築できたら，実際にconfigを行っていく．
manualを見て分かるとおり，HA構成の実現にはhooks libraryを用いる．
今回はDHCPv4のデーモンに対してHA構成を行う例を示す．DHCPv6に対しても似たconfigで実現できると思われる．

早速HA構成を組むためのconfigを見ていこう．  
[マニュアルのHA構成のconfigサンプル](https://downloads.isc.org/isc/kea/1.5.0/doc/kea-guide.html#high-availability-library)としてこのような例がある．

```
{
  "Dhcp4": {

    ...

    "hooks-libraries": [
        {
            "library": "/usr/lib/hooks/libdhcp_lease_cmds.so",
            "parameters": { }
        },
        {
            "library": "/usr/lib/hooks/libdhcp_ha.so",
            "parameters": {
                "high-availability": [ {
                    "this-server-name": "server1",
                    "mode": "load-balancing",
                    "heartbeat-delay": 10000,
                    "max-response-delay": 10000,
                    "max-ack-delay": 5000,
                    "max-unacked-clients": 5,
                    "peers": [
                        {
                            "name": "server1",
                            "url": "http://192.168.56.33:8080/",
                            "role": "primary",
                            "auto-failover": true
                        },
                        {
                            "name": "server2",
                            "url": "http://192.168.56.66:8080/",
                            "role": "secondary",
                            "auto-failover": true
                        },
                        {
                            "name": "server3",
                            "url": "http://192.168.56.99:8080/",
                            "role": "backup",
                            "auto-failover": false
                        }
                    ]
                } ]
            }
        }
    ],

    ...

  }
}
```

`hooks-libraries`として`libdhcp_ha.so`を読み込む．デフォルトでは`/user/lib/hooks`配下に存在するが，keaのinstall pathによっては異なってくるので注意しよう．基本的にはkeaのroot配下の`./lib/hooks`配下にあると考えて良さそうだ．またHA構成を組む場合，`lease-update`を送受信してリース情報を交換する必要がある．これを行うために`libdhcp_lease_cmds.so`を読み込む必要がある．
読み込んでいないとlease情報がAPI経由でやり取りすることができない．たとえばこの`libdhcp_lease_cmd.so`を読み込まずにlease情報をgetしようとすると，下記のようになる．

```
root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "lease4-get-all", "service": [ "dhcp4" ] }' http://localhost:8080/ | jq .[]
{
  "result": 2,
  "text": "'lease4-get-all' command not supported."
}
```

さらに各種パラメータのsettingもここで実施する．パラメータの意味はざっくりと下記の通りである．

```
this-server-name: HA構成のうち自身を指すユニークな名前．
mode: HAのモード．
heartbeat-delay: パートナーへのハートビートの送信間隔．[ms]
max-response-delay: パートナーをdownと判定するまでの時間．[ms]
max-ack-delay: パートナーがクライアントからの通信に応答するべきmax時間．これを超えるとunackedと判定される．[ms]
max-unacked-clients: パートナーを障害と判定するunackedクライアント数．
peers: HAを構成するホストの指定
```

これらをマニュアルを見つつ適切に設定することとなる．
このうち，動作を大きく変えるのが `mode` である．このパラメータはHAをどのモードで動作させるかを決定する．
Kea DHCPのHA動作モードは`load-balancing`と`hot-standby`のうちから選ぶ．
想像できる通り，`load-balancing`モードでは50/50のact-actによる構成，`hot-standby`モードではact-sbyのホットスタンバイ構成となる．

今回はまず`hot-standby`モードとして下記の通り設定することとする．
primary に相当するのが`kea_1`でありIPアドレスは`172.18.0.3`，secondaryに相当するのが`kea_2`でありIPアドレスは`172.18.0.2`とした．

```
this-server-name: kea_1 //(2台目は"kea_2")
mode: hot-standby
heartbeat-delay: 10000
max-response-delay: 60000
max-ack-delay: 10000
max-unacked-clients: 0
"peers": [
    {
        "name": "kea_1",
        "url": "http://172.18.0.3:8080/",
        "role": "primary",
        "auto-failover": true
    },
    {
        "name": "kea_2",
        "url": "http://172.18.0.2:8080/",
        "role": "secondary",
        "auto-failover": true
    }
]
```

上記を踏まえてconfigを作成し，各サーバに適用して動作を確認してみよう．  
たとえば私の検証では下記のようなconfgを`kea_1`に投入した．`kea_2`もほぼ同様であるが，`"this-server-name"` 等を適切に変更する．

```
{
"Dhcp4": {
    "interfaces-config": {
        "interfaces": ["eth0"],
        "dhcp-socket-type": "raw",
        "outbound-interface": "use-routing"
    },
    "control-socket": {
        "socket-type": "unix",
        "socket-name": "/tmp/kea-dhcp4-ctrl.sock"
    },
    "lease-database": {
        "type": "memfile",
        "persist": true,
        "name": "/usr/local/etc/kea/kea-leases4.csv",
        "lfc-interval": 1800
    },
    "expired-leases-processing": {
        "reclaim-timer-wait-time": 10,
        "flush-reclaimed-timer-wait-time": 25,
        "hold-reclaimed-time": 3600,
        "max-reclaim-leases": 100,
        "max-reclaim-time": 250,
        "unwarned-reclaim-cycles": 5
    },

    "renew-timer": 1200,
    "rebind-timer": 2400,
    "valid-lifetime": 3600,

    "option-data": [
        {
            "name": "domain-name-servers",
            "data": "8.8.8.8, 8.8.4.4"
        },
        {
            "name": "default-ip-ttl",
            "data": "0xf0"
        }
    ],

    "hooks-libraries": [
        {
            "library": "/usr/local/lib/hooks/libdhcp_lease_cmds.so",
            "parameters": { }
        },
        {
            "library": "/usr/local/lib/hooks/libdhcp_ha.so",
            "parameters": {
                "high-availability": [ {
                    "this-server-name": "kea_1",
                    "mode": "hot-standby",
                    "heartbeat-delay": 10000,
                    "max-response-delay": 10000,
                    "max-ack-delay": 5000,
                    "max-unacked-clients": 5,
                    "peers": [
                      {
                        "name": "kea_1",
                        "url": "http://172.18.0.2:8080/",
                        "role": "primary",
                        "auto-failover": true
                      },
                      {
                        "name": "kea_2",
                        "url": "http://172.18.0.3:8080/",
                        "role": "standby",
                        "auto-failover": true
                      }
                    ]
                } ]
            }
        }
    ],

    "reservation-mode": "disabled",
    "host-reservation-identifiers": [ "hw-address" ],

    "subnet4": [
          {
                "id": 1,
                "subnet": "172.18.0.0/16",
                "pools": [
                    { "pool": "172.18.1.1 - 172.18.1.250" }
                ],
                "option-data": [
                    {
                        "name": "routers",
                        "data": "172.18.0.1"
                    }
                ]
          }
    ]
},

"Logging":
{
  "loggers": [
    {
        "name": "kea-dhcp4",
        "output_options": [
            {
                "output": "/usr/local/var/log/kea-dhcp4.log",
                "maxver": 8,
                "maxsize": 204800,
                "flush": true
            }
        ],
        "severity": "DEBUG",
        "debuglevel": 99
    }
  ]
}
}
```

まずは`kea_1`でkeaを起動する．今回，起動には`keactrl`コマンドを用いた．
起動後にRESTAPIで`ha-heartbeat`を送信し，それぞれのサーバのステータスを見てみよう．

```
# kea_1
root@0d5f73b7ffc7:/# keactrl start
INFO/keactrl: Starting /usr/local/sbin/kea-dhcp4 -c /usr/local/etc/kea/kea-dhcp4.conf
INFO/keactrl: Starting /usr/local/sbin/kea-ctrl-agent -c /usr/local/etc/kea/kea-ctrl-agent.conf
root@0d5f73b7ffc7:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.3:8080/ | jq
[
  {
    "arguments": {
      "date-time": "Fri, 25 Oct 2019 17:50:37 GMT",
      "state": "waiting"
    },
    "result": 0,
    "text": "HA peer status returned."
  }
]
root@0d5f73b7ffc7:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.2:8080/ | jq
curl: (7) Failed to connect to 172.18.0.2 port 8080: Connection refused
root@0d5f73b7ffc7:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.3:8080/ | jq
[
  {
    "arguments": {
      "date-time": "Fri, 25 Oct 2019 17:50:42 GMT",
      "state": "partner-down"
    },
    "result": 0,
    "text": "HA peer status returned."
  }
]
root@0d5f73b7ffc7:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.2:8080/ | jq
curl: (7) Failed to connect to 172.18.0.2 port 8080: Connection refused
```
起動後，`kea_1`はstateが `waiting`から，`partner-down`にシフトしていることが見受けられる．
`kea_2`に対しても同様のリクエストを行なっているが，`kea_2`ではまだkeaを起動していないため`Connection refused`が返っている．
`kea_1`を起動し，`kea_2`を起動していない場合，`kea_1`はこの状態に収束する．


ここで`kea_2`を起動する．

```
# kea_2
root@d864a28cbf84:/# keactrl start
INFO/keactrl: Starting /usr/local/sbin/kea-dhcp4 -c /usr/local/etc/kea/kea-dhcp4.conf
INFO/keactrl: Starting /usr/local/sbin/kea-ctrl-agent -c /usr/local/etc/kea/kea-ctrl-agent.conf
root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.3:8080/ | jq
[
  {
    "arguments": {
      "date-time": "Fri, 25 Oct 2019 17:51:37 GMT",
      "state": "partner-down"
    },
    "result": 0,
    "text": "HA peer status returned."
  }
]
root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.2:8080/ | jq
[
  {
    "arguments": {
      "date-time": "Fri, 25 Oct 2019 17:51:41 GMT",
      "state": "waiting"
    },
    "result": 0,
    "text": "HA peer status returned."
  }
]
root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.3:8080/ | jq
[
  {
    "arguments": {
      "date-time": "Fri, 25 Oct 2019 17:51:47 GMT",
      "state": "partner-down"
    },
    "result": 0,
    "text": "HA peer status returned."
  }
]
root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.2:8080/ | jq
[
  {
    "arguments": {
      "date-time": "Fri, 25 Oct 2019 17:51:49 GMT",
      "state": "ready"
    },
    "result": 0,
    "text": "HA peer status returned."
  }
]
root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.3:8080/ | jq
[
  {
    "arguments": {
      "date-time": "Fri, 25 Oct 2019 17:51:54 GMT",
      "state": "hot-standby"
    },
    "result": 0,
    "text": "HA peer status returned."
  }
]
root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.2:8080/ | jq
[
  {
    "arguments": {
      "date-time": "Fri, 25 Oct 2019 17:51:55 GMT",
      "state": "ready"
    },
    "result": 0,
    "text": "HA peer status returned."
  }
]
root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.3:8080/ | jq
[
  {
    "arguments": {
      "date-time": "Fri, 25 Oct 2019 17:51:57 GMT",
      "state": "hot-standby"
    },
    "result": 0,
    "text": "HA peer status returned."
  }
]
root@d864a28cbf84:/# curl -sS -X POST -H "Content-Type: application/json" -d '{ "command": "ha-heartbeat", "service": [ "dhcp4" ] }' http:///172.18.0.2:8080/ | jq
[
  {
    "arguments": {
      "date-time": "Fri, 25 Oct 2019 17:51:59 GMT",
      "state": "hot-standby"
    },
    "result": 0,
    "text": "HA peer status returned."
  }
]
```

ログを見てみる．

```
# kea_1
root@0d5f73b7ffc7:/# grep INFO /usr/local/var/log/kea-dhcp4.log | grep ha-hooks
2019-10-25 17:50:27.312 INFO  [kea-dhcp4.ha-hooks/1358] HA_CONFIGURATION_SUCCESSFUL HA hook library has been successfully configured
2019-10-25 17:50:27.312 INFO  [kea-dhcp4.ha-hooks/1358] HA_INIT_OK loading High Availability hooks library successful
2019-10-25 17:50:27.359 INFO  [kea-dhcp4.ha-hooks/1358] HA_LOCAL_DHCP_DISABLE local DHCP service is disabled while the kea_1 is in the WAITING state
2019-10-25 17:50:27.359 INFO  [kea-dhcp4.ha-hooks/1358] HA_SERVICE_STARTED started high availability service in hot-standby mode as primary server
2019-10-25 17:50:37.372 INFO  [kea-dhcp4.ha-hooks/1358] HA_STATE_TRANSITION server transitions from WAITING to PARTNER-DOWN state, partner state is UNDEFINED
2019-10-25 17:50:37.372 INFO  [kea-dhcp4.ha-hooks/1358] HA_LEASE_UPDATES_DISABLED lease updates will not be sent to the partner while in PARTNER-DOWN state
2019-10-25 17:50:37.372 INFO  [kea-dhcp4.ha-hooks/1358] HA_LOCAL_DHCP_ENABLE local DHCP service is enabled while the kea_1 is in the PARTNER-DOWN state
2019-10-25 17:51:52.821 INFO  [kea-dhcp4.ha-hooks/1358] HA_STATE_TRANSITION server transitions from PARTNER-DOWN to HOT-STANDBY state, partner state is READY
2019-10-25 17:51:52.821 INFO  [kea-dhcp4.ha-hooks/1358] HA_LEASE_UPDATES_ENABLED lease updates will be sent to the partner while in HOT-STANDBY state
2019-10-25 17:53:11.474 INFO  [kea-dhcp4.ha-hooks/1358] HA_DEINIT_OK unloading High Availability hooks library successful
root@0d5f73b7ffc7:/#
```

```
# kea_2
root@d864a28cbf84:/# grep INFO /usr/local/var/log/kea-dhcp4.log | grep ha-hooks
2019-10-25 17:51:33.880 INFO  [kea-dhcp4.ha-hooks/901] HA_CONFIGURATION_SUCCESSFUL HA hook library has been successfully configured
2019-10-25 17:51:33.880 INFO  [kea-dhcp4.ha-hooks/901] HA_INIT_OK loading High Availability hooks library successful
2019-10-25 17:51:33.928 INFO  [kea-dhcp4.ha-hooks/901] HA_LOCAL_DHCP_DISABLE local DHCP service is disabled while the kea_2 is in the WAITING state
2019-10-25 17:51:33.928 INFO  [kea-dhcp4.ha-hooks/901] HA_SERVICE_STARTED started high availability service in hot-standby mode as standby server
2019-10-25 17:51:45.738 INFO  [kea-dhcp4.ha-hooks/901] HA_STATE_TRANSITION server transitions from WAITING to SYNCING state, partner state is PARTNER-DOWN
2019-10-25 17:51:45.738 INFO  [kea-dhcp4.ha-hooks/901] HA_LEASE_UPDATES_DISABLED lease updates will not be sent to the partner while in SYNCING state
2019-10-25 17:51:45.738 INFO  [kea-dhcp4.ha-hooks/901] HA_SYNC_START starting lease database synchronization with kea_1
2019-10-25 17:51:45.743 INFO  [kea-dhcp4.ha-hooks/901] HA_LEASES_SYNC_LEASE_PAGE_RECEIVED received 0 leases from kea_1
2019-10-25 17:51:45.745 INFO  [kea-dhcp4.ha-hooks/901] HA_SYNC_SUCCESSFUL lease database synchronization with kea_1 completed successfully in 6.724 ms
2019-10-25 17:51:45.745 INFO  [kea-dhcp4.ha-hooks/901] HA_STATE_TRANSITION server transitions from SYNCING to READY state, partner state is PARTNER-DOWN
2019-10-25 17:51:45.745 INFO  [kea-dhcp4.ha-hooks/901] HA_LEASE_UPDATES_DISABLED lease updates will not be sent to the partner while in READY state
2019-10-25 17:51:56.910 INFO  [kea-dhcp4.ha-hooks/901] HA_STATE_TRANSITION server transitions from READY to HOT-STANDBY state, partner state is HOT-STANDBY
2019-10-25 17:51:56.911 INFO  [kea-dhcp4.ha-hooks/901] HA_LEASE_UPDATES_ENABLED lease updates will be sent to the partner while in HOT-STANDBY state
2019-10-25 17:51:56.911 INFO  [kea-dhcp4.ha-hooks/901] HA_LOCAL_DHCP_ENABLE local DHCP service is enabled while the kea_2 is in the HOT-STANDBY state
2019-10-25 17:53:12.373 INFO  [kea-dhcp4.ha-hooks/901] HA_DEINIT_OK unloading High Availability hooks library successful
root@d864a28cbf84:/#
```

これらをみてみると，起動直後，これらのHAは下記のような順序で状態遷移していることがわかる．

```
2019-10-25 17:50:27.359:: kea_1: waiting     , kea_2: UNDEFINED
2019-10-25 17:50:37.372:: kea_1: partner-down, kea_2: UNDEFINED
2019-10-25 17:51:33.928:: kea_1: partner-down, kea_2: waiting
2019-10-25 17:51:45.738:: kea_1: partner-down, kea_2: syncing
2019-10-25 17:51:45.745:: kea_1: partner-down, kea_2: ready
2019-10-25 17:51:52.821:: kea_1: hot-standby,  kea_2: ready
2019-10-25 17:51:56.910:: kea_1: hot-standby , kea_2: hot-standby
```
このそれぞれのstateの意味はkeaのdocumentを参照し，簡単に理解をまとめると下記の通りである．ほぼgoogle翻訳．

```
- Backup: backup-serverの平常状態で，この状態であればアクティブなサーバーからlease-updateを受信する．

- hot-standby: hot-standbyモードのアクティブサーバーの平常状態．hot-standbyモードにおけるプライマリサーバーとスタンバイサーバーは，通常この状態をとる．プライマリサーバーはDHCPクエリに応答し，スタンバイサーバーが存在する場合，スタンバイサーバーとバックアップサーバーにlease-updateを送信する

- load-balancing: load-balancingモードのアクティブなサーバーの平常状態．loadbalancingモードにおけるプライマリサーバーとセカンダリサーバーは，通常のこの状態をとる．両方のサーバーがDHCPクエリに応答し，リース更新を相互に送信し，バックアップサーバーが存在する場合にはバックアップサーバーに送信する．

- partner-down: partnerがオフラインであることを検出すると，アクティブサーバーはこの状態に移行する．バックアップサーバが利用できなくともサーバーはこの状態に移行しない．パートナーダウン状態では，サーバーはすべてのDHCPクエリに応答し，'通常時に現在使用できないpartnerによって処理されるべきクエリ'にも応答する．

- ready: lease-databaseをアクティブなパートナーと同期した後、アクティブなサーバーはこの状態となる．この状態は、パートナーに通知される可能性がある（partner-down状態にある可能性が高いため、通常の操作に戻る可能性が高い．その場合，ready 状態にあるサーバーも続けて通常の動作を開始する．)

- syncing: アクティブなサーバーはこの状態に移行して，アクティブなパートナーからリースを取得し，ローカルリースデータベースを更新します．この状態になると，dhcp-disableを発行して，リースの取得元のパートナーのDHCPサービスを無効にします． DHCPサービスは最大60秒間無効になります．その後，同期パートナーが再び停止し，サービスを再度有効にできない場合に備えて，自動的に有効になります．同期が完了すると，同期サーバーはdhcp-enableを発行して，パートナーのDHCPサービスを再度有効にします．同期操作は同期です．サーバーは，パートナーからの応答を待っており，リースの同期が行われている間は何もしていません．データベースをパートナーと同期しないように構成されているサーバー，つまりsync-leases構成パラメーターがfalseに設定されている場合，この状態に移行することはありません．代わりに，待機状態から準備完了状態に直接移行します．

- terminated: ha-hooksライブラリがHA構成を組めず，問題を解決するために手動オペレーションが必要な場合にアクティブサーバーはこの状態となる．HAセットアップの様々なの問題により，サーバーがこの状態になる可能性がある．． Kea 1.4.0リリースの時点でのHAを終了させる唯一の問題は，各サーバーのクロックが60秒以上離れている場合である．terminated 状態のサーバーは選択されたHAモード（ロードバランシングまたはホットスタンバイ）に基づいてDHCPクライアントに応答し続けますが，lease-updateは交換されず，ha-heartbeatは送信されない．terminated状態になると，サーバーは再起動されるまでこの状態となる．オペレータはサーバーを再起動する前に，根本原因を解決する必要がある．

- wainting: 起動された各サーバーはこの状態となる．バックアップサーバーは，この状態からバックアップ状態に直接移行する．アクティブなサーバーは，その状態を確認するためにpartnerにha-heartbeatを送信する．pertnerがオフラインであると考えられる場合，サーバーはpartner-down状態に移行する．そうでない場合，sync-leases構成パラメーターの設定に応じてsyncing状態またはready状態に移行する．両方のサーバーがwaiting状態（同時起動）にあると思われる場合，プライマリサーバーが先に状態遷移する．プライマリサーバーがready状態に移行するまで，セカンダリサーバーまたはスタンバイサーバーはwaiting状態のままとなる．
```

## hot-standbyモードでのDHCPリース動作
HA構成を組むことができていることが確認できたので，この状態でDHCPのアドレスリースを行い，HA構成に受けるアドレスリース時の振る舞いを確認していこうと思う．

DHCPリースを模擬するために，今回は[dhtest](https://github.com/saravana815/dhtest)を利用した．

構築してhot-standby HA構成のサーバ群が接続されているNW上に存在するdhtest用VMで，下記の通りコマンドを実行する．

```
./dhtest -i <interface> -m <mac_addr>
```

`mac_addr` はdhtestホストから送出するDHCPパケットの`chaddr` フィールドに入るMACアドレスと思えばよい．
これを数回繰り返し実施した時の振る舞いを見てみる．
dhtestホストでは下記のようにアドレスリースが模擬できているようである．

```
[root@a50202e240fb dhtest-master]# ./dhtest -i eth0 -m 00:00:00:11:22:33
DHCP discover sent   - Client MAC : 00:00:00:11:22:33
DHCP offer received  - Offered IP : 172.18.1.1
DHCP request sent  - Client MAC : 00:00:00:11:22:33
^[[ADHCP ack received  - Acquired IP: 172.18.1.1
[root@a50202e240fb dhtest-master]# ./dhtest -i eth0 -m 00:00:00:11:22:34
DHCP discover sent   - Client MAC : 00:00:00:11:22:34
DHCP offer received  - Offered IP : 172.18.1.2
DHCP request sent  - Client MAC : 00:00:00:11:22:34
DHCP ack received  - Acquired IP: 172.18.1.2
[root@a50202e240fb dhtest-master]# ./dhtest -i eth0 -m 00:00:00:11:22:35
DHCP discover sent   - Client MAC : 00:00:00:11:22:35
DHCP offer received  - Offered IP : 172.18.1.3
DHCP request sent  - Client MAC : 00:00:00:11:22:35
DHCP ack received  - Acquired IP: 172.18.1.3
[root@a50202e240fb dhtest-master]#
```

この時，`kea_1` と`kea_2` ホストでパケットキャプチャを行うと．

```
## kea_1
root@0d5f73b7ffc7:~# tcpdump -i eth0 port 67 or  port 68 -v -n
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
^[[O06:44:35.308246 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 279)
    0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:33, length 251, xid 0x6a521a43, Flags [none]
          Client-Ethernet-Address 00:00:00:11:22:33
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message Option 53, length 1: Discover
            Parameter-Request Option 55, length 5:
              Subnet-Mask, BR, Default-Gateway, Domain-Name
              Domain-Name-Server
06:44:35.311244 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 318)
    172.18.0.3.67 > 172.18.1.1.68: BOOTP/DHCP, Reply, length 290, xid 0x6a521a43, Flags [none]
          Your-IP 172.18.1.1
          Client-Ethernet-Address 00:00:00:11:22:33
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            Subnet-Mask Option 1, length 4: 255.255.0.0
            Default-Gateway Option 3, length 4: 172.18.0.1
            Domain-Name-Server Option 6, length 8: 8.8.8.8,8.8.4.4
            Lease-Time Option 51, length 4: 3600
            DHCP-Message Option 53, length 1: Offer
            Server-ID Option 54, length 4: 172.18.0.3
            RN Option 58, length 4: 1200
            RB Option 59, length 4: 2400
06:44:35.311902 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 291)
    0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:33, length 263, xid 0x6a521a43, Flags [none]
          Client-Ethernet-Address 00:00:00:11:22:33
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message Option 53, length 1: Request
            Requested-IP Option 50, length 4: 172.18.1.1
            Server-ID Option 54, length 4: 172.18.0.3
            Parameter-Request Option 55, length 5:
              Subnet-Mask, BR, Default-Gateway, Domain-Name
              Domain-Name-Server
06:44:36.316180 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 318)
    172.18.0.3.67 > 172.18.1.1.68: BOOTP/DHCP, Reply, length 290, xid 0x6a521a43, Flags [none]
          Your-IP 172.18.1.1
          Client-Ethernet-Address 00:00:00:11:22:33
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            Subnet-Mask Option 1, length 4: 255.255.0.0
            Default-Gateway Option 3, length 4: 172.18.0.1
            Domain-Name-Server Option 6, length 8: 8.8.8.8,8.8.4.4
            Lease-Time Option 51, length 4: 3600
            DHCP-Message Option 53, length 1: ACK
            Server-ID Option 54, length 4: 172.18.0.3
            RN Option 58, length 4: 1200
            RB Option 59, length 4: 2400
06:44:38.007073 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 279)
    0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:34, length 251, xid 0x23552c97, Flags [none]
          Client-Ethernet-Address 00:00:00:11:22:34
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message Option 53, length 1: Discover
            Parameter-Request Option 55, length 5:
              Subnet-Mask, BR, Default-Gateway, Domain-Name
              Domain-Name-Server
06:44:38.009206 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 318)
    172.18.0.3.67 > 172.18.1.2.68: BOOTP/DHCP, Reply, length 290, xid 0x23552c97, Flags [none]
          Your-IP 172.18.1.2
          Client-Ethernet-Address 00:00:00:11:22:34
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            Subnet-Mask Option 1, length 4: 255.255.0.0
            Default-Gateway Option 3, length 4: 172.18.0.1
            Domain-Name-Server Option 6, length 8: 8.8.8.8,8.8.4.4
            Lease-Time Option 51, length 4: 3600
            DHCP-Message Option 53, length 1: Offer
            Server-ID Option 54, length 4: 172.18.0.3
            RN Option 58, length 4: 1200
            RB Option 59, length 4: 2400
06:44:38.009418 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 291)
    0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:34, length 263, xid 0x23552c97, Flags [none]
          Client-Ethernet-Address 00:00:00:11:22:34
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message Option 53, length 1: Request
            Requested-IP Option 50, length 4: 172.18.1.2
            Server-ID Option 54, length 4: 172.18.0.3
            Parameter-Request Option 55, length 5:
              Subnet-Mask, BR, Default-Gateway, Domain-Name
              Domain-Name-Server
06:44:39.014721 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 318)
    172.18.0.3.67 > 172.18.1.2.68: BOOTP/DHCP, Reply, length 290, xid 0x23552c97, Flags [none]
          Your-IP 172.18.1.2
          Client-Ethernet-Address 00:00:00:11:22:34
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            Subnet-Mask Option 1, length 4: 255.255.0.0
            Default-Gateway Option 3, length 4: 172.18.0.1
            Domain-Name-Server Option 6, length 8: 8.8.8.8,8.8.4.4
            Lease-Time Option 51, length 4: 3600
            DHCP-Message Option 53, length 1: ACK
            Server-ID Option 54, length 4: 172.18.0.3
            RN Option 58, length 4: 1200
            RB Option 59, length 4: 2400
06:44:40.623865 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 279)
    0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:35, length 251, xid 0x4a57a2d2, Flags [none]
          Client-Ethernet-Address 00:00:00:11:22:35
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message Option 53, length 1: Discover
            Parameter-Request Option 55, length 5:
              Subnet-Mask, BR, Default-Gateway, Domain-Name
              Domain-Name-Server
06:44:40.625059 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 318)
    172.18.0.3.67 > 172.18.1.3.68: BOOTP/DHCP, Reply, length 290, xid 0x4a57a2d2, Flags [none]
          Your-IP 172.18.1.3
          Client-Ethernet-Address 00:00:00:11:22:35
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            Subnet-Mask Option 1, length 4: 255.255.0.0
            Default-Gateway Option 3, length 4: 172.18.0.1
            Domain-Name-Server Option 6, length 8: 8.8.8.8,8.8.4.4
            Lease-Time Option 51, length 4: 3600
            DHCP-Message Option 53, length 1: Offer
            Server-ID Option 54, length 4: 172.18.0.3
            RN Option 58, length 4: 1200
            RB Option 59, length 4: 2400
06:44:40.625322 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 291)
    0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:35, length 263, xid 0x4a57a2d2, Flags [none]
          Client-Ethernet-Address 00:00:00:11:22:35
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message Option 53, length 1: Request
            Requested-IP Option 50, length 4: 172.18.1.3
            Server-ID Option 54, length 4: 172.18.0.3
            Parameter-Request Option 55, length 5:
              Subnet-Mask, BR, Default-Gateway, Domain-Name
              Domain-Name-Server
06:44:42.630578 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 318)
    172.18.0.3.67 > 172.18.1.3.68: BOOTP/DHCP, Reply, length 290, xid 0x4a57a2d2, Flags [none]
          Your-IP 172.18.1.3
          Client-Ethernet-Address 00:00:00:11:22:35
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            Subnet-Mask Option 1, length 4: 255.255.0.0
            Default-Gateway Option 3, length 4: 172.18.0.1
            Domain-Name-Server Option 6, length 8: 8.8.8.8,8.8.4.4
            Lease-Time Option 51, length 4: 3600
            DHCP-Message Option 53, length 1: ACK
            Server-ID Option 54, length 4: 172.18.0.3
            RN Option 58, length 4: 1200
            RB Option 59, length 4: 2400
```

```
## kea_2
root@d864a28cbf84:~# tcpdump -i eth0 port 67 or port 68 -v -n
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
06:44:35.308297 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 279)
    0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:33, length 251, xid 0x6a521a43, Flags [none]
    Client-Ethernet-Address 00:00:00:11:22:33
    Vendor-rfc1048 Extensions
      Magic Cookie 0x63825363
      DHCP-Message Option 53, length 1: Discover
      Parameter-Request Option 55, length 5:
        Subnet-Mask, BR, Default-Gateway, Domain-Name
        Domain-Name-Server
06:44:35.311922 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 291)
    0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:33, length 263, xid 0x6a521a43, Flags [none]
    Client-Ethernet-Address 00:00:00:11:22:33
    Vendor-rfc1048 Extensions
      Magic Cookie 0x63825363
      DHCP-Message Option 53, length 1: Request
      Requested-IP Option 50, length 4: 172.18.1.1
      Server-ID Option 54, length 4: 172.18.0.3
      Parameter-Request Option 55, length 5:
        Subnet-Mask, BR, Default-Gateway, Domain-Name
        Domain-Name-Server
06:44:38.007102 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 279)
    0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:34, length 251, xid 0x23552c97, Flags [none]
    Client-Ethernet-Address 00:00:00:11:22:34
    Vendor-rfc1048 Extensions
      Magic Cookie 0x63825363
      DHCP-Message Option 53, length 1: Discover
      Parameter-Request Option 55, length 5:
        Subnet-Mask, BR, Default-Gateway, Domain-Name
        Domain-Name-Server
06:44:38.009439 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 291)
    0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:34, length 263, xid 0x23552c97, Flags [none]
    Client-Ethernet-Address 00:00:00:11:22:34
    Vendor-rfc1048 Extensions
      Magic Cookie 0x63825363
      DHCP-Message Option 53, length 1: Request
      Requested-IP Option 50, length 4: 172.18.1.2
      Server-ID Option 54, length 4: 172.18.0.3
      Parameter-Request Option 55, length 5:
        Subnet-Mask, BR, Default-Gateway, Domain-Name
        Domain-Name-Server
06:44:40.623903 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 279)
    0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:35, length 251, xid 0x4a57a2d2, Flags [none]
    Client-Ethernet-Address 00:00:00:11:22:35
    Vendor-rfc1048 Extensions
      Magic Cookie 0x63825363
      DHCP-Message Option 53, length 1: Discover
      Parameter-Request Option 55, length 5:
        Subnet-Mask, BR, Default-Gateway, Domain-Name
        Domain-Name-Server
06:44:40.625346 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 291)
    0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 00:00:00:11:22:35, length 263, xid 0x4a57a2d2, Flags [none]
    Client-Ethernet-Address 00:00:00:11:22:35
    Vendor-rfc1048 Extensions
      Magic Cookie 0x63825363
      DHCP-Message Option 53, length 1: Request
      Requested-IP Option 50, length 4: 172.18.1.3
      Server-ID Option 54, length 4: 172.18.0.3
      Parameter-Request Option 55, length 5:
        Subnet-Mask, BR, Default-Gateway, Domain-Name
        Domain-Name-Server
```
これら2つのパケットキャプチャをみて理解できるとおり，
`kea_1`はクライアントからの3つ全てのDHCP要求(`DISCOVERY`, `REQUEST`)において，クライアントに対して`OFFER`, `ACK` を返しており，`kea_2`はクライアントからの要求には全く応答パケットを送出していないことがわかる．
これはHAモードのうち，primaryの1台のみがDHCPに応答する`hot-standby`の動作となっていることがわかる．

またこの時の`lease-update`を見てみよう．

```
## kea_1

root@0d5f73b7ffc7:/# tcpdump -i eth0 port 8080 -v -n
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
06:44:35.313735 IP (tos 0x0, ttl 64, id 57517, offset 0, flags [DF], proto TCP (6), length 403)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x59af (incorrect -> 0x066f), seq 220565762:220566113, ack 2766104306, win 254, options [nop,nop,TS val 26311210 ecr 26310630], length 351: HTTP, length: 351
  POST / HTTP/1.1
  Content-Length: 279
  Content-Type: application/json

  { "arguments": { "expire": 1572335075, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:33", "ip-address": "172.18.1.1", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }[!http]
06:44:35.315597 IP (tos 0x0, ttl 64, id 2855, offset 0, flags [DF], proto TCP (6), length 208)
    172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x58ec (incorrect -> 0x0bf1), seq 1:157, ack 351, win 235, options [nop,nop,TS val 26311210 ecr 26311210], length 156: HTTP, length: 156
  HTTP/1.1 200 OK
  Content-Length: 48
  Content-Type: application/json
  Date: Tue, 29 Oct 2019 06:44:35 GMT

  [ { "result": 0, "text": "IPv4 lease added." } ][!http]
06:44:35.315738 IP (tos 0x0, ttl 64, id 57518, offset 0, flags [DF], proto TCP (6), length 52)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xd5cc), ack 157, win 262, options [nop,nop,TS val 26311211 ecr 26311210], length 0
06:44:38.012408 IP (tos 0x0, ttl 64, id 57519, offset 0, flags [DF], proto TCP (6), length 403)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x59af (incorrect -> 0xfd19), seq 351:702, ack 157, win 262, options [nop,nop,TS val 26311480 ecr 26311210], length 351: HTTP, length: 351
  POST / HTTP/1.1
  Content-Length: 279
  Content-Type: application/json

  { "arguments": { "expire": 1572335077, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:34", "ip-address": "172.18.1.2", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }[!http]
06:44:38.015389 IP (tos 0x0, ttl 64, id 2856, offset 0, flags [DF], proto TCP (6), length 208)
    172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x58ec (incorrect -> 0x07cf), seq 157:313, ack 702, win 243, options [nop,nop,TS val 26311480 ecr 26311480], length 156: HTTP, length: 156
  HTTP/1.1 200 OK
  Content-Length: 48
  Content-Type: application/json
  Date: Tue, 29 Oct 2019 06:44:38 GMT

  [ { "result": 0, "text": "IPv4 lease added." } ][!http]
06:44:38.015425 IP (tos 0x0, ttl 64, id 57520, offset 0, flags [DF], proto TCP (6), length 52)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xd1ae), ack 313, win 270, options [nop,nop,TS val 26311480 ecr 26311480], length 0
06:44:40.625311 IP (tos 0x0, ttl 64, id 57521, offset 0, flags [DF], proto TCP (6), length 176)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x58cc (incorrect -> 0x473e), seq 702:826, ack 313, win 270, options [nop,nop,TS val 26311745 ecr 26311480], length 124: HTTP, length: 124
  POST / HTTP/1.1
  Content-Length: 53
  Content-Type: application/json

  { "command": "ha-heartbeat", "service": [ "dhcp4" ] }[!http]
06:44:40.632575 IP (tos 0x0, ttl 64, id 2857, offset 0, flags [DF], proto TCP (6), length 303)
    172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x594b (incorrect -> 0x866a), seq 313:564, ack 826, win 243, options [nop,nop,TS val 26311746 ecr 26311745], length 251: HTTP, length: 251
  HTTP/1.1 200 OK
  Content-Length: 142
  Content-Type: application/json
  Date: Tue, 29 Oct 2019 06:44:40 GMT

  [ { "arguments": { "date-time": "Tue, 29 Oct 2019 06:44:40 GMT", "state": "hot-standby" }, "result": 0, "text": "HA peer status returned." } ][!http]
06:44:40.632606 IP (tos 0x0, ttl 64, id 57522, offset 0, flags [DF], proto TCP (6), length 52)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xce1a), ack 564, win 279, options [nop,nop,TS val 26311746 ecr 26311746], length 0
06:44:41.628464 IP (tos 0x0, ttl 64, id 57523, offset 0, flags [DF], proto TCP (6), length 403)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x59af (incorrect -> 0xfb10), seq 826:1177, ack 564, win 279, options [nop,nop,TS val 26311845 ecr 26311746], length 351: HTTP, length: 351
  POST / HTTP/1.1
  Content-Length: 279
  Content-Type: application/json

  { "arguments": { "expire": 1572335080, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:35", "ip-address": "172.18.1.3", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }[!http]
06:44:41.633534 IP (tos 0x0, ttl 64, id 2858, offset 0, flags [DF], proto TCP (6), length 208)
    172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x58ec (incorrect -> 0x0080), seq 564:720, ack 1177, win 252, options [nop,nop,TS val 26311846 ecr 26311845], length 156: HTTP, length: 156
  HTTP/1.1 200 OK
  Content-Length: 48
  Content-Type: application/json
  Date: Tue, 29 Oct 2019 06:44:41 GMT

  [ { "result": 0, "text": "IPv4 lease added." } ][!http]
06:44:41.633621 IP (tos 0x0, ttl 64, id 57524, offset 0, flags [DF], proto TCP (6), length 52)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xcb4f), ack 720, win 287, options [nop,nop,TS val 26311846 ecr 26311846], length 0
06:44:42.637356 IP (tos 0x0, ttl 64, id 43254, offset 0, flags [DF], proto TCP (6), length 176)
    172.18.0.2.42824 > 172.18.0.3.8080: Flags [P.], cksum 0x58cc (incorrect -> 0x7e2b), seq 1837942929:1837943053, ack 2537261301, win 254, options [nop,nop,TS val 26311946 ecr 26310830], length 124: HTTP, length: 124
  POST / HTTP/1.1
  Content-Length: 53
  Content-Type: application/json

  { "command": "ha-heartbeat", "service": [ "dhcp4" ] }[!http]
06:44:42.640120 IP (tos 0x0, ttl 64, id 59900, offset 0, flags [DF], proto TCP (6), length 303)
    172.18.0.3.8080 > 172.18.0.2.42824: Flags [P.], cksum 0x594b (incorrect -> 0xb605), seq 1:252, ack 124, win 227, options [nop,nop,TS val 26311946 ecr 26311946], length 251: HTTP, length: 251
  HTTP/1.1 200 OK
  Content-Length: 142
  Content-Type: application/json
  Date: Tue, 29 Oct 2019 06:44:42 GMT

  [ { "arguments": { "date-time": "Tue, 29 Oct 2019 06:44:42 GMT", "state": "hot-standby" }, "result": 0, "text": "HA peer status returned." } ][!http]
06:44:42.640722 IP (tos 0x0, ttl 64, id 43255, offset 0, flags [DF], proto TCP (6), length 52)
    172.18.0.2.42824 > 172.18.0.3.8080: Flags [.], cksum 0x5850 (incorrect -> 0x01b8), ack 252, win 262, options [nop,nop,TS val 26311946 ecr 26311946], length 0
```

```
## kea_2

root@d864a28cbf84:/# tcpdump -i eth0 port 8080 -v -n
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
06:44:35.313767 IP (tos 0x0, ttl 64, id 57517, offset 0, flags [DF], proto TCP (6), length 403)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x59af (incorrect -> 0x066f), seq 220565762:220566113, ack 2766104306, win 254, options [nop,nop,TS val 26311210 ecr 26310630], length 351: HTTP, length: 351
  POST / HTTP/1.1
  Content-Length: 279
  Content-Type: application/json

  { "arguments": { "expire": 1572335075, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:33", "ip-address": "172.18.1.1", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }[!http]
06:44:35.315569 IP (tos 0x0, ttl 64, id 2855, offset 0, flags [DF], proto TCP (6), length 208)
    172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x58ec (incorrect -> 0x0bf1), seq 1:157, ack 351, win 235, options [nop,nop,TS val 26311210 ecr 26311210], length 156: HTTP, length: 156
  HTTP/1.1 200 OK
  Content-Length: 48
  Content-Type: application/json
  Date: Tue, 29 Oct 2019 06:44:35 GMT

  [ { "result": 0, "text": "IPv4 lease added." } ][!http]
06:44:35.315755 IP (tos 0x0, ttl 64, id 57518, offset 0, flags [DF], proto TCP (6), length 52)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xd5cc), ack 157, win 262, options [nop,nop,TS val 26311211 ecr 26311210], length 0
06:44:38.012495 IP (tos 0x0, ttl 64, id 57519, offset 0, flags [DF], proto TCP (6), length 403)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x59af (incorrect -> 0xfd19), seq 351:702, ack 157, win 262, options [nop,nop,TS val 26311480 ecr 26311210], length 351: HTTP, length: 351
  POST / HTTP/1.1
  Content-Length: 279
  Content-Type: application/json

  { "arguments": { "expire": 1572335077, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:34", "ip-address": "172.18.1.2", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }[!http]
06:44:38.015323 IP (tos 0x0, ttl 64, id 2856, offset 0, flags [DF], proto TCP (6), length 208)
    172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x58ec (incorrect -> 0x07cf), seq 157:313, ack 702, win 243, options [nop,nop,TS val 26311480 ecr 26311480], length 156: HTTP, length: 156
  HTTP/1.1 200 OK
  Content-Length: 48
  Content-Type: application/json
  Date: Tue, 29 Oct 2019 06:44:38 GMT

  [ { "result": 0, "text": "IPv4 lease added." } ][!http]
06:44:38.015447 IP (tos 0x0, ttl 64, id 57520, offset 0, flags [DF], proto TCP (6), length 52)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xd1ae), ack 313, win 270, options [nop,nop,TS val 26311480 ecr 26311480], length 0
06:44:40.625346 IP (tos 0x0, ttl 64, id 57521, offset 0, flags [DF], proto TCP (6), length 176)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x58cc (incorrect -> 0x473e), seq 702:826, ack 313, win 270, options [nop,nop,TS val 26311745 ecr 26311480], length 124: HTTP, length: 124
  POST / HTTP/1.1
  Content-Length: 53
  Content-Type: application/json

  { "command": "ha-heartbeat", "service": [ "dhcp4" ] }[!http]
06:44:40.632539 IP (tos 0x0, ttl 64, id 2857, offset 0, flags [DF], proto TCP (6), length 303)
    172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x594b (incorrect -> 0x866a), seq 313:564, ack 826, win 243, options [nop,nop,TS val 26311746 ecr 26311745], length 251: HTTP, length: 251
  HTTP/1.1 200 OK
  Content-Length: 142
  Content-Type: application/json
  Date: Tue, 29 Oct 2019 06:44:40 GMT

  [ { "arguments": { "date-time": "Tue, 29 Oct 2019 06:44:40 GMT", "state": "hot-standby" }, "result": 0, "text": "HA peer status returned." } ][!http]
06:44:40.632623 IP (tos 0x0, ttl 64, id 57522, offset 0, flags [DF], proto TCP (6), length 52)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xce1a), ack 564, win 279, options [nop,nop,TS val 26311746 ecr 26311746], length 0
06:44:41.628608 IP (tos 0x0, ttl 64, id 57523, offset 0, flags [DF], proto TCP (6), length 403)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [P.], cksum 0x59af (incorrect -> 0xfb10), seq 826:1177, ack 564, win 279, options [nop,nop,TS val 26311845 ecr 26311746], length 351: HTTP, length: 351
  POST / HTTP/1.1
  Content-Length: 279
  Content-Type: application/json

  { "arguments": { "expire": 1572335080, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:35", "ip-address": "172.18.1.3", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }[!http]
06:44:41.633418 IP (tos 0x0, ttl 64, id 2858, offset 0, flags [DF], proto TCP (6), length 208)
    172.18.0.2.8080 > 172.18.0.3.33988: Flags [P.], cksum 0x58ec (incorrect -> 0x0080), seq 564:720, ack 1177, win 252, options [nop,nop,TS val 26311846 ecr 26311845], length 156: HTTP, length: 156
  HTTP/1.1 200 OK
  Content-Length: 48
  Content-Type: application/json
  Date: Tue, 29 Oct 2019 06:44:41 GMT

  [ { "result": 0, "text": "IPv4 lease added." } ][!http]
06:44:41.633649 IP (tos 0x0, ttl 64, id 57524, offset 0, flags [DF], proto TCP (6), length 52)
    172.18.0.3.33988 > 172.18.0.2.8080: Flags [.], cksum 0x5850 (incorrect -> 0xcb4f), ack 720, win 287, options [nop,nop,TS val 26311846 ecr 26311846], length 0
06:44:42.637308 IP (tos 0x0, ttl 64, id 43254, offset 0, flags [DF], proto TCP (6), length 176)
    172.18.0.2.42824 > 172.18.0.3.8080: Flags [P.], cksum 0x58cc (incorrect -> 0x7e2b), seq 1837942929:1837943053, ack 2537261301, win 254, options [nop,nop,TS val 26311946 ecr 26310830], length 124: HTTP, length: 124
  POST / HTTP/1.1
  Content-Length: 53
  Content-Type: application/json

  { "command": "ha-heartbeat", "service": [ "dhcp4" ] }[!http]
06:44:42.640413 IP (tos 0x0, ttl 64, id 59900, offset 0, flags [DF], proto TCP (6), length 303)
    172.18.0.3.8080 > 172.18.0.2.42824: Flags [P.], cksum 0x594b (incorrect -> 0xb605), seq 1:252, ack 124, win 227, options [nop,nop,TS val 26311946 ecr 26311946], length 251: HTTP, length: 251
  HTTP/1.1 200 OK
  Content-Length: 142
  Content-Type: application/json
  Date: Tue, 29 Oct 2019 06:44:42 GMT

  [ { "arguments": { "date-time": "Tue, 29 Oct 2019 06:44:42 GMT", "state": "hot-standby" }, "result": 0, "text": "HA peer status returned." } ][!http]
06:44:42.640696 IP (tos 0x0, ttl 64, id 43255, offset 0, flags [DF], proto TCP (6), length 52)
    172.18.0.2.42824 > 172.18.0.3.8080: Flags [.], cksum 0x5850 (incorrect -> 0x01b8), ack 252, win 262, options [nop,nop,TS val 26311946 ecr 26311946], length 0
```

上記より，`kea_1`から`kea_2`に向けて

```
{ "arguments": { "expire": 1572335075, "force-create": true, "fqdn-fwd": false, "fqdn-rev": false, "hostname": "", "hw-address": "00:00:00:11:22:33", "ip-address": "172.18.1.1", "state": 0, "subnet-id": 1, "valid-lft": 3600 }, "command": "lease4-update", "service": [ "dhcp4" ] }
```

というjsonフォーマットで`lease-update`が送出され，

```
{ "result": 0, "text": "IPv4 lease added." }
```

という形で応答していることがわかる．
Kea DHCPにおいてはこのようにREST APIを用いて`lease-update`の交換を行うことが特徴の一つであると言えるだろう．

# まとめ
というわけで，無事Kea DHCPを使って`hot-standby`モードのHA構成を組むことができました．
本記事では以下のことを実施しました．

- ISC DHCPの後継であるKea DHCPについて紹介し，冗長構成の違いを紹介した．
- ISCのKea DHCPを用いて`hot-standby`モードのHA構成を組むことについて紹介した．
- hot-standyモードのHAにおいて実際にクライアントを模擬してDHCPによるアドレスリースを行い，2つのサーバの応答の違いをパケットキャプチャによって確認した．
- HA構成における2サーバ間の`lease-update`について，REST APIによって行われていることを確認した．

今後は，`load-balancing`モードでのHA構成の構築と，`hot-standby`構成との振る舞いの違いや，HA構成を取ることによるパフォーマンス影響，HA構成で本当に冗長化ができているのか，等について見てみようと考えています．

# References
- [Internet Systems Consortium - Kea](https://www.isc.org/kea/)
- [Kea Documentation](https://kb.isc.org/docs/kea-administrator-reference-manual)
- [Kea Performance Optimization](https://kb.isc.org/docs/kea-performance-optimization)
- [Kea High Availability vs ISC DHCP Failover - Kea DHCP](https://kb.isc.org/docs/aa-01617)