U
    Ld9T                     @   sl  d dl Z d dlZd dlZd dlZd dlZd dlZd dlZd dlZd dlm	Z	 d dl
mZmZmZ d dlZd dlmZmZmZ d dlmZmZmZmZmZ eeZdZdZG dd	 d	eZG d
d deZ G dd deZ!G dd deZ"G dd deZ#dd Z$d$ddZ%dd Z&d%ddZ'd&ddZ(G dd de j)Z*G dd de*Z+G d d! d!Z,G d"d# d#e*Z-dS )'    N)StringIO)AnyDictList)subp
temp_utilsutil)find_fallback_nicget_devicelistget_ib_interface_hwaddrget_interface_macis_ib_interfacez/run/systemd/netif/leasesaN  #!/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 <<JSON > "$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
c                   @   s   e Zd ZdZdS )NoDHCPLeaseErrorz'Raised when unable to get a DHCP lease.N__name__
__module____qualname____doc__ r   r   4/usr/lib/python3/dist-packages/cloudinit/net/dhcp.pyr   A   s   r   c                   @   s   e Zd ZdZdS )InvalidDHCPLeaseFileErrorzRaised when parsing an empty or invalid dhclient.lease file.

    Current uses are DataSourceAzure and DataSourceEc2 during ephemeral
    boot to scrape metadata.
    Nr   r   r   r   r   r   E   s   r   c                   @   s   e Zd ZdZdS )NoDHCPLeaseInterfaceErrorz7Raised when unable to find a viable interface for DHCP.Nr   r   r   r   r   r   M   s   r   c                   @   s   e Zd ZdZdS )NoDHCPLeaseMissingDhclientErrorz$Raised when unable to find dhclient.Nr   r   r   r   r   r   Q   s   r   c                   @   s   e Zd ZdZdS )NoDHCPLeaseMissingUdhcpcErrorz)Raised when unable to find udhcpc client.Nr   r   r   r   r   r   U   s   r   c              
   C   s\   | j D ]J}z| }td|j |W   S  ttfk
rN   td|j Y qX qt dS )zdistros set priority list, select based on this order which to use

    If the priority dhcp client isn't found, fall back to lower in list.
    zDHCP client selected: %szDHCP client not found: %sN)Zdhcp_client_priorityLOGdebugclient_namer   r   warning)distroclientZdhcp_clientr   r   r   select_dhcp_clientY   s    

r    c                 C   sZ   |dkr(t  }|dkrDtd t n|t krDtd| t t| }|||| S )a  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.
    Nz1Skip dhcp_discovery: Unable to find fallback nic.z8Skip dhcp_discovery: nic %s not found in get_devicelist.)r	   r   r   r   r
   r    dhcp_discovery)r   Znicdhcp_log_funcr   r   r   r   maybe_perform_dhcp_discoveryk   s    

 r#   c                 C   s   t tjt| ddS )zParse 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.F)Zlist_values)dict	configobjZ	ConfigObjr   )Zcontentr   r   r   networkd_parse_lease   s    r&   c                 C   sP   | dkrt } i }tj| s |S t| D ] }tttj| |||< q*|S )zReturn 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.N)	NETWORKD_LEASES_DIRospathisdirlistdirr&   r   	load_filejoin)leases_dZretZlfiler   r   r   networkd_load_leases   s    
r/   c                 C   sF   |d krt }t|d}t| D ]\}}|| r"||    S q"d S )N)r.   )r'   r/   sorteditemsget)Zkeynamer.   ZleasesZ_ifindexdatar   r   r   networkd_get_option_from_leases   s    

r4   c                   @   sL   e Zd ZdZedd Zedd ZeedddZeedd	d
Z	dS )
DhcpClient c                 C   s   t j d| jgddgd d S )NZpkillr      Zrcs)r   r   )clsr   r   r   kill_dhcp_client   s    zDhcpClient.kill_dhcp_clientc                 C   s*   |    td}|D ]}t| qd S )Nz/var/lib/dhcp/*)r:   globr(   remove)r9   filesfiler   r   r   clear_leases   s    
zDhcpClient.clear_leases)dhcp_interfacec                 C   s   |j d| j|ddgd d S )Nstartr   r7   r8   Zmanage_servicer   r9   r@   r   r   r   r   start_service   s       zDhcpClient.start_servicec                 C   s   |j d| jddgd d S )Nstopr   r7   r8   rB   rC   r   r   r   stop_service   s    zDhcpClient.stop_serviceN)
r   r   r   r   classmethodr:   r?   strrD   rF   r   r   r   r   r5      s   

r5   c                   @   st   e Zd ZdZdd Zeeeeee	f  dddZ
ddd	Zed
d Zedd ZedddZedd ZdS )IscDhclientdhclientc                 C   s&   t d| _| js"td t d S )NrJ   z7Skip dhclient configuration: No dhclient command found.)r   whichdhclient_pathr   r   r   selfr   r   r   __init__   s    zIscDhclient.__init__)
lease_filereturnc                 C   s   t dt j}g }t| }t|dkr6td| ||D ]V}g }|	dD ]4}|
 dddd}|stqR||	dd	 qR|t| q@|std
| |S )a7  Parse the given dhcp lease file returning all leases as dicts.

        Return a list of dicts of dhcp options. Each dict contains key value
        pairs a specific lease in order from oldest to newest.

        @raises: InvalidDHCPLeaseFileError on empty of unparseable leasefile
            content.
        zlease {(?P<lease>.*?)}\nr   z&Cannot parse empty dhcp lease file {0};"r6   zoption  r7   z1Cannot parse dhcp lease file {0}. No leases found)recompileDOTALLr   r,   lenr   formatfindallsplitstripreplaceappendr$   )rP   Zlease_regexZdhcp_leasesZlease_contentZleaseZlease_optionsliner   r   r   parse_dhcp_lease_file   s,    

z!IscDhclient.parse_dhcp_lease_fileNc              
   C   s  t d| d}d}d}tt t| t| W 5 Q R X |j| t	|rdt
|dd  }d||f }tjdd	}	tj|	|d
 }t|| z"t|| j||||\}
}W nB tjk
r } z t d|j|j|j t|W 5 d}~X Y nX tj||gddd}|r@t dddd |D  g S d}d}tddD ]z}t| }zt|}W n tk
r   Y n:X t |}|dkrt d| t!|t"j# d} qt$%d qR|st &d||d |dk	r||
| | '|S )a  Run dhclient on the interface without scripts/filesystem artifacts.

        @param dhclient_cmd_path: Full path to the dhclient used.
        @param interface: Name of the network interface on which to dhclient.
        @param dhcp_log_func: A callable accepting the dhclient output and
            error streams.

        @return: A list of dicts of representing the dhcp leases parsed from
            the dhclient.lease file or empty list.
        !Performing a dhcp discovery on %sz/run/dhclient.pidz/run/dhclient.leaseNz20:%s$   z0interface "%s" {send dhcp-client-identifier %s;}TZ	needs_exez-dhclient.confz3dhclient exited with code: %s stderr: %r stdout: %r   g{Gz?)ZmaxwaitZnaplenz+dhclient did not produce expected files: %sz, c                 s   s   | ]}t j|V  qd S )N)r(   r)   basename).0fr   r   r   	<genexpr>I  s     z-IscDhclient.dhcp_discovery.<locals>.<genexpr>unknownFr   i  r7   zkilling dhclient with pid=%szCdhclient(pid=%s, parentpid=%s) failed to daemonize after %s secondsg      $@)(r   r   
contextlibsuppressFileNotFoundErrorr(   r<   net_opslink_upr   r   r   get_tmp_ancestorr)   r-   r   
write_filer   Zbuild_dhclient_cmdrL   ProcessExecutionError	exit_codestderrstdoutr   Zwait_for_filesr   ranger,   r\   int
ValueErrorZget_proc_ppidkillsignalSIGKILLtimesleeperrorr`   )rN   	interfacer"   r   Zpid_filerP   Zconfig_filedhcp_client_identifierZinterface_dhclient_contenttmp_dirouterrr}   ZmissingZppidZ
daemonized_Zpid_contentpidr   r   r   r!      s    
		  



zIscDhclient.dhcp_discoveryc                    s.    d dd td D }g } fdd}d}t|D ]\}}||k rPq<t|}|tdd	krd
}t||d |k r|||t||d  |  S d||d |d  }	d||d ||  }
|| }n4|tddkrnd}t||d |k r(|||t||d  |  S d||d |d  dg }	d||d ||  }
|| }n|td
dkr d}t||d |k r|||t||d  |  S d||d |d  ddg }	d||d ||  }
|| }n|tdd
krd}t||d |k rJ|||t||d  |  S d||d |d  dddg }	d||d ||  }
|| }n|dkrd}t||d |k r|||t||d  |  S d}	d||d ||  }
|| }nt	d| |  S |
d|	|f |
f q<|S )a  
        parse rfc3442 format and return a list containing tuple of strings.

        The tuple is composed of the network_address (including net length) and
        gateway for a parsed static route.  It can parse two formats of
        rfc3442, one from dhcpcd and one from dhclient (isc).

        @param rfc3442: string in rfc3442 format (isc or dhcpd)
        @returns: list of tuple(str, str) for all valid parsed routes until the
                  first parsing error.

        e.g.:

        sr=parse_static_routes(        "32,169,254,169,254,130,56,248,255,0,130,56,240,1")
        sr=[
            ("169.254.169.254/32", "130.56.248.255"),         ("0.0.0.0/0", "130.56.240.1")
        ]

        sr2 = parse_static_routes(        "24.191.168.128 192.168.128.1,0 192.168.128.1")
        sr2 = [
            ("191.168.128.0/24", "192.168.128.1"),        ("0.0.0.0/0", "192.168.128.1")
        ]

        Python version of isc-dhclient's hooks:
           /etc/dhcp/dhclient-exit-hooks.d/rfc3442-classless-routes
        rR   c                 S   s   g | ]}|r|qS r   r   )rf   tokr   r   r   
<listcomp>  s      z3IscDhclient.parse_static_routes.<locals>.<listcomp>z[, .]c                    s   d| || f }t | d S )NzRFC3442 string malformed.  Current route has CIDR of %s and requires %s significant octets, but only %s remain. Verify DHCP rfc3442-classless-static-routes value: %s)r   r}   )ZcidrZrequiredZremainmsgrfc3442r   r   _trunc_error  s
    
z5IscDhclient.parse_static_routes.<locals>._trunc_errorr      !   	   N.r7   rd            0            z0.0.0.0zSParsed invalid net length "%s".  Verify DHCP rfc3442-classless-static-routes value.z%s/%s)rstriprU   r[   	enumeraterv   ru   rX   r-   r   r}   r^   )r   tokensstatic_routesr   Zcurrent_idxidxr   Z
net_lengthZreq_toksZnet_addressZgatewayr   r   r   parse_static_routesj  sx    !
	 "


zIscDhclient.parse_static_routesc                  C   sJ   dddg} | D ]6}t j|rtt |dkrtd| |  S qd S )Nz/var/lib/dhclientz/var/lib/dhcpz/var/lib/NetworkManagerr   zUsing %s lease directory)r(   r)   existsrX   r+   r   r   )Zsupported_dirsdr   r   r   get_dhclient_d  s    
zIscDhclient.get_dhclient_dc                 C   sz   | d krt  } | sd S t| }d}d }|D ]F}|dr>q.|dsJq.tj| |}tj|}||kr.|}|}q.|S )NZ	dhclient6)z.leasez.leases)	rI   r   r(   r+   
startswithendswithr)   r-   getmtime)Zlease_dZlease_filesZlatest_mtimeZlatest_filefnameZabs_pathmtimer   r   r   get_latest_lease  s$    



zIscDhclient.get_latest_leasec              	   C   s`   t | dL}|D ]@}d|kr|dd}t|dkr|d }td| |}qW 5 Q R X |S )Nrzdhcp-server-identifierz ;
rT   r   zFound DHCP identifier %s)openr\   r[   rX   r   r   )rP   fdr_   ZwordsZdhcptokZlatest_addressr   r   r   !parse_dhcp_server_from_lease_file  s    z-IscDhclient.parse_dhcp_server_from_lease_file)NN)N)r   r   r   r   rO   staticmethodrH   r   r   r   r`   r!   r   r   r   r   r   r   r   r   rI      s   $  
v
j
 rI   c                   @   s   e Zd ZdZdd ZdS )DhcpcdZdhcpcdc                 C   s   t dd S )NzDhcpcd not yet implemented)r   rM   r   r   r   rO     s    zDhcpcd.__init__N)r   r   r   r   rO   r   r   r   r   r     s   r   c                   @   s"   e Zd ZdZdd ZdddZdS )Udhcpcudhcpcc                 C   s&   t d| _| js"td t d S )Nr   z3Skip udhcpc configuration: No udhcpc command found.)r   rK   udhcpc_pathr   r   r   rM   r   r   r   rO     s    
zUdhcpc.__init__Nc                 C   s  t d| tjdd}tj||d }tt	 t
| W 5 Q R X |j| tj|d}t|td | jddd	|d
|ddddg}t|rt|dd}|dd|dd g ztj|d|idd\}	}
W nB tjk
r } z t d|j|j|j t|W 5 d}~X Y nX |dk	r0||	|
 tt|}|d  }|r~dd t|ddd |ddd D |d< |gS )as  Run udhcpc on the interface without scripts or filesystem artifacts.

        @param interface: Name of the network interface on which to run udhcpc.
        @param dhcp_log_func: A callable accepting the udhcpc output and
            error streams.

        @return: A list of dicts of representing the dhcp leases parsed from
            the udhcpc lease file.
        ra   Trc   z.lease.jsonudhcpc_scripti  z-OZstaticroutesz-iz-sz-nz-qz-fz-v)Zethernet_formatz-xz0x3d:%s:r6   Z
LEASE_FILE)Z
update_envZcapturez1udhcpc exited with code: %s stderr: %r stdout: %rNr   c                 S   s   g | ]}|qS r   r   )rf   ir   r   r   r   j  s    z)Udhcpc.dhcp_discovery.<locals>.<listcomp>r   r7   )r   r   r   ro   r(   r)   r-   rj   rk   rl   r<   rm   rn   r   rp   UDHCPC_SCRIPTr   r   r   extendr]   r   rq   rr   rs   rt   r   Z	load_jsonr,   r[   zip)rN   r~   r"   r   r   rP   r   cmdr   r   r   r}   Z
lease_jsonr   r   r   r   r!   !  sf       


zUdhcpc.dhcp_discovery)NN)r   r   r   r   rO   r!   r   r   r   r   r     s
   	  r   )NN)N)N).abcrj   r;   Zloggingr(   rU   ry   r{   ior   typingr   r   r   r%   Z	cloudinitr   r   r   Zcloudinit.netr	   r
   r   r   r   Z	getLoggerr   r   r'   r   	Exceptionr   r   r   r   r   r    r#   r&   r/   r4   ABCr5   rI   r   r   r   r   r   r   <module>   s>   
!



  L