Coverage for drivers/iscsilib.py : 39%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) Citrix Systems Inc.
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU Lesser General Public License as published
5# by the Free Software Foundation; version 2.1 only.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU Lesser General Public License for more details.
11#
12# You should have received a copy of the GNU Lesser General Public License
13# along with this program; if not, write to the Free Software Foundation, Inc.,
14# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16INITIATORNAME_FILE = '/etc/iscsi/initiatorname.iscsi'
18import util
19import os
20import scsiutil
21import time
22import socket
23import re
24import shutil
25import xs_errors
26import lock
27import glob
28import tempfile
29import contextlib
30from configparser import RawConfigParser
31import io
32from functools import wraps
33from typing import Iterator
36# The 3.x kernel brings with it some iSCSI path changes in sysfs
37_KERNEL_VERSION = os.uname()[2]
38if _KERNEL_VERSION.startswith('2.6'): 38 ↛ 39line 38 didn't jump to line 39, because the condition on line 38 was never true
39 _GENERIC_SESSION_PATH = ('/sys/class/iscsi_host/host%s/device/session*/' +
40 'iscsi_session*/')
41 _GENERIC_CONNECTION_PATH = ('/sys/class/iscsi_host/host%s/device/' +
42 'session*/connection*/iscsi_connection*/')
43else:
44 _GENERIC_SESSION_PATH = ('/sys/class/iscsi_host/host%s/device/session*/' +
45 'iscsi_session/session*/')
46 _GENERIC_CONNECTION_PATH = ('/sys/class/iscsi_host/host%s/device/' +
47 'session*/connection*/iscsi_connection/connection*/')
49_REPLACEMENT_TMO_MPATH = 15
50_REPLACEMENT_TMO_DEFAULT = 144
51_REPLACEMENT_TMO_STANDARD = 120
53_ISCSI_DB_PATH = '/var/lib/iscsi'
56@contextlib.contextmanager
57def iscsi_lock() -> Iterator[None]:
58 """Context manager to serialize iscsiadm calls. Blocks until lock is available."""
59 iscsiadm_lock = lock.Lock(lock.LOCK_TYPE_ISCSIADM_RUNNING, 'iscsiadm')
60 iscsiadm_lock.acquire()
61 try:
62 yield
63 finally:
64 iscsiadm_lock.release()
67@contextlib.contextmanager
68def iscsi_try_lock() -> Iterator[bool]:
69 """
70 Context manager to serialize iscsiadm calls.
71 Yields True if the lock was acquired, False immediately if unavailable.
72 """
73 iscsiadm_lock = lock.Lock(lock.LOCK_TYPE_ISCSIADM_RUNNING, 'iscsiadm')
74 acquired = iscsiadm_lock.acquireNoblock()
75 try:
76 yield acquired
77 finally:
78 if acquired: 78 ↛ exitline 78 didn't return from function 'iscsi_try_lock', because the condition on line 78 was never false
79 iscsiadm_lock.release()
82def with_iscsi_lock(fn):
83 """Decorator to run a function whilst holding the iSCSI lock."""
84 @wraps(fn)
85 def wrapper(*args, **kwargs):
86 with iscsi_lock():
87 return fn(*args, **kwargs)
88 return wrapper
91def doexec_locked(cmd):
92 """Executes via util.doexec the command specified whilst holding lock"""
93 if os.path.basename(cmd[0]) == 'iscsiadm':
94 with iscsi_lock():
95 return util.doexec(cmd)
96 return util.doexec(cmd)
99def noexn_on_failure(cmd):
100 """Executes via util.doexec the command specified as best effort."""
101 (rc, stdout, stderr) = doexec_locked(cmd)
102 return (stdout, stderr)
105def exn_on_failure(cmd, message):
106 """Executes via util.doexec the command specified. If the return code is
107 non-zero, raises an ISCSIError with the given message"""
108 (rc, stdout, stderr) = doexec_locked(cmd)
109 if rc == 0:
110 return (stdout, stderr)
111 else:
112 msg = 'rc: %d, stdout: %s, stderr: %s' % (rc, stdout, stderr)
113 raise xs_errors.XenError('SMGeneral', opterr=msg)
116def parse_node_output(text, targetIQN):
117 """helper function - parses the output of iscsiadm for discovery and
118 get_node_records"""
120 def dotrans(x):
121 (rec, iqn) = x.split()
122 (portal, tpgt) = rec.split(',')
123 return (portal, tpgt, iqn)
124 unfiltered_map = [dotrans(x) for x in text.split('\n') if
125 match_targetIQN(targetIQN, x)]
126 # We need to filter duplicates orignating from doing the discovery using
127 # multiple interfaces
128 filtered_map = []
129 for input_value in unfiltered_map:
130 if input_value not in filtered_map: 130 ↛ 129line 130 didn't jump to line 129, because the condition on line 130 was never false
131 filtered_map.append(input_value)
132 return filtered_map
135def parse_IP_port(portal):
136 """Extract IP address and port number from portal information.
138 Input: String encoding the IP address and port of form:
139 - x.x.x.x:p (IPv4)
140 or
141 - [xxxx:xxxx:...:xxxx]:p (IPv6)
143 Return tuple of IP and port (without square brackets in case of IPv6):
144 """
145 (ipaddr, port) = portal.split(',')[0].rsplit(':', 1)
146 if ipaddr[0] == '[': 146 ↛ 148line 146 didn't jump to line 148, because the condition on line 146 was never true
147 # This is IPv6, strip off [ ] surround
148 ipaddr = ipaddr[1:-1]
149 return (ipaddr, port)
152def save_rootdisk_nodes(tmpdirname):
153 root_iqns = get_rootdisk_IQNs()
154 if root_iqns:
155 srcdirs = [os.path.join(_ISCSI_DB_PATH, 'nodes', iqn) for iqn in root_iqns]
156 util.doexec(['/bin/cp', '-a'] + srcdirs + [tmpdirname])
159def restore_rootdisk_nodes(tmpdirname):
160 root_iqns = get_rootdisk_IQNs()
161 if root_iqns:
162 srcdirs = [os.path.join(tmpdirname, iqn) for iqn in root_iqns]
163 util.doexec(['/bin/cp', '-a'] + srcdirs +
164 [os.path.join(_ISCSI_DB_PATH, 'nodes')])
167def discovery(target, port, chapuser, chappass, targetIQN="any",
168 interfaceArray=["default"]):
169 """Run iscsiadm in discovery mode to obtain a list of the
170 TargetIQNs available on the specified target and port. Returns
171 a list of triples - the portal (ip:port), the tpgt (target portal
172 group tag) and the target name"""
174 # Save configuration of root LUN nodes and restore after discovery
175 # otherwise when we do a discovery on the same filer as is hosting
176 # our root disk we'll reset the config of the root LUNs
178 # FIXME: Replace this with TemporaryDirectory when moving to Python3
179 tmpdirname = tempfile.mkdtemp()
180 try:
181 save_rootdisk_nodes(tmpdirname)
183 if ':' in target: 183 ↛ 184line 183 didn't jump to line 184, because the condition on line 183 was never true
184 targetstring = "[%s]:%s" % (target, str(port))
185 else:
186 targetstring = "%s:%s" % (target, str(port))
187 cmd_base = ["-t", "st", "-p", targetstring]
188 for interface in interfaceArray:
189 cmd_base.append("-I")
190 cmd_base.append(interface)
191 cmd_disc = ["iscsiadm", "-m", "discovery"] + cmd_base
192 cmd_discdb = ["iscsiadm", "-m", "discoverydb"] + cmd_base
193 auth_args = ["-n", "discovery.sendtargets.auth.authmethod", "-v", "CHAP",
194 "-n", "discovery.sendtargets.auth.username", "-v", chapuser,
195 "-n", "discovery.sendtargets.auth.password", "-v", chappass]
196 fail_msg = "Discovery failed. Check target settings and " \
197 "username/password (if applicable)"
198 try:
199 if chapuser != "" and chappass != "":
200 exn_on_failure(cmd_discdb + ["-o", "new"], fail_msg)
201 exn_on_failure(cmd_discdb + ["-o", "update"] + auth_args, fail_msg)
202 cmd = cmd_discdb + ["--discover"]
203 else:
204 cmd = cmd_disc
205 (stdout, stderr) = exn_on_failure(cmd, fail_msg)
206 except:
207 raise xs_errors.XenError('ISCSILogin')
208 finally:
209 restore_rootdisk_nodes(tmpdirname)
210 finally:
211 shutil.rmtree(tmpdirname) 211 ↛ exitline 211 didn't except from function 'discovery', because the raise on line 207 wasn't executed
213 return parse_node_output(stdout, targetIQN)
216def get_node_records(targetIQN="any"):
217 """Return the node records that the iscsi daemon already knows about"""
218 cmd = ["iscsiadm", "-m", "node"]
219 (rc, stdout, stderr) = doexec_locked(cmd)
220 if rc != 0:
221 util.SMlog(f"get_node_records: iscsiadm rc={rc} stderr={stderr}")
222 return []
223 return parse_node_output(stdout, targetIQN)
226def set_chap_settings (portal, targetIQN, username, password, username_in, password_in):
227 """Sets the username and password on the session identified by the
228 portal/targetIQN combination"""
229 failuremessage = "Failed to set CHAP settings"
230 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
231 "update", "-n", "node.session.auth.authmethod", "-v", "CHAP"]
232 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
234 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
235 "update", "-n", "node.session.auth.username", "-v",
236 username]
237 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
239 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
240 "update", "-n", "node.session.auth.password", "-v",
241 password]
242 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
244 if (username_in != ""):
245 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
246 "update", "-n", "node.session.auth.username_in", "-v",
247 username_in]
248 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
250 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
251 "update", "-n", "node.session.auth.password_in", "-v",
252 password_in]
253 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
256def remove_chap_settings(portal, targetIQN):
257 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
258 "update", "-n", "node.session.auth.authmethod", "-v", "None"]
259 (stdout, stderr) = noexn_on_failure(cmd)
261 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
262 "update", "-n", "node.session.auth.username", "-v", ""]
263 (stdout, stderr) = noexn_on_failure(cmd)
265 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
266 "update", "-n", "node.session.auth.password", "-v", ""]
267 (stdout, stderr) = noexn_on_failure(cmd)
269 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
270 "update", "-n", "node.session.auth.username_in", "-v", ""]
271 (stdout, stderr) = noexn_on_failure(cmd)
273 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
274 "update", "-n", "node.session.auth.password_in", "-v", ""]
275 (stdout, stderr) = noexn_on_failure(cmd)
278def get_node_config (portal, targetIQN):
279 """ Using iscsadm to get the current configuration of a iscsi node.
280 The output is parsed in ini format, and returned as a dictionary."""
281 failuremessage = "Failed to get node configurations"
282 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "-S"]
283 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
284 ini_sec = "root"
285 str_fp = io.StringIO("[%s]\n%s" % (ini_sec, stdout))
286 parser = RawConfigParser()
287 parser.read_file(str_fp)
288 str_fp.close()
289 return dict(parser.items(ini_sec))
292def set_replacement_tmo (portal, targetIQN, mpath):
293 key = "node.session.timeo.replacement_timeout"
294 try:
295 current_tmo = int((get_node_config(portal, targetIQN))[key])
296 except:
297 # Assume a standard TMO setting if get_node_config fails
298 current_tmo = _REPLACEMENT_TMO_STANDARD
299 # deliberately leave the "-p portal" arguments out, so that all the portals
300 # always share the same config (esp. in corner case when switching from
301 # mpath -> non-mpath, where we are only going to operate on one path). The
302 # parameter could be useful if we want further flexibility in the future.
303 cmd = ["iscsiadm", "-m", "node", "-T", targetIQN, # "-p", portal,
304 "--op", "update", "-n", key, "-v"]
305 fail_msg = "Failed to set replacement timeout"
306 if mpath:
307 # Only switch if the current config is a well-known non-mpath setting
308 if current_tmo in [_REPLACEMENT_TMO_DEFAULT, _REPLACEMENT_TMO_STANDARD]:
309 cmd.append(str(_REPLACEMENT_TMO_MPATH))
310 (stdout, stderr) = exn_on_failure(cmd, fail_msg)
311 else:
312 # the current_tmo is a customized value, no change
313 util.SMlog("Keep the current replacement_timout value: %d." % current_tmo)
314 else:
315 # Only switch if the current config is a well-known mpath setting
316 if current_tmo in [_REPLACEMENT_TMO_MPATH, _REPLACEMENT_TMO_STANDARD]:
317 cmd.append(str(_REPLACEMENT_TMO_DEFAULT))
318 (stdout, stderr) = exn_on_failure(cmd, fail_msg)
319 else:
320 # the current_tmo is a customized value, no change
321 util.SMlog("Keep the current replacement_timout value: %d." % current_tmo)
324def get_current_initiator_name():
325 """Looks in the config file to see if we've already got a initiator name,
326 returning it if so, or else returning None"""
327 if os.path.exists(INITIATORNAME_FILE):
328 try:
329 f = open(INITIATORNAME_FILE, 'r')
330 for line in f.readlines():
331 if line.strip().startswith("#"):
332 continue
333 if "InitiatorName" in line:
334 IQN = line.split("=")[1]
335 currentIQN = IQN.strip()
336 f.close()
337 return currentIQN
338 f.close()
339 except IOError as e:
340 return None
341 return None
344def get_system_alias():
345 return socket.gethostname()
348def set_current_initiator_name(localIQN):
349 """Sets the initiator name in the config file. Raises an xs_error on error"""
350 try:
351 alias = get_system_alias()
352 # MD3000i alias bug workaround
353 if len(alias) > 30:
354 alias = alias[0:30]
355 f = open(INITIATORNAME_FILE, 'w')
356 f.write('InitiatorName=%s\n' % localIQN)
357 f.write('InitiatorAlias=%s\n' % alias)
358 f.close()
359 except IOError as e:
360 raise xs_errors.XenError('ISCSIInitiator', \
361 opterr='Could not set initator name')
364def login(portal, target, username, password, username_in="", password_in="",
365 multipath=False):
366 if username != "" and password != "":
367 set_chap_settings(portal, target, username, password, username_in, password_in)
368 else:
369 remove_chap_settings(portal, target)
371 set_replacement_tmo(portal, target, multipath)
372 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", target, "-l"]
373 failuremessage = "Failed to login to target."
374 try:
375 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
376 except:
377 raise xs_errors.XenError('ISCSILogin')
380def logout(portal, target, all=False):
381 if all:
382 cmd = ["iscsiadm", "-m", "node", "-T", target, "-u"]
383 else:
384 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", target, "-u"]
385 failuremessage = "Failed to log out of target"
386 try:
387 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
388 except:
389 raise xs_errors.XenError('ISCSILogout')
392def delete(target):
393 cmd = ["iscsiadm", "-m", "node", "-o", "delete", "-T", target]
394 failuremessage = "Failed to delete target"
395 try:
396 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
397 except:
398 raise xs_errors.XenError('ISCSIDelete')
401def get_luns(targetIQN, portal):
402 refresh_luns(targetIQN, portal)
403 luns = []
404 path = os.path.join("/dev/iscsi", targetIQN, portal)
405 try:
406 for file in util.listdir(path):
407 if file.find("LUN") == 0 and file.find("_") == -1:
408 lun = file.replace("LUN", "")
409 luns.append(lun)
410 return luns
411 except util.CommandException as inst:
412 raise xs_errors.XenError('ISCSIDevice', opterr='Failed to find any LUNs')
415def is_iscsi_daemon_running():
416 cmd = ["/usr/bin/systemctl", "is-active", "iscsid.service"]
417 (rc, stdout, stderr) = util.doexec(cmd)
418 return (rc == 0)
421def stop_daemon():
422 if is_iscsi_daemon_running():
423 cmd = ["/usr/bin/systemctl", "stop", "iscsid.service"]
424 failuremessage = "Failed to stop iscsi daemon"
425 exn_on_failure(cmd, failuremessage)
428@with_iscsi_lock
429def restart_daemon():
430 stop_daemon()
431 with contextlib.suppress(OSError):
432 shutil.rmtree(os.path.join(_ISCSI_DB_PATH, 'nodes'))
433 with contextlib.suppress(OSError):
434 shutil.rmtree(os.path.join(_ISCSI_DB_PATH, 'send_targets'))
435 cmd = ["/usr/bin/systemctl", "start", "iscsid.service"]
436 failuremessage = "Failed to start iscsi daemon"
437 exn_on_failure(cmd, failuremessage)
440def wait_for_devs(targetIQN, portal):
441 path = os.path.join("/dev/iscsi", targetIQN, portal)
442 for i in range(0, 15):
443 if os.path.exists(path):
444 return True
445 time.sleep(1)
446 return False
449def refresh_luns(targetIQN, portal):
450 wait_for_devs(targetIQN, portal)
451 try:
452 path = os.path.join("/dev/iscsi", targetIQN, portal)
453 id = scsiutil.getSessionID(path)
454 f = open('/sys/class/scsi_host/host%s/scan' % id, 'w')
455 f.write('- - -\n')
456 f.close()
457 time.sleep(2) # FIXME
458 except:
459 pass
462def get_IQN_paths():
463 """Return the list of iSCSI session directories"""
464 return glob.glob(_GENERIC_SESSION_PATH % '*')
467def get_targetIQN(iscsi_host):
468 """Get target IQN from sysfs for given iSCSI host number"""
469 iqn_file = os.path.join(_GENERIC_SESSION_PATH % iscsi_host, 'targetname')
470 targetIQN = util.get_single_entry(glob.glob(iqn_file)[0])
471 return targetIQN
474def get_targetIP_and_port(iscsi_host):
475 """Get target IP address and port for given iSCSI host number"""
476 connection_dir = _GENERIC_CONNECTION_PATH % iscsi_host
477 ip = util.get_single_entry(glob.glob(os.path.join(
478 connection_dir, 'persistent_address'))[0])
479 port = util.get_single_entry(glob.glob(os.path.join(
480 connection_dir, 'persistent_port'))[0])
481 return (ip, port)
484def get_path(targetIQN, portal, lun):
485 """Gets the path of a specified LUN - this should be e.g. '1' or '5'"""
486 path = os.path.join("/dev/iscsi", targetIQN, portal)
487 return os.path.join(path, "LUN" + lun)
490def get_path_safe(targetIQN, portal, lun):
491 """Gets the path of a specified LUN, and ensures that it exists.
492 Raises an exception if it hasn't appeared after the timeout"""
493 path = get_path(targetIQN, portal, lun)
494 for i in range(0, 15):
495 if os.path.exists(path):
496 return path
497 time.sleep(1)
498 raise xs_errors.XenError('ISCSIDevice', \
499 opterr='LUN failed to appear at path %s' % path)
502def match_target(tgt, s):
503 regex = re.compile(tgt)
504 return regex.search(s, 0)
507def match_targetIQN(tgtIQN, s):
508 if not len(s):
509 return False
510 if tgtIQN == "any":
511 return True
512 # Extract IQN from iscsiadm -m session
513 # Ex: tcp: [17] 10.220.98.9:3260,1 iqn.2009-01.xenrt.test:iscsi4181a93e
514 siqn = s.split(",")[1].split()[1].strip()
515 return (siqn == tgtIQN)
518def match_session(s):
519 regex = re.compile("^tcp:")
520 return regex.search(s, 0)
523def _compare_sessions_to_tgt(session_output, tgtIQN, tgt=''):
524 for line in session_output.split('\n'):
525 if match_targetIQN(tgtIQN, line) and match_session(line):
526 if len(tgt): 526 ↛ 530line 526 didn't jump to line 530, because the condition on line 526 was never false
527 if match_target(tgt, line):
528 return True
529 else:
530 return True
531 return False
534def _checkTGT(tgtIQN, tgt=''):
535 if not is_iscsi_daemon_running():
536 return False
537 failuremessage = "Failure occured querying iscsi daemon"
538 cmd = ["iscsiadm", "-m", "session"]
539 try:
540 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
541 # Recent versions of iscsiadm return error it this list is empty.
542 # Quick and dirty handling
543 except Exception as e:
544 util.SMlog("%s failed with %s" % (cmd, e.args))
545 stdout = ""
546 return _compare_sessions_to_tgt(stdout, tgtIQN, tgt)
549def get_rootdisk_IQNs():
550 """Return the list of IQNs for targets required by root filesystem"""
551 if not os.path.isdir('/sys/firmware/ibft/'): 551 ↛ 553line 551 didn't jump to line 553, because the condition on line 551 was never false
552 return []
553 dirs = [x for x in os.listdir('/sys/firmware/ibft/') if x.startswith('target')]
554 return [open('/sys/firmware/ibft/%s/target-name' % d).read().strip() for d in dirs]
557def _checkAnyTGT():
558 if not is_iscsi_daemon_running():
559 return False
560 rootIQNs = get_rootdisk_IQNs()
561 failuremessage = "Failure occured querying iscsi daemon"
562 cmd = ["iscsiadm", "-m", "session"]
563 try:
564 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
565 # Recent versions of iscsiadm return error it this list is empty.
566 # Quick and dirty handling
567 except Exception as e:
568 util.SMlog("%s failed with %s" % (cmd, e.args))
569 stdout = ""
570 for session in filter(match_session, stdout.split('\n')):
571 iqn = session.split()[-1]
572 if not iqn in rootIQNs:
573 return True
574 return False
577def ensure_daemon_running_ok(localiqn):
578 """Check that the daemon is running and the initiator name is correct"""
579 if not is_iscsi_daemon_running():
580 set_current_initiator_name(localiqn)
581 restart_daemon()
582 else:
583 currentiqn = get_current_initiator_name()
584 if currentiqn != localiqn:
585 if _checkAnyTGT():
586 raise xs_errors.XenError('ISCSIInitiator', \
587 opterr='Daemon already running with ' \
588 + 'target(s) attached using ' \
589 + 'different IQN')
590 set_current_initiator_name(localiqn)
591 restart_daemon()
594def get_iscsi_interfaces():
595 result = []
596 try:
597 # Get all configured iscsiadm interfaces
598 cmd = ["iscsiadm", "-m", "iface"]
599 (stdout, stderr) = exn_on_failure(cmd,
600 "Failure occured querying iscsi daemon")
601 # Get the interface (first column) from a line such as default
602 # tcp,<empty>,<empty>,<empty>,<empty>
603 for line in stdout.split("\n"):
604 line_element = line.split(" ")
605 interface_name = line_element[0]
606 # ignore interfaces which aren't marked as starting with
607 # c_.
608 if len(line_element) == 2 and interface_name[:2] == "c_":
609 result.append(interface_name)
610 except:
611 # Ignore exception from exn on failure, still return the default
612 # interface
613 pass
614 # In case there are no configured interfaces, still add the default
615 # interface
616 if len(result) == 0:
617 result.append("default")
618 return result