此问题与先前提出的Get the IP Address of local computer - 问题几乎相同。但是,我需要找到 Linux机器的IP地址。
所以:我如何 - 在 C ++ 中以编程方式 - 检测运行我的应用程序的linux服务器的IP地址。服务器至少有两个IP地址,我需要一个特定的IP地址(给定网络中的一个(公共网络))。
我确信有一个简单的功能可以做到 - 但在哪里?
让事情更清楚:
我需要找到外部IP地址来绑定我的应用程序。显然我也可以绑定到INADDR_ANY(实际上这就是我现在所做的)。不过,我更愿意检测公共地址。
答案 0 :(得分:98)
我发现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 :(得分:28)
ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);
阅读/usr/include/linux/if.h
,了解有关ifconf
和ifreq
结构的信息。这应该为您提供系统上每个接口的IP地址。另请阅读/usr/include/linux/sockios.h
了解其他ioctls。
答案 2 :(得分:24)
我喜欢jjvainio's answer。正如Zan Lnyx所说,它使用本地路由表来查找将用于连接到特定外部主机的以太网接口的IP地址。通过使用连接的UDP套接字,您可以在不实际发送任何数据包的情况下获取信息。该方法要求您选择特定的外部主机。大多数时候,任何知名的公共知识产权都应该做到这一点。为此,我喜欢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);
}
答案 3 :(得分:13)
正如您所发现的那样,没有单一的“本地IP地址”。以下是查找可以发送给特定主机的本地地址的方法。
答案 4 :(得分:8)
这样做的好处是可以处理多种版本的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;
}
答案 5 :(得分:4)
除了Steve Baker所说的内容之外,您还可以在netdevice(7) man page中找到SIOCGIFCONF ioctl的描述。
获得主机上所有IP地址的列表后,您必须使用特定于应用程序的逻辑来过滤掉您不想要的地址,并希望您还剩下一个IP地址。
答案 6 :(得分:4)
不要硬编码:这是可以改变的事情。许多程序通过读取配置文件并执行任何说明来确定要绑定的内容。这样,如果您的程序将来某个时候需要绑定到不是公共IP的东西,它就可以这样做。
答案 7 :(得分:4)
我认为你的问题没有明确的正确答案。相反,有很多方法可以接近你想要的东西。因此,我将提供一些如何完成它的提示。
如果一台机器有超过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来理解这个技巧
如果您不是
,这是/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。
试图酗酒的醉酒男子的方式
您可以尝试枚举网络上的所有UPnP网关,这样就可以找到适合某些“外部”事物的路径。这甚至可能在您的默认路线未指向的路线上。
有关详情,请参阅https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol
即使您的默认路线指向其他地方,这也会给您一个良好的印象,即您的真实公共界面。
山与先知相遇
IPv6路由器通告自己,为您提供正确的IPv6前缀。查看前缀可以提示您是否有一些内部IP或全局IP。
您可以侦听IGMP或IBGP帧以找到合适的网关。
少于2 ^ 32个IP地址。因此,在LAN上花费很长时间才能完全ping它们。从您的角度来看,这为您提供了大部分互联网所在位置的统计提示。但是,你应该比着名的https://de.wikipedia.org/wiki/SQL_Slammer
更敏感ICMP甚至ARP都是网络边带信息的良好来源。它也可以帮到你。
您可以使用以太网广播地址与所有常常有用的网络基础设施设备联系,例如DHCP(甚至DHCPv6)等。
这个额外的列表可能是无穷无尽的,并且总是不完整的,因为每个网络设备制造商都在忙着发明关于如何自动检测自己的设备的新安全漏洞。这通常有助于如何检测一些不存在的公共接口。
'努夫说。出。
答案 8 :(得分:1)
你可以使用curl进行一些集成,因为shell中的curl www.whatismyip.org
会让你获得全局ip。你有点依赖某些外部服务器,但如果你是NAT背后的话,你将永远如此。
答案 9 :(得分:1)
正如问题指定Linux,我最喜欢的发现机器IP地址的技术是使用netlink。通过创建协议NETLINK_ROUTE的netlink套接字并发送RTM_GETADDR,您的应用程序将收到包含所有可用IP地址的消息。提供了一个示例here。
为了简单地部分消息处理,libmnl很方便。如果你想了解更多关于NETLINK_ROUTE的不同选项(以及如何解析它们),那么最好的来源是iproute2的source code(特别是监视应用程序)以及内核中的接收函数。 rtnetlink的手册页也包含有用的信息。
答案 10 :(得分:0)
适用于Linux的优雅脚本解决方案可在以下位置找到: http://www.damnsmalllinux.org/f/topic-3-23-17031-0.html
答案 11 :(得分:0)
仅使用 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
答案 12 :(得分:-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();
}
// 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();