この記事の続きになります。前回はFreeRTOSとLwIPを使ったサンプルの初期化部分のソースコードが理解できたので、DHCP Clientのソースコードの中身を見ていきたいと思います。
なお、FreeRTOSの詳しい説明に関しては割愛します。FreeRTOSの内容については下記の記事にもまとめているので、こちらを参照していただければと思います。
https://qiita.com/tom_S/items/9d81f876fa83c66a6065
この記事ではDHCPの機能とDHCP Clientについて軽く説明した後、サンプルで実装されているDHCP Clientの実装を見ていきます。
前回の記事でも少々説明しましたが、DHCP Clientの実装は「DHCP_Thread」にて行っているので、こちらを見ていきます。
DHCPの機能
DHCP(Dynamic Host Configuration Protocol)は、IPアドレスの設定を自動で行う仕組みです。また、設定するIPアドレスが他のネットワークインターフェースと重複しないようにIPアドレスを管理します。
DHCPを使用する際は、DHCP ClientとDHCP Serverが存在していないと動作しません。
IPアドレスの設定フローとしては下記となります。
- DHCP ClientがDHCP Serverへ、IPアドレスの設定を要求するパケット(DHCP Discover)を送信
- DHCPサーバーからDHCP Clientへ割り振られたIPアドレスを送信(DHCP Offer)
- DHCP Clientは割り振られたIPアドレスを設定したことを示すパケット(DHCP Request)をDHCP Serverへ送信
- DHCP Serverは、送ったIPアドレスが設定されたことを承認するパケット(DHCP ACK)をDHCP Clientへ送信
DHCP_Thread
上記で説明したDHCPのフローを基にサンプルのソースコード(DHCP_Thread)を確認していきます。
DHCP_Thread()はapp_ethernet.cに実装されています。下記の通りです。
void DHCP_Thread(void const * argument)
{
struct netif *netif = (struct netif *) argument;
ip_addr_t ipaddr;
ip_addr_t netmask;
ip_addr_t gw;
struct dhcp *dhcp;
#ifdef USE_LCD
uint8_t iptxt[20];
#endif
for (;;)
{
switch (DHCP_state)
{
case DHCP_START:
{
ip_addr_set_zero_ip4(&netif->ip_addr);
ip_addr_set_zero_ip4(&netif->netmask);
ip_addr_set_zero_ip4(&netif->gw);
DHCP_state = DHCP_WAIT_ADDRESS;
#ifdef USE_LCD
LCD_UsrLog (" State: Looking for DHCP server ...\n");
#else
BSP_LED_Off(LED1);
BSP_LED_Off(LED2);
#endif
/* DHCP Discoverパケットを送信(受信処理はLwIPの内部処理で行う) */
dhcp_start(netif);
}
break;
case DHCP_WAIT_ADDRESS:
{
/* DHCPでIPアドレスが取得できていない */
if (dhcp_supplied_address(netif))
{
DHCP_state = DHCP_ADDRESS_ASSIGNED;
#ifdef USE_LCD
sprintf((char *)iptxt, "%s", ip4addr_ntoa(netif_ip4_addr(netif)));
LCD_UsrLog ("IP address assigned by a DHCP server: %s\n", iptxt);
#else
BSP_LED_On(LED1);
BSP_LED_Off(LED2);
#endif
}
else
{
dhcp = (struct dhcp *)netif_get_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP);
/* DHCP timeout */
if (dhcp->tries > MAX_DHCP_TRIES)
{
DHCP_state = DHCP_TIMEOUT;
/* Static address used */
IP_ADDR4(&ipaddr, IP_ADDR0 ,IP_ADDR1 , IP_ADDR2 , IP_ADDR3 );
IP_ADDR4(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3);
IP_ADDR4(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
netif_set_addr(netif, ip_2_ip4(&ipaddr), ip_2_ip4(&netmask), ip_2_ip4(&gw));
#ifdef USE_LCD
sprintf((char *)iptxt, "%s", ip4addr_ntoa(netif_ip4_addr(netif)));
LCD_UsrLog ("DHCP Timeout !! \n");
LCD_UsrLog ("Static IP address: %s\n", iptxt);
#else
BSP_LED_On(LED1);
BSP_LED_Off(LED2);
#endif
}
}
}
break;
case DHCP_LINK_DOWN:
{
DHCP_state = DHCP_OFF;
#ifdef USE_LCD
LCD_UsrLog ("The network cable is not connected \n");
#else
BSP_LED_Off(LED1);
BSP_LED_On(LED2);
#endif
}
break;
default: break;
}
/* wait 500 ms */
osDelay(500);
}
}
上記のスレッドの機能としては下記となります。
- Ethernetが接続された際にDHCP Clientとして動作し、Ethernetが切断されたら状態を「DHCP_state」の値が「DHCP_START」なら、dhcp_start()を呼び出し、「DHCP_state」の値を「DHCP_WAIT_ADDRESS」に変更
- dhcp_start()はLwIPのAPIで、DHCP Client制御用のインスタンスを作成し、IPアドレスの設定を要求するパケット(DHCP Discover)を送信する
- 「DHCP_state」の値が「DHCP_WAIT_ADDRESS」なら、dhcp_supplied_address()を呼び出してIPアドレスを取得できたかを確認。所得できた状態ならIPアドレスを設定し、「DHCP_state」の値を「DHCP_ADDRESS_ASSIGNED」に変更。タイムアウトしており、IPアドレスを取得できていない場合は、固定のIPアドレスを設定して「DHCP_state」の値を「DHCP_TIMEOUT」に変更
- dhcp_supplied_address()はLwIPのAPIで、IPアドレスを取得したかどうかをbool値で返す
- 「DHCP_state」の値が「DHCP_LINK_DOWN」なら、「DHCP_state」の値を「DHCP_OFF」に変更
DHCPのステート「DHCP_state」は、このスレッド「DHCP_Thread()」とEthernetの接続状況が変わった際に呼ばれるコールバック「ethernet_link_status_updated()」で値が変わります。
ethernet_link_status_updated
「ethernet_link_status_updated()」はこちらの記事で説明した初期化時のサンプルコードで「netif_set_link_callback()」を使って、コールバックとして登録されています。実装は下記のとおりです。
void ethernet_link_status_updated(struct netif *netif)
{
if (netif_is_up(netif))
{
#if LWIP_DHCP
/* Update DHCP state machine */
DHCP_state = DHCP_START;
#elif defined(USE_LCD)
uint8_t iptxt[20];
sprintf((char *)iptxt, "%s", ip4addr_ntoa(netif_ip4_addr(netif)));
LCD_UsrLog ("Static IP address: %s\n", iptxt);
#else
BSP_LED_On(LED1);
BSP_LED_Off(LED2);
#endif /* LWIP_DHCP */
}
else
{
#if LWIP_DHCP
/* Update DHCP state machine */
DHCP_state = DHCP_LINK_DOWN;
#elif defined(USE_LCD)
LCD_UsrLog ("The network cable is not connected \n");
#else
BSP_LED_Off(LED1);
BSP_LED_On(LED2);
#endif /* LWIP_DHCP */
}
}
「ethernet_link_status_updated()」の機能としては、下記のようになります。LEDの制御も含まれているのですが、DHCPと関係ないので割愛します。
- Ethernetが接続状態になったら、「DHCP_state」の値を「DHCP_START」に変更する
- Ethernetが切断状態になったら、「DHCP_state」の値を「DHCP_LINK_DOWN」に変更する
DHCP Clientの実装のまとめ
上記で示したDHCP Clientのサンプルソースコードにおける振る舞いをまとめると、下記のようになります。
- Ethernetケーブルが接続された際に、IPアドレスの設定を要求するパケット(DHCP Discover)を送信する
- DHCP Offer、DHCP Request、DHCP ACKの処理はLwIP内で行っている
- Ethernetケーブルが切断された際は、IPアドレスは設定されていない状態に戻る
- Ethernetケーブルを切断→接続を行うことで、再度DHCPのIPアドレス取得フローが始まる
- DHCPでIPを取得できたかどうかは、スレッド内でポーリングして確認している
シーケンスを図にまとめると下記のようになります。
サンプルソースのためこのような実装になっているかと思うのですが、本来ならDHCPスレッドはメッセージボックスやイベントグループなどの通知で動作させる形にするのが妥当かと思います。
このあたりも、全てのサンプルソースコードが読み終わった際に修正してみたいと思います。
次の記事ではHTTP Serverのサンプルソースを見ていきます。
参考資料
DHCP
https://milestone-of-se.nesuke.com/nw-basic/grasp-nw/dhcp/
https://www.challenge-cf.jp/post/wireshark%E3%81%A7%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88%E3%81%8Cdhcp%E3%82%B5%E3%83%BC%E3%83%90%E3%81%8B%E3%82%89ip%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E3%81%BE%E3%81%A7%E3%81%AE%E5%90%84%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E8%A6%8B%E3%81%A6%E3%81%BF%E3%81%9F