How to run a script when there is a change in your local IP?
Before marking as a duplicate: this is not about dynamic DNS or similar things.
I know how to run a script every time my networks goes up; it is a simple matter of adding a script in the directory /etc/NetworkManager/dispatcher.d
like this one:
#!/bin/bash
IF=$1
STATUS=$2
case "$2" in
up)
logger -s "NM Script up $IF triggered"
su rmano -c /home/romano/bin/myscript
;;
down)
logger -s "NM Script down $IF triggered"
;;
*)
;;
esac
In my case, myscript
is a very simple ifconfig -a > ~/Dropbox/myifconfig.txt
--- I use it because I need to know my local IP from everywhere in the University, and it will change often.
So far so good, the system works ok. But unfortunately the DHCP here is configured so that the IP sometime is changed without a down/up of the interface. In that case the script is (logically) not run, and the file is not updated.
I cannot use a DynDNS approach because the change is in the local IP, not the externally visible one.
I could poll, by simply putting the script in cron and execute it every minute or write a slightly more complex one (...if the IP is changed write the file otherwise do nothing) and putting it again as a background task, but it is not elegant. So the question is:
Is there any way to trigger a script when my local IP changes?
UPDATE 1
I tried to put a script in /etc/dhcp/dhclient-enter-hooks.d/
, based on the existing /etc/dhcp/dhclient-enter-hooks.d/resolvconf
, but it will not be triggered. I suspect (confirmation needed) that NM (network manager) is doing the dhcp negotiation by itself, without calling the dhcp command...
Solution 1:
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)
Solution 2:
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!
Solution 3:
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()
Solution 4:
Although NetworkManager is using dhclient, it provides its own binaries as a replacement for the dhclient-scripts. (For reference: you can find the NM binary at /usr/lib/NetworkManager/nm-dhcp-client.action
).
Maybe you could take a different approach: NM is issuing an DBus signal on all events. You could listen on the system DBus for the appropiate event and trigger your script based on this...