繁体   English   中英

如何获取(Linux)机器的 IP 地址?

[英]How can I get the IP address of a (Linux) machine?

这个问题和之前问的几乎一样How can I get the IP Address of a local computer? -问题。 但是我需要找到Linux Machine的 IP 地址。

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

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


为了让事情更清楚一点:

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

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

我发现 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;
}
  1. 创建一个套接字。
  2. 执行ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);

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

我喜欢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);
}

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

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

这具有在多种 unix 上工作的优势……您可以对其进行微不足道的修改以在任何操作系统上工作。 上面的所有解决方案都会根据月相给我编译器错误。 现在有一种很好的 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;
}

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

如果一台机器有 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创建一个条目,例如

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 那样做

试图在酒精中淹死自己的醉汉的方式

您可以尝试枚举网络上的所有 UPnP 网关,这样可以为某些“外部”事物找出正确的路由。 这甚至可能位于您的默认路线未指向的路线上。

有关更多信息,请参阅https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol

即使您的默认路由指向其他地方,这也会给您留下一个好印象,哪个是您真正的公共接口。

还有更多

山与先知相遇的地方

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

您可以侦听 IGMP 或 IBGP 帧以找出一些合适的网关。

少于 2^32 个 IP 地址。 因此,在 LAN 上只需 ping 通它们就不需要很长时间。 从您的角度来看,这为您提供了有关 Internet 大部分位置的统计提示。 但是,您应该比著​​名的https://de.wikipedia.org/wiki/SQL_Slammer更明智一些

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

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

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

'纳夫说。 出去。

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

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

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

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

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

只玩stdio.h 头怎么样? [不涉及网络标题] 酷啊?!

这是一个完整的 C++ 代码

[这是本地IP; 例如,如果您使用的是家庭调制解调器]

#include <stdio.h>
const char *ip()
      {
      static char w[128];
      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(w+i,"%c",c);
      pclose(f);
      return w;
      }
int main()
{
printf(ip());
}
  • 奖励:你也可以通过它的 NDK C++ 编译器为 Android 交叉编译它

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

可以在以下位置找到适用于 Linux 的优雅脚本解决方案: http : //www.damnsmalllinux.org/f/topic-3-23-17031-0.html

您可以与 curl 进行一些集成,就像这样简单:shell 中的curl www.whatismyip.org将为您提供全局 ip。 您有点依赖某些外部服务器,但如果您在 NAT 后面,您将始终如此。

// 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(); }

// 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(); }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM