tags:

views:

2010

answers:

9

Is there a way to detect IP address changes on the local machine in Linux programmatically using C++?

+1  A: 

One way would be to write a cron job which contains a call to one the gethost family of library functions. If you use gethostbyname() you can compare the return values of h_addr_list. See man gethostbyname.

If you're want to do this from within your program, spawn a pthread which does the same thing, then sleeps for some arbitrary period of time.

Dana the Sane
+4  A: 

It is not easy in any way. Each linux distribution uses different places to store IP addresses, etc. (more variation if you consider other UNIX variants). You can use, for example, /sbin/ifconfig to obtain the IP addresses of the interfaces, but you cannot even be sure if you'll find it at this place, or at all, etc.

Also, given you have that executable, you have to set up a thread calling it to obtain the data with a given period (say 5 seconds), and interpret the output. It may vary, for example, if you have bridges, etc. etc. That is, it is not easy.

A solution that comes to my mind is, if you have the opportunity of using GNOME or some other widespread distribution as KDE, you can rely on the messages/informations they give. For example, NetworkManager outputs a signal to the DBUS standard bus when a device changes. You have to implement a listener for those signal. Information here (not working right now, so here is a cache). Note the different messages when a new interface is added, or when one of them changes the IP address. This is the best way I can think of right now.

Diego Sevilla
+1  A: 

If your users use NetworkManager, you can poll NetworkManager.Connection.Active and NetworkManager.IP4Config via D-Bus to get a more cross distribution way of determining this information.

Sean
+4  A: 

In C, to get the current IP I use:

    int s;
    struct ifreq ifr = {};

    s = socket(PF_INET, SOCK_DGRAM, 0);

    strncpy(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name));

    if (ioctl(s, SIOCGIFADDR, &ifr) >= 0)
        printf("%s\n",
          inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));

Replace "eth0" with the interface you're looking at. All you now need to do is poll for a change.

ste
This is exactly how "ifconfig" does to get the current IP address of an interface. To find out what interface names exist it actually opens and parses "/proc/net/dev".There should be a way to get an event from the kernel though when the IP changes. My guess is using uevents.
Sadly, this isn't reliable for modern systems, where a single interface may have multiple addresses without use of sub-interfaces.
Nicholas Knight
+1  A: 

If iproute2 is installed and you're on a 2.6 kernel,

/sbin/ip monitor

Will output changes in local interface status and addresses to stdout. Your program can read this.

You could also use the same low level mechanism as the iproute2 tool does (I think it's a netlink socket).

MarkR
A: 

Check out the simple ioctl interfaces in libvirt, in particular virsh. You probably aren't using virtualization, but the wrappers might be useful to you, especially if you use bridging.

Moreover, why not get the source from your distro for whatever program is controlling networking and study it?

Finally, if dealing with dhcp, try inotify and watch /etc/hosts (and more). Don't depend on iproute headers being installed .. WHATEVER you do, don't suid a program that talks to 'ip'.

Gotta love Linux, 20 ways to accomplish the same thing.

Tim Post
A: 

From man page of rtnetlink:

DESCRIPTION

Rtnetlink allows the kernel's routing tables to be read and altered. It is used within the kernel to communicate between various subsystems, though this usage is not documented here, and for communication with user-space programs. Network routes, ip addresses, link parameters, neighbor setups, queueing disciplines, traffic classes and packet classifiers may all be controlled through NETLINK_ROUTE sockets. It is based on netlink messages, see netlink(7) for more information.

0x6adb015
A: 

ste's suggestion to use ioctl SIOCGIFADDR used to be technically correct, unfortunately it is unreliable for modern Linux systems, where a single interface can have multiple addresses without using sub-interfaces (e.g. eth0:1) as was done with the now-obsolete ifconfig.

Your best bet is to use getifaddrs(3), which is present from glibc 2.3: http://www.kernel.org/doc/man-pages/online/pages/man3/getifaddrs.3.html

Unfortunately it's somewhat inefficient (you get back a linked list of all addresses on all interfaces and will have to iterate through to find the ones you're interested in), but in most cases you're probably not checking it more than once a minute or so, making the overhead tolerable.

Nicholas Knight
A: 

here you go.. this does it without polling.

it only listens for RTM_NEWADDR but it should be easy to change to support RTM_DELADDR if you need

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>

int
main()
{
    struct sockaddr_nl addr;
    int sock, len;
    char buffer[4096];
    struct nlmsghdr *nlh;

    if ((sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) {
        perror("couldn't open NETLINK_ROUTE socket");
        return 1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_groups = RTMGRP_IPV4_IFADDR;

    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("couldn't bind");
        return 1;
    }

    nlh = (struct nlmsghdr *)buffer;
    while ((len = recv(sock, nlh, 4096, 0)) > 0) {
        while ((NLMSG_OK(nlh, len)) && (nlh->nlmsg_type != NLMSG_DONE)) {
            if (nlh->nlmsg_type == RTM_NEWADDR) {
                struct ifaddrmsg *ifa = (struct ifaddrmsg *) NLMSG_DATA(nlh);
                struct rtattr *rth = IFA_RTA(ifa);
                int rtl = IFA_PAYLOAD(nlh);

                while (rtl && RTA_OK(rth, rtl)) {
                    if (rth->rta_type == IFA_LOCAL) {
                        uint32_t ipaddr = htonl(*((uint32_t *)RTA_DATA(rth)));
                        char name[IFNAMSIZ];
                        if_indextoname(ifa->ifa_index, name);
                        printf("%s is now %d.%d.%d.%d\n",
                               name,
                               (ipaddr >> 24) & 0xff,
                               (ipaddr >> 16) & 0xff,
                               (ipaddr >> 8) & 0xff,
                               ipaddr & 0xff);
                    }
                    rth = RTA_NEXT(rth, rtl);
                }
            }
            nlh = NLMSG_NEXT(nlh, len);
        }
    }
    return 0;
}
Danny Dulai