Here's some notes from my digging into the code paths being followed. Using the messages logged by NM to syslog I've been able to reconstruct the call heirarchy. In summary, when the state transitions from "expire" to "done":
nm_dhcp_client_set_state () fires a NM_DHCP_STATE_EXPIRE signal
dhcp6_state_changed () receives NM_DHCP_STATE_EXPIRE signal and calls
dhcp6_fail (self, FALSE)
dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, FALSE) nm_dhcp_client_stop () NM_DHCP_CLIENT_GET_CLASS (self)->stop (self, release, priv->duid);
I've not been able to identify why the behaviour appears to have changed recently. Inspecting the changelogs and patches in both isc-dhcp and network-manager hasn't revealed anything.
It definitely seems to be Network Manager's issue since it is killing the dhclient process... however... if dhclient tried to gain a new lease immediately it 'deprefered' the expired lease (thus sending NM additional state change signals) then maybe NM wouldn't kill the process.
Something else I noticed - which misled me for a while - is that the dhclient lease files written by Network Manager for IP4v and IPv6 use different syntax for the timestamps. From 'man 5 dhclient.conf' "LEASE DECLARATIONS" the "renew" "rebind" "expire" dates can have two formats: "default" or "local". This is set by "db-time-format" in the configuration.
The NM IPv4.lease file uses the "default" format (" renew 4 2016/03/31 06:32:42;") but the IPv6.lease file seems to use "local" ("renew 43200;") but without the "epoch" keyword. The code seems to indicate the manual is confusing the *server* settings with client settings, or else IPv6 is using a different scheme entirely via its 'clientenv_add()' mechanism.
NM generates this config file - there is no 'db-time-format':
---
# Created by NetworkManager
/* Stopping the client is left up to the controlling device
* explicitly since we may want to quit NetworkManager but not terminate
* the DHCP client.
*/
Here's some notes from my digging into the code paths being followed. Using the messages logged by NM to syslog I've been able to reconstruct the call heirarchy. In summary, when the state transitions from "expire" to "done":
nm_dhcp_ client_ set_state () fires a NM_DHCP_ STATE_EXPIRE signal
dhcp6_state_changed () receives NM_DHCP_ STATE_EXPIRE signal and calls cleanup( self, CLEANUP_ TYPE_DECONFIGUR E, FALSE)
nm_dhcp_ client_ stop ()
NM_ DHCP_CLIENT_ GET_CLASS (self)->stop (self, release, priv->duid);
dhcp6_fail (self, FALSE)
dhcp6_
I've not been able to identify why the behaviour appears to have changed recently. Inspecting the changelogs and patches in both isc-dhcp and network-manager hasn't revealed anything.
It definitely seems to be Network Manager's issue since it is killing the dhclient process... however... if dhclient tried to gain a new lease immediately it 'deprefered' the expired lease (thus sending NM additional state change signals) then maybe NM wouldn't kill the process.
Something else I noticed - which misled me for a while - is that the dhclient lease files written by Network Manager for IP4v and IPv6 use different syntax for the timestamps. From 'man 5 dhclient.conf' "LEASE DECLARATIONS" the "renew" "rebind" "expire" dates can have two formats: "default" or "local". This is set by "db-time-format" in the configuration.
default: <weekday> <year>/ <month> /<day> <hour>: <minute> :<second> since-epoch> ; # <day-name> <month-name> <day-number> <hours> :<minutes> :<seconds> <year>
local: epoch <seconds-
The NM IPv4.lease file uses the "default" format (" renew 4 2016/03/31 06:32:42;") but the IPv6.lease file seems to use "local" ("renew 43200;") but without the "epoch" keyword. The code seems to indicate the manual is confusing the *server* settings with client settings, or else IPv6 is using a different scheme entirely via its 'clientenv_add()' mechanism.
NM generates this config file - there is no 'db-time-format':
---
# Created by NetworkManager
send fqdn.fqdn "hephaestion. lan.iam. tj"; # added by NetworkManager
send fqdn.encoded on;
send fqdn.server-update on;
also request dhcp6.name-servers; search;
also request dhcp6.domain-
also request dhcp6.client-id;
---
The dhcp6 lease file extract (showing the last entry is an expired one:- -life 86400;
$ date --date @$(echo 1459002474+86400 | bc)
Sun Mar 27 15:27:54 BST 2016
---
lease6 {
interface "wlp3s0";
ia-na 3b:bd:74:85 {
starts 1459002474;
renew 43200;
rebind 69120;
iaaddr 2a02:8011:2007::2 {
starts 1459002474;
preferred
max-life 86400;
}
}
---
---
Notes from the source-code analysis.
static const GEnumValue values[] = { STATE_UNKNOWN, "NM_DHCP_ STATE_UNKNOWN" , "nm-dhcp- state-unknown" }, STATE_BOUND, "NM_DHCP_ STATE_BOUND" , "nm-dhcp- state-bound" }, STATE_TIMEOUT, "NM_DHCP_ STATE_TIMEOUT" , "nm-dhcp- state-timeout" }, STATE_DONE" , "nm-dhcp- state-done" }, STATE_EXPIRE, "NM_DHCP_ STATE_EXPIRE" , "nm-dhcp- state-expire" }, STATE_FAIL" , "nm-dhcp- state-fail" }, STATE_MAX, "__NM_DHCP_ STATE_MAX" , "--nm-dhcp- state-max" }, STATE_MAX" , "nm-dhcp-state-max" },
{ NM_DHCP_
{ NM_DHCP_
{ NM_DHCP_
{ NM_DHCP_STATE_DONE, "NM_DHCP_
{ NM_DHCP_
{ NM_DHCP_STATE_FAIL, "NM_DHCP_
{ __NM_DHCP_
{ NM_DHCP_STATE_MAX, "NM_DHCP_
{ 0, NULL, NULL }
};
client_start () CLIENT_ SIGNAL_ STATE_CHANGED, G_CALLBACK (client_ state_changed) , self);
\-- g_signal_connect (client, NM_DHCP_
client_ state_changed () STATE_TIMEOUT)
remove_ client (self, client); handlers_ disconnect_ by_func (client, client_ state_changed, self);
\-- if (state >= NM_DHCP_
\-- g_signal_
/* Stopping the client is left up to the controlling device
* explicitly since we may want to quit NetworkManager but not terminate
* the DHCP client.
*/
dhcp6_start_ with_link_ ready () manager_ start_ip6 dhcp6_client) {
priv-> dhcp6_state_ sigid = g_signal_connect (priv-> dhcp6_client,
NM_ DHCP_CLIENT_ SIGNAL_ STATE_CHANGED,
G_ CALLBACK (dhcp6_ state_changed) ,
self) ;
\-- priv->dhcp6_client = nm_dhcp_
if (priv->
}
dhcp6_state_changed () STATE_EXPIRE: TYPE_DECONFIGUR E, FALSE) TYPE_DECONFIGUR E || cleanup_type == CLEANUP_ TYPE_REMOVED)
nm_ dhcp_client_ stop ()
\- - NM_DHCP_ CLIENT_ GET_CLASS (self)->stop (self, release, priv->duid);
nm_log_ info (LOGD_DHCP, "(%s): canceled DHCP transaction, DHCP client pid %d" DHCP_LEVEL_ MANAGED) {
else if (priv->ip6_state == IP_DONE)
nm_ device_ state_changed (self, NM_DEVICE_ STATE_FAILED, NM_DEVICE_ STATE_REASON_ IP_CONFIG_ EXPIRED) ;
nm_dhcp_ client_ set_state (self, NM_DHCP_STATE_DONE, NULL, NULL);
switch (state) {
...
case NM_DHCP_
/* Ignore expiry before we even have a lease (NAK, old lease, etc) */
if (priv->ip6_state != IP_CONF)
dhcp6_fail (self, FALSE);
\-- dhcp6_cleanup(self, CLEANUP_
if ( cleanup_type == CLEANUP_
if (priv->dhcp6_mode == NM_RDISC_
if (timeout || (priv->ip6_state == IP_CONF))
...
...
break;
...
case NM_DHCP_STATE_FAIL:
dhcp6_fail (self, FALSE);
nm_dhcp_ client_ set_state () STATE_BOUND)
timeout_ cleanup (self); STATE_TIMEOUT)
watch_ cleanup (self);
"(%s): DHCPv%c state changed %s -> %s",
priv-> iface,
priv-> ipv6 ? '6' : '4',
state_ to_string (priv->state),
state_ to_string (new_state));
\-- if (new_state >= NM_DHCP_
if (new_state >= NM_DHCP_
...
nm_log_info (priv->ipv6 ? LOGD_DHCP6 : LOGD_DHCP4,
priv->state = new_state;
g_signal_ emit (G_OBJECT (self),
signals[ SIGNAL_ STATE_CHANGED] , 0,
new_ state,
ip_config,
options) ;
nm_device_ state_changed (self, state, reason) STATE_ACTIVATED :
_LOGI (LOGD_DEVICE, "Activation: successful, device activated.");
nm_dispatcher _call (DISPATCHER_ ACTION_ UP, nm_act_ request_ get_connection (req), self, NULL, NULL, NULL);
break; STATE_FAILED: uses_assumed_ connection (self)) {
/ * Avoid tearing down assumed connection, assume it's connected */
nm_ device_ queue_state (self,
NM_DEVICE_ STATE_ACTIVATED ,
NM_DEVICE_ STATE_REASON_ CONNECTION_ ASSUMED) ;
break;
_set_state_full (self, state, reason, FALSE); # _set_state_full (self, state, reason, quitting)
\-- switch (state)
...
case NM_DEVICE_
case NM_DEVICE_
if (nm_device_
}