Coverage for libs/sm/core/iscsi.py: 34%
372 statements
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
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'
18from sm.core import util
19import os
20from sm.core import scsiutil
21import time
22import socket
23import re
24import shutil
25from sm.core import xs_errors
26from sm.core import lock
27import glob
28import tempfile
29from configparser import RawConfigParser
30import io
32# The 3.x kernel brings with it some iSCSI path changes in sysfs
33_KERNEL_VERSION = os.uname()[2]
34if _KERNEL_VERSION.startswith('2.6'): 34 ↛ 35line 34 didn't jump to line 35, because the condition on line 34 was never true
35 _GENERIC_SESSION_PATH = ('/sys/class/iscsi_host/host%s/device/session*/' +
36 'iscsi_session*/')
37 _GENERIC_CONNECTION_PATH = ('/sys/class/iscsi_host/host%s/device/' +
38 'session*/connection*/iscsi_connection*/')
39else:
40 _GENERIC_SESSION_PATH = ('/sys/class/iscsi_host/host%s/device/session*/' +
41 'iscsi_session/session*/')
42 _GENERIC_CONNECTION_PATH = ('/sys/class/iscsi_host/host%s/device/' +
43 'session*/connection*/iscsi_connection/connection*/')
45_REPLACEMENT_TMO_MPATH = 15
46_REPLACEMENT_TMO_DEFAULT = 144
47_REPLACEMENT_TMO_STANDARD = 120
49_ISCSI_DB_PATH = '/var/lib/iscsi'
52def doexec_locked(cmd):
53 """Executes via util.doexec the command specified whilst holding lock"""
54 _lock = None
55 if os.path.basename(cmd[0]) == 'iscsiadm':
56 _lock = lock.Lock(lock.LOCK_TYPE_ISCSIADM_RUNNING, 'iscsiadm')
57 _lock.acquire()
58 # util.SMlog("%s" % cmd)
59 (rc, stdout, stderr) = util.doexec(cmd)
60 if _lock is not None and _lock.held():
61 _lock.release()
62 return (rc, stdout, stderr)
65def noexn_on_failure(cmd):
66 """Executes via util.doexec the command specified as best effort."""
67 (rc, stdout, stderr) = doexec_locked(cmd)
68 return (stdout, stderr)
71def exn_on_failure(cmd, message):
72 """Executes via util.doexec the command specified. If the return code is
73 non-zero, raises an ISCSIError with the given message"""
74 (rc, stdout, stderr) = doexec_locked(cmd)
75 if rc == 0:
76 return (stdout, stderr)
77 else:
78 msg = 'rc: %d, stdout: %s, stderr: %s' % (rc, stdout, stderr)
79 raise xs_errors.XenError('SMGeneral', opterr=msg)
82def parse_node_output(text, targetIQN):
83 """helper function - parses the output of iscsiadm for discovery and
84 get_node_records"""
86 def dotrans(x):
87 (rec, iqn) = x.split()
88 (portal, tpgt) = rec.split(',')
89 return (portal, tpgt, iqn)
90 unfiltered_map = [dotrans(x) for x in text.split('\n') if
91 match_targetIQN(targetIQN, x)]
92 # We need to filter duplicates orignating from doing the discovery using
93 # multiple interfaces
94 filtered_map = []
95 for input_value in unfiltered_map:
96 if input_value not in filtered_map: 96 ↛ 95line 96 didn't jump to line 95, because the condition on line 96 was never false
97 filtered_map.append(input_value)
98 return filtered_map
101def parse_IP_port(portal):
102 """Extract IP address and port number from portal information.
104 Input: String encoding the IP address and port of form:
105 - x.x.x.x:p (IPv4)
106 or
107 - [xxxx:xxxx:...:xxxx]:p (IPv6)
109 Return tuple of IP and port (without square brackets in case of IPv6):
110 """
111 (ipaddr, port) = portal.split(',')[0].rsplit(':', 1)
112 if ipaddr[0] == '[': 112 ↛ 114line 112 didn't jump to line 114, because the condition on line 112 was never true
113 # This is IPv6, strip off [ ] surround
114 ipaddr = ipaddr[1:-1]
115 return (ipaddr, port)
118def save_rootdisk_nodes(tmpdirname):
119 root_iqns = get_rootdisk_IQNs()
120 if root_iqns:
121 srcdirs = [os.path.join(_ISCSI_DB_PATH, 'nodes', iqn) for iqn in root_iqns]
122 util.doexec(['/bin/cp', '-a'] + srcdirs + [tmpdirname])
125def restore_rootdisk_nodes(tmpdirname):
126 root_iqns = get_rootdisk_IQNs()
127 if root_iqns:
128 srcdirs = [os.path.join(tmpdirname, iqn) for iqn in root_iqns]
129 util.doexec(['/bin/cp', '-a'] + srcdirs +
130 [os.path.join(_ISCSI_DB_PATH, 'nodes')])
133def rescan_target(portal, target):
134 """
135 Rescan the given iscsi portal and target
136 (used by the sm-core-libs compatibility layer)
137 """
138 sessions_cmd = ["iscsiadm", "-m", "session"]
139 failure_message = "Failed to read iscsi sessions"
140 (stdout, _) = exn_on_failure(sessions_cmd, failure_message)
142 session_re = r"^[^\[]*\[(\d+)\] " + portal + r",\d+ " + target
144 for line in stdout.splitlines():
145 session_match = re.match(session_re, line)
146 if session_match:
147 session_id = session_match.group(1)
148 rescan_cmd = ["iscsiadm", "-m", "session", '-r', session_id, '--rescan']
149 (_, _) = exn_on_failure(rescan_cmd, "Failed to rescan session")
152def discovery(target, port, chapuser, chappass, targetIQN="any",
153 interface_array=["default"]):
154 """Run iscsiadm in discovery mode to obtain a list of the
155 TargetIQNs available on the specified target and port. Returns
156 a list of triples - the portal (ip:port), the tpgt (target portal
157 group tag) and the target name"""
159 # Save configuration of root LUN nodes and restore after discovery
160 # otherwise when we do a discovery on the same filer as is hosting
161 # our root disk we'll reset the config of the root LUNs
163 # FIXME: Replace this with TemporaryDirectory when moving to Python3
164 tmpdirname = tempfile.mkdtemp()
165 try:
166 save_rootdisk_nodes(tmpdirname)
168 if ':' in target: 168 ↛ 169line 168 didn't jump to line 169, because the condition on line 168 was never true
169 targetstring = "[%s]:%s" % (target, str(port))
170 else:
171 targetstring = "%s:%s" % (target, str(port))
172 cmd_base = ["-t", "st", "-p", targetstring]
173 for interface in interface_array:
174 cmd_base.append("-I")
175 cmd_base.append(interface)
176 cmd_disc = ["iscsiadm", "-m", "discovery"] + cmd_base
177 cmd_discdb = ["iscsiadm", "-m", "discoverydb"] + cmd_base
178 auth_args = ["-n", "discovery.sendtargets.auth.authmethod", "-v", "CHAP",
179 "-n", "discovery.sendtargets.auth.username", "-v", chapuser,
180 "-n", "discovery.sendtargets.auth.password", "-v", chappass]
181 fail_msg = "Discovery failed. Check target settings and " \
182 "username/password (if applicable)"
183 try:
184 if chapuser != "" and chappass != "":
185 exn_on_failure(cmd_discdb + ["-o", "new"], fail_msg)
186 exn_on_failure(cmd_discdb + ["-o", "update"] + auth_args, fail_msg)
187 cmd = cmd_discdb + ["--discover"]
188 else:
189 cmd = cmd_disc
190 (stdout, stderr) = exn_on_failure(cmd, fail_msg)
191 except:
192 raise xs_errors.XenError('ISCSILogin')
193 finally:
194 restore_rootdisk_nodes(tmpdirname)
195 finally:
196 shutil.rmtree(tmpdirname) 196 ↛ exitline 196 didn't except from function 'discovery', because the raise on line 192 wasn't executed
198 return parse_node_output(stdout, targetIQN)
201def get_node_records(targetIQN="any"):
202 """Return the node records that the iscsi daemon already knows about"""
203 cmd = ["iscsiadm", "-m", "node"]
204 failuremessage = "Failed to obtain node records from iscsi daemon"
205 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
206 return parse_node_output(stdout, targetIQN)
209def set_chap_settings (portal, targetIQN, username, password, username_in, password_in):
210 """Sets the username and password on the session identified by the
211 portal/targetIQN combination"""
212 failuremessage = "Failed to set CHAP settings"
213 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
214 "update", "-n", "node.session.auth.authmethod", "-v", "CHAP"]
215 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
217 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
218 "update", "-n", "node.session.auth.username", "-v",
219 username]
220 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
222 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
223 "update", "-n", "node.session.auth.password", "-v",
224 password]
225 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
227 if (username_in != ""):
228 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
229 "update", "-n", "node.session.auth.username_in", "-v",
230 username_in]
231 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
233 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
234 "update", "-n", "node.session.auth.password_in", "-v",
235 password_in]
236 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
239def remove_chap_settings(portal, targetIQN):
240 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
241 "update", "-n", "node.session.auth.authmethod", "-v", "None"]
242 (stdout, stderr) = noexn_on_failure(cmd)
244 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
245 "update", "-n", "node.session.auth.username", "-v", ""]
246 (stdout, stderr) = noexn_on_failure(cmd)
248 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
249 "update", "-n", "node.session.auth.password", "-v", ""]
250 (stdout, stderr) = noexn_on_failure(cmd)
252 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
253 "update", "-n", "node.session.auth.username_in", "-v", ""]
254 (stdout, stderr) = noexn_on_failure(cmd)
256 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "--op",
257 "update", "-n", "node.session.auth.password_in", "-v", ""]
258 (stdout, stderr) = noexn_on_failure(cmd)
261def get_node_config (portal, targetIQN):
262 """ Using iscsadm to get the current configuration of a iscsi node.
263 The output is parsed in ini format, and returned as a dictionary."""
264 failuremessage = "Failed to get node configurations"
265 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", targetIQN, "-S"]
266 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
267 ini_sec = "root"
268 str_fp = io.StringIO("[%s]\n%s" % (ini_sec, stdout))
269 parser = RawConfigParser()
270 parser.read_file(str_fp)
271 str_fp.close()
272 return dict(parser.items(ini_sec))
275def set_replacement_tmo (portal, targetIQN, mpath):
276 key = "node.session.timeo.replacement_timeout"
277 try:
278 current_tmo = int((get_node_config(portal, targetIQN))[key])
279 except:
280 # Assume a standard TMO setting if get_node_config fails
281 current_tmo = _REPLACEMENT_TMO_STANDARD
282 # deliberately leave the "-p portal" arguments out, so that all the portals
283 # always share the same config (esp. in corner case when switching from
284 # mpath -> non-mpath, where we are only going to operate on one path). The
285 # parameter could be useful if we want further flexibility in the future.
286 cmd = ["iscsiadm", "-m", "node", "-T", targetIQN, # "-p", portal,
287 "--op", "update", "-n", key, "-v"]
288 fail_msg = "Failed to set replacement timeout"
289 if mpath:
290 # Only switch if the current config is a well-known non-mpath setting
291 if current_tmo in [_REPLACEMENT_TMO_DEFAULT, _REPLACEMENT_TMO_STANDARD]:
292 cmd.append(str(_REPLACEMENT_TMO_MPATH))
293 (stdout, stderr) = exn_on_failure(cmd, fail_msg)
294 else:
295 # the current_tmo is a customized value, no change
296 util.SMlog("Keep the current replacement_timout value: %d." % current_tmo)
297 else:
298 # Only switch if the current config is a well-known mpath setting
299 if current_tmo in [_REPLACEMENT_TMO_MPATH, _REPLACEMENT_TMO_STANDARD]:
300 cmd.append(str(_REPLACEMENT_TMO_DEFAULT))
301 (stdout, stderr) = exn_on_failure(cmd, fail_msg)
302 else:
303 # the current_tmo is a customized value, no change
304 util.SMlog("Keep the current replacement_timout value: %d." % current_tmo)
307def get_current_initiator_name():
308 """Looks in the config file to see if we've already got a initiator name,
309 returning it if so, or else returning None"""
310 if os.path.exists(INITIATORNAME_FILE):
311 try:
312 f = open(INITIATORNAME_FILE, 'r')
313 for line in f.readlines():
314 if line.strip().startswith("#"):
315 continue
316 if "InitiatorName" in line:
317 IQN = line.split("=")[1]
318 currentIQN = IQN.strip()
319 f.close()
320 return currentIQN
321 f.close()
322 except IOError as e:
323 return None
324 return None
327def get_system_alias():
328 return socket.gethostname()
331def set_current_initiator_name(localIQN):
332 """Sets the initiator name in the config file. Raises an xs_error on error"""
333 try:
334 alias = get_system_alias()
335 # MD3000i alias bug workaround
336 if len(alias) > 30:
337 alias = alias[0:30]
338 f = open(INITIATORNAME_FILE, 'w')
339 f.write('InitiatorName=%s\n' % localIQN)
340 f.write('InitiatorAlias=%s\n' % alias)
341 f.close()
342 except IOError as e:
343 raise xs_errors.XenError('ISCSIInitiator', \
344 opterr='Could not set initator name')
347def get_portal_target_ref(portal, target):
348 return '{}#{}'.format(portal, target)
351def login(portal, target, username, password, username_in="", password_in="",
352 multipath=False):
353 if username != "" and password != "":
354 set_chap_settings(portal, target, username, password, username_in, password_in)
355 else:
356 remove_chap_settings(portal, target)
358 set_replacement_tmo(portal, target, multipath)
359 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", target, "-l"]
360 failuremessage = "Failed to login to target."
361 try:
362 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
363 except:
364 raise xs_errors.XenError('ISCSILogin')
367def logout(portal, target, all=False):
368 if all:
369 cmd = ["iscsiadm", "-m", "node", "-T", target, "-u"]
370 else:
371 cmd = ["iscsiadm", "-m", "node", "-p", portal, "-T", target, "-u"]
372 failuremessage = "Failed to log out of target"
373 try:
374 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
375 except:
376 raise xs_errors.XenError('ISCSILogout')
379def delete(target):
380 cmd = ["iscsiadm", "-m", "node", "-o", "delete", "-T", target]
381 failuremessage = "Failed to delete target"
382 try:
383 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
384 except:
385 raise xs_errors.XenError('ISCSIDelete')
388def get_luns(targetIQN, portal):
389 refresh_luns(targetIQN, portal)
390 luns = []
391 path = os.path.join("/dev/iscsi", targetIQN, portal)
392 try:
393 for file in util.listdir(path):
394 if file.find("LUN") == 0 and file.find("_") == -1:
395 lun = file.replace("LUN", "")
396 luns.append(lun)
397 return luns
398 except util.CommandException as inst:
399 raise xs_errors.XenError('ISCSIDevice', opterr='Failed to find any LUNs')
402def is_iscsi_daemon_running():
403 cmd = ["/usr/bin/systemctl", "is-active", "iscsid.service"]
404 (rc, stdout, stderr) = util.doexec(cmd)
405 return (rc == 0)
408def stop_daemon():
409 if is_iscsi_daemon_running():
410 cmd = ["/usr/bin/systemctl", "stop", "iscsid.service"]
411 failuremessage = "Failed to stop iscsi daemon"
412 exn_on_failure(cmd, failuremessage)
415def restart_daemon():
416 stop_daemon()
417 if os.path.exists(os.path.join(_ISCSI_DB_PATH, 'nodes')): 417 ↛ 426line 417 didn't jump to line 426, because the condition on line 417 was never false
418 try:
419 shutil.rmtree(os.path.join(_ISCSI_DB_PATH, 'nodes'))
420 except:
421 pass
422 try:
423 shutil.rmtree(os.path.join(_ISCSI_DB_PATH, 'send_targets'))
424 except:
425 pass
426 cmd = ["/usr/bin/systemctl", "start", "iscsid.service"]
427 failuremessage = "Failed to start iscsi daemon"
428 exn_on_failure(cmd, failuremessage)
431def wait_for_devs(targetIQN, portal):
432 path = os.path.join("/dev/iscsi", targetIQN, portal)
433 for i in range(0, 15):
434 if os.path.exists(path):
435 return True
436 time.sleep(1)
437 return False
440def refresh_luns(targetIQN, portal):
441 wait_for_devs(targetIQN, portal)
442 try:
443 path = os.path.join("/dev/iscsi", targetIQN, portal)
444 id = scsiutil.getSessionID(path)
445 f = open('/sys/class/scsi_host/host%s/scan' % id, 'w')
446 f.write('- - -\n')
447 f.close()
448 time.sleep(2) # FIXME
449 except:
450 pass
453def get_IQN_paths():
454 """Return the list of iSCSI session directories"""
455 return glob.glob(_GENERIC_SESSION_PATH % '*')
458def get_targetIQN(iscsi_host):
459 """Get target IQN from sysfs for given iSCSI host number"""
460 iqn_file = os.path.join(_GENERIC_SESSION_PATH % iscsi_host, 'targetname')
461 targetIQN = util.get_single_entry(glob.glob(iqn_file)[0])
462 return targetIQN
465def get_targetIP_and_port(iscsi_host):
466 """Get target IP address and port for given iSCSI host number"""
467 connection_dir = _GENERIC_CONNECTION_PATH % iscsi_host
468 ip = util.get_single_entry(glob.glob(os.path.join(
469 connection_dir, 'persistent_address'))[0])
470 port = util.get_single_entry(glob.glob(os.path.join(
471 connection_dir, 'persistent_port'))[0])
472 return (ip, port)
475def get_path(targetIQN, portal, lun):
476 """Gets the path of a specified LUN - this should be e.g. '1' or '5'"""
477 path = os.path.join("/dev/iscsi", targetIQN, portal)
478 return os.path.join(path, "LUN" + lun)
481def get_path_safe(targetIQN, portal, lun):
482 """Gets the path of a specified LUN, and ensures that it exists.
483 Raises an exception if it hasn't appeared after the timeout"""
484 path = get_path(targetIQN, portal, lun)
485 for i in range(0, 15):
486 if os.path.exists(path):
487 return path
488 time.sleep(1)
489 raise xs_errors.XenError('ISCSIDevice', \
490 opterr='LUN failed to appear at path %s' % path)
493def match_target(tgt, s):
494 regex = re.compile(tgt)
495 return regex.search(s, 0)
498def match_targetIQN(tgtIQN, s):
499 if not len(s):
500 return False
501 if tgtIQN == "any":
502 return True
503 # Extract IQN from iscsiadm -m session
504 # Ex: tcp: [17] 10.220.98.9:3260,1 iqn.2009-01.xenrt.test:iscsi4181a93e
505 siqn = s.split(",")[1].split()[1].strip()
506 return (siqn == tgtIQN)
509def match_session(s):
510 regex = re.compile("^tcp:")
511 return regex.search(s, 0)
514def _compare_sessions_to_tgt(session_output, tgtIQN, tgt=''):
515 for line in session_output.split('\n'):
516 if match_targetIQN(tgtIQN, line) and match_session(line):
517 if len(tgt): 517 ↛ 521line 517 didn't jump to line 521, because the condition on line 517 was never false
518 if match_target(tgt, line):
519 return True
520 else:
521 return True
522 return False
525def _checkTGT(tgtIQN, tgt=''):
526 if not is_iscsi_daemon_running():
527 return False
528 failuremessage = "Failure occured querying iscsi daemon"
529 cmd = ["iscsiadm", "-m", "session"]
530 try:
531 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
532 # Recent versions of iscsiadm return error it this list is empty.
533 # Quick and dirty handling
534 except Exception as e:
535 util.SMlog("%s failed with %s" % (cmd, e.args))
536 stdout = ""
537 return _compare_sessions_to_tgt(stdout, tgtIQN, tgt)
540def get_rootdisk_IQNs():
541 """Return the list of IQNs for targets required by root filesystem"""
542 if not os.path.isdir('/sys/firmware/ibft/'): 542 ↛ 544line 542 didn't jump to line 544, because the condition on line 542 was never false
543 return []
544 dirs = [x for x in os.listdir('/sys/firmware/ibft/') if x.startswith('target')]
545 return [open('/sys/firmware/ibft/%s/target-name' % d).read().strip() for d in dirs]
548def _checkAnyTGT():
549 if not is_iscsi_daemon_running():
550 return False
551 rootIQNs = get_rootdisk_IQNs()
552 failuremessage = "Failure occured querying iscsi daemon"
553 cmd = ["iscsiadm", "-m", "session"]
554 try:
555 (stdout, stderr) = exn_on_failure(cmd, failuremessage)
556 # Recent versions of iscsiadm return error it this list is empty.
557 # Quick and dirty handling
558 except Exception as e:
559 util.SMlog("%s failed with %s" % (cmd, e.args))
560 stdout = ""
561 for e in filter(match_session, stdout.split('\n')):
562 iqn = e.split()[-1]
563 if not iqn in rootIQNs:
564 return True
565 return False
568def ensure_daemon_running_ok(localiqn):
569 """Check that the daemon is running and the initiator name is correct"""
570 if not is_iscsi_daemon_running():
571 set_current_initiator_name(localiqn)
572 restart_daemon()
573 else:
574 currentiqn = get_current_initiator_name()
575 if currentiqn != localiqn:
576 if _checkAnyTGT():
577 raise xs_errors.XenError('ISCSIInitiator', \
578 opterr='Daemon already running with ' \
579 + 'target(s) attached using ' \
580 + 'different IQN')
581 set_current_initiator_name(localiqn)
582 restart_daemon()
585def get_iscsi_interfaces():
586 result = []
587 try:
588 # Get all configured iscsiadm interfaces
589 cmd = ["iscsiadm", "-m", "iface"]
590 (stdout, stderr) = exn_on_failure(cmd,
591 "Failure occured querying iscsi daemon")
592 # Get the interface (first column) from a line such as default
593 # tcp,<empty>,<empty>,<empty>,<empty>
594 for line in stdout.split("\n"):
595 line_element = line.split(" ")
596 interface_name = line_element[0]
597 # ignore interfaces which aren't marked as starting with
598 # c_.
599 if len(line_element) == 2 and interface_name[:2] == "c_":
600 result.append(interface_name)
601 except:
602 # Ignore exception from exn on failure, still return the default
603 # interface
604 pass
605 # In case there are no configured interfaces, still add the default
606 # interface
607 if len(result) == 0:
608 result.append("default")
609 return result