拡張ソケットAPI (RFC 3542)の簡単な例として、
1.ユニキャスト送信時のホップリミットの制御
2.パスMTUの取得
をやってみましょう。
その前に
0.インターフェースインデックスの表示
を実装することにします。
使用するプロジェクトは、SimpleClientMEcho(Windows版, Linux版)です。
IPv6のアドレスは次のような構造体で定義されています。(4.3BSD)
struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ }; struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */ };
IPv4のアドレスは次のような構造体で定義されています。
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ };
比較してみると、sockaddr_in6にはsin6_flowinfoとsin6_scope_idが追加されています。
sin6_flowinfoはQoSの機能に使用します(優先順を指定できます)
sin6_scope_idがインターフェースインデックスですこれを表示する関数を作ります。
以下のソースは、SimpleClientMEcho(Linux版)ですが、Windows版も同様に対応出来ます。
パラメータとしては、getaddrinfoで取得し、connectで使用する
アドレス情報(addrinfo)を渡します。
WindowsのコンソールはESCシーケンスによる画面制御ができませんので
fprintf(stderr, “\x1b[1;1H\x1b[0J”);はコメントアウトしてください。
//====================================================================== // function // 接続に使用されるインターフェースインデックスを調べる // parameter // addrinfo *pAddr [in]接続に使用されるアドレス情報 // return // TRUE/FALSE //====================================================================== BOOL ShowInterfaceIndex(addrinfo *pAddr) { BOOL fRet = FALSE; sockaddr_in6 addr6; if (pAddr->ai_family == AF_INET6) { memcpy(&addr6, pAddr->ai_addr, sizeof(sockaddr_in6)); fprintf(stderr, “\x1b[1;1H\x1b[0J”); fprintf(stderr, “Index:%d\n”, addr6.sin6_scope_id); fRet = TRUE; } return(fRet); }
パスMTUを表示する関数を作ります。
TCP/IPでは接続済みソケットに対してパスMTUを調べることができます。
(残念ながらWindowsではIPV6_PATHMTUが定義されていないので使えません)
構造体ip6_mtuinfoのip6m_mtuにMTUの値が入ります。
struct ip6_mtuinfo { struct sockaddr_in6 ip6m_addr; /* dst address including zone ID */ uint32_t ip6m_mtu; /* path MTU in host byte order */ };
パラメータとしては、接続済みソケットとアドレスファミリを渡します。
WindowsのコンソールはESCシーケンスによる画面制御ができませんので
fprintf(stderr, “\x1b[4;1H\x1b[0J”);はコメントアウトしてください。
//====================================================================== // function // PATH_MTUを調べる // このオプションは処理系によって未対応なのでIPV6_PATHMTUの定義有無で判断する // 通常はPATH_MTU=1500Bytesと想定しデータは1024Bytes程度にすれば問題ない // parameter // SOCKET fd [in]接続済みソケット // int iFamily [in]アドレスファミリ(AF_INET/AF_INET6) // return // TRUE/FALSE //====================================================================== BOOL ShowPathMTU(SOCKET fd, int iFamily) { BOOL fRet = FALSE; #ifdef IPV6_PATHMTU if (iFamily == AF_INET6) { struct ip6_mtuinfo mtuinfo; socklen_t infolen = sizeof(mtuinfo); int iRet = getsockopt(fd, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &infolen); if (iRet == 0) { fprintf(stderr, “\x1b[4;1H\x1b[0J”); fprintf(stderr, “PATH MTU %d\n”, mtuinfo.ip6m_mtu); fRet = TRUE; } } #endif return(fRet); }
ホップリミットを変更する関数を作ります。
ユニキャストのホップリミットを変更するのでIPV6_UNICAST_HOPSを使用します。
パラメータとしては、ソケットとアドレスファミリと新しい値を渡します。
以下の関数はIPv4に対してTTLを設定するようにしてあります。
ホップリミットを2にすると、ルータを越えることができません(リンクローカル
に制限)、ホップリミットは接続(connect)処理の前に設定するようにします。
WindowsのコンソールはESCシーケンスによる画面制御ができませんので
fprintf(stderr, “\x1b の行をコメントアウトしてください。
//====================================================================== // function // HopLimit(IPv6)/TTL(IPv4)を設定する // parameter // SOCKET fd [in]接続済みソケット // int iFamily [in]アドレスファミリ(AF_INET/AF_INET6) // int iHopLimit [in](HopLimit – 1)台のルータを超えることができる // return // TRUE/FALSE //====================================================================== BOOL SetHopLimit(SOCKET fd, int iFamily, int iLimit) { BOOL fRet = FALSE; int iRet; DWORD dwHoplimit; socklen_t iOptlen; if (iFamily == AF_INET6) { // 現在の値を取得して表示する iOptlen = sizeof(dwHoplimit); iRet = getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&dwHoplimit, &iOptlen); if (iRet == 0) { fprintf(stderr, “\x1b[2;1H\x1b[0J”); fprintf(stderr, “HopLimitOrg %d\n”, dwHoplimit); } // 新しい値を設定する dwHoplimit = iLimit; iOptlen = sizeof(dwHoplimit); iRet = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&dwHoplimit, iOptlen); if (iRet == 0) { // 設定後の値を表示する iRet = getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&dwHoplimit, &iOptlen); if (iRet == 0) { fprintf(stderr, “\x1b[3;1H\x1b[0J”); fprintf(stderr, “HopLimitNew %d\n”, dwHoplimit); fRet = TRUE; } } } else if (iFamily == AF_INET) { // 現在の値を取得して表示する iOptlen = sizeof(dwHoplimit); iRet = getsockopt(fd, IPPROTO_IP, IP_TTL, (char *)&dwHoplimit, &iOptlen); if (iRet == 0) { fprintf(stderr, “\x1b[2;1H\x1b[0J”); fprintf(stderr, “TTL %d\n”, dwHoplimit); } // 新しい値を設定する dwHoplimit = iLimit; iOptlen = sizeof(dwHoplimit); iRet = setsockopt(fd, IPPROTO_IP, IP_TTL, (char *)&dwHoplimit, iOptlen); if (iRet == 0) { // 設定後の値を表示する iRet = getsockopt(fd, IPPROTO_IP, IP_TTL, (char *)&dwHoplimit, &iOptlen); if (iRet == 0) { fprintf(stderr, “\x1b[3;1H\x1b[0J”); fprintf(stderr, “TTLNew %d\n”, dwHoplimit); fRet = TRUE; } } } return(fRet); }
全体のコード(SimpleClient.cpp)は以下の通りです。
作成した関数(インターフェースインデックスの表示、パスMTUの表示、ホップリミットの変更)は
BOOL CreateAndConnectSocket(LPCSTR szAddress, WORD wPort)の中で呼んでいます。
【SimpleClient.cpp】 #include “stdThread.h” #include “SendRecvThread.h” // 送受信スレッドを使うため #include “MySyncObject.h” // 同期オブジェクトを使うため #ifdef _MSC_VER // Windowsのとき #define DISABLE_C4996 __pragma(warning(push)) __pragma(warning(disable:4996)) #define ENABLE_C4996 __pragma(warning(pop)) #else // Linuxのとき #define DISABLE_C4996 #define ENABLE_C4996 #endif // メニューで使用する文字 #define CMD_QUIT_CHAR ‘q’ #define CMD_SEND_MSG_CHAR ‘m’ // 関数の宣言 BOOL CreateAndConnectSocket(LPCSTR szAddress, WORD wPort); // ソケットの作成と接続処理 BOOL DestroySocket(SOCKET &fd); // 切断とソケットの破棄 void Stop(); // すべてのソケットを破棄する void DispMenu(); // メニューの表示 int GetKeyString(LPSTR pszString, int iSize); // キーボード入力文字の取得 BOOL KillZombei(); // ゾンビ状態のスレッドを破棄する BOOL ShowInterfaceIndex(addrinfo *pAddr); // ★インターフェースを表示する BOOL ShowPathMTU(SOCKET fd, int iFamily); // ★MTUを表示する BOOL SetHopLimit(SOCKET fd, int iFamily, int iLimit); // ★ホップリミットを設定する // 変数の宣言 CSendRecvThread *m_pCSendRecvThread = NULL; ConnectionInfoRec *m_pConInfo = NULL; CMySyncObject *m_pCMySyncObject = NULL; int main(int argc, char *argv[]) { char szKeyInBuff[81]; m_pCMySyncObject = new CMySyncObject(); m_pCMySyncObject->Initialize(); // 起動パラメータチェック if (argc != 3) { fprintf(stderr, “Usage: %s <ServerAddress> <ServerPort>\n”, argv[0]); goto L_END; } // ソケットの作成と接続処理 // 送受信スレッドを開始する if (CreateAndConnectSocket(argv[1], (WORD)atol(argv[2])) == FALSE) goto L_END; // メニューの表示 DispMenu(); while (1) { if (KillZombei() == TRUE) // 切断してたら終了 break; // キーボードから入力された文字列が’q’なら終了 // ‘m’ならメッセージを入力後は送信する switch (GetKeyString(szKeyInBuff, sizeof(szKeyInBuff))) { case 0: // 入力なし、何もしない break; case CMD_QUIT_CHAR: // ‘q’入力終了 goto L_END; case CMD_SEND_MSG_CHAR: // ‘m’入力szKeyInBuffに文字列が格納されている m_pCSendRecvThread->SetSendData(szKeyInBuff, strlen(szKeyInBuff)); // メニューの表示 DispMenu(); break; } } L_END: // 切断とすべてのソケットの破棄 Stop(); m_pCMySyncObject->Uninitialize(); SAFE_DELETE(m_pCMySyncObject) return(0); } //============================================== // function // TCPソケットの作成, connectの実行 // 接続成功したとき送受信スレッドを開始する // parameter // LPCSTR szAddress [in]接続先アドレス // WORD wPort [in]接続先ポート // return // TRUE/FALSE //============================================== BOOL CreateAndConnectSocket(LPCSTR szAddress, WORD wPort) { BOOL fRet = TRUE; SOCKET fd; struct addrinfo hints, *pres = NULL, *pTemp = NULL; char szPort[NI_MAXSERV]; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; // TCP DISABLE_C4996 sprintf(szPort, “%d”, wPort); // ポート番号=サービス ENABLE_C4996 // アドレスを指定してgetaddrinfoを実行 接続先アドレス情報を得る if (getaddrinfo(szAddress, szPort, &hints, &pres) == 0) { pTemp = pres; while (pTemp != NULL) { // socket, connectを実行 if ((fd = socket(pTemp->ai_family, pTemp->ai_socktype, pTemp->ai_protocol)) == INVALID_SOCKET) goto L_NEXT; //===================================== // ★使用するインターフェースを表示する ShowInterfaceIndex(pTemp); // ★HopLimitを取得設定する // とりあえず10にしてみました SetHopLimit(fd, pTemp->ai_family, 10); //===================================== if (connect(fd, pTemp->ai_addr, (int)pTemp->ai_addrlen) == SOCKET_ERROR) { DispErrorMsg(“Err:connect”); DestroySocket(fd); goto L_NEXT; } //===================================== // ★接続が成功したのでMTUを調べる ShowPathMTU(fd, pTemp->ai_family); //===================================== // 接続成功したのでこのソケット採用することにして抜ける fRet = TRUE; break; L_NEXT: pTemp = pTemp->ai_next; } freeaddrinfo(pres); } if (fd == INVALID_SOCKET) // 接続に失敗 { DispErrorMsg(“Err:connect”); fRet = FALSE; goto L_END; } // 送信スレッドを開始する m_pConInfo = (ConnectionInfoRec *)calloc(1, sizeof(ConnectionInfoRec)); m_pConInfo->fdClient = fd; m_pConInfo->pCMySyncObject = m_pCMySyncObject; m_pCSendRecvThread = new CSendRecvThread(m_pConInfo); m_pCSendRecvThread->Begin(); L_END: return(fRet); } //============================================== // function // TCPソケットの破棄 // parameter // SOCKET &fd[in/out]破棄するソケット // return // TRUE/FALSE //============================================== BOOL DestroySocket(SOCKET &fd) { if (fd != INVALID_SOCKET) { fprintf(stderr, “DestroySocket()\n”); shutdown(fd, SHUT_RDWR); // 受信も送信も停止 close(fd); fd = INVALID_SOCKET; } return(TRUE); } //============================================== // function // 送受信スレッドの終了、ソケットの破棄 // parameter // なし // return // なし //============================================== void Stop() { fprintf(stderr, “Stop()\n”); if (m_pCSendRecvThread != NULL) { m_pCSendRecvThread->End(); m_pCSendRecvThread->WaitForEnd(); SAFE_DELETE(m_pCSendRecvThread) DestroySocket(m_pConInfo->fdClient); SAFE_FREE(m_pConInfo) } } //============================================== // function // メニューの表示 // parameter // なし // return // なし //============================================== void DispMenu() { fprintf(stderr, “\x1b[6;1H\x1b[0J”); fprintf(stderr, “%c:quit %c:メッセージ送信 : “, CMD_QUIT_CHAR, CMD_SEND_MSG_CHAR); } #if 0 //============================================== // function // キーボード入力(改行まで)の取得 // 入力がないときはすぐにリターン // parameter // LPSTR pszString [in/out]データ格納用バッファ // int iSize [in]バッファのサイズ // return // 0:入力なし CMD_QUIT_CHAR:終了 CMD_SEND_MSG_CHAR:メッセージ送信 //============================================== int GetKeyString(LPSTR pszString, int iSize) { BOOL fRet = FALSE; int iRet = 0; if (kbhit()) { switch (getchar()) { case CMD_QUIT_CHAR: iRet = CMD_QUIT_CHAR; break; case CMD_SEND_MSG_CHAR: fprintf(stderr, “\nInput Msg : “); fgets(pszString, iSize, stdin); if (strlen(pszString) > 0) { // 改行まで読み込んでいるので if (pszString[strlen(pszString) – 1] == ‘\n’) pszString[strlen(pszString) – 1] = 0; iRet = CMD_SEND_MSG_CHAR; } break; default: break; } } return(iRet); } #else // リモートデバッグではttyじゃないのでkbhitは使えない // 標準入力は0なのでselectのままでOKですが、練習のためにpollに int GetKeyString(LPSTR pszString, int iSize) { BOOL fRet = FALSE; int iRet = 0; pollfd fds[1] = {0}; fds[0].fd = 0; // 0:標準入力 1:標準出力 2:標準エラー fds[0].events = POLLIN; // Enterキー入力を対象に poll(fds, 1, 10); if (fds[0].revents & POLLIN) // ENTERキー入力があった { memset(pszString, 0, iSize); read(0, pszString, iSize); switch (pszString[0]) { case CMD_QUIT_CHAR: iRet = CMD_QUIT_CHAR; break; case CMD_SEND_MSG_CHAR: while (1) { poll(fds, 1, 10); if (fds[0].revents & POLLIN) read(0, pszString, iSize); else break; } fprintf(stderr, “\n送信メッセージを入力してください : “); memset(pszString, 0, iSize); read(0, pszString, iSize); // ENTERキーまで読み込む if (strlen(pszString) > 0) { // 改行まで読み込んでいるので if (pszString[strlen(pszString) – 1] == ‘\n’) pszString[strlen(pszString) – 1] = 0; iRet = CMD_SEND_MSG_CHAR; } break; default: break; } } return(iRet); } #endif //============================================== // function // 切断済みのCSendRecvThreadを破棄する // parameter // なし // return // TRUE:破棄した/FALSE:破棄すべきものがなかった //============================================== BOOL KillZombei() { BOOL fRet = FALSE; if (m_pCSendRecvThread != NULL) { if (m_pCSendRecvThread->IsZombie() == TRUE) { m_pCSendRecvThread->End(); m_pCSendRecvThread->WaitForEnd(); SAFE_DELETE(m_pCSendRecvThread); DestroySocket(m_pConInfo->fdClient); SAFE_FREE(m_pConInfo) fRet = TRUE; } } return(fRet); } //==============================================———————— // function // ★接続に使用されるインターフェースインデックスを調べる // parameter // addrinfo *pAddr [in]接続に使用されるアドレス情報 // return // TRUE/FALSE //==============================================———————— BOOL ShowInterfaceIndex(addrinfo *pAddr) { BOOL fRet = FALSE; sockaddr_in6 addr6; if (pAddr->ai_family == AF_INET6) { memcpy(&addr6, pAddr->ai_addr, sizeof(sockaddr_in6)); fprintf(stderr, “\x1b[1;1H\x1b[0J”); fprintf(stderr, “Index:%d\n”, addr6.sin6_scope_id); fRet = TRUE; } return(fRet); } //==============================================———————— // function // ★PATH_MTUを調べる // このオプションは処理系によって未対応なのでIPV6_PATHMTUの定義有無で判断する // 通常はPATH_MTU=1500Bytesと想定しデータは1024Bytesにすれば問題ない // parameter // SOCKET fd [in]接続済みソケット // int iFamily [in]アドレスファミリ(AF_INET/AF_INET6) // return // TRUE/FALSE //==============================================———————— BOOL ShowPathMTU(SOCKET fd, int iFamily) { BOOL fRet = FALSE; #ifdef IPV6_PATHMTU if (iFamily == AF_INET6) { struct ip6_mtuinfo mtuinfo; socklen_t infolen = sizeof(mtuinfo); int iRet = getsockopt(fd, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &infolen); if (iRet == 0) { fprintf(stderr, “\x1b[4;1H\x1b[0J”); fprintf(stderr, “PATH MTU %d\n”, mtuinfo.ip6m_mtu); fRet = TRUE; } } #endif return(fRet); } //==============================================———————— // function // ★HopLimit(IPv6)/TTL(IPv4)を設定する // parameter // SOCKET fd [in]接続済みソケット // int iFamily [in]アドレスファミリ(AF_INET/AF_INET6) // int iHopLimit [in](HopLimit – 1)台のルータを超えることができる // return // TRUE/FALSE //==============================================———————— BOOL SetHopLimit(SOCKET fd, int iFamily, int iLimit) { BOOL fRet = FALSE; int iRet; DWORD dwHoplimit; socklen_t iOptlen; if (iFamily == AF_INET6) { // 現在の値を取得して表示する iOptlen = sizeof(dwHoplimit); iRet = getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&dwHoplimit, &iOptlen); if (iRet == 0) { fprintf(stderr, “\x1b[2;1H\x1b[0J”); fprintf(stderr, “HopLimitOrg %d\n”, dwHoplimit); } // 新しい値を設定する dwHoplimit = iLimit; iOptlen = sizeof(dwHoplimit); iRet = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&dwHoplimit, iOptlen); if (iRet == 0) { // 設定後の値を表示する iRet = getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&dwHoplimit, &iOptlen); if (iRet == 0) { fprintf(stderr, “\x1b[3;1H\x1b[0J”); fprintf(stderr, “HopLimitNew %d\n”, dwHoplimit); fRet = TRUE; } } } else if (iFamily == AF_INET) { // 現在の値を取得して表示する iOptlen = sizeof(dwHoplimit); iRet = getsockopt(fd, IPPROTO_IP, IP_TTL, (char *)&dwHoplimit, &iOptlen); if (iRet == 0) { fprintf(stderr, “\x1b[2;1H\x1b[0J”); fprintf(stderr, “TTL %d\n”, dwHoplimit); } // 新しい値を設定する dwHoplimit = iLimit; iOptlen = sizeof(dwHoplimit); iRet = setsockopt(fd, IPPROTO_IP, IP_TTL, (char *)&dwHoplimit, iOptlen); if (iRet == 0) { // 設定後の値を表示する iRet = getsockopt(fd, IPPROTO_IP, IP_TTL, (char *)&dwHoplimit, &iOptlen); if (iRet == 0) { fprintf(stderr, “\x1b[3;1H\x1b[0J”); fprintf(stderr, “TTLNew %d\n”, dwHoplimit); fRet = TRUE; } } } return(fRet); }
それでは、動かしてみましょう
SimpleServerMChat(Linux版, Windows版)と今作成したSimpleClientMEcho(Linux版)で
やってみましょう
先ずはSimpleServerMChat(Linux版)→SimpleClientMEcho(Linux版)
$ ./SimpleClientPollMEcho ::1 10000 Index:0 HopLimitOrg 64 HopLimitNew 10 PATH MTU 65536 q:quit m:メッセージ送信 :
$ ./SimpleServerMChat 10000 CreateAndBindSocket() 3 2 2 10 4 10 2 10 DoListen() DoAccept() ::1 DoWork()
サーバとクライアントを同一のRaspberryPiで実行したので
アドレスとしてループバック(::1)を指定してみました。
インターフェースを指定していないのでインターフェースインデックスは
デフォルトの0と表示されます。(ループバックやグローバルアドレスの場合
インターフェースをしてしなくても接続できます)
ホップリミットは変更前が64で10に変更されています。
パスMTUは65536、同一のRaspberryPiではWORD一杯まで大丈夫ですね
今度はSimpleServerMChat(Linux版)→SimpleClientMEcho(Windows版)
$ ./SimpleClientPollMEcho
fe80::7985:ee4e:75:1c54%eth0 10000
Index:2
HopLimitOrg 64
HopLimitNew 10
PATH MTU 1500
q:quit m:メッセージ送信 :
C:\SimpleServerMChat.exe 10000 CreateAndBindSocket() 188 23 2 23 192 2 2 23 DoListen() DoAccept() fe80::581c:f2d9:e72f:9b7a%7 DoWork()
eth0を指定、インターフェースインデックスが2と表示されます。
ホップリミットは変更前が64で10に変更されています。
(リンクローカルアドレスの場合、インターフェースの指定は必須です)
パスMTUは1500、これはイーサネットのMTUですね
今回作成したSimpleClientMEcho(Windows版, Linux版)のプロジェクトです。
Windows版については、ESCシーケンスによる画面制御が動作するように
しました(Windows10標準のコマンドプロンプトで動作します)