summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Illfelder <illfelder@users.noreply.github.com>2016-09-30 14:25:30 -0700
committerGitHub <noreply@github.com>2016-09-30 14:25:30 -0700
commit6e062933a41e0a64f4b9991a1918e2b8425b1950 (patch)
tree9af63452a3c44fc87c7b2257b7978798375094c6
parent60cbf6e3922c666f9237d7b310ec7872b24bf236 (diff)
downloadgoogle-compute-image-packages-6e062933a41e0a64f4b9991a1918e2b8425b1950.tar.gz
Improved network interface configuration on EL. (#340)
Enterprise Linux distributions use NetworkManager. This may interfere with dhclient when enabling network interfaces. On Linux distributions with Network manager, new interfaces are configured using ifcfg files. We disable network manager and standardize the dhclient script used in EL.
-rw-r--r--google_compute_engine/instance_setup/instance_config.py1
-rwxr-xr-xgoogle_compute_engine/instance_setup/instance_setup.py2
-rwxr-xr-xgoogle_compute_engine/network_setup/network_setup.py103
-rw-r--r--google_compute_engine/network_setup/tests/network_setup_test.py191
-rw-r--r--google_compute_engine_init/systemd/google-accounts-daemon.service2
-rw-r--r--google_compute_engine_init/systemd/google-clock-skew-daemon.service2
-rw-r--r--google_compute_engine_init/systemd/google-ip-forwarding-daemon.service2
-rw-r--r--google_compute_engine_init/systemd/google-shutdown-scripts.service2
-rw-r--r--google_compute_engine_init/systemd/google-startup-scripts.service2
-rwxr-xr-xgoogle_compute_engine_init/sysvinit/google-accounts-daemon1
-rwxr-xr-xgoogle_compute_engine_init/sysvinit/google-clock-skew-daemon1
-rwxr-xr-xgoogle_compute_engine_init/sysvinit/google-ip-forwarding-daemon1
-rwxr-xr-xgoogle_compute_engine_init/sysvinit/google-startup-scripts2
-rw-r--r--google_compute_engine_init/upstart/google-accounts-daemon.conf2
-rw-r--r--google_compute_engine_init/upstart/google-clock-skew-daemon.conf2
-rw-r--r--google_compute_engine_init/upstart/google-ip-forwarding-daemon.conf2
-rw-r--r--google_compute_engine_init/upstart/google-network-setup.conf2
-rw-r--r--google_compute_engine_init/upstart/google-startup-scripts.conf2
-rwxr-xr-xgoogle_config/build_packages.sh3
-rwxr-xr-xgoogle_config/sbin/google-dhclient-script806
20 files changed, 1043 insertions, 88 deletions
diff --git a/google_compute_engine/instance_setup/instance_config.py b/google_compute_engine/instance_setup/instance_config.py
index f13351b..675db3a 100644
--- a/google_compute_engine/instance_setup/instance_config.py
+++ b/google_compute_engine/instance_setup/instance_config.py
@@ -67,6 +67,7 @@ class InstanceConfig(config_manager.ConfigManager):
},
'NetworkInterfaces': {
'setup': 'true',
+ 'dhclient_script': '/sbin/google-dhclient-script',
'dhcp_command': '',
},
}
diff --git a/google_compute_engine/instance_setup/instance_setup.py b/google_compute_engine/instance_setup/instance_setup.py
index 612b46e..e69a773 100755
--- a/google_compute_engine/instance_setup/instance_setup.py
+++ b/google_compute_engine/instance_setup/instance_setup.py
@@ -97,7 +97,7 @@ class InstanceSetup(object):
key_type: string, the type of the SSH key.
key_dest: string, a file location to store the SSH key.
"""
- # Create a temporary file to save create the RSA keys.
+ # Create a temporary file to save the created RSA keys.
with tempfile.NamedTemporaryFile(prefix=key_type, delete=True) as temp:
temp_key = temp.name
diff --git a/google_compute_engine/network_setup/network_setup.py b/google_compute_engine/network_setup/network_setup.py
index 52c6399..17eea62 100755
--- a/google_compute_engine/network_setup/network_setup.py
+++ b/google_compute_engine/network_setup/network_setup.py
@@ -15,8 +15,11 @@
"""Enables the network interfaces provided in metadata."""
+import fileinput
import logging.handlers
import optparse
+import os
+import re
import subprocess
from google_compute_engine import config_manager
@@ -30,13 +33,15 @@ class NetworkSetup(object):
network_interfaces = 'instance/network-interfaces'
- def __init__(self, dhcp_command=None, debug=False):
+ def __init__(self, dhclient_script=None, dhcp_command=None, debug=False):
"""Constructor.
Args:
+ dhclient_script: string, the path to a dhclient script used by dhclient.
dhcp_command: string, a command to enable Ethernet interfaces.
debug: bool, True if debug output should write to the console.
"""
+ self.dhclient_script = dhclient_script or '/sbin/google-dhclient-script'
self.dhcp_command = dhcp_command
facility = logging.handlers.SysLogHandler.LOG_DAEMON
self.logger = logger.Logger(
@@ -45,19 +50,90 @@ class NetworkSetup(object):
self.network_utils = network_utils.NetworkUtils(logger=self.logger)
self._SetupNetworkInterfaces()
- def _EnableNetworkInterfaces(self, interfaces):
+ def _ModifyInterface(
+ self, interface_config, config_key, config_value, replace=False):
+ """Write a value to a config file if not already present.
+
+ Args:
+ interface_config: string, the path to a config file.
+ config_key: string, the configuration key to set.
+ config_value: string, the value to set for the configuration key.
+ replace: bool, replace the configuration option if already present.
+ """
+ config_entry = '%s=%s' % (config_key, config_value)
+ if not open(interface_config).read().count(config_key):
+ with open(interface_config, 'a') as config:
+ config.write('%s\n' % config_entry)
+ elif replace:
+ for line in fileinput.input(interface_config, inplace=True):
+ print(re.sub(r'%s=.*' % config_key, config_entry, line.rstrip()))
+
+ def _DisableNetworkManager(self, interfaces):
+ """Disable network manager management on a list of network interfaces.
+
+ Args:
+ interfaces: list of string, the output device names enable.
+ """
+ interface_path = '/etc/sysconfig/network-scripts'
+ for interface in interfaces:
+ interface_config = os.path.join(interface_path, 'ifcfg-%s' % interface)
+ if os.path.exists(interface_config):
+ self._ModifyInterface(
+ interface_config, 'DEVICE', interface, replace=False)
+ self._ModifyInterface(
+ interface_config, 'NM_CONTROLLED', 'no', replace=True)
+ else:
+ with open(interface_config, 'w') as interface_file:
+ interface_content = [
+ '# Added by Google.',
+ 'BOOTPROTO=none',
+ 'DEFROUTE=no',
+ 'DEVICE=%s' % interface,
+ 'IPV6INIT=no',
+ 'NM_CONTROLLED=no',
+ 'NOZEROCONF=yes',
+ '',
+ ]
+ interface_file.write('\n'.join(interface_content))
+ self.logger.info('Created config file for interface %s.', interface)
+
+ def _ConfigureNetwork(self, interfaces):
"""Enable the list of network interfaces.
Args:
- interface: list of string, the output devices to enable.
+ interfaces: list of string, the output device names enable.
"""
+ self.logger.info('Enabling the Ethernet interfaces %s.', interfaces)
+ dhclient_command = ['dhclient']
+ if os.path.exists(self.dhclient_script):
+ dhclient_command += ['-sf', self.dhclient_script]
try:
- self.logger.info('Enabling the Ethernet interface %s.', interfaces)
- subprocess.check_call(['dhclient', '-r'] + interfaces)
- subprocess.check_call(['dhclient'] + interfaces)
+ subprocess.check_call(dhclient_command + ['-x'] + interfaces)
+ subprocess.check_call(dhclient_command + interfaces)
except subprocess.CalledProcessError:
self.logger.warning('Could not enable interfaces %s.', interfaces)
+ def _EnableNetworkInterfaces(self, interfaces):
+ """Enable the list of network interfaces.
+
+ Args:
+ interfaces: list of string, the output device names to enable.
+ """
+ # The default Ethernet interface is enabled by default. Do not attempt to
+ # enable interfaces if only one interface is specified in metadata.
+ if not interfaces or len(interfaces) <= 1:
+ return
+
+ if self.dhcp_command:
+ try:
+ subprocess.check_call([self.dhcp_command])
+ except subprocess.CalledProcessError:
+ self.logger.warning('Could not enable Ethernet interfaces.')
+ else:
+ if os.path.exists('/etc/sysconfig/network-scripts'):
+ self._DisableNetworkManager(interfaces)
+ self._ConfigureNetwork(interfaces)
+
def _SetupNetworkInterfaces(self):
"""Get network interfaces metadata and enable each Ethernet interface."""
result = self.watcher.GetMetadata(
@@ -73,18 +149,7 @@ class NetworkSetup(object):
message = 'Network interface not found for MAC address: %s.'
self.logger.warning(message, mac_address)
- # The default Ethernet interface is enabled by default. Do not attempt to
- # enable interfaces if only one interface is specified in metadata.
- if len(interfaces) <= 1:
- return
-
- if self.dhcp_command:
- try:
- subprocess.check_call([self.dhcp_command])
- except subprocess.CalledProcessError:
- self.logger.warning('Could not enable Ethernet interfaces.')
- else:
- self._EnableNetworkInterfaces(interfaces)
+ self._EnableNetworkInterfaces(interfaces)
def main():
@@ -96,6 +161,8 @@ def main():
instance_config = config_manager.ConfigManager()
if instance_config.GetOptionBool('NetworkInterfaces', 'setup'):
NetworkSetup(
+ dhclient_script=instance_config.GetOptionString(
+ 'NetworkInterfaces', 'dhclient_script'),
dhcp_command=instance_config.GetOptionString(
'NetworkInterfaces', 'dhcp_command'),
debug=bool(options.debug))
diff --git a/google_compute_engine/network_setup/tests/network_setup_test.py b/google_compute_engine/network_setup/tests/network_setup_test.py
index 4c3f01e..947c77c 100644
--- a/google_compute_engine/network_setup/tests/network_setup_test.py
+++ b/google_compute_engine/network_setup/tests/network_setup_test.py
@@ -15,9 +15,13 @@
"""Unittest for network_setup.py module."""
+import os
+import shutil
import subprocess
+import tempfile
from google_compute_engine.network_setup import network_setup
+from google_compute_engine.test_compat import builtin
from google_compute_engine.test_compat import mock
from google_compute_engine.test_compat import unittest
@@ -25,6 +29,9 @@ from google_compute_engine.test_compat import unittest
class NetworkSetupTest(unittest.TestCase):
def setUp(self):
+ # Create a temporary directory.
+ self.test_dir = tempfile.mkdtemp()
+
self.mock_logger = mock.Mock()
self.mock_watcher = mock.Mock()
self.mock_ip_forwarding_utils = mock.Mock()
@@ -36,8 +43,13 @@ class NetworkSetupTest(unittest.TestCase):
self.mock_setup.watcher = self.mock_watcher
self.mock_setup.network_utils = self.mock_network_utils
self.mock_setup.network_interfaces = self.metadata_key
+ self.mock_setup.dhclient_script = '/bin/script'
self.mock_setup.dhcp_command = ''
+ def tearDown(self):
+ # Remove the directory after the test.
+ shutil.rmtree(self.test_dir)
+
@mock.patch('google_compute_engine.network_setup.network_setup.network_utils')
@mock.patch('google_compute_engine.network_setup.network_setup.metadata_watcher')
@mock.patch('google_compute_engine.network_setup.network_setup.logger')
@@ -58,32 +70,139 @@ class NetworkSetupTest(unittest.TestCase):
]
self.assertEqual(mocks.mock_calls, expected_calls)
+ def testModifyInterface(self):
+ config_file = os.path.join(self.test_dir, 'config.cfg')
+ config_content = [
+ '# File comment.\n',
+ 'A="apple"\n',
+ 'B=banana\n',
+ 'B=banana\n',
+ ]
+ with open(config_file, 'w') as config:
+ for line in config_content:
+ config.write(line)
+
+ # Write a value for an existing config without overriding it.
+ network_setup.NetworkSetup._ModifyInterface(
+ self.mock_setup, config_file, 'A', 'aardvark', replace=False)
+ self.assertEquals(open(config_file).readlines(), config_content)
+ # Write a value for a config that is not already set.
+ network_setup.NetworkSetup._ModifyInterface(
+ self.mock_setup, config_file, 'C', 'none', replace=False)
+ config_content.append('C=none\n')
+ self.assertEquals(open(config_file).readlines(), config_content)
+ # Write a value for an existing config with replacement.
+ network_setup.NetworkSetup._ModifyInterface(
+ self.mock_setup, config_file, 'A', 'aardvark', replace=True)
+ config_content[1] = 'A=aardvark\n'
+ self.assertEquals(open(config_file).readlines(), config_content)
+ # Write a value for an existing config with multiple occurrences.
+ network_setup.NetworkSetup._ModifyInterface(
+ self.mock_setup, config_file, 'B', '"banana"', replace=True)
+ config_content[2] = config_content[3] = 'B="banana"\n'
+ self.assertEquals(open(config_file).readlines(), config_content)
+
+ @mock.patch('google_compute_engine.network_setup.network_setup.os.path.exists')
+ def testDisableNetworkManager(self, mock_exists):
+ mock_open = mock.mock_open()
+ mocks = mock.Mock()
+ mocks.attach_mock(mock_exists, 'exists')
+ mocks.attach_mock(mock_open, 'open')
+ mocks.attach_mock(self.mock_logger, 'logger')
+ mocks.attach_mock(self.mock_setup._ModifyInterface, 'modify')
+ mock_exists.side_effect = [True, False]
+
+ with mock.patch('%s.open' % builtin, mock_open, create=False):
+ network_setup.NetworkSetup._DisableNetworkManager(
+ self.mock_setup, ['eth0', 'eth1'])
+ expected_calls = [
+ mock.call.exists('/etc/sysconfig/network-scripts/ifcfg-eth0'),
+ mock.call.modify(mock.ANY, 'DEVICE', 'eth0', replace=False),
+ mock.call.modify(mock.ANY, 'NM_CONTROLLED', 'no', replace=True),
+ mock.call.exists('/etc/sysconfig/network-scripts/ifcfg-eth1'),
+ mock.call.open('/etc/sysconfig/network-scripts/ifcfg-eth1', 'w'),
+ mock.call.open().__enter__(),
+ mock.call.open().write(mock.ANY),
+ mock.call.open().__exit__(None, None, None),
+ mock.call.logger.info(mock.ANY, 'eth1'),
+ ]
+ self.assertEqual(mocks.mock_calls, expected_calls)
+
@mock.patch('google_compute_engine.network_setup.network_setup.subprocess.check_call')
- def testEnableNetworkInterfaces(self, mock_call):
+ @mock.patch('google_compute_engine.network_setup.network_setup.os.path.exists')
+ def testConfigureNetwork(self, mock_exists, mock_call):
mocks = mock.Mock()
+ mocks.attach_mock(mock_exists, 'exists')
mocks.attach_mock(mock_call, 'call')
mocks.attach_mock(self.mock_logger, 'logger')
- mocks.attach_mock(self.mock_watcher, 'watcher')
- mocks.attach_mock(self.mock_network_utils, 'network')
+ mock_exists.side_effect = [True, False, False]
mock_call.side_effect = [
- None, None, subprocess.CalledProcessError(1, 'Test')]
+ None, None, None, None, subprocess.CalledProcessError(1, 'Test')]
- network_setup.NetworkSetup._EnableNetworkInterfaces(
- self.mock_setup, ['a', 'b', 'c'])
- network_setup.NetworkSetup._EnableNetworkInterfaces(
- self.mock_setup, [])
+ network_setup.NetworkSetup._ConfigureNetwork(self.mock_setup, ['a', 'b'])
+ network_setup.NetworkSetup._ConfigureNetwork(self.mock_setup, ['c'])
+ network_setup.NetworkSetup._ConfigureNetwork(self.mock_setup, [])
expected_calls = [
- # Successfully enable the network interface.
- mock.call.logger.info(mock.ANY, ['a', 'b', 'c']),
- mock.call.call(['dhclient', '-r', 'a', 'b', 'c']),
- mock.call.call(['dhclient', 'a', 'b', 'c']),
+ # Successfully configure the network using a managed dhclient script.
+ mock.call.logger.info(mock.ANY, ['a', 'b']),
+ mock.call.exists('/bin/script'),
+ mock.call.call(['dhclient', '-sf', '/bin/script', '-x', 'a', 'b']),
+ mock.call.call(['dhclient', '-sf', '/bin/script', 'a', 'b']),
+ # Successfully configure the network using the default dhclient script.
+ mock.call.logger.info(mock.ANY, ['c']),
+ mock.call.exists('/bin/script'),
+ mock.call.call(['dhclient', '-x', 'c']),
+ mock.call.call(['dhclient', 'c']),
# Exception while enabling the network interface.
mock.call.logger.info(mock.ANY, []),
- mock.call.call(['dhclient', '-r']),
+ mock.call.exists('/bin/script'),
+ mock.call.call(['dhclient', '-x']),
mock.call.logger.warning(mock.ANY, []),
]
self.assertEqual(mocks.mock_calls, expected_calls)
+ @mock.patch('google_compute_engine.network_setup.network_setup.subprocess.check_call')
+ @mock.patch('google_compute_engine.network_setup.network_setup.os.path.exists')
+ def testEnableNetworkInterfaces(self, mock_exists, mock_call):
+ mocks = mock.Mock()
+ mocks.attach_mock(mock_exists, 'exists')
+ mocks.attach_mock(mock_call, 'call')
+ mocks.attach_mock(self.mock_logger, 'logger')
+ mocks.attach_mock(self.mock_setup._DisableNetworkManager, 'disable')
+ mocks.attach_mock(self.mock_setup._ConfigureNetwork, 'configure')
+ mock_exists.side_effect = [True, False]
+ mock_call.side_effect = [None, subprocess.CalledProcessError(1, 'Test')]
+
+ # Return immediately with fewer than two interfaces.
+ network_setup.NetworkSetup._EnableNetworkInterfaces(self.mock_setup, None)
+ network_setup.NetworkSetup._EnableNetworkInterfaces(self.mock_setup, [])
+ # Enable interfaces with network manager enabled.
+ network_setup.NetworkSetup._EnableNetworkInterfaces(
+ self.mock_setup, ['A', 'B'])
+ # Enable interfaces with network manager is not present.
+ network_setup.NetworkSetup._EnableNetworkInterfaces(
+ self.mock_setup, ['C', 'D'])
+ # Run a user supplied command successfully.
+ self.mock_setup.dhcp_command = 'success'
+ network_setup.NetworkSetup._EnableNetworkInterfaces(
+ self.mock_setup, ['E', 'F'])
+ # Run a user supplied command and logger error messages.
+ self.mock_setup.dhcp_command = 'failure'
+ network_setup.NetworkSetup._EnableNetworkInterfaces(
+ self.mock_setup, ['G', 'H'])
+
+ expected_calls = [
+ mock.call.exists('/etc/sysconfig/network-scripts'),
+ mock.call.disable(['A', 'B']),
+ mock.call.configure(['A', 'B']),
+ mock.call.exists('/etc/sysconfig/network-scripts'),
+ mock.call.configure(['C', 'D']),
+ mock.call.call(['success']),
+ mock.call.call(['failure']),
+ mock.call.logger.warning(mock.ANY),
+ ]
+ self.assertEqual(mocks.mock_calls, expected_calls)
+
def testSetupNetworkInterfaces(self):
mocks = mock.Mock()
mocks.attach_mock(self.mock_logger, 'logger')
@@ -97,6 +216,7 @@ class NetworkSetupTest(unittest.TestCase):
with mock.patch.object(
network_setup.NetworkSetup, '_EnableNetworkInterfaces'):
+ self.mock_setup.dhcp_command = 'command'
network_setup.NetworkSetup._SetupNetworkInterfaces(self.mock_setup)
expected_calls = [
mock.call.watcher.GetMetadata(
@@ -110,48 +230,3 @@ class NetworkSetupTest(unittest.TestCase):
mock.call.setup._EnableNetworkInterfaces(['eth0', 'eth1']),
]
self.assertEqual(mocks.mock_calls, expected_calls)
-
- def testSetupNetworkInterfacesSkip(self):
- mocks = mock.Mock()
- mocks.attach_mock(self.mock_logger, 'logger')
- mocks.attach_mock(self.mock_watcher, 'watcher')
- mocks.attach_mock(self.mock_network_utils, 'network')
- mocks.attach_mock(self.mock_setup, 'setup')
- self.mock_watcher.GetMetadata.return_value = [{'mac': '1'}]
-
- with mock.patch.object(
- network_setup.NetworkSetup, '_EnableNetworkInterfaces'):
- network_setup.NetworkSetup._SetupNetworkInterfaces(self.mock_setup)
- expected_calls = [
- mock.call.watcher.GetMetadata(
- metadata_key=self.metadata_key, recursive=True),
- mock.call.network.GetNetworkInterface('1'),
- ]
- self.assertEqual(mocks.mock_calls, expected_calls)
-
- @mock.patch('google_compute_engine.network_setup.network_setup.subprocess.check_call')
- def testSetupNetworkInterfacesCommand(self, mock_call):
- mocks = mock.Mock()
- mocks.attach_mock(mock_call, 'call')
- mocks.attach_mock(self.mock_logger, 'logger')
- mocks.attach_mock(self.mock_watcher, 'watcher')
- mocks.attach_mock(self.mock_network_utils, 'network')
- mocks.attach_mock(self.mock_setup, 'setup')
- self.mock_watcher.GetMetadata.return_value = [
- {'mac': '1'}, {'mac': '2'}]
- self.mock_network_utils.GetNetworkInterface.side_effect = ['eth0', 'eth1']
- mock_call.side_effect = subprocess.CalledProcessError(1, 'Test')
-
- with mock.patch.object(
- network_setup.NetworkSetup, '_EnableNetworkInterfaces'):
- self.mock_setup.dhcp_command = 'command'
- network_setup.NetworkSetup._SetupNetworkInterfaces(self.mock_setup)
- expected_calls = [
- mock.call.watcher.GetMetadata(
- metadata_key=self.metadata_key, recursive=True),
- mock.call.network.GetNetworkInterface('1'),
- mock.call.network.GetNetworkInterface('2'),
- mock.call.call(['command']),
- mock.call.logger.warning(mock.ANY),
- ]
- self.assertEqual(mocks.mock_calls, expected_calls)
diff --git a/google_compute_engine_init/systemd/google-accounts-daemon.service b/google_compute_engine_init/systemd/google-accounts-daemon.service
index 04d74b5..125e776 100644
--- a/google_compute_engine_init/systemd/google-accounts-daemon.service
+++ b/google_compute_engine_init/systemd/google-accounts-daemon.service
@@ -1,6 +1,6 @@
[Unit]
Description=Google Compute Engine Accounts Daemon
-After=network.target google-instance-setup.service
+After=network.target google-instance-setup.service google-network-setup.service
Before=sshd.service
Requires=network.target
diff --git a/google_compute_engine_init/systemd/google-clock-skew-daemon.service b/google_compute_engine_init/systemd/google-clock-skew-daemon.service
index 511f55b..476abde 100644
--- a/google_compute_engine_init/systemd/google-clock-skew-daemon.service
+++ b/google_compute_engine_init/systemd/google-clock-skew-daemon.service
@@ -1,6 +1,6 @@
[Unit]
Description=Google Compute Engine Clock Skew Daemon
-After=network.target google-instance-setup.service
+After=network.target google-instance-setup.service google-network-setup.service
Requires=network.target
[Service]
diff --git a/google_compute_engine_init/systemd/google-ip-forwarding-daemon.service b/google_compute_engine_init/systemd/google-ip-forwarding-daemon.service
index d8d98ad..d3704c6 100644
--- a/google_compute_engine_init/systemd/google-ip-forwarding-daemon.service
+++ b/google_compute_engine_init/systemd/google-ip-forwarding-daemon.service
@@ -1,6 +1,6 @@
[Unit]
Description=Google Compute Engine IP Forwarding Daemon
-After=network.target google-instance-setup.service
+After=network.target google-instance-setup.service google-network-setup.service
Requires=network.target
[Service]
diff --git a/google_compute_engine_init/systemd/google-shutdown-scripts.service b/google_compute_engine_init/systemd/google-shutdown-scripts.service
index 04c82fd..3561089 100644
--- a/google_compute_engine_init/systemd/google-shutdown-scripts.service
+++ b/google_compute_engine_init/systemd/google-shutdown-scripts.service
@@ -1,7 +1,7 @@
[Unit]
Description=Google Compute Engine Shutdown Scripts
After=local-fs.target network-online.target network.target rsyslog.service
-After=google-instance-setup.service
+After=google-instance-setup.service google-network-setup.service
Wants=local-fs.target network-online.target network.target
[Service]
diff --git a/google_compute_engine_init/systemd/google-startup-scripts.service b/google_compute_engine_init/systemd/google-startup-scripts.service
index 1c373c5..9c04d79 100644
--- a/google_compute_engine_init/systemd/google-startup-scripts.service
+++ b/google_compute_engine_init/systemd/google-startup-scripts.service
@@ -1,7 +1,7 @@
[Unit]
Description=Google Compute Engine Startup Scripts
After=local-fs.target network-online.target network.target rsyslog.service
-After=google-instance-setup.service
+After=google-instance-setup.service google-network-setup.service
Wants=local-fs.target network-online.target network.target
[Service]
diff --git a/google_compute_engine_init/sysvinit/google-accounts-daemon b/google_compute_engine_init/sysvinit/google-accounts-daemon
index 96b0a55..4f37c52 100755
--- a/google_compute_engine_init/sysvinit/google-accounts-daemon
+++ b/google_compute_engine_init/sysvinit/google-accounts-daemon
@@ -17,6 +17,7 @@
# Provides: google_accounts_daemon
# X-Start-Before: ssh
# Required-Start: $local_fs $network $named $syslog $google_instance_setup
+# $google_network_setup
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop:
diff --git a/google_compute_engine_init/sysvinit/google-clock-skew-daemon b/google_compute_engine_init/sysvinit/google-clock-skew-daemon
index 02b0011..92e1607 100755
--- a/google_compute_engine_init/sysvinit/google-clock-skew-daemon
+++ b/google_compute_engine_init/sysvinit/google-clock-skew-daemon
@@ -16,6 +16,7 @@
### BEGIN INIT INFO
# Provides: google_clock_skew_daemon
# Required-Start: $network $syslog $google_instance_setup
+# $google_network_setup
# Required-Stop: $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
diff --git a/google_compute_engine_init/sysvinit/google-ip-forwarding-daemon b/google_compute_engine_init/sysvinit/google-ip-forwarding-daemon
index 92e72a8..6aefc0b 100755
--- a/google_compute_engine_init/sysvinit/google-ip-forwarding-daemon
+++ b/google_compute_engine_init/sysvinit/google-ip-forwarding-daemon
@@ -16,6 +16,7 @@
### BEGIN INIT INFO
# Provides: google_ip_forwarding_daemon
# Required-Start: $network $syslog $google_instance_setup
+# $google_network_setup
# Required-Stop: $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
diff --git a/google_compute_engine_init/sysvinit/google-startup-scripts b/google_compute_engine_init/sysvinit/google-startup-scripts
index 2ee8a56..c9d61d1 100755
--- a/google_compute_engine_init/sysvinit/google-startup-scripts
+++ b/google_compute_engine_init/sysvinit/google-startup-scripts
@@ -15,7 +15,7 @@
#
### BEGIN INIT INFO
# Provides: google_startup_scripts
-# Required-Start: $all $google_instance_setup
+# Required-Start: $all $google_instance_setup $google_network_setup
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop:
diff --git a/google_compute_engine_init/upstart/google-accounts-daemon.conf b/google_compute_engine_init/upstart/google-accounts-daemon.conf
index 85ef7a5..189ac2d 100644
--- a/google_compute_engine_init/upstart/google-accounts-daemon.conf
+++ b/google_compute_engine_init/upstart/google-accounts-daemon.conf
@@ -1,5 +1,5 @@
# Manages accounts from metadata SSH keys.
-start on stopped google-instance-setup
+start on stopped google-network-setup
respawn
exec /usr/bin/google_accounts_daemon
diff --git a/google_compute_engine_init/upstart/google-clock-skew-daemon.conf b/google_compute_engine_init/upstart/google-clock-skew-daemon.conf
index 5213e9c..9c17ad4 100644
--- a/google_compute_engine_init/upstart/google-clock-skew-daemon.conf
+++ b/google_compute_engine_init/upstart/google-clock-skew-daemon.conf
@@ -1,5 +1,5 @@
# Sync the system clock on migration.
-start on stopped google-instance-setup
+start on stopped google-network-setup
respawn
exec /usr/bin/google_clock_skew_daemon
diff --git a/google_compute_engine_init/upstart/google-ip-forwarding-daemon.conf b/google_compute_engine_init/upstart/google-ip-forwarding-daemon.conf
index 6d1fd05..7446310 100644
--- a/google_compute_engine_init/upstart/google-ip-forwarding-daemon.conf
+++ b/google_compute_engine_init/upstart/google-ip-forwarding-daemon.conf
@@ -1,5 +1,5 @@
# Manages IP forwarding.
-start on stopped google-instance-setup
+start on stopped google-network-setup
respawn
exec /usr/bin/google_ip_forwarding_daemon
diff --git a/google_compute_engine_init/upstart/google-network-setup.conf b/google_compute_engine_init/upstart/google-network-setup.conf
index 2255544..69c25ac 100644
--- a/google_compute_engine_init/upstart/google-network-setup.conf
+++ b/google_compute_engine_init/upstart/google-network-setup.conf
@@ -1,4 +1,6 @@
# Enables network interfaces on boot.
start on stopped google-instance-setup
+task
+
exec /usr/bin/google_network_setup
diff --git a/google_compute_engine_init/upstart/google-startup-scripts.conf b/google_compute_engine_init/upstart/google-startup-scripts.conf
index 6fa68a7..10569ea 100644
--- a/google_compute_engine_init/upstart/google-startup-scripts.conf
+++ b/google_compute_engine_init/upstart/google-startup-scripts.conf
@@ -1,4 +1,4 @@
# Runs a startup script from metadata.
-start on stopped google-instance-setup
+start on stopped google-network-setup
exec /usr/bin/google_metadata_script_runner --script-type startup
diff --git a/google_config/build_packages.sh b/google_config/build_packages.sh
index 00acc2a..4d1f0a6 100755
--- a/google_config/build_packages.sh
+++ b/google_config/build_packages.sh
@@ -85,7 +85,8 @@ for build in "${BUILD[@]}"; do
case "$build" in
el6) # RHEL/CentOS 6
build_distro 'el6' 'rpm' \
- 'bin/set_hostname=/etc/dhcp/dhclient-exit-hooks'
+ 'bin/set_hostname=/etc/dhcp/dhclient-exit-hooks' \
+ 'sbin/google-dhclient-script=/sbin/google-dhclient-script'
;;
el7) # RHEL/CentOS 7
build_distro 'el7' 'rpm' \
diff --git a/google_config/sbin/google-dhclient-script b/google_config/sbin/google-dhclient-script
new file mode 100755
index 0000000..2f5b5e7
--- /dev/null
+++ b/google_config/sbin/google-dhclient-script
@@ -0,0 +1,806 @@
+#!/bin/bash
+#
+# dhclient-script: Network interface configuration script run by
+# dhclient based on DHCP client communication
+#
+# Copyright (C) 2008-2014 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Author(s): David Cantrell <dcantrell@redhat.com>
+# Jiri Popelka <jpopelka@redhat.com>
+#
+# ----------
+# This script is a rewrite/reworking on dhclient-script originally
+# included as part of dhcp-970306:
+# dhclient-script for Linux. Dan Halbert, March, 1997.
+# Updated for Linux 2.[12] by Brian J. Murrell, January 1999.
+# Modified by David Cantrell <dcantrell@redhat.com> for Fedora and RHEL
+#
+# This script is found in EL 7 and used to fix local routing in EL 6.
+# ----------
+
+PATH=/bin:/usr/bin:/sbin
+# scripts in dhclient.d/ use $SAVEDIR (#833054)
+SAVEDIR=/var/lib/dhclient
+
+LOGFACILITY="local7"
+LOGLEVEL="notice"
+
+ETCDIR="/etc/dhcp"
+
+logmessage() {
+ msg="${1}"
+ logger -p ${LOGFACILITY}.${LOGLEVEL} -t "NET" "dhclient: ${msg}"
+}
+
+eventually_add_hostnames_domain_to_search() {
+# For the case when hostname for this machine has a domain that is not in domain_search list
+# 1) get a hostname with `ipcalc --hostname` or `hostname`
+# 2) get the domain from this hostname
+# 3) add this domain to search line in resolv.conf if it's not already
+# there (domain list that we have recently added there is a parameter of this function)
+# We can't do this directly when generating resolv.conf in make_resolv_conf(), because
+# we need to first save the resolv.conf with obtained values before we can call `ipcalc --hostname`.
+# See bug 637763
+ search="${1}"
+ if need_hostname; then
+ status=1
+ if [ -n "${new_ip_address}" ]; then
+ eval $(/bin/ipcalc --silent --hostname ${new_ip_address} ; echo "status=$?")
+ elif [ -n "${new_ip6_address}" ]; then
+ eval $(/bin/ipcalc --silent --hostname ${new_ip6_address} ; echo "status=$?")
+ fi
+
+ if [ ${status} -eq 0 ]; then
+ domain=$(echo $HOSTNAME | cut -s -d "." -f 2-)
+ fi
+ else
+ domain=$(hostname 2>/dev/null | cut -s -d "." -f 2-)
+ fi
+
+ if [ -n "${domain}" ] &&
+ [ ! "${domain}" = "localdomain" ] &&
+ [ ! "${domain}" = "localdomain6" ] &&
+ [ ! "${domain}" = "(none)" ] &&
+ [[ ! "${domain}" = *\ * ]]; then
+ is_in="false"
+ for s in ${search}; do
+ if [ "${s}" = "${domain}" ] ||
+ [ "${s}" = "${domain}." ]; then
+ is_in="true"
+ fi
+ done
+
+ if [ "${is_in}" = "false" ]; then
+ # Add domain name to search list (#637763)
+ sed -i -e "s/${search}/${search} ${domain}/" /etc/resolv.conf
+ fi
+ fi
+}
+
+make_resolv_conf() {
+ [ "${PEERDNS}" = "no" ] && return
+
+ if [ "${reason}" = "RENEW" ] &&
+ [ "${new_domain_name}" = "${old_domain_name}" ] &&
+ [ "${new_domain_name_servers}" = "${old_domain_name_servers}" ]; then
+ return
+ fi
+
+ if [ -n "${new_domain_name}" ] ||
+ [ -n "${new_domain_name_servers}" ] ||
+ [ -n "${new_domain_search}" ]; then
+ rscf="$(mktemp ${TMPDIR:-/tmp}/XXXXXX)"
+ [[ -z "${rscf}" ]] && return
+ echo "; generated by /usr/sbin/dhclient-script" > ${rscf}
+
+ if [ -n "${SEARCH}" ]; then
+ search="${SEARCH}"
+ else
+ if [ -n "${new_domain_search}" ]; then
+ # Remove instaces of \032 (#450042)
+ search="${new_domain_search//\\032/ }"
+ elif [ -n "${new_domain_name}" ]; then
+ # Note that the DHCP 'Domain Name Option' is really just a domain
+ # name, and that this practice of using the domain name option as
+ # a search path is both nonstandard and deprecated.
+ search="${new_domain_name}"
+ fi
+ fi
+
+ if [ -n "${search}" ]; then
+ echo "search ${search}" >> $rscf
+ fi
+
+ if [ -n "${RES_OPTIONS}" ]; then
+ echo "options ${RES_OPTIONS}" >> ${rscf}
+ fi
+
+ for nameserver in ${new_domain_name_servers} ; do
+ echo "nameserver ${nameserver}" >> ${rscf}
+ done
+
+ change_resolv_conf ${rscf}
+ rm -f ${rscf}
+
+ if [ -n "${search}" ]; then
+ eventually_add_hostnames_domain_to_search "${search}"
+ fi
+ elif [ -n "${new_dhcp6_name_servers}" ] ||
+ [ -n "${new_dhcp6_domain_search}" ]; then
+ rscf="$(mktemp ${TMPDIR:-/tmp}/XXXXXX)"
+ [[ -z "${rscf}" ]] && return
+ echo "; generated by /usr/sbin/dhclient-script" > ${rscf}
+
+ if [ -n "${SEARCH}" ]; then
+ search="${SEARCH}"
+ else
+ if [ -n "${new_dhcp6_domain_search}" ]; then
+ search="${new_dhcp6_domain_search//\\032/ }"
+ fi
+ fi
+
+ if [ -n "${search}" ]; then
+ echo "search ${search}" >> $rscf
+ fi
+
+ if [ -n "${RES_OPTIONS}" ]; then
+ echo "options ${RES_OPTIONS}" >> ${rscf}
+ fi
+
+ shopt -s nocasematch
+ for nameserver in ${new_dhcp6_name_servers} ; do
+ # If the nameserver has a link-local address
+ # add a <zone_id> (interface name) to it.
+ if [[ "$nameserver" =~ ^fe80:: ]]
+ then
+ zone_id="%${interface}"
+ else
+ zone_id=
+ fi
+ echo "nameserver ${nameserver}$zone_id" >> ${rscf}
+ done
+ shopt -u nocasematch
+
+ change_resolv_conf ${rscf}
+ rm -f ${rscf}
+
+ if [ -n "${search}" ]; then
+ eventually_add_hostnames_domain_to_search "${search}"
+ fi
+ fi
+}
+
+exit_with_hooks() {
+ exit_status="${1}"
+
+ if [ -x ${ETCDIR}/dhclient-exit-hooks ]; then
+ . ${ETCDIR}/dhclient-exit-hooks
+ fi
+
+ exit ${exit_status}
+}
+
+quad2num() {
+ if [ $# -eq 4 ]; then
+ let n="${1} << 24 | ${2} << 16 | ${3} << 8 | ${4}"
+ echo "${n}"
+ return 0
+ else
+ echo "0"
+ return 1
+ fi
+}
+
+ip2num() {
+ IFS="." quad2num ${1}
+}
+
+num2ip() {
+ let n="${1}"
+ let o1="(n >> 24) & 0xff"
+ let o2="(n >> 16) & 0xff"
+ let o3="(n >> 8) & 0xff"
+ let o4="n & 0xff"
+ echo "${o1}.${o2}.${o3}.${o4}"
+}
+
+get_network_address() {
+# get network address for the given IP address and (netmask or prefix)
+ ip="${1}"
+ nm="${2}"
+
+ if [ -n "${ip}" -a -n "${nm}" ]; then
+ if [[ "${nm}" = *.* ]]; then
+ ipcalc -s -n ${ip} ${nm} | cut -d '=' -f 2
+ else
+ ipcalc -s -n ${ip}/${nm} | cut -d '=' -f 2
+ fi
+ fi
+}
+
+get_prefix() {
+# get prefix for the given IP address and mask
+ ip="${1}"
+ nm="${2}"
+
+ if [ -n "${ip}" -a -n "${nm}" ]; then
+ ipcalc -s -p ${ip} ${nm} | cut -d '=' -f 2
+ fi
+}
+
+class_bits() {
+ let ip=$(IFS='.' ip2num $1)
+ let bits=32
+ let mask='255'
+ for ((i=0; i <= 3; i++, 'mask<<=8')); do
+ let v='ip&mask'
+ if [ "$v" -eq 0 ] ; then
+ let bits-=8
+ else
+ break
+ fi
+ done
+ echo $bits
+}
+
+is_router_reachable() {
+ # handle DHCP servers that give us a router not on our subnet
+ router="${1}"
+ routersubnet="$(get_network_address ${router} ${new_subnet_mask})"
+ mysubnet="$(get_network_address ${new_ip_address} ${new_subnet_mask})"
+
+ if [ ! "${routersubnet}" = "${mysubnet}" ]; then
+ ip -4 route replace ${router}/32 dev ${interface}
+ if [ "$?" -ne 0 ]; then
+ logmessage "failed to create host route for ${router}"
+ return 1
+ fi
+ fi
+
+ return 0
+}
+
+add_default_gateway() {
+ router="${1}"
+
+ if is_router_reachable ${router} ; then
+ metric=""
+ if [ $# -gt 1 ] && [ ${2} -gt 0 ]; then
+ metric="metric ${2}"
+ fi
+ ip -4 route replace default via ${router} dev ${interface} ${metric}
+ if [ $? -ne 0 ]; then
+ logmessage "failed to create default route: ${router} dev ${interface} ${metric}"
+ return 1
+ else
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+execute_client_side_configuration_scripts() {
+# execute any additional client side configuration scripts we have
+ if [ "${1}" == "config" ] || [ "${1}" == "restore" ]; then
+ for f in ${ETCDIR}/dhclient.d/*.sh ; do
+ if [ -x ${f} ]; then
+ subsystem="${f%.sh}"
+ subsystem="${subsystem##*/}"
+ . ${f}
+ "${subsystem}_${1}"
+ fi
+ done
+ fi
+}
+
+flush_dev() {
+# Instead of bringing the interface down (#574568)
+# explicitly clear the ARP cache and flush all addresses & routes.
+ ip -4 addr flush dev ${1} >/dev/null 2>&1
+ ip -4 route flush dev ${1} >/dev/null 2>&1
+ ip -4 neigh flush dev ${1} >/dev/null 2>&1
+}
+
+dhconfig() {
+ if [ -n "${old_ip_address}" ] && [ -n "${alias_ip_address}" ] &&
+ [ ! "${alias_ip_address}" = "${old_ip_address}" ]; then
+ # possible new alias, remove old alias first
+ ip -4 addr del ${old_ip_address} dev ${interface} label ${interface}:0
+ fi
+
+ if [ -n "${old_ip_address}" ] &&
+ [ ! "${old_ip_address}" = "${new_ip_address}" ]; then
+ # IP address changed. Delete all routes, and clear the ARP cache.
+ flush_dev ${interface}
+ fi
+
+ if [ "${reason}" = "BOUND" ] || [ "${reason}" = "REBOOT" ] ||
+ [ ! "${old_ip_address}" = "${new_ip_address}" ] ||
+ [ ! "${old_subnet_mask}" = "${new_subnet_mask}" ] ||
+ [ ! "${old_network_number}" = "${new_network_number}" ] ||
+ [ ! "${old_broadcast_address}" = "${new_broadcast_address}" ] ||
+ [ ! "${old_routers}" = "${new_routers}" ] ||
+ [ ! "${old_interface_mtu}" = "${new_interface_mtu}" ]; then
+ ip -4 addr add ${new_ip_address}/${new_prefix} broadcast ${new_broadcast_address} dev ${interface} \
+ valid_lft ${new_dhcp_lease_time} preferred_lft ${new_dhcp_lease_time} >/dev/null 2>&1
+ ip link set dev ${interface} up
+
+ # The 576 MTU is only used for X.25 and dialup connections
+ # where the admin wants low latency. Such a low MTU can cause
+ # problems with UDP traffic, among other things. As such,
+ # disallow MTUs from 576 and below by default, so that broken
+ # MTUs are ignored, but higher stuff is allowed (1492, 1500, etc).
+ if [ -n "${new_interface_mtu}" ] && [ ${new_interface_mtu} -gt 576 ]; then
+ ip link set dev ${interface} mtu ${new_interface_mtu}
+ fi
+
+ # static routes
+ if [ -n "${new_classless_static_routes}" ] ||
+ [ -n "${new_static_routes}" ]; then
+ if [ -n "${new_classless_static_routes}" ]; then
+ IFS=', |' static_routes=(${new_classless_static_routes})
+ else
+ IFS=', |' static_routes=(${new_static_routes})
+ fi
+ route_targets=()
+
+ for((i=0; i<${#static_routes[@]}; i+=2)); do
+ target=${static_routes[$i]}
+ if [ -n "${new_classless_static_routes}" ]; then
+ if [ ${target} = "0" ]; then
+ # If the DHCP server returns both a Classless Static Routes option and
+ # a Router option, the DHCP client MUST ignore the Router option. (RFC3442)
+ new_routers=""
+ prefix="0"
+ else
+ prefix=${target%%.*}
+ target=${target#*.}
+ IFS="." target_arr=(${target})
+ unset IFS
+ ((pads=4-${#target_arr[@]}))
+ for j in $(seq $pads); do
+ target="${target}.0"
+ done
+
+ # Client MUST zero any bits in the subnet number where the corresponding bit in the mask is zero.
+ # In other words, the subnet number installed in the routing table is the logical AND of
+ # the subnet number and subnet mask given in the Classless Static Routes option. (RFC3442)
+ target="$(get_network_address ${target} ${prefix})"
+ fi
+ else
+ prefix=$(class_bits ${target})
+ fi
+ gateway=${static_routes[$i+1]}
+
+ # special case 0.0.0.0 to allow static routing for link-local addresses
+ # (including IPv4 multicast) which will not have a next-hop (#769463, #787318)
+ if [ "${gateway}" = "0.0.0.0" ]; then
+ valid_gateway=0
+ scope='scope link'
+ else
+ is_router_reachable ${gateway}
+ valid_gateway=$?
+ scope=''
+ fi
+ if [ ${valid_gateway} -eq 0 ]; then
+ metric=''
+ for t in ${route_targets[@]}; do
+ if [ ${t} = ${target} ]; then
+ if [ -z "${metric}" ]; then
+ metric=1
+ else
+ ((metric=metric+1))
+ fi
+ fi
+ done
+
+ if [ -n "${metric}" ]; then
+ metric="metric ${metric}"
+ fi
+
+ ip -4 route replace ${target}/${prefix} proto static via ${gateway} dev ${interface} ${metric} ${scope}
+
+ if [ $? -ne 0 ]; then
+ logmessage "failed to create static route: ${target}/${prefix} via ${gateway} dev ${interface} ${metric}"
+ else
+ route_targets=(${route_targets[@]} ${target})
+ fi
+ fi
+ done
+ fi
+
+ # gateways
+ if [[ ( "${DEFROUTE}" != "no" ) &&
+ (( -z "${GATEWAYDEV}" ) || ( "${GATEWAYDEV}" = "${interface}" )) ]]; then
+ if [[ ( -z "$GATEWAY" ) ||
+ (( -n "$DHCLIENT_IGNORE_GATEWAY" ) && ( "$DHCLIENT_IGNORE_GATEWAY" = [Yy]* )) ]]; then
+ metric="${METRIC:-}"
+ let i="${METRIC:-0}"
+ default_routers=()
+
+ for router in ${new_routers} ; do
+ added_router=-
+
+ for r in ${default_routers[@]} ; do
+ if [ "${r}" = "${router}" ]; then
+ added_router=1
+ fi
+ done
+
+ if [ -z "${router}" ] ||
+ [ "${added_router}" = "1" ] ||
+ [ $(IFS=. ip2num ${router}) -le 0 ] ||
+ [[ ( "${router}" = "${new_broadcast_address}" ) &&
+ ( "${new_subnet_mask}" != "255.255.255.255" ) ]]; then
+ continue
+ fi
+
+ default_routers=(${default_routers[@]} ${router})
+ add_default_gateway ${router} ${metric}
+ let i=i+1
+ metric=${i}
+ done
+ elif [ -n "${GATEWAY}" ]; then
+ routersubnet=$(get_network_address ${GATEWAY} ${new_subnet_mask})
+ mysubnet=$(get_network_address ${new_ip_address} ${new_subnet_mask})
+
+ if [ "${routersubnet}" = "${mysubnet}" ]; then
+ ip -4 route replace default via ${GATEWAY} dev ${interface}
+ fi
+ fi
+ fi
+
+ else # RENEW||REBIND - only update address lifetimes
+ ip -4 addr change ${new_ip_address}/${new_prefix} broadcast ${new_broadcast_address} dev ${interface} \
+ valid_lft ${new_dhcp_lease_time} preferred_lft ${new_dhcp_lease_time} >/dev/null 2>&1
+ fi
+
+ if [ ! "${new_ip_address}" = "${alias_ip_address}" ] &&
+ [ -n "${alias_ip_address}" ]; then
+ # Reset the alias address (fix: this should really only do this on changes)
+ ip -4 addr flush dev ${interface} label ${interface}:0 >/dev/null 2>&1
+ ip -4 addr add ${alias_ip_address}/${alias_prefix} broadcast ${alias_broadcast_address} dev ${interface} label ${interface}:0
+ ip -4 route replace ${alias_ip_address}/32 dev ${interface}
+ fi
+
+ # After dhclient brings an interface UP with a new IP address, subnet mask,
+ # and routes, in the REBOOT/BOUND states -> search for "dhclient-up-hooks".
+ if [ "${reason}" = "BOUND" ] || [ "${reason}" = "REBOOT" ] ||
+ [ ! "${old_ip_address}" = "${new_ip_address}" ] ||
+ [ ! "${old_subnet_mask}" = "${new_subnet_mask}" ] ||
+ [ ! "${old_network_number}" = "${new_network_number}" ] ||
+ [ ! "${old_broadcast_address}" = "${new_broadcast_address}" ] ||
+ [ ! "${old_routers}" = "${new_routers}" ] ||
+ [ ! "${old_interface_mtu}" = "${new_interface_mtu}" ]; then
+
+ if [ -x ${ETCDIR}/dhclient-${interface}-up-hooks ]; then
+ . ${ETCDIR}/dhclient-${interface}-up-hooks
+ elif [ -x ${ETCDIR}/dhclient-up-hooks ]; then
+ . ${ETCDIR}/dhclient-up-hooks
+ fi
+ fi
+
+ make_resolv_conf
+
+ if [ -n "${new_host_name}" ] && need_hostname; then
+ hostname ${new_host_name} || echo "See -nc option in dhclient(8) man page."
+ fi
+
+ if [[ ( "${DHCP_TIME_OFFSET_SETS_TIMEZONE}" = [yY1]* ) &&
+ ( -n "${new_time_offset}" ) ]]; then
+ # DHCP option "time-offset" is requested by default and should be
+ # handled. The geographical zone abbreviation cannot be determined
+ # from the GMT offset, but the $ZONEINFO/Etc/GMT$offset file can be
+ # used - note: this disables DST.
+ ((z=new_time_offset/3600))
+ ((hoursWest=$(printf '%+d' $z)))
+
+ if (( $hoursWest < 0 )); then
+ # tzdata treats negative 'hours west' as positive 'gmtoff'!
+ ((hoursWest*=-1))
+ fi
+
+ tzfile=/usr/share/zoneinfo/Etc/GMT$(printf '%+d' ${hoursWest})
+ if [ -e ${tzfile} ]; then
+ cp -fp ${tzfile} /etc/localtime
+ touch /etc/localtime
+ fi
+ fi
+
+ execute_client_side_configuration_scripts "config"
+}
+
+# Section 18.1.8. (Receipt of Reply Messages) of RFC 3315 says:
+# The client SHOULD perform duplicate address detection on each of
+# the addresses in any IAs it receives in the Reply message before
+# using that address for traffic.
+add_ipv6_addr_with_DAD() {
+ ip -6 addr add ${new_ip6_address}/${new_ip6_prefixlen} \
+ dev ${interface} scope global valid_lft ${new_max_life} \
+ preferred_lft ${new_preferred_life}
+
+ # repeatedly test whether newly added address passed
+ # duplicate address detection (DAD)
+ for i in $(seq 5); do
+ sleep 1 # give the DAD some time
+
+ addr=$(ip -6 addr show dev ${interface} \
+ | grep ${new_ip6_address}/${new_ip6_prefixlen})
+
+ # tentative flag == DAD is still not complete
+ tentative=$(echo "${addr}" | grep tentative)
+ # dadfailed flag == address is already in use somewhere else
+ dadfailed=$(echo "${addr}" | grep dadfailed)
+
+ if [ -n "${dadfailed}" ] ; then
+ # address was added with valid_lft/preferred_lft 'forever', remove it
+ ip -6 addr del ${new_ip6_address}/${new_ip6_prefixlen} dev ${interface}
+ exit_with_hooks 3
+ fi
+ if [ -z "${tentative}" ] ; then
+ if [ -n "${addr}" ]; then
+ # DAD is over
+ return 0
+ else
+ # address was auto-removed (or not added at all)
+ exit_with_hooks 3
+ fi
+ fi
+ done
+ return 0
+}
+
+dh6config() {
+ if [ -n "${old_ip6_prefix}" ] ||
+ [ -n "${new_ip6_prefix}" ]; then
+ echo Prefix ${reason} old=${old_ip6_prefix} new=${new_ip6_prefix}
+ exit_with_hooks 0
+ fi
+
+ case "${reason}" in
+ BOUND6)
+ if [ -z "${new_ip6_address}" ] ||
+ [ -z "${new_ip6_prefixlen}" ]; then
+ exit_with_hooks 2
+ fi
+
+ add_ipv6_addr_with_DAD
+
+ make_resolv_conf
+ ;;
+
+ RENEW6|REBIND6)
+ if [[ -n "${new_ip6_address}" ]] &&
+ [[ -n "${new_ip6_prefixlen}" ]]; then
+ if [[ ! "${new_ip6_address}" = "${old_ip6_address}" ]]; then
+ add_ipv6_addr_with_DAD
+ else # only update address lifetimes
+ ip -6 addr change ${new_ip6_address}/${new_ip6_prefixlen} \
+ dev ${interface} scope global valid_lft ${new_max_life} \
+ preferred_lft ${new_preferred_life}
+ fi
+ fi
+
+ if [ ! "${new_dhcp6_name_servers}" = "${old_dhcp6_name_servers}" ] ||
+ [ ! "${new_dhcp6_domain_search}" = "${old_dhcp6_domain_search}" ]; then
+ make_resolv_conf
+ fi
+ ;;
+
+ DEPREF6)
+ if [ -z "${new_ip6_prefixlen}" ]; then
+ exit_with_hooks 2
+ fi
+
+ ip -6 addr change ${new_ip6_address}/${new_ip6_prefixlen} \
+ dev ${interface} scope global preferred_lft 0
+ ;;
+ esac
+
+ execute_client_side_configuration_scripts "config"
+}
+
+
+#
+# ### MAIN
+#
+
+if [ -x ${ETCDIR}/dhclient-enter-hooks ]; then
+ exit_status=0
+
+ # dhclient-enter-hooks can abort dhclient-script by setting
+ # the exit_status variable to a non-zero value
+ . ${ETCDIR}/dhclient-enter-hooks
+ if [ ${exit_status} -ne 0 ]; then
+ exit ${exit_status}
+ fi
+fi
+
+if [ ! -r /etc/sysconfig/network-scripts/network-functions ]; then
+ echo "Missing /etc/sysconfig/network-scripts/network-functions, exiting." >&2
+ exit 1
+fi
+
+if [ ! -r /etc/rc.d/init.d/functions ]; then
+ echo "Missing /etc/rc.d/init.d/functions, exiting." >&2
+ exit 1
+fi
+
+. /etc/sysconfig/network-scripts/network-functions
+. /etc/rc.d/init.d/functions
+
+if [ -f /etc/sysconfig/network ]; then
+ . /etc/sysconfig/network
+fi
+
+if [ -f /etc/sysconfig/networking/network ]; then
+ . /etc/sysconfig/networking/network
+fi
+
+cd /etc/sysconfig/network-scripts
+CONFIG="${interface}"
+need_config ${CONFIG}
+source_config >/dev/null 2>&1
+
+new_prefix="$(get_prefix ${new_ip_address} ${new_subnet_mask})"
+old_prefix="$(get_prefix ${old_ip_address} ${old_subnet_mask})"
+alias_prefix="$(get_prefix ${alias_ip_address} ${alias_subnet_mask})"
+
+case "${reason}" in
+ MEDIUM|ARPCHECK|ARPSEND)
+ # Do nothing
+ exit_with_hooks 0
+ ;;
+
+ PREINIT)
+ if [ -n "${alias_ip_address}" ]; then
+ # Flush alias, its routes will disappear too.
+ ip -4 addr flush dev ${interface} label ${interface}:0 >/dev/null 2>&1
+ fi
+
+ # upstream dhclient-script removes (ifconfig $interface 0 up) old adresses in PREINIT,
+ # but we sometimes (#125298) need (for iSCSI/nfs root to have a dhcp interface) to keep the existing ip
+ # flush_dev ${interface}
+ ip link set dev ${interface} up
+ if [ -n "${DHCLIENT_DELAY}" ] && [ ${DHCLIENT_DELAY} -gt 0 ]; then
+ # We need to give the kernel some time to get the interface up.
+ sleep ${DHCLIENT_DELAY}
+ fi
+
+ exit_with_hooks 0
+ ;;
+
+ PREINIT6)
+ # ensure interface is up
+ ip link set dev ${interface} up
+
+ # remove any stale addresses from aborted clients
+ ip -6 addr flush dev ${interface} scope global permanent
+
+ # we need a link-local address to be ready (not tentative)
+ for i in $(seq 50); do
+ linklocal=$(ip -6 addr show dev ${interface} scope link)
+ # tentative flag means DAD is still not complete
+ tentative=$(echo "${linklocal}" | grep tentative)
+ [[ -n "${linklocal}" && -z "${tentative}" ]] && exit_with_hooks 0
+ sleep 0.1
+ done
+
+ exit_with_hooks 0
+ ;;
+
+ BOUND|RENEW|REBIND|REBOOT)
+ if [ -z "${interface}" ] || [ -z "${new_ip_address}" ]; then
+ exit_with_hooks 2
+ fi
+ if arping -D -q -c2 -I ${interface} ${new_ip_address}; then
+ dhconfig
+ exit_with_hooks 0
+ else # DAD failed, i.e. address is already in use
+ ARP_REPLY=$(arping -D -c2 -I ${interface} ${new_ip_address} | grep reply | awk '{print toupper($5)}' | cut -d "[" -f2 | cut -d "]" -f1)
+ OUR_MACS=$(ip link show | grep link | awk '{print toupper($2)}' | uniq)
+ if [[ "${OUR_MACS}" = *"${ARP_REPLY}"* ]]; then
+ # in RENEW the reply can come from our system, that's OK
+ dhconfig
+ exit_with_hooks 0
+ else
+ exit_with_hooks 1
+ fi
+ fi
+ ;;
+
+ BOUND6|RENEW6|REBIND6|DEPREF6)
+ dh6config
+ exit_with_hooks 0
+ ;;
+
+ EXPIRE6|RELEASE6|STOP6)
+ if [ -z "${old_ip6_address}" ] || [ -z "${old_ip6_prefixlen}" ]; then
+ exit_with_hooks 2
+ fi
+
+ ip -6 addr del ${old_ip6_address}/${old_ip6_prefixlen} \
+ dev ${interface}
+
+ execute_client_side_configuration_scripts "restore"
+
+ if [ -x ${ETCDIR}/dhclient-${interface}-down-hooks ]; then
+ . ${ETCDIR}/dhclient-${interface}-down-hooks
+ elif [ -x ${ETCDIR}/dhclient-down-hooks ]; then
+ . ${ETCDIR}/dhclient-down-hooks
+ fi
+
+ exit_with_hooks 0
+ ;;
+
+ EXPIRE|FAIL|RELEASE|STOP)
+ execute_client_side_configuration_scripts "restore"
+
+ if [ -x ${ETCDIR}/dhclient-${interface}-down-hooks ]; then
+ . ${ETCDIR}/dhclient-${interface}-down-hooks
+ elif [ -x ${ETCDIR}/dhclient-down-hooks ]; then
+ . ${ETCDIR}/dhclient-down-hooks
+ fi
+
+ if [ -n "${alias_ip_address}" ]; then
+ # Flush alias
+ ip -4 addr flush dev ${interface} label ${interface}:0 >/dev/null 2>&1
+ fi
+
+ if [ -n "${old_ip_address}" ]; then
+ # Delete addresses/routes/arp cache.
+ flush_dev ${interface}
+ fi
+
+ if [ -n "${alias_ip_address}" ]; then
+ ip -4 addr add ${alias_ip_address}/${alias_prefix} broadcast ${alias_broadcast_address} dev ${interface} label ${interface}:0
+ ip -4 route replace ${alias_ip_address}/32 dev ${interface}
+ fi
+
+ exit_with_hooks 0
+ ;;
+
+ TIMEOUT)
+ if [ -n "${new_routers}" ]; then
+ if [ -n "${alias_ip_address}" ]; then
+ ip -4 addr flush dev ${interface} label ${interface}:0 >/dev/null 2>&1
+ fi
+
+ ip -4 addr add ${new_ip_address}/${new_prefix} \
+ broadcast ${new_broadcast_address} dev ${interface} \
+ valid_lft ${new_dhcp_lease_time} preferred_lft ${new_dhcp_lease_time}
+ set ${new_routers}
+
+ if ping -q -c 1 -w 10 -I ${interface} ${1}; then
+ dhconfig
+ exit_with_hooks 0
+ fi
+
+ flush_dev ${interface}
+ exit_with_hooks 1
+ else
+ exit_with_hooks 1
+ fi
+ ;;
+
+ *)
+ logmessage "unhandled state: ${reason}"
+ exit_with_hooks 1
+ ;;
+esac
+
+exit_with_hooks 0