diff options
23 files changed, 1052 insertions, 95 deletions
diff --git a/google_compute_engine/accounts/accounts_utils.py b/google_compute_engine/accounts/accounts_utils.py index 1b18eb1..4096086 100644 --- a/google_compute_engine/accounts/accounts_utils.py +++ b/google_compute_engine/accounts/accounts_utils.py @@ -278,10 +278,12 @@ class AccountsUtils(object): if not bool(USER_REGEX.match(user)): self.logger.warning('Invalid user account name %s.', user) return False - if not self._GetUser(user) and not self._AddUser(user): - return False - if not self._UpdateUserGroups(user, self.groups): - return False + if not self._GetUser(user): + # User does not exist. Attempt to create the user and add them to the + # appropriate user groups. + if not (self._AddUser(user) and + self._UpdateUserGroups(user, self.groups)): + return False # Don't try to manage account SSH keys with a shell set to disable # logins. This helps avoid problems caused by operator and root sharing 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/build_packages.sh b/google_compute_engine_init/build_packages.sh index 4d8c20b..94a0146 100755 --- a/google_compute_engine_init/build_packages.sh +++ b/google_compute_engine_init/build_packages.sh @@ -70,7 +70,7 @@ function build_distro() { "google-compute-daemon: ${init_config}/rpm_replace" \ --url 'https://github.com/GoogleCloudPlatform/compute-image-packages' \ --vendor 'Google Compute Engine Team' \ - --version '2.1.0' \ + --version '2.1.1' \ "${init_files[@]}" } 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..0040241 100755 --- a/google_config/build_packages.sh +++ b/google_config/build_packages.sh @@ -46,7 +46,7 @@ function build_distro() { --rpm-dist "${distro}" \ --url 'https://github.com/GoogleCloudPlatform/compute-image-packages' \ --vendor 'Google Compute Engine Team' \ - --version '2.0.0' \ + --version '2.1.0' \ "${COMMON_FILES[@]}" \ "${files[@]:2}" } @@ -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 @@ -32,7 +32,7 @@ setuptools.setup( packages=setuptools.find_packages(), scripts=glob.glob('scripts/*'), url='https://github.com/GoogleCloudPlatform/compute-image-packages', - version='2.2.3', + version='2.2.4', # Entry points create scripts in /usr/bin that call a function. entry_points={ 'console_scripts': [ |