如何在C语言中获取我的非回环网络IP地址?

3

为了两个主机之间的通信,我需要将我的主机IP地址发送到另一个站点。问题在于,如果我请求我的IP地址,可能会返回本地环回IP地址(127.x.x.x),而不是网络(以太网)IP地址。

我使用以下代码:

char myhostname[32];


gethostname(myhostname, 32);
hp = gethostbyname(myhostname);
unsigned my_ip = *(unsigned*)(hp->h_addr);

if( (my_ip % 256) == 127) {
  /* Wrong IP adress as it's 127.x.x.x */
  printf("Error, local IP address!");
  return;
}

唯一的解决方法是确保我的主机名在/etc/hosts中位于真实网络地址之后,而不是本地环回地址(例如Ubuntu的默认设置)。
是否有一种方法可以在不依赖/etc/hosts内容的情况下解决这个问题?
编辑:我更改了上面的代码,所以它使用了getaddrinfo,但我仍然得到回送设备的编号(127.0,0,1),而不是真正的IP地址。
struct addrinfo hint = {0};
struct addrinfo *aip = NULL;
unsigned ip = 0;
struct sockaddr_in *sinp = NULL;

hint.ai_family = AF_INET; /* IPv4 */
hint.ai_socktype = SOCK_STREAM;

if(getaddrinfo(hostname, NULL, &hint, &aip) != 0) {
    return 0;
}
sinp = (struct sockaddr_in *) aip->ai_addr;
ip   = *(unsigned *) &sinp->sin_addr;

我曾经得到过一个包含三个addrinfo的列表,其中包括SOCK_STREAM、SOCK_DGRAM和SOCK_RAW,但提示信息阻止了这种情况。所以我的问题仍然存在...

2
gethostbyname已经被弃用多年(其中一个原因是它只能使用一个地址族)。正如qrdl所提到的,您应该使用getaddrinfo。 - bortzmeyer
好的,谢谢提供信息。这段原始代码已经有12年历史了。 - Roalt
10个回答

9

有一个名为getaddrinfo()的POSIX函数,它返回给定主机名的地址链表,因此您只需要浏览该列表并查找非回环地址。

请参见man getaddrinfo


4

这不是答案,但是一个相关的评论:请注意,一旦您开始在数据包内容中发送寻址信息,您就面临着使应用程序无法通过NAT:ing路由器和/或防火墙工作的风险。

这些技术依赖于IP数据包头中的信息来跟踪流量,如果应用程序在数据包内交换寻址信息,在此检查时仍然不可见,则可能会导致故障。

当然,这可能与您的应用程序完全无关,但在这种情况下我认为值得指出。


是的,这并不重要,因为它总是在本地局域网上运行,但是你提到的确与更大规模的应用程序相关。 - Roalt
小心:代码有一种超越您最初意图的长久存在方式。只要意识到总有一天,会有人想要在本地局域网之外运行它。(这可能没有任何意义,但他们会想这么做)。 - Michael Kohne

4

发送的数据包中会包含源地址信息,无需重复传输。这个信息是在接受远程主机通信时获得的(参见Beej网络编程指南,特别是有关accept()部分)。


3
我遇到了这样的情况:当只有/etc/hosts中有信息时,我使用getaddrinfo获取IP地址列表时,每次都返回127.0.0.1。结果发现,主机名被别名为localhost...这是很容易忽视的。具体情况如下:
/etc/hosts文件内容如下:
127.0.0.1 localhost.localdomain localhost foo
::1 localhost6.localdomain6 localhost6
172.16.1.248 foo
172.16.1.249 bie
172.16.1.250 bletch
现在,当你调用host="foo"的getaddrinfo函数时,它会3次返回127.0.0.1。错误在于,foo既出现在“127.0.0.1”行上,也出现在“172.16.1.248”行上。一旦我从“127.0.0.1”行中删除了foo,一切都正常了。
希望对某些人有所帮助。

是的,那很可能就是问题所在,我也自己发现了。你的系统应该确保主机名设置为正确的IP地址。因此,如果没有以太网连接,可能还可以正常运行,但一旦添加了额外的连接,系统就应该更改/etc/hosts文件。 - Roalt

1
看这里: 编程方式发现公共IP 请注意,在某些情况下,计算机可能具有多个非环回IP地址,在这种情况下,该问题的答案告诉您如何获取暴露在互联网上的那个IP地址。

我需要从C语言中获取它,而不是从Shell脚本或Java中。 - Roalt
至少目前为止,被接受的答案仍然相关 - 你可以从 C 访问那个网站。 - David Z

0

你已经接近成功了。我不确定你是如何从 hp 中获取 my_ip 的。

gethostbyname() 返回一个指向 hostent 结构体的指针,该结构体具有一个 h_addr_list 字段。

h_addr_list 字段是一个以 null 结尾的 IP 地址列表,列出了绑定到该主机的所有 IP 地址。

我认为你获取到回环地址是因为它是 h_addr_list 中的第一个条目。

编辑:应该像这样工作:

gethostname(myhostname, 32);
hp = gethostbyname(myhostname);
unsigned my_ip = *(unsigned*)(hp->h_addr);

for (int i = 0; hp->h_addr_list[i] != 0; ++i) {
  if (hp->h_addr_list[i] != INADDR_LOOPBACK) {
    // hp->addr_list[i] is a non-loopback address
  }
}
// no address found

自1997年4月(RFC 2133发布)以来,gethostbyname已被弃用。 - bortzmeyer
我添加了一行代码,从hp中获取了我的IP地址,现在已经更新到原始问题中。 - Roalt

0
如果/etc/hosts仍然存在且没有更改,查找所有h_addr_list条目是无济于事的。

你的意思是,在/etc/hosts文件中应该或不应该放置什么?问题在于,在我们的正常系统(RHEL)中,主机名没有放置在127.0.0.1条目之后(而是真实的eth0地址),但在像Ubuntu这样的系统中,他们将主机名放在127.0.0.1条目之后。 - Roalt
尝试删除(如果不确定,请重命名)文件/ etc / hosts,然后您将直接获得eth0 IP,如果这对您可行。可能是最糟糕的选择,但如果您只需要担心一台机器... - gc .

0

你的新代码硬编码了IPv4(在hint.ai_family字段中),这是一个糟糕的想法。

除此之外,你已经接近成功了,只需要循环遍历getaddrinfo的结果就可以了。你的代码只获取了第一个IP地址,但是有一个aip->ai_next字段可以跟随...

struct addrinfo {
       ...
       struct addrinfo *ai_next;       /* next structure in linked list */
};

在getaddrinfo结构体后设置断点,并浏览ai_next并不能揭示所有不同的IP地址,我只能得到本地地址或真实以太网地址,这取决于/etc/hosts中主机名的放置位置。 - Roalt
正如我在评论中所说,算法本身就有缺陷。我的回答只是为了解释如何实现这个有缺陷的算法 :-) - bortzmeyer

0
即使计算机只有一个物理网络接口(这可能是正确的假设,也可能不是,即使是上网本也有两个 - 以太网和 WLAN),VPN 可以添加更多 IP 地址。无论如何,另一端的主机应该能够确定您的主机用于联系它的 IP。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接