web-dev-qa-db-ja.com

マシンのIPアドレスを取得します

この質問は、以前に尋ねられた質問とほぼ同じです ローカルコンピューターのIPアドレスを取得する -質問。ただし、Linux MachineのIPアドレスを見つける必要があります。

だから:どのように-プログラムでC++で-アプリケーションが実行されているLinuxサーバーのIPアドレスを検出します。サーバーには少なくとも2つのIPアドレスがあり、特定のアドレス(特定のネットワーク内のアドレス(パブリックアドレス))が必要です。

私はそれを行う簡単な関数があると確信しています-しかし、どこですか?


物事を少し明確にするために:

  • サーバーには明らかに「localhost」があります:127.0.0.1
  • サーバーの内部(管理)IPアドレスは172.16.x.xです。
  • サーバーの外部(パブリック)IPアドレスは80.190.x.xです。

アプリケーションをバインドするために外部IPアドレスを見つける必要があります。明らかにINADDR_ANYにバインドすることもできます(実際、それが現時点で行っていることです)。ただし、パブリックアドレスを検出したいと思います。

90
BlaM

Os xでioctlソリューションに問題があることがわかりました(POSIXに準拠しているため、Linuxに似ているはずです)。ただし、getifaddress()を使用すると同じことが簡単にできます。osx 10.5では問題なく機能し、以下でも同じようになります。

マシンのすべてのIPv4アドレスを出力する簡単な例を以下に示しました(getifaddrsが成功したこと、つまり0を返すことも確認する必要があります)。

IPv6アドレスも表示するように更新しました。

#include <stdio.h>      
#include <sys/types.h>
#include <ifaddrs.h>
#include <netinet/in.h> 
#include <string.h> 
#include <arpa/inet.h>

int main (int argc, const char * argv[]) {
    struct ifaddrs * ifAddrStruct=NULL;
    struct ifaddrs * ifa=NULL;
    void * tmpAddrPtr=NULL;

    getifaddrs(&ifAddrStruct);

    for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
        if (!ifa->ifa_addr) {
            continue;
        }
        if (ifa->ifa_addr->sa_family == AF_INET) { // check it is IP4
            // is a valid IP4 Address
            tmpAddrPtr=&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
            char addressBuffer[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
            printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer); 
        } else if (ifa->ifa_addr->sa_family == AF_INET6) { // check it is IP6
            // is a valid IP6 Address
            tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
            char addressBuffer[INET6_ADDRSTRLEN];
            inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
            printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer); 
        } 
    }
    if (ifAddrStruct!=NULL) freeifaddrs(ifAddrStruct);
    return 0;
}
99
Twelve47
  1. ソケットを作成します。
  2. ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);を実行します

ifconfおよびifreq構造体については、/usr/include/linux/if.hをお読みください。これにより、システム上の各インターフェイスのIPアドレスがわかります。追加のioctlについては、/usr/include/linux/sockios.hも参照してください。

28
Steve Baker

jjvainio's answer が好きです。 Zan Lnyxが言うように、ローカルルーティングテーブルを使用して、特定の外部ホストへの接続に使用されるイーサネットインターフェースのIPアドレスを検索します。接続されたUDPソケットを使用すると、実際にパケットを送信せずに情報を取得できます。このアプローチでは、特定の外部ホストを選択する必要があります。ほとんどの場合、よく知られているパブリックIPでトリックを行う必要があります。この目的のためにGoogleのパブリックDNSサーバーアドレス8.8.8.8が好きですが、別の外部ホストIPを選択したい場合があります。完全なアプローチを示すコードを次に示します。

void GetPrimaryIp(char* buffer, size_t buflen) 
{
    assert(buflen >= 16);

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    assert(sock != -1);

    const char* kGoogleDnsIp = "8.8.8.8";
    uint16_t kDnsPort = 53;
    struct sockaddr_in serv;
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_addr.s_addr = inet_addr(kGoogleDnsIp);
    serv.sin_port = htons(kDnsPort);

    int err = connect(sock, (const sockaddr*) &serv, sizeof(serv));
    assert(err != -1);

    sockaddr_in name;
    socklen_t namelen = sizeof(name);
    err = getsockname(sock, (sockaddr*) &name, &namelen);
    assert(err != -1);

    const char* p = inet_ntop(AF_INET, &name.sin_addr, buffer, buflen);
    assert(p);

    close(sock);
}
24
4dan

あなたが知っているように、単一の「ローカルIPアドレス」のようなものはありません。特定のホストに送信できるローカルアドレスを確認する方法は次のとおりです。

  1. UDPソケットを作成する
  2. ソケットを外部アドレス(最終的にローカルアドレスを受信するホスト)に接続します
  3. Getsocknameを使用してローカルアドレスを取得します
14
jjvainio

これには、unixの多くのフレーバーで作業するという利点があります...そして、任意のo/sで作業するためにそれを簡単に変更できます。上記のすべてのソリューションでは、月の満ち欠けに応じてコンパイラエラーが発生します。良いPOSIXの方法がある瞬間...これを使用しないでください(これが書かれた時点ではそうではありませんでした)。

// ifconfig | Perl -ne 'print "$1\n" if /inet addr:([\d.]+)/'

#include <stdlib.h>

int main() {
        setenv("LANG","C",1);
        FILE * fp = popen("ifconfig", "r");
        if (fp) {
                char *p=NULL, *e; size_t n;
                while ((getline(&p, &n, fp) > 0) && p) {
                   if (p = strstr(p, "inet ")) {
                        p+=5;
                        if (p = strchr(p, ':')) {
                            ++p;
                            if (e = strchr(p, ' ')) {
                                 *e='\0';
                                 printf("%s\n", p);
                            }
                        }
                   }
              }
        }
        pclose(fp);
        return 0;
}
8
Erik Aronesty

Steve Bakerが言ったことに加えて、SIOCGIFCONF ioctlの説明は netdevice(7)manページ にあります。

ホスト上のすべてのIPアドレスのリストを取得したら、アプリケーション固有のロジックを使用して、不要なアドレスを除外し、1つのIPアドレスが残っていることを望みます。

4
camh

ハードコーディングしないでください。これは、変更可能なものです。多くのプログラムは、設定ファイルを読み込んで、何でも言うことをすることによって、何にバインドするかを決定します。このようにして、将来、プログラムがパブリックIPではない何かにバインドする必要がある場合、バインドできます。

4
Thanatos

あなたの質問に対する決定的な正解があるとは思いません。代わりに、希望に近づける方法の大きな束があります。それゆえ、私はそれを成し遂げる方法のいくつかのヒントを提供します。

マシンに3つ以上のインターフェースがある場合(loは1つとしてカウントされます)、適切なインターフェースを簡単に自動検出するのに問題があります。以下に、その方法に関するいくつかのレシピを示します。

たとえば、問題は、ホストがDMZファイアウォールの背後にあるNAT内にあり、パブリックIPをプライベートIPに変更して要求を転送する場合です。マシンには10個のインターフェースがありますが、公開インターフェースに対応するインターフェースは1つだけです。

ダブルNATを使用している場合、ファイアウォールがソースIPをまったく異なるものに変換する場合でも、自動検出は機能しません。そのため、デフォルトルートがパブリックインターフェースを備えたインターフェースにつながっているかどうかさえ確信できません。

デフォルトルートを介して検出する

これは物事を自動検出するための私の推奨方法です

ip r get 1.1.1.1のようなものは通常、デフォルトルートを持つインターフェイスを示します。

お気に入りのスクリプト/プログラミング言語でこれを再作成する場合は、strace ip r get 1.1.1.1を使用し、黄色いレンガの道を進んでください。

/etc/hostsで設定します

あなたがコントロールを維持したい場合、これは私の推奨事項です

/etc/hostsのようなエントリを作成できます

80.190.1.3 publicinterfaceip

次に、このエイリアスpublicinterfaceipを使用して、パブリックインターフェイスを参照できます。

悲しいことにhaproxyはIPv6でこのトリックを理解しません

環境を使用する

これは、rootではない場合の/etc/hostsの適切な回避策です。

/etc/hostsと同じです。ただし、この環境を使用してください。これには/etc/profileまたは~/.profileを試すことができます。

したがって、プログラムで変数MYPUBLICIPが必要な場合は、次のようなコードを含めることができます(これはCです。気軽にC++を作成できます)。

#define MYPUBLICIPENVVAR "MYPUBLICIP"

const char *mypublicip = getenv(MYPUBLICIPENVVAR);

if (!mypublicip) { fprintf(stderr, "please set environment variable %s\n", MYPUBLICIPENVVAR); exit(3); }

そのため、スクリプト/プログラムを/path/to/your/scriptのように呼び出すことができます

MYPUBLICIP=80.190.1.3 /path/to/your/script

これはcrontabでも機能します。

すべてのインターフェイスを列挙し、不要なインターフェイスを排除します

ipを使用できない場合の必死の方法

望ましくないことがわかっている場合は、すべてのインターフェイスを列挙し、すべての誤ったインターフェイスを無視できます。

ここですでに答えがあるようです https://stackoverflow.com/a/265978/490291 このアプローチに対して。

DLNAのように実行する

酒にhimselfれようとする酒に酔った男の道

ネットワーク上のすべてのUPnPゲートウェイを列挙しようとすると、何らかの方法で「外部」の適切なルートを見つけることができます。これは、デフォルトルートが指し示していないルート上にある場合もあります。

詳細については、おそらく https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol を参照してください。

これにより、デフォルトルートが他の場所を指している場合でも、どれが実際のパブリックインターフェースであるかがよくわかります。

さらにあります

山が預言者と出会う場所

IPv6ルーターは自分自身をアドバタイズして、正しいIPv6プレフィックスを提供します。プレフィックスを見ると、内部IPがあるかグローバルIPであるかがわかります。

IGMPまたはIBGPフレームをリッスンして、適切なゲートウェイを見つけることができます。

IPアドレスが2 ^ 32未満です。したがって、LANですべてのpingを実行するだけで長くかかりません。これにより、インターネットの大部分があなたの観点からどこにあるかについての統計的なヒントが得られます。ただし、有名な https://de.wikipedia.org/wiki/SQL_Slammer よりも賢明である必要があります

ICMPおよびARPでさえ、ネットワークサイドバンド情報の優れたソースです。それもあなたを助けるかもしれません。

イーサネットブロードキャストアドレスを使用して、DHCP(DHCPv6を含む)などの多くの場合に役立つすべてのネットワークインフラストラクチャデバイスに接続できます。

ネットワークデバイスのすべてのメーカーは、自身のデバイスを自動検出する方法に関する新しいセキュリティホールを大々的に発明しているため、この追加リストはおそらく無限で常に不完全です。これは、パブリックインターフェイスが存在しない場合に、パブリックインターフェイスを検出する方法に役立ちます。

'言っ途切れる。でる。

4
Tino

次のように簡単にcurlと統合できます:シェルのcurl www.whatismyip.orgは、グローバルIPを取得します。外部サーバーにある程度依存していますが、NATの背後にいる場合は常にそうなります。

1
Zeist

質問ではLinuxを指定しているため、マシンのIPアドレスを検出するための私のお気に入りの手法は、netlinkを使用することです。プロトコルNETLINK_ROUTEのnetlinkソケットを作成し、RTM_GETADDRを送信すると、アプリケーションは使用可能なすべてのIPアドレスを含むメッセージを受信します。例が提供されています here

メッセージ処理の一部を単純にするには、libmnlが便利です。 NETLINK_ROUTEのさまざまなオプション(およびそれらがどのように解析されるか)を理解することに興味がある場合、最適なソースは、iproute2(特にモニターアプリケーション)の ソースコード と受信機能です。カーネル内。 rtnetlink のmanページにも有用な情報が含まれています。

1

Linux向けのエレガントなスクリプトソリューションは次の場所にあります。 http://www.damnsmalllinux.org/f/topic-3-23-17031-0.html

0
Dave Sieving