101

This Question is almost the same as the previously asked How can I get the IP Address of a local computer? -Question. However I need to find the IP address(es) of a Linux Machine.

So: How do I - programmatically in C++ - detect the IP addresses of the linux server my application is running on. The servers will have at least two IP addresses and I need a specific one (the one in a given network (the public one)).

I'm sure there is a simple function to do that - but where?


To make things a bit clearer:

  • The server will obviously have the "localhost": 127.0.0.1
  • The server will have an internal (management) IP address: 172.16.x.x
  • The server will have an external (public) IP address: 80.190.x.x

I need to find the external IP address to bind my application to it. Obviously I can also bind to INADDR_ANY (and actually that's what I do at the moment). I would prefer to detect the public address, though.

Nimantha
  • 6,405
  • 6
  • 28
  • 69
BlaM
  • 28,465
  • 32
  • 91
  • 105
  • 3
    Why mark down the popen of ifconfig? It's really the right thing to do. Libraries change, but ifconfig and popen will always be there. – Erik Aronesty May 18 '11 at 13:40
  • 3
    No, `ifconfig`, `route` etc are deprecated for the `ip` command. Now you should use that instead. – Anders Sep 12 '14 at 22:58
  • ifconfig still, today, works on more architectures than any other approach. So change it to the ip command.when/if appropriate. popen is still the better solution. – Erik Aronesty Sep 13 '16 at 12:54

12 Answers12

117

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;
}
Josh Kelley
  • 56,064
  • 19
  • 146
  • 246
Twelve47
  • 3,924
  • 3
  • 22
  • 29
  • 7
    +1 and thanks for bringing this nice approach instead of those boring ioctl's! However, your handling of IP6 is still incorrect - you should cast to sockaddr_in6, i.e. something like `tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;` – Andrey Oct 14 '11 at 10:04
  • 2
    @Andrey, thanks. I've updated my answer (haven't had a chance to test it though) – Twelve47 Oct 17 '11 at 11:14
  • 2
    gives `lo` and `wlan0` interfaces in my case. What I require is `wlan0` or `eth0` which has internet connection.So, Should I use `strcmp` or thre's a better way? – Sudip Bhattarai Feb 27 '16 at 07:14
28
  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.

Vinay Shukla
  • 1,818
  • 13
  • 41
Steve Baker
  • 4,323
  • 1
  • 20
  • 15
  • I was under the impression it will only give you the address of the interface it binds to. – Matt Joiner Mar 12 '11 at 07:06
  • @MattJoiner No, take a look at the man page, SIOCGIFCONF returns info on all interfaces. Part of the man page quoted here: "Return a list of interface (transport layer) addresses. This currently means only addresses of the AF_INET (IPv4) family for compatibility. The user passes a ifconf structure as argument to the ioctl. It contains a pointer to an array of ifreq structures in ifc_req and its length in bytes in ifc_len. The kernel fills the ifreqs with all current L3 interface addresses that are running" – Stéphane Mar 26 '17 at 00:44
26

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);
}
Community
  • 1
  • 1
4dan
  • 1,033
  • 10
  • 14
15

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
jjvainio
  • 571
  • 1
  • 5
  • 7
  • I've never done it this way but I like it. It uses the OS routing table automatically and is much easier than scanning the interface list. – Zan Lynx Oct 21 '08 at 00:09
  • 5
    This assumes that you know what an outside address will be. This is often not the case in data centers. – Christopher Jun 14 '10 at 20:25
  • You could always try three that are assigned to different continents (IANA makes this easy) and accept best two out of three. – Joshua Nov 09 '10 at 04:09
9

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;
}
Erik Aronesty
  • 11,620
  • 5
  • 64
  • 44
  • thank you for this solution, works great for me and comes very handy in many situations when you just want to get some data from cl-programs – zitroneneis Apr 09 '12 at 15:43
  • Although ifconfig outputs the required data, I would strongly discourage using any program this way. It's much better to actually get into ifconfig's source and get the functionality than run it and parse its output. – Zaur Nasibov Jun 12 '12 at 11:52
  • @zitroneneis : c programmers sometimes forget to think like perl programmers.... and vice versa. – Erik Aronesty Nov 05 '12 at 16:06
  • This does not work on non-English linuxs, e.g. in German debian output of "inet addr" is "inet Adresse" instead. – Martin Meeser Nov 18 '13 at 11:29
  • One of my linux colleagues just gave me this: `/sbin/ifconfig eth1 | grep \"inet addr\" | cut -d':' -f2 | cut -d' ' -f1`. Of course this just moves the "parsing" to the console and language problem still exists. – Martin Meeser Nov 18 '13 at 11:34
  • 1
    @MartinMeeser : made it language neutral, by looking for "inet " then ":" separately. – Erik Aronesty Jan 22 '14 at 16:09
  • 1
    Then you have other languages which isn't even Latin based character set. The sollution is to not use command tools, as they get deprecated like `ifconfig`. You should use `ip` now. The *proper* way of generate language neutral output is to set `LANG` environment variable to `C`, like: `LANG=C ls -l` – Anders Sep 12 '14 at 23:03
  • `sh: 1: ifconfig: not found` on Debian. – gsamaras Aug 05 '15 at 16:11
  • Change it to the "ip" command then, not that hard. System internals change faster than command lines. – Erik Aronesty Sep 13 '16 at 12:55
  • A new version or mix versions on your environment will disrupt parse scripts. Never a good advise to parse the text response: always for humans. So many rewrites finally teach me the hard way. – fcm Aug 28 '17 at 03:09
  • New system library versions will also disrupt stuff. I agree... go ahead and use a library call if a good cross-platform POSIX one exists. But damned if that *isnt* the case for lots of things you may need. – Erik Aronesty Oct 05 '17 at 13:05
  • 1
    Thanks. It is an easy and awesome solution! – Ahmad Afkande Aug 11 '20 at 05:24
6

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.

Community
  • 1
  • 1
Tino
  • 9,583
  • 5
  • 55
  • 60
5

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.

camh
  • 40,988
  • 13
  • 62
  • 70
4

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.

Thanatos
  • 42,585
  • 14
  • 91
  • 146
2

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.

Kristian Evensen
  • 1,315
  • 13
  • 14
1

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

PYK
  • 3,674
  • 29
  • 17
0

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.

  • I never understand why more people don't use this site. They even have a machine readable page so you don't have to screen scrap the information. – deft_code Aug 29 '09 at 00:12
  • 1
    You may not get correct IP if you are operating inside proxy. – Jack Jul 12 '10 at 05:38
  • 1
    http://icanhazip.com returns the IP and nothing more. Perfect for embedding in a bash script! – lukecyca Mar 16 '11 at 20:42
  • http://ipinfo.io/ip is another alternative. If you curl http://ipinfo.io it also returns the hostname, geolocation info and more in JSON format. – Ben Dowling Jul 06 '13 at 02:41
-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();
}

Zoe
  • 27,060
  • 21
  • 118
  • 148
Chaza
  • 1
  • 13
    This is a heavy, windows-only (question about linux), badly formatted solution relying on services out of local control via fragile parsing of website content. Can't find any positive side of this "solution". – viraptor May 18 '11 at 13:38
  • 1
    Hah! The HTML parsing even looks for a comment that is a warning about exactly what is being done. – notlesh Apr 01 '15 at 18:23