获取机器的IP地址


94

此问题与先前询问的获取本地计算机的IP地址-问题几乎相同。但是我需要找到Linux机器的IP地址。

所以:我如何-以编程方式在C ++中 -检测运行我的应用程序的linux服务器的IP地址。这些服务器将至少具有两个IP地址,而我需要一个特定的IP地址(给定网络中的一个IP地址(公共地址))。

我敢肯定有一个简单的功能可以做到这一点-但在哪里?


为了使事情更清楚:

  • 该服务器显然将具有“ localhost”:127.0.0.1
  • 服务器将具有一个内部(管理)IP地址:172.16.xx
  • 服务器将具有一个外部(公共)IP地址:80.190.xx

我需要找到外部IP地址以将我的应用程序绑定到该IP地址。显然,我也可以绑定到INADDR_ANY(实际上,这就是我目前所做的)。不过,我希望检测一下公共地址。


3
为什么要记下ifconfig的popen?这确实是正确的事情。库会发生变化,但是ifconfig和popen将始终存在。
Erik Aronesty,2011年

3
不建议使用ifconfigroute等等ip。现在,您应该使用它。
Anders 2014年

如今,ifconfig仍在比其他方法更多的体系结构上工作。因此,如果需要,请将其更改为ip命令。popen仍然是更好的解决方案。
埃里克·阿隆斯蒂

Answers:


104

我发现ioctl解决方案在os x上是有问题的(这是POSIX兼容的,因此应该类似于linux)。但是getifaddress()可以让您轻松地执行相同的操作,它在os x 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;
}

7
+1,感谢您提供这种不错的方法,而不是那些无聊的ioctl!但是,您对IP6的处理仍然不正确-您应该转换为sockaddr_in6,例如tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
Andrey

2
@安德烈,谢谢。我已经更新了答案(虽然没有机会进行测试)
2011年

2
在我的情况下给出lowlan0接口。我需要的是哪个wlan0eth0具有互联网连接的。那么,我应该使用strcmp还是更好的方法?
Sudip Bhattarai

28
  1. 创建一个套接字。
  2. 执行 ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);

阅读/usr/include/linux/if.h有关ifconfifreq结构的信息。这应该为您提供系统上每个接口的IP地址。另请阅读/usr/include/linux/sockios.h其他ioctl。


我的印象是它只会为您提供绑定到的接口的地址。
马特·乔纳

@MattJoiner不,请看一下手册页,SIOCGIFCONF返回所有接口上的信息。手册页的部分在此处引用:“返回接口(传输层)地址列表。当前,这仅表示出于兼容性考虑的AF_INET(IPv4)系列地址。用户将ifconf结构作为参数传递给ioctl。它包含一个。指针ifreq结构的在ifc_req阵列及在字节ifc_len长度内核填充ifreqs与所有正在运行的当前L3接口地址”
斯特凡

25

我喜欢jjvainio的回答。正如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);
}

2
这给我私有IP 127。*。*。*。也许是因为我落后于NAT?
eugene

14

如您所知,没有一个“本地IP地址”之类的东西。这是查找可以发送到特定主机的本地地址的方法。

  1. 创建一个UDP套接字
  2. 将套接字连接到外部地址(最终将接收本地地址的主机)
  3. 使用getsockname获取本地地址

我从来没有这样做过,但我喜欢。它自动使用OS路由表,并且比扫描接口列表要容易得多。
Zan Lynx

5
假设您知道外部地址是什么。在数据中心通常不是这种情况。
Christopher 2010年

您总是可以尝试分配给不同大洲的三个(IANA可以轻松实现这一点),并接受三分之二的最佳选择。
约书亚

9

这具有可处理多种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;
}

谢谢您的解决方案,对我来说非常有用,并且在许多情况下非常有用,当您只想从cl程序中获取一些数据时
zitroneneis 2012年

尽管ifconfig输出了所需的数据,但是我强烈不鼓励以这种方式使用任何程序。实际进入ifconfig的源并获得功能比运行它并解析其输出要好得多。
Zaur Nasibov 2012年

1
@MartinMeeser:通过分别查找“ inet”和“:”使其语言中立。
Erik Aronesty 2014年

1
然后,您还有其他甚至不是基于拉丁字符集的语言。解决方案是不使用命令工具,因为它们已被弃用ifconfig。您ip现在应该使用。生成语言中立输出的正确方法是将LANG环境变量设置为C,例如:LANG=C ls -l
Anders

1
谢谢。这是一个简单而出色的解决方案!
艾哈迈德·阿夫坎德

5

我认为您的问题没有确定的正确答案。相反,有很多方法可以使您接近自己想要的东西。因此,我将提供一些提示如何完成它。

如果一台机器有两个以上的接口(lo算作一个),您将很难自动检测正确的接口。这里有一些如何做的食谱。

例如,问题在于主机是否位于NAT防火墙后面的DMZ中,该防火墙将公共IP更改为某些私有IP并转发请求。您的计算机可能有10个接口,但是只有一个对应于公共接口。

如果您使用双NAT,则即使自动检测也不起作用,在这种情况下,防火墙甚至会将源IP转换为完全不同的内容。因此,您甚至无法确定默认路由会导致您的接口带有公共接口。

通过默认路由检测到

这是我推荐的自动检测事物的方法

喜欢的东西ip r get 1.1.1.1通常会告诉你它具有默认路由的接口。

如果要用自己喜欢的脚本/编程语言重新创建它,请使用 strace ip r get 1.1.1.1并遵循黄色的砖砌路。

设置它 /etc/hosts

如果您想保持控制,这是我的建议

您可以在/etc/hostslike中创建一个条目

80.190.1.3 publicinterfaceip

然后,您可以使用该别名publicinterfaceip来引用您的公共接口。

可悲的是haproxy,IPv6并没有解决这个问题

使用环境

这是一个很好的解决方法/etc/hosts,以防您不root

与相同/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一样

醉酒的男人想把自己淹死在酒精中的方式

您可以尝试枚举网络上的所有UPnP网关,并以此方式为某些“外部”事物找到正确的路由。甚至可能在默认路由未指向的路由上。

有关此内容的更多信息,请参见https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol

即使您的默认路由指向其他位置,也可以给您一个很好的印象,即哪个是您真正的公共接口。

还有更多

山与先知相遇的地方

IPv6路由器会自我宣传,为您提供正确的IPv6前缀。查看前缀可以提示您它是否具有某些内部IP或全局IP。

您可以侦听IGMP或IBGP帧,以找到合适的网关。

IP地址少于2 ^ 32。因此,在局域网上仅对它们全部执行ping操作并不需要很长时间。从您的角度来看,这可以为您提供有关Internet大部分位置的统计提示。但是,您应该比著​​名的https://de.wikipedia.org/wiki/SQL_Slammer更加明智。

ICMP甚至ARP是网络边带信息的良好来源。它也可能对您有帮助。

您可以使用以太网广播地址来联系通常会有所帮助的所有网络基础设施设备,例如DHCP(甚至DHCPv6)等等。

这个额外的列表可能是无止境的,而且总是不完整的,因为每个网络设备制造商都在忙于发明如何自动检测自己的设备的新安全漏洞。这通常对如何检测不应该存在的公共接口很有帮助。

纳夫说。出来


4

除了Steve Baker所说的那样,您还可以在netdevice(7)手册页中找到SIOCGIFCONF ioctl的描述。

一旦获得了主机上所有IP地址的列表,就必须使用特定于应用程序的逻辑来过滤掉不需要的地址,并希望剩下一个IP地址。


4

不要对其进行硬编码:这是可以改变的事情。许多程序通过读取配置文件并执行任何说明来找出要绑定的内容。这样,如果您的程序在将来的某个时候需要绑定到非公共IP上,则可以这样做。


2

正如问题指定的是Linux一样,发现机器IP地址的最喜欢的技术是使用netlink。通过创建协议NETLINK_ROUTE的netlink套接字并发送RTM_GETADDR,您的应用程序将收到一条包含所有可用IP地址的消息。这里提供一个例子

为了简单地处理部分消息,libmnl很方便。如果您对NETLINK_ROUTE的不同选项(以及它们的解析方式)有更多的了解,最好的来源是iproute2 的源代码(尤其是监视应用程序)以及内核中的接收函数。rtnetlink的手册页还包含有用的信息。


1

您可以轻松地与curl进行一些集成:curl www.whatismyip.org从shell中获取全局IP。您有点依赖某些外部服务器,但是如果您使用的是NAT,那么您永远都是这样。


我不明白为什么更多的人不使用该网站。他们甚至具有机器可读的页面,因此您不必在屏幕上抓取信息。
deft_code,2009年

1
如果在代理内部进行操作,则可能无法获得正确的IP。
杰克

1
icanhazip.com返回IP,仅此而已。完美嵌入bash脚本中!
lukecyca 2011年

ipinfo.io/ip是另一种选择。如果您卷曲ipinfo.io,它还会以JSON格式返回主机名,地理位置信息等。
Ben Dowling


-10

// Use a HTTP request to a well known server that echo's back the public IP address void GetPublicIP(CString & csIP) { // Initialize COM bool bInit = false; if (SUCCEEDED(CoInitialize(NULL))) { // COM was initialized bInit = true;

    // Create a HTTP request object
    MSXML2::IXMLHTTPRequestPtr HTTPRequest;
    HRESULT hr = HTTPRequest.CreateInstance("MSXML2.XMLHTTP");
    if (SUCCEEDED(hr))
    {
        // Build a request to a web site that returns the public IP address
        VARIANT Async;
        Async.vt = VT_BOOL;
        Async.boolVal = VARIANT_FALSE;
        CComBSTR ccbRequest = L"http://whatismyipaddress.com/";

        // Open the request
        if (SUCCEEDED(HTTPRequest->raw_open(L"GET",ccbRequest,Async)))
        {
            // Send the request
            if (SUCCEEDED(HTTPRequest->raw_send()))
            {
                // Get the response
                CString csRequest = HTTPRequest->GetresponseText();

                // Parse the IP address
                CString csMarker = "<!-- contact us before using a script to get your IP address -->";
                int iPos = csRequest.Find(csMarker);
                if (iPos == -1)
                    return;
                iPos += csMarker.GetLength();
                int iPos2 = csRequest.Find(csMarker,iPos);
                if (iPos2 == -1)
                    return;

                // Build the IP address
                int nCount = iPos2 - iPos;
                csIP = csRequest.Mid(iPos,nCount);
            }
        }
    }
}

// Unitialize COM
if (bInit)
    CoUninitialize();

}


10
这是一个笨重的,仅Windows(关于linux的问题),格式不正确的解决方案,它依赖于通过脆弱的网站内容解析而不受本地控制的服务。找不到该“解决方案”的任何积极方面。
维普托

1
哈哈!HTML解析甚至会寻找注释,以警告正在执行的操作。
notlesh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.