How to run a script when there is a change in your local IP?

According to the man page for NetmorkManager, one of the events is

dhcp4-change
          The DHCPv4 lease has changed (renewed, rebound, etc).

I think you can simply change

up) 

to

dhcp4-change|up)

I am providing a script that listens on dbus signals, which will allow you to react faster than if you were to poll for changes on your current network configuration. It helps on systems where scripts /etc/ are not executed when you would like them to (like on my 14.04 system).

my enter/exit hooks.d don't work

NetworkManager starts dhclient with the flag -sf /usr/lib/NetworkManager/nm-dhcp-client.action which seems to override the normal enter/exit hook behaviour. The default behavior with dhclient is to call scripts in /etc/dhcp/dhclient-{enter,exit}-hooks.d. Those don't get called at all on my system.

my NetworkManager dispatcher.d scripts don't work either

NM does however invoke a different set of scripts, in /etc/NetworkManager/dispatcher.d, to inform of various events. The NetworkManager (8) man page defines dhcp4-change and dhcp6-change actions which would seem to do exactly what you want. Despite what the manpage says, on my system at least, only up and down actions get invoked. I can't get those scripts to fire on anything else. So this is not a great avenue to monitor IP changes either.

so, snoop directly on dbus signals emitted by NM

nm-dhcp-client.action (source), from the command line, simply converts all the environment variables set by dhclient into a dbus signal. Those environment variables are defined in man dhclient-script (8). One of particular interest is $new_ip_address. What you could do, as suggested by @Bernhard, is to monitor the signal and act accordingly based on its contents.

Here is a program that will snoop all event data signalled by that binary:

#!/bin/bash -e

#
# This script listens for the org.freedesktop.nm_dhcp_client signal.
# The signal is emitted every time dhclient-script would execute.
# It has the same contents as the environment passed to
# dhclient-script (8). Refer to manpage for variables of interest.
#

# "org.freedesktop.nm_dhcp_client" is an undocumented signal name,
# as far as I could tell. it is emitted by nm-dhcp-client.action,
# which is from the NetworkManager package source code.
# 

# detail: todo cleanup subprocess on exit. if the parent exits, 
#       the subprocess will linger until it tries to print
#       at which point it will get SIGPIPE and clean itself.
#       trap on bash's EXIT signal to do proper cleanup.


mkfifo /tmp/monitor-nm-change

(
    dbus-monitor --system "type='signal',interface='org.freedesktop.nm_dhcp_client'"
) > /tmp/monitor-nm-change &

exec </tmp/monitor-nm-change
rm /tmp/monitor-nm-change

while read EVENT; do
    #change this condition to the event you're interested in
    if echo "$EVENT" | grep -q BOUND6; then
        # do something interesting
        echo "current ipv6 addresses:"
        ip addr show | grep inet6
    fi
done

The output of dbus-monitor is not straightforward to parse in scripts. Perhaps it is easier to trigger on the presence of a certain keyword(s), e.g. new_ip_address, and from there use different tools to get the information that changed (e.g. ip or ifconfig).

# example output data from dbus-monitor for that signal
...
dict entry(
string "new_routers"
variant             array of bytes "192.168.2.11"
)
dict entry(
string "new_subnet_mask"
variant             array of bytes "255.255.255.0"
)
dict entry(
string "new_network_number"
variant             array of bytes "192.168.2.0"
)
dict entry(
string "new_ip_address"
variant             array of bytes "192.168.2.4"
)
dict entry(
string "pid"
variant             array of bytes "12114"
)
dict entry(
string "reason"
variant             array of bytes "REBOOT"
)
dict entry(
string "interface"
variant             array of bytes "eth0"
)
...

Give it a shot!


Polling approach with python script. Basic idea is to continuously parse output of ip -4 -o add show <INTERFACE> and compare current result with previous iteration

#!/usr/bin/env python3
import subprocess
import sys

def get_ip():
    # Simple function that parses output
    # of ip command and returns interface ip
    # replace wlan7 with your interface
    command = 'ip -4 -o addr show wlan7'.split()
    ip = None
    try:
        ip = subprocess.check_output(command).decode().split()[3]
    except IndexError:
        return
    finally:
        if ip:
           return ip

def main():
    # do while loop
    # Exits only when change occurs
    address = get_ip()
    while address == get_ip():
          address = get_ip()

    # Trigger script once we're out of loop
    subprocess.call(['zenity','--info','--text','IP CHANGED'])


if __name__ == '__main__':
    # use while loop if yout want this script to run
    # continuously
    while True:
        try:
            main()
        except KeyboardInterrupt:
            sys.exit()