如何获取(Linux)机器的 IP 地址?
Posted
技术标签:
【中文标题】如何获取(Linux)机器的 IP 地址?【英文标题】:How can I get the IP address of a (Linux) machine? 【发布时间】:2010-09-17 18:31:41 【问题描述】:这个问题和之前问的How can I get the IP Address of a local computer?-Question 差不多。但是我需要找到 Linux 机器的 IP 地址。
那么:我如何 - 在 C++ 中以编程方式 - 检测运行我的应用程序的 linux 服务器的 IP 地址。服务器将至少有两个 IP 地址,我需要一个特定的 IP 地址(给定网络中的一个(公共网络))。
我确信有一个简单的函数可以做到这一点 - 但在哪里?
为了让事情更清楚一点:
服务器显然会有“localhost”:127.0.0.1 服务器将有一个内部(管理)IP 地址:172.16.x.x 服务器将有一个外部(公共)IP 地址:80.190.x.x我需要找到外部 IP 地址来将我的应用程序绑定到它。显然我也可以绑定到 INADDR_ANY (实际上这就是我现在所做的)。不过,我更愿意检测公共地址。
【问题讨论】:
为什么要标记 ifconfig 的 popen?这确实是正确的做法。库会发生变化,但 ifconfig 和 popen 会一直存在。 否,ifconfig
、route
等已被 ip
命令弃用。现在你应该改用它。
ifconfig 今天仍然适用于比任何其他方法更多的架构。因此,将其更改为 ip 命令。何时/如果合适。 popen 仍然是更好的解决方案。
【参考方案1】:
我发现 os x 上的 ioctl 解决方案有问题(它符合 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 感谢您带来这种好方法而不是那些无聊的 ioctl!但是,您对 IP6 的处理仍然不正确 - 您应该转换为 sockaddr_in6,即类似于tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
@Andrey,谢谢。我已经更新了我的答案(虽然还没有机会测试它)
在我的例子中提供lo
和wlan0
接口。我需要的是有互联网连接的wlan0
或eth0
。那么,我应该使用strcmp
还是更好的方法?【参考方案2】:
-
创建一个套接字。
执行
ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);
阅读/usr/include/linux/if.h
以获取有关ifconf
和ifreq
结构的信息。这应该为您提供系统上每个接口的 IP 地址。另请阅读 /usr/include/linux/sockios.h
了解其他 ioctl。
【讨论】:
我的印象是它只会给你它绑定的接口的地址。 @MattJoiner 不,看一下手册页,SIOCGIFCONF 返回所有接口的信息。此处引用的手册页的一部分:“返回接口(传输层)地址列表。这目前仅表示 AF_INET (IPv4) 系列的地址以实现兼容性。用户将 ifconf 结构作为参数传递给 ioctl。它包含一个指向 ifc_req 中的 ifreq 结构数组的指针及其在 ifc_len 中的长度(以字节为单位)。内核用所有当前正在运行的 L3 接口地址填充 ifreqs"【参考方案3】:我喜欢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);
【讨论】:
这给了我私有 ip 127.*.*.* 。也许是因为我落后于 NAT?【参考方案4】:正如您所发现的,没有单一的“本地 IP 地址”这样的东西。以下是查找可以发送到特定主机的本地地址的方法。
-
创建 UDP 套接字
将套接字连接到外部地址(最终将接收本地地址的主机)
使用getsockname获取本地地址
【讨论】:
我从来没有这样做过,但我喜欢它。它自动使用操作系统路由表,比扫描接口列表容易得多。 这假设您知道外部地址是什么。数据中心通常不是这种情况。 您总是可以尝试分配到不同大陆的三个(IANA 让这很容易)并接受三个中最好的两个。【参考方案5】:这具有适用于多种风格的 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-programs 获取一些数据的许多情况下非常方便 虽然 ifconfig 输出了所需的数据,但我强烈反对以这种方式使用任何程序。实际进入 ifconfig 的源代码并获取功能比运行它并解析其输出要好得多。 @MartinMeeser :通过分别查找“inet”然后“:”使其语言中立。 那么你有其他语言,甚至不是基于拉丁语的字符集。解决方案是不使用命令工具,因为它们像ifconfig
一样被弃用。你现在应该使用ip
。生成语言中性输出的正确方法是将LANG
环境变量设置为C
,如:LANG=C ls -l
谢谢。这是一个简单而出色的解决方案!【参考方案6】:
我认为您的问题没有明确的正确答案。相反,有很多方法可以接近你想要的。因此,我将提供一些提示如何完成它。
如果一台机器有超过 2 个接口(@987654324@ 计为一个),您将无法轻松地自动检测正确的接口。以下是一些关于如何做到这一点的食谱。
问题是,例如,如果主机位于 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://***.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)等。
这个额外的列表可能是无穷无尽的,而且总是不完整的,因为每个网络设备制造商都在忙于发明如何自动检测自己的设备的新安全漏洞。这通常对如何检测一些不应该存在的公共接口有很大帮助。
'纳夫说。出去。
【讨论】:
【参考方案7】:除了 Steve Baker 所说的,您可以在netdevice(7) man page 中找到对 SIOCGIFCONF ioctl 的描述。
一旦您获得了主机上所有 IP 地址的列表,您将不得不使用特定于应用程序的逻辑来过滤掉您不想要的地址,并希望您只剩下一个 IP 地址。
【讨论】:
【参考方案8】:不要硬编码:这是可以改变的事情。许多程序通过读入配置文件并执行任何操作来确定要绑定的内容。这样,如果您的程序将来某个时候需要绑定到不是公共 IP 的东西,它就可以这样做。
【讨论】:
【参考方案9】:由于问题指定了 Linux,我最喜欢的发现机器 IP 地址的技术是使用 netlink。通过创建协议 NETLINK_ROUTE 的 netlink 套接字并发送 RTM_GETADDR,您的应用程序将收到包含所有可用 IP 地址的消息。 here 提供了一个示例。
为了简单地处理部分消息,libmnl 很方便。如果您想了解更多关于 NETLINK_ROUTE 的不同选项(以及如何解析它们)的信息,最好的来源是 iproute2 的source code(尤其是监控应用程序)以及内核中的接收函数。 rtnetlink 的手册页也包含有用的信息。
【讨论】:
【参考方案10】:您如何只使用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
【讨论】:
【参考方案11】:您可以与 curl 进行一些集成,就像:curl www.whatismyip.org
从 shell 中获取您的全局 IP。你有点依赖一些外部服务器,但如果你在 NAT 后面,你将永远如此。
【讨论】:
我不明白为什么更多的人不使用这个网站。他们甚至有一个机器可读的页面,因此您不必筛选信息。 如果您在代理内部操作,您可能无法获得正确的 IP。 icanhazip.com 返回 IP,仅此而已。非常适合嵌入 bash 脚本! ipinfo.io/ip 是另一种选择。如果您 curl ipinfo.io,它还会以 JSON 格式返回主机名、地理位置信息等。【参考方案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();
【讨论】:
这是一个沉重的、仅限 Windows(关于 linux 的问题)、格式错误的解决方案,它依赖于通过脆弱的网站内容解析而不受本地控制的服务。找不到这个“解决方案”的任何积极方面。 哈! html 解析甚至会寻找一个注释,该注释是关于正在执行的操作的警告。以上是关于如何获取(Linux)机器的 IP 地址?的主要内容,如果未能解决你的问题,请参考以下文章