301 $?ˆl–Ðz¾”ji?u4 \¸î*bÑ¿Õ „eÇ„/ÔÀÒ?÷?# Copyright (C) 2017 Canonical Ltd. # # Author: Chad Smith # # This file is part of cloud-init. See LICENSE file for license information. import abc import glob import logging import os import re import signal import socket import struct import time from contextlib import suppress from io import StringIO from subprocess import TimeoutExpired from typing import Any, Callable, Dict, List, Optional, Tuple import configobj from cloudinit import subp, temp_utils, util from cloudinit.net import get_interface_mac, is_ib_interface LOG = logging.getLogger(__name__) NETWORKD_LEASES_DIR = "/run/systemd/netif/leases" DHCLIENT_FALLBACK_LEASE_DIR = "/var/lib/dhclient" # Match something.lease or something.leases DHCLIENT_FALLBACK_LEASE_REGEX = r".+\.leases?$" UDHCPC_SCRIPT = """#!/bin/sh log() { echo "udhcpc[$PPID]" "$interface: $2" } [ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1 case $1 in bound|renew) cat < "$LEASE_FILE" { "interface": "$interface", "fixed-address": "$ip", "subnet-mask": "$subnet", "routers": "${router%% *}", "static_routes" : "${staticroutes}" } JSON ;; deconfig) log err "Not supported" exit 1 ;; leasefail | nak) log err "configuration failed: $1: $message" exit 1 ;; *) echo "$0: Unknown udhcpc command: $1" >&2 exit 1 ;; esac """ class NoDHCPLeaseError(Exception): """Raised when unable to get a DHCP lease.""" class InvalidDHCPLeaseFileError(NoDHCPLeaseError): """Raised when parsing an empty or invalid dhclient.lease file. Current uses are DataSourceAzure and DataSourceEc2 during ephemeral boot to scrape metadata. """ class NoDHCPLeaseInterfaceError(NoDHCPLeaseError): """Raised when unable to find a viable interface for DHCP.""" class NoDHCPLeaseMissingDhclientError(NoDHCPLeaseError): """Raised when unable to find dhclient.""" def maybe_perform_dhcp_discovery( distro, nic=None, dhcp_log_func=None ) -> Dict[str, Any]: """Perform dhcp discovery if nic valid and dhclient command exists. If the nic is invalid or undiscoverable or dhclient command is not found, skip dhcp_discovery and return an empty dict. @param nic: Name of the network interface we want to run dhclient on. @param dhcp_log_func: A callable accepting the dhclient output and error streams. @return: A list of dicts representing dhcp options for each lease obtained from the dhclient discovery if run, otherwise an empty list is returned. """ interface = nic or distro.fallback_interface if interface is None: LOG.debug("Skip dhcp_discovery: Unable to find fallback nic.") raise NoDHCPLeaseInterfaceError() return distro.dhcp_client.dhcp_discovery(interface, dhcp_log_func, distro) def networkd_parse_lease(content): """Parse a systemd lease file content as in /run/systemd/netif/leases/ Parse this (almost) ini style file even though it says: # This is private data. Do not parse. Simply return a dictionary of key/values.""" return dict(configobj.ConfigObj(StringIO(content), list_values=False)) def networkd_load_leases(leases_d=None): """Return a dictionary of dictionaries representing each lease found in lease_d.i The top level key will be the filename, which is typically the ifindex.""" if leases_d is None: leases_d = NETWORKD_LEASES_DIR ret: Dict[str, dict] = {} if not os.path.isdir(leases_d): return ret for lfile in os.listdir(leases_d): ret[lfile] = networkd_parse_lease( util.load_text_file(os.path.join(leases_d, lfile)) ) return ret def networkd_get_option_from_leases(keyname, leases_d=None): if leases_d is None: leases_d = NETWORKD_LEASES_DIR leases = networkd_load_leases(leases_d=leases_d) for _ifindex, data in sorted(leases.items()): if data.get(keyname): return data[keyname] return No