UDPプログラムをDTLS化してみる

# 1. はじめに

この記事ではUDPで通信するC言語の簡単なサンプルプログラムをDTLSをベースの安全なプログラムに変身させてみます。DTLS(Datagram Transport Layer Security)はIETFの標準プロトコルの一つで、UDPのように軽量なプロトコル上でセキュリティを実現します。UDPは軽量ですがパケットの順序性やパケット消失時の再送などの保障がありません。そういう不安定なトランスポートの上でもセキュリティが実現できるように、DTLSでは接続確立のためのハンドシェーク中だけ順序性を保証したりパケット消失を検出したりします。

最初のステップでは、BSDソケットを使ったUDPベースのプログラムをDTLSベースのプログラムに変換してみます。UDPベースのプログラムにDTLSに必要な宣言や処理を加えるだけで、全体的な構造を変更せずに変換することができます。

DTLSの仕様は最近最新版の[DTLS1.3が正式発行](https://datatracker.ietf.org/doc/rfc9147/)されました。セキュリティプロトコルライブラリーのwolfSSLはβ版ながら既にサポートを開始しているので、次のステップではそれを利用してDTLS1.3でプログラムを動作させてみます。

組込システムでは、UDP通信はBSDソケットとは異なるAPIが提供されている場合も多くあります。最後の例では、そういう場合にUDPメッセージ送受信関数に簡単なラッパー(コールバック関数)を用意して、DTLS化する例を紹介します。


# 2. ライブラリーとサンプルプログラムの入手

wolfSSLライブラリーのソースコード一式をダウンロードしてビルド、インストールしておきます。DTLS1.3の機能はβ扱いですが、オープンソースの最新版に組み込まれています。[wolfSSLのダウンロードページ](https://www.wolfssl.jp/download/) で最新版のオープンソース wolfssl-5.4.0.zip をダウンロード、解凍し、wolfsslのディレクトリーに移動ます。

DTLSオプションとDTLS1.3オプションを有効化指定しconfigureコマンドの実行。オプションDTLSとDLTS v1.3が"yes" となっていることを確認します。

```
$ ./configure --enable-dtls --enable-dlts13
...
   * DTLS:                       yes
   * DTLS v1.3:                  yes
```

ビルド、インストールします。

```
$ make check
$ sudo make install
```

サンプルプログラムはGithubレポジトリから一式を入手します。wolfssl-examples/dtlsの下にDTLS関連のサンプルプログラム一式があるので、makeコマンドでコンパイルします。

$ git clone --depth 1 https://github.com/wolfssl/wolfssl-examples
$ cd wolfssl-examples/dtls
$ make

# 3. 簡単なUDPプログラム

サンプルプログラムの中からUDPクライアントのプログラム([client-udp.c](https://github.com/wolfSSL/wolfssl-examples/blob/master/dtls/client-udp.c))を出発点に変更していくことにします。プログラムはBSD Socketを使ったUDPサーバへのメッセージ送信、サーバからのメッセージ受信を繰り返すだけの単純なものです。送信メッセージは標準入力から入力し、入力がなくなれば(EOF)プログラムを終了します。

はじめにUDP用のソケットを確保し、相手先サーバのIPアドレスなどの情報を設定します。IPアドレスはコマンドの第一アーギュメントで指定します。ポート番号は11111を使用します。

sendtoでサーバに対してメッセージを送信し、recvfromでメッセージを受信します。

ヘッダーや変数宣言を除いたプログラムは概ね下のようになります。

```
int main(int argc, char** argv)
{

    /* ソケットを確保 */
    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
       printf("cannot create a socket.");
       return 1;
    }

　　　　　　　　/* ソケットの設定 (サーバIP, ポート番号など */

    while (fgets(sendLine, MAXLINE, stdin) != NULL) {

	    /* サーバにメッセージ送信 */
        if ( ( sendto(sockfd, sendLine, strlen(sendLine) - 1, 0, servAddr_in,
                        servLen)) == -1) {
            printf("Error in sending.\n");
        }

	    /* サーバからのメッセージ受信 */
        if ( (recvlen = recvfrom(sockfd, recvLine, MAXLINE, 0, NULL, NULL))
                == -1) {
            printf("Error in receiving.\n");
        }

        recvLine[recvlen] = '\0';
        fputs(recvLine, stdout);
    }
    return 0;
}
```

[サーバプログラム(server-udp.c)](https://github.com/wolfSSL/wolfssl-examples/blob/master/dtls/server-udp.c)は概ね次のようになります。クライアントと違い、確保したソケットをbindし、recvfromで特定のポートへのメッセージを待つようにします。

```
int main (void)
{
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("cannot create socket");
        return 0;
    }

    if (bind(sockfd, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0) {
        perror("bind failed");
        return 0;
    }

    for (;;) {
        recvLen = recvfrom(sockfd, buf, MSGLEN, 0,
                (struct sockaddr *)&cliAddr, &cliAddrLen);

        printf("heard %d bytes\n", recvLen);

        if (recvLen > 0) {
            buf[recvLen] = 0;
            printf("I heard this: \"%s\"\n", buf);
        }

        if (sendto(sockfd, buf, sizeof(buf), 0,
                    (struct sockaddr *)&cliAddr, cliAddrLen) < 0) {
            printf("\"sendto\" failed.\n");
            return 1;
        } 
        /* 正常に送信できたら、繰り返し次を受信 */
    }
    return 0;
}
```

簡単のため同じマシンの上の二つのプロセス(コマンドウィンドウ)の間で通信させてみます。
あらかじめWiresharkをLocal Loopbackで起動しておきます。

サーバ側を起動します。

```
$ ./server-udp
waiting for client message on port 11111
```
次にクライアントを起動して、メッセージを入力すると、サーバ側からのエコーバックが受信され出力されます。
^Dで終了します。

```
$ ./client-udp 127.0.0.1
hello server
hello server
```
Wiresharkでフィルター "udp.port==11111" を指定してパケットを見てみると、このような一往復のメッセージを見ることができます。

```
No.	    Time	    Source	    Port	Destination	Port	Protocol	Length	Info
4428	211.336279	127.0.0.1	62669	127.0.0.1	11111	UDP	        44	    62669 → 11111 Len=12
4429	211.336376	127.0.0.1	11111	127.0.0.1	62669	UDP	        4128	11111 → 62669 Len=4096
```
行をクリックしてパケットの中身を見てみると、生のメッセージがそのまま格納されていることがわかります。

```
User Datagram Protocol, Src Port: 62669, Dst Port: 11111
    Source Port: 62669
    Destination Port: 11111
    Length: 20
    ...
    [Timestamps]
    UDP payload (12 bytes)
Data: 68656c6c6f20736572766572
0000   68 65 6c 6c 6f 20 73 65 72 76 65 72               hello server
```

# 4. クライアント側をDTLS化してみる

次に、このプログラムをベースにDTLSプログラム化してみます。完成したプログラムは[client-dtls13.c](https://github.com/wolfSSL/wolfssl-examples/blob/master/dtls/client-dtls13.c)を参照してください。UDPとDTLSの主な違いは以下の点です。

- UDPには「接続」処理は存在しておらず、クライアントは通信したいメッセージをいきなり相手に対して送信することができます。DTLSはセキュリティのために一連のメッセージ送受信の前に「接続」処理が必要です。
- 接続時に意図した正しい相手であることの確認(サーバ認証、クライアント認証)を行います。TLSと同様にサーバ認証は必須、クライアント認証はオプションです。
- 当然ですが、送受信するメッセージは暗号化によって秘匿され、また一貫性も保証します。

それらのために、次のような追加変更が必要となります。

1) DTLS利用準備
一連のDTLS処理を管理するためにコンテキストの管理ブロックを使います。一つの管理ブロックを確保して、サーバ認証、クライアント認証に必要な証明書や鍵をロードしておきます。また、一つのDTLS接続を管理するための管理ブロックも用意します。この管理ブロックには確保したソケットを登録しておきます。

2) DTLS接続
通信の相手方にDTLS接続を要求します。UDPの場合、メッセージはデータグラムとして一つ一つが個別のメッセージ送受信として扱われますが、DTLSでは安全な接続を確保し、その上で通信します。

3) DTLSメッセージ送信、受信
アプリケーションの一塊のメッセージを送受信しますが、DTLS暗号化、復号のためのAPIを使います。

4) DTLS切断
通信の相手方にDTLS切断を要求します。接続と異なり、どちらからでも要求することができます。

5) DTLSリソース解放 
DTLS接続やコンテクストの管理ブロックを解放します。


では、具体的なプログラムを項目ごとに見ていきます。

1)  DTLS利用準備はプログラムの先頭付近で行います。wolfSSL_CTX_newでコンテクスト管理ブロックを確保します。この時、使用するDTLSプロ後コルのバージョンを指定します。ここではDTLS1.2を指定します。

次に、クライアント側ではwolfSSL_CTX_load_verify_locationsでサーバ認証のためのCA証明書をロードします。


```
int main (int argc, char** argv)
{

    if ( (ctx = wolfSSL_CTX_new(wolfDTLSv1_2_client_method())) == NULL) {
        fprintf(stderr, "wolfSSL_CTX_new error.\n");
        return 1;
    }
    /* CA証明書のロード */
    if (wolfSSL_CTX_load_verify_locations(ctx, certs, 0)
	    != SSL_SUCCESS) {
        fprintf(stderr, "Error loading %s, please check the file.\n", certs);
        return 1;
    }


```


2) DTLS接続処理は次のようになります。

UDPソケットの確保したあと、、wolfSSL_newでDTLS接続のための管理ブロックを確保します。次に、wolfSSL_dtls_set_peerで通信相手を登録し、wolfSSL_set_fdでソケットディスクリプタを登録した上で、wolfSSL_connectでDTLS接続処理を行います。

```

    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
       printf("cannot create a socket.");
       return 1;
    }

    ssl = wolfSSL_new(ctx);
    if (ssl == NULL) {
        printf("unable to get ssl object");
        return 1;
    }

    wolfSSL_dtls_set_peer(ssl, &servAddr, sizeof(servAddr));
    wolfSSL_set_fd(ssl, sockfd);
    
    /* DTLS接続要求 */
    if (wolfSSL_connect(ssl) != SSL_SUCCESS) {
	    err1 = wolfSSL_get_error(ssl, 0);
	    printf("err = %d, %s\n", err1, wolfSSL_ERR_reason_error_string(err1));
	    printf("SSL_connect failed");
        return 1;
    }

```

3) DTLSメッセージ送信、受信では、sendto をwolfSSL_writeに、recvfromをwolfSSL_readに置き換えてDTLSの暗号化処理をさせます。

```
        if ( ( wolfSSL_write(ssl, sendLine, strlen(sendLine)))
                != strlen(sendLine)) {
            printf("SSL_write failed");
        }

        n = wolfSSL_read(ssl, recvLine, sizeof(recvLine)-1);
        if (n < 0) {
            readErr = wolfSSL_get_error(ssl, 0);
            if (readErr != SSL_ERROR_WANT_READ) {
                printf("wolfSSL_read failed");
            }
        }
```

4) DTLS切断はwolfSSL_shutdownで行います。
5) DTLSリソース解放 は wolfSSL_freeとwolfSSL_CTX_freeです。wolfSSLライブラリ処理全体を終了する場合はwolfSSL_Cleanupを呼びます。

```
    wolfSSL_shutdown(ssl);
    wolfSSL_free(ssl);
    close(sockfd);
    wolfSSL_CTX_free(ctx);
    wolfSSL_Cleanup();
```

# 5.サーバ側をDTLS化してみる 

サーバ側も考え方としては概ねクライアント側と同様ですが、以下に、クライアント側と異なる点を中心に説明します。
完成したプログラムは[server-dtls13.c](https://github.com/wolfSSL/wolfssl-examples/blob/master/dtls/server-dtls.c)を参照してください。


サーバ側では、クライアントからのサーバ認証の要求に対応できるように、コンテクストにはサーバ証明書とプライベート鍵をロードします。

```

    /* サーバ証明書のロード　*/
    if (wolfSSL_CTX_use_certificate_file(ctx, servCertLoc, SSL_FILETYPE_PEM) != 
                                                                 SSL_SUCCESS) {
        printf("Error loading %s, please check the file.\n", servCertLoc);
        return 1;
    }
    /* プライベート鍵のロード */
    if (wolfSSL_CTX_use_PrivateKey_file(ctx, servKeyLoc,
                SSL_FILETYPE_PEM) != SSL_SUCCESS) {
        printf("Error loading %s, please check the file.\n", servKeyLoc);
        return 1;
    }
```

サーバ側では、多数のクライアントから同時に接続要求が入ってしまうような場合に対処できるようにしないといけません。そのために、ソケットを特定のポートにbindした後、MSG_PEEKモードでrecvfrom　でメッセージを受信します。このモードでは、recvfromはメッセージが受信されてもメッセージバッファの内容は変更されないので、次回recvfromを呼び出すと同じメッセージを読み出すことができます。これを使うことで、TCPのaccept処理のように、クライアントからのDTLS接続要求(と思われる)のUDPパケットが到着するまで待つことができます。パケットが到着したら、wolfSSL_acceptを呼び出しDTLS接続のアプセプト処理を実行します。


```
        if (bind(listenfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0) {
            printf("Bind failed.\n");
            break;
        }

        ret = (int)recvfrom(listenfd, (char *)&b, sizeof(b), MSG_PEEK,
                (struct sockaddr*)&cliaddr, &cliLen);

```

クライアントからの接続要求を処理するためにwolfSSL_acceptを呼び出します。

```
        if (wolfSSL_accept(ssl) != SSL_SUCCESS) {

            int e = wolfSSL_get_error(ssl, 0);

            printf("error = %d, %s\n", e, wolfSSL_ERR_reason_error_string(e));
            printf("SSL_accept failed.\n");n
            break;
        }
```

makeのマクロ指定を"CFLAGS=-DUSE_DTLS12"としてmakeして、DTLSのサーバ、クライアントを先程と同じように起動してみます。

```
#ifndef USE_DTLS12
            wolfDTLSv1_3_server_method()
#else
            wolfDTLSv1_2_server_method()
#endif
```

コマンド名はserver-dtls13、client-dtls13となっていますが、DTLS1.2指定でmakeしているので、DTLS1.2で通信が行われます。


```
$ make clean
$ make CFLAGS=-DUSE_DTLS12
$ ./server-dtls13
```

```
$ ./client-dtls13 127.0.0.1
```

Wiresharkのフィルターには、udp.port==11111を指定します。

```
No.	Time	    Source	    Port	Destination	Port	Protocol	Length	Info
5	42.136110	127.0.0.1	57782	127.0.0.1	11111	DTLSv1.2	213	    Client Hello
6	42.136548	127.0.0.1	11111	127.0.0.1	57782	DTLSv1.2	92	    Hello Verify Request
7	42.136705	127.0.0.1	57782	127.0.0.1	11111	DTLSv1.2	245	    Client Hello
8	42.137193	127.0.0.1	11111	127.0.0.1	57782	DTLSv1.2	139	    Server Hello
9	42.137349	127.0.0.1	11111	127.0.0.1	57782	DTLSv1.2	1323	Certificate
10	42.150915	127.0.0.1	11111	127.0.0.1	57782	DTLSv1.2	386	    Server Key Exchange
11	42.150956	127.0.0.1	11111	127.0.0.1	57782	DTLSv1.2	57	    Server Hello Done
12	42.155125	127.0.0.1	57782	127.0.0.1	11111	DTLSv1.2	123	    Client Key Exchange
13	42.155401	127.0.0.1	57782	127.0.0.1	11111	DTLSv1.2	107	    Change Cipher Spec, Encrypted Handshake Message
14	42.157500	127.0.0.1	11111	127.0.0.1	57782	DTLSv1.2	107	    Change Cipher Spec, Encrypted Handshake Message
72	55.381907	127.0.0.1	57782	127.0.0.1	11111	DTLSv1.2	82	    Application Data
73	55.382051	127.0.0.1	11111	127.0.0.1	57782	DTLSv1.2	92	    Application Data
87	94.425780	127.0.0.1	57782	127.0.0.1	11111	DTLSv1.2	71	    Encrypted Alert
88	94.425928	127.0.0.1	11111	127.0.0.1	57782	DTLSv1.2	71	    Encrypted Alert

```

# 6. DTLS1.3を試してみる

DTLS1.2と1.3のアプリケーションプログラム上の違いは、wolfSSL_CTX_newのアーギュメントでプロトコルバージョンをDTLS1.3と指定する点だけです。

- クライアント側：wolfDTLSv1_3_client_method()
- サーバ側：　　　wolfDTLSv1_3_server_method()

また、場合によっては相手側はv1.3だけでなくサポートされている範囲で最新のバージョンで接続を許すという場合は、次のように指定します。

- クライアント側：wolfDTLS_client_method()
- サーバ側：　　　wolfDTLS_server_method()

完成形のプログラム(client-dtls13.c)の中では、下のようにUSE_DTLS12マクロが定義されている場合はDTLS1.2, されていない場合はDTLS1.3が指定されるようになっています。

```
if ( (ctx = wolfSSL_CTX_new(
#ifndef USE_DTLS12
            wolfDTLSv1_3_client_method()
#else
            wolfDTLSv1_2_client_method()
#endif
            )) == NULL) {
```

makeをマクロ指定無しでmakeし、DTLS13を試してみます。

```
$ make clean
$ make
$ ./server-dtls13
```

```
$ ./client-dtls13 127.0.0.1
```

DTLS1.3ではServer Helloの後はハンドシェークも暗号化されているので、DTLSとは認識されず単なるUDPパケットとしてキャプチャーされているのがわかります。また、Hello Verify Requestがなくなり、ハンドシェーク中のパケットの往復回数もTLS1.3と同様に少なくなっていることがわかります。TLSと違い、TCP接続のパケット往復も無くなるので、パケット往復のオーバーヘッドが大きい公衆インターネットなどではスループットはかなり向上するはずです。

```
No.	Time	    Source	    Port	Destination	Port	Protocol	Length	Info
3	20.596897	127.0.0.1	59424	127.0.0.1	11111	DTLSv1.2	291		Client Hello
4	20.599458	127.0.0.1	11111	127.0.0.1	59424	DTLSv1.2	176		Server Hello
5	20.601558	127.0.0.1	11111	127.0.0.1	59424	UDP	        68		11111 → 59424 Len=36
6	20.601772	127.0.0.1	11111	127.0.0.1	59424	UDP	        1335	11111 → 59424 Len=1303
7	20.613529	127.0.0.1	11111	127.0.0.1	59424	UDP	        326		11111 → 59424 Len=294
8	20.613625	127.0.0.1	11111	127.0.0.1	59424	UDP	        98		11111 → 59424 Len=66
9	20.614134	127.0.0.1	59424	127.0.0.1	11111	UDP	        98		59424 → 11111 Len=66
10	20.614251	127.0.0.1	11111	127.0.0.1	59424	UDP	        72		11111 → 59424 Len=40
11	26.809640	127.0.0.1	59424	127.0.0.1	11111	UDP	        67		59424 → 11111 Len=35
12	26.809782	127.0.0.1	11111	127.0.0.1	59424	UDP	        77		11111 → 59424 Len=45
```

# 7. BSDソケットでない場合

　最後に、BSDソケットではないUDPプログラムの場合について説明します。組み込みシステム向けのUDPではBSD互換でなないもの、類似したAPIは提供しているものの前に紹介したようなreadfromでのMSG_PEEK機能のような機能が提供されていないものがあります。そのような場合は、メッセージ送信、受信のラッパー関数(コールバック関数)を用意して機能とインタフェースを合わせます。用意した機能は実行時に登録用のAPIで登録します。

完成した[サーバ(server-dtls-callback.c)](https://github.com/wolfSSL/wolfssl-examples/blob/master/dtls/server-dtls-callback.c)と[クライアント(client-dtls-callback.c)](https://github.com/wolfSSL/wolfssl-examples/blob/master/dtls/client-dtls-callback.c)はこちらで参照できます。

メッセージ受信、送信用コールバック関数を適当な関数名で定義します (my_IORecv, my_IOSend)。第二アーギュメントはバッファーのポインタ、第三アーギュメントはバッファのバイトサイズが引き渡されるので、受信関数では、このバッファにメッセージを受信するようにUDPメッセージ受信関数を呼び出します。ここでは、参考のためにrecvfromを呼び出す場合の例を示しています。送信側でも同様にこのバッファのメッセージを指定のサイズ送信するようにUDP送信関数を呼び出します。この例ではsendtoを呼び出す場合の例を示しています。
受信、送信の際のソケットディスクリプターについては後で説明します。

受信、送信関数から戻ったら、正常終了の場合は、実際に受信、送信できたバイト数を返却値として返します。エラーの場合は、エラーコードをWOLFSSLの対応するエラーコードに変換して関数の返却地として返します。エラー種別を細かく区別して返却すれば、アプリケーション側でwolfSSL_get_error関数にて詳細値を取得することができますが、エラーの場合は最低限度、WOLFSSL_CBIO_ERR_GENERALを返却するようにします。受信、送信バイト数がゼロの場合はエラー扱いとします。

```
int my_IORecv(WOLFSSL* ssl, char* buff, int sz, void* ctx)
{

    recvd = recvfrom(shared->sd, buff, sz, 0, &addr, &addrSz);

    if (recvd == -1) {
        switch (errno) {
        case EWOULDBLOCK:
        ...
        default:
            fprintf(stderr, "general error\n");
            return WOLFSSL_CBIO_ERR_GENERAL;
        }
    }
    else if (recvd == 0) {
        printf("Connection closed\n");
        return WOLFSSL_CBIO_ERR_CONN_CLOSE;
    }

    /* successful receive */
    return recvd;
}
```

```
int my_IOSend(WOLFSSL* ssl, char* buff, int sz, void* ctx)
{
    /* Send datagram */
    sent = sendto(shared->sd, buff, sz, 0, addr, addrSz);
    if (sent == -1) {
        switch (errno) {
        case EWOULDBLOCK:
        ...
        default:
            fprintf(stderr, "general error\n");
            return WOLFSSL_CBIO_ERR_GENERAL;
        }
    }
    else if (sent == 0) {
        printf("Connection closed\n");
        return 0;
    }

    /* successful send */
    return sent;
}
```

アプリケーションではコンテクストを確保したら、コンテクストにコールバック関数を登録します。

```
   if ((ctx = wolfSSL_CTX_new(wolfDTLSv1_2_server_method())) == NULL) {
        printf("wolfSSL_CTX_new error.\n");
        ret = -1;
        goto exit;
    }
    ...
    /* Register callbacks */
    wolfSSL_CTX_SetIORecv(ctx, my_IORecv);
    wolfSSL_CTX_SetIOSend(ctx, my_IOSend);
```

アプリケーション側では、コールバック関数との間で共有しなければならないデータを管理するために下のような管理ブロックを用意します。この中には、コールバック関数側で使用すべきソケットディスクリプターやMSG_PEEK処理と同等の処理ができるように受信したメッセージをバッファリングしておいたりするのに利用します。

また、UDPはTCPのような接続の概念がないために、最初のreadfromで受信したメッセージの送信元にメッセージ送信コールバックのほうで返信できるようにする必要があります。アプリケーションのbind後のreadfromで得た送信元アドレスとそのサイズはcliAddとcliSzに格納しておいて、メッセージ送信時の送信先アドレスとして使用します。

```
typedef struct SharedDtls {
    WOLFSSL*           ssl;           /* WOLFSSL object being shared */
    int                sd;            /* socket fd */
    struct sockaddr_in cliAddr;       /* server sockaddr */
    socklen_t          cliSz;         /* length of servAddr */
    byte               rxBuf[DTLS_MTU];
    word32             rxSz;
} SharedDtls;

```

```
        shared.sd = listenfd;

        wolfSSL_SetIOWriteCtx(ssl, &shared);
        wolfSSL_SetIOReadCtx(ssl, &shared);
```

BSDソケットを使ったサーバで、クライアントからの接続要求メッセージをMSG_PEEKモードのrecvfromで受信する部分(下記)を、管理ブロックを使った方法に書き換えます。

```
        (int)recvfrom(listenfd, (char *)&b, sizeof(b), MSG_PEEK,
                (struct sockaddr*)&cliaddr, &cliLen);
```

管理ブロックを使ったクライアントからの接続メッセージ処理。受信したバイト数を管理ブロックのrxSzに設定し、アプリケーションとしては"ret = 0"としておきます。

```
        ret = (int)recvfrom(listenfd, (char*)shared.rxBuf, sizeof(shared.rxBuf), 0,
                (struct sockaddr*)&shared.cliAddr, &shared.cliSz);
        if (ret < 0) {
            /* エラー */
            continue;
        }
        else if (ret > 0) {
            shared.rxSz = ret;
            ret = 0; 
        }
```

受信、送信コールバックの第四アーギュメントにこの管理ブリックへのポインタが引き渡され、先頭付近にはこの管理ブロックの処理を記述します。


送信コールバックでは、メッセージの受信元IPアドレスが送信先となるように共有データから設定します。

```
int my_IOSend(WOLFSSL* ssl, char* buff, int sz, void* ctx)
{
    SharedDtls* shared = (SharedDtls*)ctx;

    if (shared) {
        addr = (const struct sockaddr*)&shared->cliAddr;
        addrSz = shared->cliSz;
    }
    ...
```

メッセージ受信、送信関数のソケットディスクリプタには共有データに保存したディスクリプタを渡します。

```
    recvd = recvfrom(shared->sd, buff, sz, 0, &addr, &addrSz);
```

```
    sent = sendto(shared->sd, buff, sz, 0, addr, addrSz);
```

サーバ側の受信コールックでは、アプリケーション側で先行して受信したメッセージの内容を受信バッファに戻します。

```
int my_IORecv(WOLFSSL* ssl, char* buff, int sz, void* ctx)
{
    SharedDtls* shared = (SharedDtls*)ctx;

    /* handle "peek" rx data */
    if (shared->rxSz > 0) {
        recvd = shared->rxSz;
        if (recvd > sz)
            recvd = sz;
        memcpy(buff, shared->rxBuf, recvd);
        shared->rxSz -= recvd;
        memcpy(shared->rxBuf, &shared->rxBuf[recvd], shared->rxSz);
    }
    ...
```


# 8. まとめ

本稿では、UDPプログラムをDTLS化する方法についてまとめました。BSDソケットではない場合も多少の追加コーディングが必要となりますが、ライブラリーに変更を加えることなく実行時のコールバック登録で対応できることが理解できたかと思います。