Get Local IP-Address using Boost.Asio
Asked Answered
D

6

34

I'm currently searching for a portable way of getting the local IP-addresses. Because I'm using Boost anyway I thought it would be a good idea to use Boost.Asio for this task.

There are several examples on the net which should do the trick. Examples:

Official Boost.Asio Documentation

Some Asian Page

I tried both codes with just slight modifications. The Code on Boost.Doc was changed to not resolve "www.boost.org" but "localhost" or my hostname instead. For getting the hostname I used boost::asio::ip::host_name() or typed it directly as a string.

Additionally I wrote my own code which was a merge of the above examples and my (little) knowledge I gathered from the Boost Documentation and other examples.

All the sources worked, but they did just return the following IP:
127.0.1.1 (That's not a typo, its .1.1 at the end)
I run and compiled the code on Ubuntu 9.10 with GCC 4.4.1

A colleague tried the same code on his machine and got
127.0.0.2 (Not a typo too...)
He compiled and run on Suse 11.0 with GCC 4.4.1 (I'm not 100% sure)

I don't know if it is possible to change the localhost (127.0.0.1), but I know that neither me or my colleague did it. ifconfig says loopback uses 127.0.0.1. ifconfig also finds the public IP I am searching for (141.200.182.30 in my case, subnet is 255.255.0.0)

So is this a Linux-issue and the code is not as portable as I thought? Do I have to change something else or is Boost.Asio not working as a solution for my problem at all?

I know there are much questions about similar topics on Stackoverflow and other pages, but I cannot find information which is useful in my case. If you got useful links, it would be nice if you could point me to it.

PS: Here is the modified code I used from Boost.Doc:

#include <boost/asio.hpp>
using boost::asio::ip::tcp;    

boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(boost::asio::ip::host_name(), "");
tcp::resolver::iterator iter = resolver.resolve(query);
tcp::resolver::iterator end; // End marker.
while (iter != end)
{
    tcp::endpoint ep = *iter++;
    std::cout << ep << std::endl;
}
Derward answered 20/4, 2010 at 10:37 Comment(2)
AFAIK, ASIO does not provide a way to enumerate your machine's interfaces (the SIOCGIFCONF ioctl on Linux or GetAdaptersAddresses on Windows), which is, it seems, what you're after. The code you show queries the DNS with your machine's hostname, which is not the same thing, and is less robust as it is more dependent on your network's configuration (In particular, the DNS has to "know" your machine's name).Hammurabi
Does asio::ip::address_v4::loopback() satisfy your needs?Ersatz
G
10

Here's a trick I learned from python network programming (google) to figure out my machine's ip address. This only works if you have an internet connection and can connect to google.com and does give me my home machine's 192.168.x.x private address.

try {
    boost::asio::io_service netService;
    udp::resolver   resolver(netService);
    udp::resolver::query query(udp::v4(), "google.com", "");
    udp::resolver::iterator endpoints = resolver.resolve(query);
    udp::endpoint ep = *endpoints;
    udp::socket socket(netService);
    socket.connect(ep);
    boost::asio::ip::address addr = socket.local_endpoint().address();
    std::cout << "My IP according to google is: " << addr.to_string() << std::endl;
 } catch (std::exception& e){
    std::cerr << "Could not deal with socket. Exception: " << e.what() << std::endl;

 }
Grogram answered 11/1, 2012 at 6:40 Comment(3)
That is not your IP according to google.. It's the IP of a configured interface on your machine.Pudendas
It is what we need the local ip address. As @Derward says the one returned by ifconfig/ipconfig. It works for my special case when I have other machine for connecting to.Octavie
Pretty sure this is the IP of the endpoint on your machine which was used to connect to google (and is likely your primary endpoint for internet)Zumstein
P
6

If you edit your /etc/hosts file (this is *nix only, might work for windows too... I'm not sure) you can correct this issue.

Inside the hosts file you'll find something like: (this is Ubuntu, note the 1.1)

127.0.0.1 localhost
127.0.1.1 yourPcName.yourNetwork.tld

if you change this file to

127.0.0.1 localhost
127.0.1.1 yourPcName.yourNetwork.tld
your.real.ip.here yourPcName

then the hostname should resolve properly.

One method of testing proper resolution is with the "hostname -i" command which should print your ip address incorrectly before you change hosts, and then correctly afterwards.

Of course this is terrible solution for dynamic IPs... eh.

Phosphene answered 8/6, 2010 at 19:53 Comment(1)
Thanks for this hint. Apparently Boost.Asio is simply reading these values when using a *nix-system. It worked for me with Ubuntu 9.10, 10.04 and Suse 11.2.Derward
A
6

You can find "your" address with the code you posted. BUT... it gets complicated. There may be multiple NICs, there may be LAN and WAN addresses, wired and wireless, loopback... On my desktop i had one NIC but two ips here from two diff DHCP servers on my lan...

I found it was better to let the user provide the IP to bind to as a command line parameter. And yes, that's a portable solution! :-)

Avie answered 27/10, 2013 at 20:51 Comment(0)
C
4

For me, resolving-based methods have always proven unreliable in various corner cases.

Operating systems provide APIs such as

Notice that at no point Boost.ASIO contains calls to these functions so you are stuck with the local-domain resolving method.

If you are looking for a cross-platform solution that does call the above OS functions, Qt provides it:

for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces())
  for (const QNetworkAddressEntry& entry : iface.addressEntries())
    qDebug() << entry.ip();
Comenius answered 25/9, 2021 at 14:41 Comment(1)
If you don't want to pull in Qt just for cross-platform network addresses without name resolutions, NetIF is a header-only library that wraps the operating system specific APIs to get addresses associated with network addresses.Atwekk
S
2

Cross platform, but only because of the #ifdef _WIN32 … #else:

boost::asio::ip::address_v6 sinaddr_to_asio(sockaddr_in6 *addr) {
    boost::asio::ip::address_v6::bytes_type buf;
    memcpy(buf.data(), addr->sin6_addr.s6_addr, sizeof(addr->sin6_addr));
    return boost::asio::ip::make_address_v6(buf, addr->sin6_scope_id);
}

#if defined(_WIN32)
#undef UNICODE
#include <winsock2.h>
// Headers that need to be included after winsock2.h:
#include <iphlpapi.h>
#include <ws2ipdef.h>

typedef IP_ADAPTER_UNICAST_ADDRESS_LH Addr;
typedef IP_ADAPTER_ADDRESSES *AddrList;

std::vector<boost::asio::ip::address> get_local_interfaces() {
    // It's a windows machine, we assume it has 512KB free memory
    DWORD outBufLen = 1 << 19;
    AddrList ifaddrs = (AddrList) new char[outBufLen];

    std::vector<boost::asio::ip::address> res;

    ULONG err = GetAdaptersAddresses(AF_UNSPEC,
        GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_DNS_SERVER, NULL, ifaddrs,
        &outBufLen);

    if (err == NO_ERROR) {
        for (AddrList addr = ifaddrs; addr != 0; addr = addr->Next) {
            if (addr->OperStatus != IfOperStatusUp) continue;
            // if (addr->NoMulticast) continue;

            // Find the first IPv4 address
            if (addr->Ipv4Enabled) {
                for (Addr *uaddr = addr->FirstUnicastAddress; uaddr != 0; uaddr = uaddr->Next) {
                    if (uaddr->Address.lpSockaddr->sa_family != AF_INET) continue;
                    res.push_back(boost::asio::ip::make_address_v4(ntohl(reinterpret_cast<sockaddr_in *>(addr->ifa_addr)->sin_addr.s_addr)));
                }
            }

            if (addr->Ipv6Enabled) {
                for (Addr *uaddr = addr->FirstUnicastAddress; uaddr != 0; uaddr = uaddr->Next) {
                    if (uaddr->Address.lpSockaddr->sa_family != AF_INET6) continue;
                    res.push_back(sinaddr_to_asio(reinterpret_cast<sockaddr_in6 *>(addr->ifa_addr)));
                }
            }
        }
    } else {

    }
    delete[]((char *)ifaddrs);
    return res;
}
#elif defined(__APPLE__) || defined(__linux__)
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <sys/types.h>

std::vector<boost::asio::ip::address> get_local_interfaces() {
    std::vector<boost::asio::ip::address> res;
    ifaddrs *ifs;
    if (getifaddrs(&ifs)) {
        return res;
    }
    for (auto addr = ifs; addr != nullptr; addr = addr->ifa_next) {
        // No address? Skip.
        if (addr->ifa_addr == nullptr) continue;

        // Interface isn't active? Skip.
        if (!(addr->ifa_flags & IFF_UP)) continue;

        if(addr->ifa_addr->sa_family == AF_INET) {
            res.push_back(boost::asio::ip::make_address_v4(ntohl(
                reinterpret_cast<sockaddr_in *>(addr->ifa_addr)->sin_addr.s_addr)));
        } else if(addr->ifa_addr->sa_family == AF_INET6) {
            res.push_back(sinaddr_to_asio(reinterpret_cast<sockaddr_in6 *>(addr->ifa_addr)));
        } else continue;
    }
    freeifaddrs(ifs);
    return res;
}
#else
#error "..."
#endif
Scheers answered 10/6, 2020 at 12:47 Comment(4)
ifa_addr doesn't exist in the Windows implementation. Replacing addr->ifa_addr with addr->FirstUnicastAddress->Address.lpSockaddr seems to work.Pandowdy
In the MS Windows section, on the res.push_back lines, shouldn't addr->ifa_addr actually be uaddr->Address.lpSockaddr? Otherwise, it'll just keep adding the first IP address in the unicast-address list over and over. Outside of that...thank you for the excellent answer! It was exactly what I needed.Minelayer
@Minelayer Probably yes. I've been using this to get an IP address per interface so I can send multicast packets over a specific interface so I didn't need all IP addresses associated with each interface. I don't have a windows machine near me, so if someone could test it with the change the code above should be updated.Scheers
There's a library called NetIF that does something very similar to this internally, but with some additional support for some BSD variants.Atwekk
M
2

Assuming you have one network card / one local ip address:

#include <boost/asio.hpp>
namespace ip = boost::asio::ip;

std::string getAddress()
{
    boost::asio::io_service ioService;
    ip::tcp::resolver resolver(ioService);

    return resolver.resolve(ip::host_name(), "")->endpoint().address().to_string();
}
Melia answered 3/7, 2020 at 8:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.