1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
#!/usr/bin/python3
#
# Copyright (C) 2018 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre <mathieu.trudel-lapierre@canonical.com>
#
# 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; version 3.
#
# 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/>.
'''netplan ip command line'''
import logging
import os
import sys
import subprocess
from subprocess import CalledProcessError
from .. import utils
lease_path = {
'networkd': {
'pattern': 'run/systemd/netif/leases/{lease_id}',
'method': 'ifindex',
},
'NetworkManager': {
'pattern': 'var/lib/NetworkManager/internal-{lease_id}-{interface}.lease',
'method': 'nm_connection',
},
}
class NetplanIp(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='ip',
description='Retrieve IP information from the system',
leaf=False)
def run(self):
self.command_leases = NetplanIpLeases()
# subcommand: leases
p_ip_leases = self.subparsers.add_parser('leases',
help='Display IP leases',
add_help=False)
p_ip_leases.set_defaults(func=self.command_leases.run, commandclass=self.command_leases)
self.parse_args()
self.run_command()
class NetplanIpLeases(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='ip leases',
description='Display IP leases',
leaf=True)
def run(self):
self.parser.add_argument('interface',
help='Interface for which to display IP lease settings.')
self.parser.add_argument('--root-dir',
help='Search for configuration files in this root directory instead of /')
self.func = self.command_ip_leases
self.parse_args()
self.run_command()
def command_ip_leases(self):
if self.interface == 'help': # pragma: nocover (covered in autopkgtest)
self.print_usage()
def find_lease_file(mapping):
def lease_method_ifindex():
ifindex_f = os.path.join('/sys/class/net', self.interface, 'ifindex')
try:
with open(ifindex_f) as f:
return f.readlines()[0].strip()
except Exception as e:
logging.debug('Cannot read file %s: %s', ifindex_f, str(e))
raise
def lease_method_nm_connection():
# FIXME: handle older versions of NM where 'nmcli dev show' doesn't exist
try:
nmcli_dev_out = utils.nmcli_out(['dev', 'show', self.interface])
for line in nmcli_dev_out.splitlines():
if 'GENERAL.CONNECTION' in line:
conn_id = line.split(':')[1].rstrip().strip()
nmcli_con_out = utils.nmcli_out(['con', 'show', 'id', conn_id])
for line in nmcli_con_out.splitlines():
if 'connection.uuid' in line:
return line.split(':')[1].rstrip().strip()
except Exception as e:
raise Exception('Could not find a NetworkManager connection for the interface: %s' % str(e))
raise Exception('Could not find a NetworkManager connection for the interface')
lease_pattern = lease_path[mapping['backend']]['pattern']
lease_method = lease_path[mapping['backend']]['method']
try:
lease_id = eval("lease_method_" + lease_method)()
# We found something to build the path to the lease file with,
# at this point we may have something to look at; but if not,
# we'll rely on open() throwing an error.
# This might happen if networkd doesn't use DHCP for the interface,
# for instance.
path = os.path.join('/',
os.path.abspath(self.root_dir) if self.root_dir else "",
lease_pattern.format(interface=self.interface,
lease_id=lease_id))
# Fallback to 'dhclient' if no lease of NetworkManager's
# internal DHCP client is found
if not os.path.isfile(path):
path = path.replace('NetworkManager/internal-', 'NetworkManager/dhclient-')
with open(path) as f:
for line in f.readlines():
print(line.rstrip())
except Exception as e:
print("No lease found for interface '%s': %s" % (self.interface, str(e)),
file=sys.stderr)
sys.exit(1)
# XXX: This code path is only still supported for legacy reasons and
# not supposed to be called in the scope of a systemd generator. The
# 'netplan status' command should be used instead. Should it be moved to
# the 'configure' binary to keep legacy functionality around?
# See also the comment in main() in src/generate.c.
argv = [utils.get_generator_path()]
if self.root_dir:
argv += ['--root-dir', self.root_dir]
argv += ['--mapping', self.interface]
# Extract out of the generator our mapping in a dict.
logging.debug('command ip leases: running %s', argv)
try:
out = subprocess.check_output(argv, text=True)
except CalledProcessError: # pragma: nocover (better be covered in autopkgtest)
print("No lease found for interface '%s' (not managed by Netplan)" % self.interface, file=sys.stderr)
sys.exit(1)
mapping = {}
mapping_s = out.split(',')
for keyvalue in mapping_s:
key, value = keyvalue.strip().split('=')
mapping[key] = value
find_lease_file(mapping)
|