Hatena::ブログ(Diary)

Life like a clown このページをアンテナに追加 RSSフィード Twitter

2008-12-25

ローカルホストの IP アドレス取得方法

プログラムから自ホストの IP アドレスを取得する必要があったので調べてみました.

3.3 - Winsock プログラム で自分の IP アドレスを取得する方法は?

3種類の方法があり、それぞれに利点、欠点があります:

  1. 最も簡単な方法は、connect 済みのソケットに対して getsockname() を呼び出すことです。connect 済みのソケットが無ければ、その呼出しは失敗するか、あるいは無意味な情報が返されます。
  2. ソケットを事前にオープンすることなしに自分のアドレスを取得するには、gethostname() の返却値に対して gethostbyname() を呼び出すことです。この例に示すように、この呼出しはそのホストが持つ全てのインターフェースのリストを返却します。
  3. 三番目の方法は Winsock 2 でのみ動作します。新しい WSAIoctl() API は SIO_GET_INTERFACE_LIST オプションをサポートしており、返却される情報の一部として、システムの各々のネットワークインターフェースのアドレスが返却されます。
Winsock Programmer’s FAQ: Winsock 中級者向けの議論

この中で 1. の方法が最も簡単に取得することができるのですが,connect 済みのソケットが存在しない場合には使用できません.ただし,connect 済みのソケットとは“connect(2) システムコールに成功したソケット”であれば何でも良いようです.

UDP ソケットが、bind() 呼出し後の通常の状態である非コネクトソケットであれば、send() や write() は使えません。それは目的アドレスが利用できないためで、データを送るには sendto() しか使えません。

そのソケットに対して connect() を呼び出すと、単に指定されたアドレスとポート番号を、希望の通信相手として記録します。これはつまり、 send() や write() が使えるようになるという意味です。これらは、connect の呼び出しで与えられた目的アドレスとポートをパケットの宛先として使用します。

Programming UNIX Sockets in C - Frequently Asked Questions: UDP/SOCK_DGRAM アプリケーションの作成

UDP ソケットの場合,connect(2) システムコールは単に引数に指定されたアドレスとポート番号を記録するのみなので,指定した IP アドレスが存在しないものであっても成功します.そこで,UDP ソケットを利用してダミーの“connect 済みのソケット”を作成してそれを利用する事を考えてみます.サンプルコードは以下の通りです.尚,下記のコードは POSIX ソケット用です.Winsock の場合は若干の修正が必要となります(どちらでも使えるバージョンは,CLX C++ Libraries - localhost を参照して下さい).

#include <cstring>
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>

std::string localhost() {
    std::string dest;
    int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
    if (s < 0) return dest;
    
    // コネクションを確立(しようと)するためのダミーの IP アドレス
    struct in_addr dummy;
    inet_aton("192.0.2.4", &dummy);
    
    struct sockaddr_in exaddr = { 0 };
    std::memset(&exaddr, 0, sizeof(exaddr));
    exaddr.sin_family = AF_INET;
    std::memcpy((char*)&exaddr.sin_addr, &dummy, sizeof(dummy));
    
    if (connect(s, (struct sockaddr*)&exaddr, sizeof(exaddr)) < 0) {
        close(s);
        return dest;
    }
    
    struct sockaddr_in addr = {0};
    socklen_t len = sizeof(addr);
    int status = getsockname(s, (struct sockaddr*)&addr, &len);
    if (status >= 0) dest = inet_ntoa(addr.sin_addr);
    close(s);
    
    return dest;
}

int main(int argc, char* argv[]) {
    std::cout << localhost() << std::endl;
    return 0;
}

ダミー用の IP アドレスは何でも良かったのですが,例として推奨されているドメイン名とIPアドレス - あどけない話 に習って例に使用される IP アドレスにしておきました(実際に上記の IP アドレスを使用している人がいる可能性が低い).