How to detect IP address change programmatically in Linux?
Asked Answered
O

10

36

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

Overriding answered 23/2, 2009 at 23:3 Comment(3)
+1 : Common question, glad to see it asked here.Sporangium
Danny, IMHO if your solution is not polling , then what is ?? you clearly wait to receive a particular message from the socket, also quiet complicated when one can simply poll the output of ifconfig or use getifaddrs as others suggestedUlloa
using libnl library https://mcmap.net/q/419280/-how-to-detect-ip-address-change-programmatically-in-linuxAulos
S
6

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.

Spermatozoid answered 24/2, 2009 at 1:5 Comment(2)
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.Vashtee
Sadly, this isn't reliable for modern systems, where a single interface may have multiple addresses without use of sub-interfaces.Deglutition
T
61

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;
}
Terminal answered 1/3, 2010 at 1:38 Comment(10)
Great code. Is there a way to only watch a specific interface for a change?Ore
@Ore the struct ifaddrmsg has a member ifa_index, which is the interface index of the interface the address is associated with.Terminal
It should be noted in the manpage for these netlink API, it is recommended not to use this low level interface, but rather the libnetlink lib ... Which in turn lists the use of this lib as a bug and points to the use of libmnl insteadSnapp
Since this uses the Netlink protocol, here is a document describing what it is inai.de/documents/Netlink_Protocol.pdfSnapp
@DannyDulai note that in case of a "virtual interface" such as eth0:0 it will have the same index as its hardware interface eth0.Tasimeter
@Tasimeter -- so do you have a better way to get the full name?Terminal
@DannyDulai netlink sends the name of the virtual interface along with the rest in the RTM_NEWADDR message, the rta_type == IFA_LABEL is its ascii string. Note that the name of this kind of "interface" is only sent by Netlink in these packets, not in the interface change notifications.Tasimeter
I found a cleaner version of this code that handles both RTM_NEWADDR and RTM_DELADDR as well as handling both IPv4 and IPv6 cases herePalsy
isn't that while loop considered as polling?Aulos
@Vencat, the outer while() is not considering polling because the recv() blocks until there is an event to be processed. The inner 2 while()s are just iterating.Terminal
S
6

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.

Spermatozoid answered 24/2, 2009 at 1:5 Comment(2)
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.Vashtee
Sadly, this isn't reliable for modern systems, where a single interface may have multiple addresses without use of sub-interfaces.Deglutition
T
4

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.

Turnage answered 23/2, 2009 at 23:19 Comment(0)
A
4

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.

Akeylah answered 23/2, 2009 at 23:36 Comment(0)
C
3

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

Cocker answered 24/2, 2009 at 10:41 Comment(0)
D
3

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.

Deglutition answered 16/11, 2009 at 21:34 Comment(0)
H
1

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.

Hargrove answered 23/2, 2009 at 23:8 Comment(0)
P
1

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;
}
Puffy answered 30/5, 2018 at 0:53 Comment(0)
E
0

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.

Edan answered 16/11, 2009 at 21:7 Comment(0)
A
0

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;
}
Aulos answered 4/5, 2021 at 15:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.