Is there a way to detect IP address changes on the local machine in Linux programmatically using C++?
-
2+1 : Common question, glad to see it asked here. – Tim Post Feb 24 '09 at 10:51
-
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 suggested – user3529114 Apr 13 '14 at 13:54
-
using libnl library https://stackoverflow.com/a/67387335/10334333 – Vencat May 04 '21 at 15:11
10 Answers
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;
}

- 6,794
- 1
- 34
- 41

- 1,657
- 12
- 14
-
1Great code. Is there a way to only watch a specific interface for a change? – watain Feb 02 '11 at 16:36
-
6@watain the `struct ifaddrmsg` has a member `ifa_index`, which is the interface index of the interface the address is associated with. – Danny Dulai Feb 11 '11 at 07:47
-
2It 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](http://man7.org/linux/man-pages/man3/libnetlink.3.html) ... Which in turn lists the use of this lib as a bug and points to the use of [libmnl](http://www.netfilter.org/projects/libmnl/) instead – Joakim Aug 24 '15 at 09:16
-
Since this uses the Netlink protocol, here is a document describing what it is http://inai.de/documents/Netlink_Protocol.pdf – Joakim Aug 24 '15 at 09:30
-
@DannyDulai note that in case of a "virtual interface" such as `eth0:0` it will have the same index as its hardware interface `eth0`. – deb0ch Feb 27 '17 at 13:55
-
-
@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. – deb0ch Mar 06 '17 at 00:07
-
1I found a cleaner version of this code that handles both `RTM_NEWADDR` and `RTM_DELADDR` as well as handling both IPv4 and IPv6 cases [here](https://github.com/angt/ipevent/blob/e0a4c4dfe8ac193345315d55f320ab212dbda784/ipevent.c) – Frak Aug 13 '18 at 21:10
-
-
5@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. – Danny Dulai May 04 '21 at 13:19
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.

- 818
- 2
- 9
- 16
-
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. – nly Feb 24 '09 at 11:20
-
4Sadly, this isn't reliable for modern systems, where a single interface may have multiple addresses without use of sub-interfaces. – Nicholas Knight Nov 16 '09 at 21:35
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.

- 28,636
- 4
- 59
- 87
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.

- 401
- 3
- 7
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.

- 15,774
- 5
- 45
- 57
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).

- 62,604
- 14
- 116
- 151
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;
}

- 5,098
- 50
- 48
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.

- 14,762
- 8
- 58
- 80
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.

- 7,473
- 4
- 24
- 38
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;
}

- 1,272
- 11
- 36