Amit Wadhwa
Amit Wadhwa

Reputation: 735

How to detect IP address change programmatically in Linux?

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

Upvotes: 36

Views: 35331

Answers (10)

Vencat
Vencat

Reputation: 1632

Using the libnl-3 library, detect link and ip4 address change.

Reference - https://www.infradead.org/~tgr/libnl/doc/core.html#_introduction

#include <netlink/netlink.h>
#include <netlink/socket.h>
#include <netlink/msg.h>
#include <arpa/inet.h>
#include <iostream>

static char ip4Addr[INET_ADDRSTRLEN];

static int parseAddress(struct nlmsghdr *hdr)
{
    std::cout << "parseAddress" << std::endl;

    struct ifaddrmsg *iface = (struct ifaddrmsg *)nlmsg_data(hdr);

    struct nlattr *attrs[IFA_MAX + 1];

    if (nlmsg_parse(hdr, sizeof(struct ifaddrmsg), attrs, IFA_MAX, nullptr) < 0)
    {
        std::cerr << "problem parsing Netlink response" << std::endl;
        return -1;
    }

    if (attrs[IFA_ADDRESS] == nullptr)
    {
        std::cerr << "Address Never Received "
                  << std::endl;
        return -1;
    }

    inet_ntop(iface->ifa_family, nla_data(attrs[IFA_ADDRESS]), ip4Addr, sizeof(ip4Addr));

    if ((hdr->nlmsg_type == RTM_NEWADDR) && (iface->ifa_family == AF_INET))
    {
        std::cout << "IPv4 Address added : " << ip4Addr << std::endl;
    }

    if ((hdr->nlmsg_type == RTM_DELADDR) && (iface->ifa_family == AF_INET))
    {
        std::cout << "IPv4 Address deleted : " << ip4Addr << std::endl;
    }
    return 0;
}

static int parseLink(struct nlmsghdr *hdr)
{
    std::cout << "parseLink" << std::endl;
    struct ifinfomsg *iface = (struct ifinfomsg *)nlmsg_data(hdr);

    struct nlattr *attrs[IFLA_MAX + 1];

    if (nlmsg_parse(hdr, sizeof(struct ifinfomsg), attrs, IFLA_MAX, nullptr) < 0)
    {
        std::cerr << "problem parsing Netlink response" << std::endl;
        return -1;
    }

    if (attrs[IFLA_IFNAME] != nullptr)
    {
        if (hdr->nlmsg_type == RTM_NEWLINK)
        {
            std::cout << (char *)nla_data(attrs[IFLA_IFNAME]) << std::endl;
        }
        else if (hdr->nlmsg_type == RTM_DELLINK)
        {
            std::cout << (char *)nla_data(attrs[IFLA_IFNAME]) << std::endl;
        }
    }
    return 0;
}

static int receiveNewMsg(struct nl_msg *msg, void *arg)
{
    struct nlmsghdr *nlh = nlmsg_hdr(msg);
    int len = nlh->nlmsg_len;
    int type = nlh->nlmsg_type;
    while (nlmsg_ok(nlh, len))
    {
        if (type != RTM_NEWLINK && type != RTM_DELLINK && type != RTM_NEWADDR && type != RTM_DELADDR)
        {
            if (nlh->nlmsg_type == NLMSG_DONE)
            {
                std::cout << "message complete" << std::endl;
            }
            nlh = nlmsg_next(nlh, &len);
            continue;
        }
        if ((nlh->nlmsg_type == RTM_NEWLINK) || (nlh->nlmsg_type == RTM_DELLINK))
        {
            parseLink(nlh);
        }
        if ((nlh->nlmsg_type == RTM_NEWADDR) || (nlh->nlmsg_type == RTM_DELADDR))
        {
            parseAddress(nlh);
        }
        nlh = nlmsg_next(nlh, &len);
    }
    return 1;
}

int main(int argc, char const *argv[])
{
    struct nl_sock *sk;

    /* Allocate a new socket */
    sk = nl_socket_alloc();

    /*
    * Notifications do not use sequence numbers, disable sequence number checking.
    */
    nl_socket_disable_seq_check(sk);

    /*
    * Define a callback function, which will be called for each notification received
    */
    nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, receiveNewMsg, nullptr);
    nl_socket_modify_cb(sk, NL_CB_FINISH, NL_CB_CUSTOM, receiveNewMsg, nullptr);

    /* Connect to routing netlink protocol */
    nl_connect(sk, NETLINK_ROUTE);

    /* Subscribe to link notifications group */
    nl_socket_add_memberships(sk, RTNLGRP_LINK, 0);
    nl_socket_add_memberships(sk, RTNLGRP_IPV4_IFADDR, 0);

    /*
    * Start receiving messages. The function nl_recvmsgs_default() will block
    * until one or more netlink messages (notification) are received which
    * will be passed on to my_func().
    */

    while (1)
        nl_recvmsgs_default(sk);

    return 0;
}

Upvotes: 0

Danny Dulai
Danny Dulai

Reputation: 1675

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) {
                        char name[IFNAMSIZ];
                        if_indextoname(ifa->ifa_index, name);
                        char ip[INET_ADDRSTRLEN];
                        inet_ntop(AF_INET, RTA_DATA(rth), ip, sizeof(ip));
                        printf("interface %s ip: %s\n", name, ip);
                    }
                    rth = RTA_NEXT(rth, rtl);
                }
            }
            nlh = NLMSG_NEXT(nlh, len);
        }
    }
    return 0;
}

Upvotes: 61

bleater
bleater

Reputation: 5499

Complete tested example in C with notifications watched for in separate thread:

#include <sys/socket.h> // AF_INET, socket(), bind()
#include <ifaddrs.h> // struct ifaddrs, getifaddrs()
#include <netinet/in.h> // struct sockaddr_in
#include <arpa/inet.h> // inet_ntoa(), htonl()
#include <net/if.h> // if_indextoname()
#include <pthread.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <stdbool.h>

typedef enum {
    IP_ADDR_ADD,
    IP_ADDR_REMOVE
} ip_address_change_notification_type_t;

typedef void (*ip_address_change_notification_callback_t)(ip_address_change_notification_type_t type, uint32_t ipaddr, void *userdata);

static int ip_address_change_notification_socket = -1;
static pthread_t ip_address_change_notification_thread;
static ip_address_change_notification_callback_t ip_address_change_notification_callback;
static void *ip_address_change_notification_callback_userdata;

void *ip_address_change_notification_worker(void *arg)
{
    fprintf(stderr, "ip_address_change_notification_worker entered.\n");
    if (ip_address_change_notification_socket == -1) {
        goto done;
    }

    char buffer[4096];
    struct nlmsghdr *nlh = (struct nlmsghdr *)buffer;
    int len;
    while ((len = recv(ip_address_change_notification_socket, nlh, sizeof(buffer), 0)) > 0) {
        for (; (NLMSG_OK(nlh, len)) && (nlh->nlmsg_type != NLMSG_DONE); nlh = NLMSG_NEXT(nlh, len)) {
            if (nlh->nlmsg_type == RTM_NEWADDR) {
                fprintf(stderr, "Netlink: RTM_NEWADDR\n");
            } else if (nlh->nlmsg_type == RTM_DELADDR) {
                fprintf(stderr, "Netlink: RTM_DELADDR\n");
            } else {
                fprintf(stderr, "Netlink: nlmsg_type=%d\n", nlh->nlmsg_type);
                continue; // Some other kind of announcement.
            }

            struct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA(nlh);
            struct rtattr *rth = IFA_RTA(ifa);
            int rtl = IFA_PAYLOAD(nlh);
            for (; rtl && RTA_OK(rth, rtl); rth = RTA_NEXT(rth,rtl)) {
                char name[IFNAMSIZ];
                uint32_t ipaddr;

                if (rth->rta_type != IFA_LOCAL) continue;
                ipaddr = *((uint32_t *)RTA_DATA(rth)); // In network byte-order.
                fprintf(stderr, "Interface %s %s has IP address %s\n", if_indextoname(ifa->ifa_index, name), (nlh->nlmsg_type == RTM_NEWADDR ? "now" : "no longer"), inet_ntoa(*((struct in_addr *)&ipaddr)));
                if (ip_address_change_notification_callback) (*ip_address_change_notification_callback)((nlh->nlmsg_type == RTM_NEWADDR ? IP_ADDR_ADD : IP_ADDR_REMOVE), ipaddr, ip_address_change_notification_callback_userdata);
            }
        }
    }

done:
    fprintf(stderr, "ip_address_change_notification_worker exited.\n");
    return (NULL);
}

bool begin_ip_address_change_notifications(ip_address_change_notification_callback_t callback, void *userdata)
{
    if (ip_address_change_notification_socket != -1) return false;

    ip_address_change_notification_callback = callback;
    ip_address_change_notification_callback_userdata = userdata;

    if ((ip_address_change_notification_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) {
        perror("begin_ip_address_change_notifications socket");
        return false;
    }

    struct sockaddr_nl addr;
    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_groups = RTMGRP_IPV4_IFADDR;
    if (bind(ip_address_change_notification_socket, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("begin_ip_address_change_notifications bind");
        goto bail;
    }

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, 1); // Preclude the need to do pthread_join on the thread after it exits.
    int err = pthread_create(&ip_address_change_notification_thread, &attr, ip_address_change_notification_worker, NULL);
    pthread_attr_destroy(&attr);
    if (err != 0) {
        fprintf(stderr, "Error creating ip address change notification thread.\n");
        goto bail;
    }

    return (true);

bail:
    close(ip_address_change_notification_socket);
    ip_address_change_notification_socket = -1;

    ip_address_change_notification_callback = NULL;
    ip_address_change_notification_callback_userdata = NULL;
    return false;
}

void end_ip_address_change_notifications(void)
{
    if (ip_address_change_notification_socket == -1) return;

    pthread_cancel(ip_address_change_notification_thread);

    close(ip_address_change_notification_socket);
    ip_address_change_notification_socket = -1;

    ip_address_change_notification_callback = NULL;
    ip_address_change_notification_callback_userdata = NULL;
}

Upvotes: 1

Nicholas Knight
Nicholas Knight

Reputation: 16045

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.

Upvotes: 3

0x6adb015
0x6adb015

Reputation: 7811

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.

Upvotes: 0

MarkR
MarkR

Reputation: 63616

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).

Upvotes: 3

ste
ste

Reputation: 828

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.

Upvotes: 6

Sean
Sean

Reputation: 401

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.

Upvotes: 4

Diego Sevilla
Diego Sevilla

Reputation: 29021

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.

Upvotes: 4

Dana the Sane
Dana the Sane

Reputation: 15208

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.

Upvotes: 1

Related Questions