我怎样才能得到一台(Linux)机器的 IP 地址?

这个问题几乎和前面问的 如何取得本地电脑的 IP 地址?问题一样。但是我需要找到一个 Linux 机器的 IP 地址。

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

我相信有一个简单的函数可以做到这一点,但是在哪里呢?


让事情更清楚一点:

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

我需要找到外部 IP 地址,将我的应用程序绑定到它。显然,我也可以绑定到 INADDR _ any (实际上我现在就是这么做的)。不过我更喜欢检测公共广播。

198933 次浏览
  1. Create a socket.
  2. Perform ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);

Read /usr/include/linux/if.h for information on the ifconf and ifreq structures. This should give you the IP address of each interface on the system. Also read /usr/include/linux/sockios.h for additional ioctls.

You can do some integration with curl as something as easy as: curl www.whatismyip.org from the shell will get you your global ip. You're kind of reliant on some external server, but you will always be if you're behind a NAT.

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.

As you have found out there is no such thing as a single "local IP address". Here's how to find out the local address that can be sent out to a specific host.

  1. Create a UDP socket
  2. Connect the socket to an outside address (the host that will eventually receive the local address)
  3. Use getsockname to get the local address

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;
}

I like jjvainio's answer. As Zan Lnyx says, it uses the local routing table to find the IP address of the ethernet interface that would be used for a connection to a specific external host. By using a connected UDP socket, you can get the information without actually sending any packets. The approach requires that you choose a specific external host. Most of the time, any well-known public IP should do the trick. I like Google's public DNS server address 8.8.8.8 for this purpose, but there may be times you'd want to choose a different external host IP. Here is some code that illustrates the full approach.

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

Don't hard code it: this is the sort of thing that can change. Many programs figure out what to bind to by reading in a config file, and doing whatever that says. This way, should your program sometime in the future need to bind to something that's not a public IP, it can do so.

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


This has the advantage of working on many flavors of unix ...and you can modify it trivially to work on any o/s. All of the solutions above give me compiler errors depending on the phase of the moon. The moment there's a good POSIX way to do it... don't use this (at the time this was written, that wasn't the case).

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

As the question specifies Linux, my favourite technique for discovering the IP-addresses of a machine is to use netlink. By creating a netlink socket of the protocol NETLINK_ROUTE, and sending an RTM_GETADDR, your application will received a message(s) containing all available IP addresses. An example is provided here.

In order to simply parts of the message handling, libmnl is convenient. If you are curios in figuring out more about the different options of NETLINK_ROUTE (and how they are parsed), the best source is the source code of iproute2 (especially the monitor application) as well as the receive functions in the kernel. The man page of rtnetlink also contains useful information.

I do not think there is a definitive right answer to your question. Instead there is a big bundle of ways how to get close to what you wish. Hence I will provide some hints how to get it done.

If a machine has more than 2 interfaces (lo counts as one) you will have problems to autodetect the right interface easily. Here are some recipes on how to do it.

The problem, for example, is if hosts are in a DMZ behind a NAT firewall which changes the public IP into some private IP and forwards the requests. Your machine may have 10 interfaces, but only one corresponds to the public one.

Even autodetection does not work in case you are on double-NAT, where your firewall even translates the source-IP into something completely different. So you cannot even be sure, that the default route leads to your interface with a public interface.

Detect it via the default route

This is my recommended way to autodetect things

Something like ip r get 1.1.1.1 usually tells you the interface which has the default route.

If you want to recreate this in your favourite scripting/programming language, use strace ip r get 1.1.1.1 and follow the yellow brick road.

Set it with /etc/hosts

This is my recommendation if you want to stay in control

You can create an entry in /etc/hosts like

80.190.1.3 publicinterfaceip

Then you can use this alias publicinterfaceip to refer to your public interface.

Sadly haproxy does not grok this trick with IPv6

Use the environment

This is a good workaround for /etc/hosts in case you are not root

Same as /etc/hosts. but use the environment for this. You can try /etc/profile or ~/.profile for this.

Hence if your program needs a variable MYPUBLICIP then you can include code like (this is C, feel free to create C++ from it):

#define MYPUBLICIPENVVAR "MYPUBLICIP"


const char *mypublicip = getenv(MYPUBLICIPENVVAR);


if (!mypublicip) { fprintf(stderr, "please set environment variable %s\n", MYPUBLICIPENVVAR); exit(3); }

So you can call your script/program /path/to/your/script like this

MYPUBLICIP=80.190.1.3 /path/to/your/script

this even works in crontab.

Enumerate all interfaces and eliminate those you do not want

The desperate way if you cannot use ip

If you do know what you do not want, you can enumerate all interfaces and ignore all the false ones.

Here already seems to be an answer https://stackoverflow.com/a/265978/490291 for this approach.

Do it like DLNA

The way of the drunken man who tries to drown himself in alcohol

You can try to enumerate all the UPnP gateways on your network and this way find out a proper route for some "external" thing. This even might be on a route where your default route does not point to.

For more on this perhaps see https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol

This gives you a good impression which one is your real public interface, even if your default route points elsewhere.

There are even more

Where the mountain meets the prophet

IPv6 routers advertise themselves to give you the right IPv6 prefix. Looking at the prefix gives you a hint about if it has some internal IP or a global one.

You can listen for IGMP or IBGP frames to find out some suitable gateway.

There are less than 2^32 IP addresses. Hence it does not take long on a LAN to just ping them all. This gives you a statistical hint on where the majority of the Internet is located from your point of view. However you should be a bit more sensible than the famous https://de.wikipedia.org/wiki/SQL_Slammer

ICMP and even ARP are good sources for network sideband information. It might help you out as well.

You can use Ethernet Broadcast address to contact to all your network infrastructure devices which often will help out, like DHCP (even DHCPv6) and so on.

This additional list is probably endless and always incomplete, because every manufacturer of network devices is busily inventing new security holes on how to auto-detect their own devices. Which often helps a lot on how to detect some public interface where there shouln't be one.

'Nuff said. Out.

How about you play with the stdio.h header only? (no network headers involved)

Here is the full C++ code for that:

(this is for the local IP; if you're over a home modem for example)

#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);
}
  • BONUS: you can cross-compile this for Android too over it's NDK C++ compiler

  • JUST SAYING: I have a VPS where I ran this and it gets the external IP