summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Illfelder <illfelder@google.com>2016-09-30 14:38:24 -0700
committerMax Illfelder <illfelder@google.com>2016-09-30 14:38:24 -0700
commit14a50a6217a39e1f50f2ebade802cf76500a673a (patch)
treecb5d049759846fb0727d3a0e352d3d3ea5d2fd2b
parente843cc6919bbed8203ef8ce87b5a3120d4e86800 (diff)
parent6e811bf83b643af130275206a814d14fc0399821 (diff)
downloadgoogle-compute-image-packages-14a50a6217a39e1f50f2ebade802cf76500a673a.tar.gz
Merge branch 'development'20160930
-rw-r--r--google_compute_engine/accounts/accounts_utils.py10
-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
-rwxr-xr-xgoogle_compute_engine_init/build_packages.sh2
-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.sh5
-rwxr-xr-xgoogle_config/sbin/google-dhclient-script806
-rwxr-xr-xsetup.py2
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
diff --git a/setup.py b/setup.py
index a2d650d..c9adf14 100755
--- a/setup.py
+++ b/setup.py
@@ -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': [