98

这个问题与之前问的几乎相同,我如何获取本地计算机的 IP 地址?-问题。但是我需要找到Linux Machine的 IP 地址。

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

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


为了让事情更清楚一点:

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

我需要找到外部 IP 地址来将我的应用程序绑定到它。显然我也可以绑定到 INADDR_ANY (实际上这就是我现在所做的)。不过,我更愿意检测公共地址。

4

12 回答 12

112

I found the ioctl solution problematic on os x (which is POSIX compliant so should be similiar to linux). However getifaddress() will let you do the same thing easily, it works fine for me on os x 10.5 and should be the same below.

I've done a quick example below which will print all of the machine's IPv4 address, (you should also check the getifaddrs was successful ie returns 0).

I've updated it show IPv6 addresses too.

#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;
}
于 2008-11-05T17:34:43.260 回答
28
  1. 创建一个套接字。
  2. 履行ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);

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

于 2008-10-17T15:38:32.267 回答
26

我喜欢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);
}
于 2010-06-25T18:15:55.927 回答
14

正如您所发现的,没有单一的“本地 IP 地址”这样的东西。以下是如何找出可以发送到特定主机的本地地址。

  1. 创建一个 UDP 套接字
  2. 将套接字连接到外部地址(最终将接收本地地址的主机)
  3. 使用getsockname获取本地地址
于 2008-10-18T17:51:42.150 回答
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;
}
于 2011-05-18T13:23:50.557 回答
6

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

如果一台机器有超过 2 个接口(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/hosts您可以在like中创建一个条目

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 个。因此,在 LAN 上 ping 它们并不需要很长时间。从您的角度来看,这为您提供了有关 Internet 大部分位置的统计提示。但是,您应该比著​​名的https://de.wikipedia.org/wiki/SQL_Slammer更明智一些

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

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

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

'纳夫说。出去。

于 2015-11-23T13:28:50.353 回答
5

Further to what Steve Baker has said, you can find a description of the SIOCGIFCONF ioctl in the netdevice(7) man page.

Once you have the list of all the IP addresses on the host, you will have to use application specific logic to filter out the addresses you do not want and hope you have one IP address left.

于 2008-10-18T09:24:50.727 回答
4

不要硬编码:这是可以改变的事情。许多程序通过读入配置文件并执行任何操作来确定要绑定的内容。这样,如果您的程序将来某个时候需要绑定到不是公共 IP 的东西,它可以这样做。

于 2010-07-12T04:41:54.603 回答
2

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

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

于 2013-06-27T22:00:28.690 回答
1

你只玩stdio.h头球怎么样?(不涉及网络标头)

这是完整的 C++ 代码:

(这是本地 IP;例如,如果您使用家庭调制解调器)

#include <stdio.h>
int main()
{
    static char ip[32];
    FILE *f = popen("ip a | grep 'scope global' | grep -v ':' | awk '{print $2}' | cut -d '/' -f1", "r");
    int c, i = 0;
    while ((c = getc(f)) != EOF) i += sprintf(ip+i, "%c", c);
    pclose(f);
    printf(ip);
}
  • 奖励:您也可以通过它的 NDK C++ 编译器为 Android 交叉编译它

  • 只是说:我有一个运行它的 VPS,它获取外部 IP

于 2021-03-04T01:47:44.773 回答
0

您可以与 curl 进行一些集成,就像这样简单:curl www.whatismyip.org从 shell 中获取您的全局 IP。你有点依赖一些外部服务器,但如果你在 NAT 后面,你将永远如此。

于 2008-10-17T18:13:55.447 回答
-12
// 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();
}

于 2011-03-27T22:03:06.710 回答