File: linux_net.py

package info (click to toggle)
python-os-vif 4.2.1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 948 kB
  • sloc: python: 5,503; makefile: 25; sh: 2
file content (416 lines) | stat: -rw-r--r-- 13,922 bytes parent folder | download | duplicates (2)
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# Derived from nova/network/linux_net.py
#
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""Implements vlans, bridges using linux utilities."""

import glob
import os
import re

from os_vif.internal.ip.api import ip as ip_lib
from oslo_concurrency import processutils
from oslo_log import log as logging
from oslo_utils import excutils

from vif_plug_ovs import exception
from vif_plug_ovs import privsep

LOG = logging.getLogger(__name__)

VIRTFN_RE = re.compile(r"virtfn(\d+)")

# phys_port_name only contains the VF number
INT_RE = re.compile(r"^(\d+)$")
# phys_port_name contains VF## or vf##
VF_RE = re.compile(r"vf(\d+)", re.IGNORECASE)
# phys_port_name contains PF## or pf##
PF_RE = re.compile(r"pf(\d+)", re.IGNORECASE)
# bus_info (bdf) contains <bus>:<dev>.<func>
PF_FUNC_RE = re.compile(r"\.(\d+)", 0)
# phys_port_name contains p##
UPLINK_PORT_RE = re.compile(r"p(\d+)", re.IGNORECASE)

_SRIOV_TOTALVFS = "sriov_totalvfs"
NIC_NAME_LEN = 14


def _update_device_mtu(dev, mtu):
    if not mtu:
        return
    set_device_mtu(dev, mtu)


@privsep.vif_plug.entrypoint
def delete_net_dev(dev):
    """Delete a network device only if it exists."""
    if ip_lib.exists(dev):
        try:
            ip_lib.delete(dev, check_exit_code=[0, 2, 254])
            LOG.debug("Net device removed: '%s'", dev)
        except processutils.ProcessExecutionError:
            with excutils.save_and_reraise_exception():
                LOG.error("Failed removing net device: '%s'", dev)


@privsep.vif_plug.entrypoint
def create_veth_pair(dev1_name, dev2_name, mtu):
    """Create a pair of veth devices with the specified names,
    deleting any previous devices with those names.
    """
    for dev in [dev1_name, dev2_name]:
        delete_net_dev(dev)

    ip_lib.add(dev1_name, 'veth', peer=dev2_name)
    for dev in [dev1_name, dev2_name]:
        ip_lib.set(dev, state='up')
        ip_lib.set(dev, promisc='on')
        _update_device_mtu(dev, mtu)


@privsep.vif_plug.entrypoint
def update_veth_pair(dev1_name, dev2_name, mtu):
    """Update a pair of veth devices with new configuration."""
    for dev in [dev1_name, dev2_name]:
        _update_device_mtu(dev, mtu)


def _disable_ipv6(bridge):
    """Disable ipv6 if available for bridge. Must be called from
       privsep context.
    """
    # NOTE(sean-k-mooney): os-vif disables ipv6 to ensure the Bridge
    # does not acquire an ipv6 auto config or link local address.
    # This is required to prevent bug 1302080.
    # https://bugs.launchpad.net/neutron/+bug/1302080
    disv6 = ('/proc/sys/net/ipv6/conf/%s/disable_ipv6' %
             bridge)
    if os.path.exists(disv6):
        with open(disv6, 'w') as f:
            f.write('1')


# TODO(ralonsoh): extract into common module
def _arp_filtering(bridge):
    """Prevent the bridge from replying to ARP messages with machine local IPs

    1. Reply only if the target IP address is local address configured on the
       incoming interface.
    2. Always use the best local address.
    """
    arp_params = [('/proc/sys/net/ipv4/conf/%s/arp_ignore' % bridge, '1'),
                  ('/proc/sys/net/ipv4/conf/%s/arp_announce' % bridge, '2')]
    for parameter, value in arp_params:
        if os.path.exists(parameter):
            with open(parameter, 'w') as f:
                f.write(value)


@privsep.vif_plug.entrypoint
def ensure_bridge(bridge):
    if not ip_lib.exists(bridge):
        # NOTE(sean-k-mooney): we set mac ageing to 0 to disable mac ageing
        # on the hybrid plug bridge to avoid packet loss during live
        # migration. This avoids bug #1715317 and related bug #1414559
        ip_lib.add(bridge, 'bridge', ageing=0)
    _disable_ipv6(bridge)
    _arp_filtering(bridge)
    # we bring up the bridge to allow it to switch packets
    set_interface_state(bridge, 'up')


@privsep.vif_plug.entrypoint
def delete_bridge(bridge, dev):
    if ip_lib.exists(bridge):
        # Note(sean-k-mooney): this will detach all ports on
        # the bridge before deleting the bridge.
        ip_lib.delete(bridge, check_exit_code=[0, 2, 254])
        # however it will not set the detached interface down
        # so we set the dev down if dev is not None and exists.
        if dev and ip_lib.exists(dev):
            set_interface_state(dev, "down")


@privsep.vif_plug.entrypoint
def add_bridge_port(bridge, dev):
    ip_lib.set(dev, master=bridge)


@privsep.vif_plug.entrypoint
def set_device_mtu(dev, mtu):
    """Set the device MTU."""
    if ip_lib.exists(dev):
        ip_lib.set(dev, mtu=mtu, check_exit_code=[0, 2, 254])


@privsep.vif_plug.entrypoint
def set_interface_state(interface_name, port_state):
    ip_lib.set(interface_name, state=port_state, check_exit_code=[0, 2, 254])


def _parse_vf_number(phys_port_name):
    """Parses phys_port_name and returns VF number or None.

    To determine the VF number of a representor, parse phys_port_name
    in the following sequence and return the first valid match. If none
    match, then the representor is not for a VF.
    """
    match = INT_RE.search(phys_port_name)
    if match:
        return match.group(1)
    match = VF_RE.search(phys_port_name)
    if match:
        return match.group(1)
    return None


def _parse_pf_number(phys_port_name):
    """Parses phys_port_name and returns PF number or None.

    To determine the PF number of a representor, parse phys_port_name in
    the following sequence and return the first valid match. If none
    match, then the representor is not for a PF.
    """
    match = PF_RE.search(phys_port_name)
    if match:
        return match.group(1)
    return None


# This function is taken from nova/pci/utils.py
def get_function_by_ifname(ifname):
    """Given the device name, returns the PCI address of a device
    and returns True if the address is in a physical function.
    """
    dev_path = "/sys/class/net/%s/device" % ifname
    sriov_totalvfs = 0
    if os.path.isdir(dev_path):
        try:
            # sriov_totalvfs contains the maximum possible VFs for this PF
            dev_path_file = os.path.join(dev_path, _SRIOV_TOTALVFS)
            with open(dev_path_file, 'r') as fd:
                sriov_totalvfs = int(fd.readline().rstrip())
                return (os.readlink(dev_path).strip("./"),
                        sriov_totalvfs > 0)
        except (IOError, ValueError):
            return os.readlink(dev_path).strip("./"), False
    return None, False


def _get_pf_func(pf_ifname):
    """Gets PF function number using pf_ifname and returns function
    number or None.
    """

    address_str, pf = get_function_by_ifname(pf_ifname)
    if not address_str:
        return None
    match = PF_FUNC_RE.search(address_str)
    if match:
        return match.group(1)
    return None


def get_representor_port(pf_ifname, vf_num):
    """Get the representor netdevice which is corresponding to the VF.

    This method gets PF interface name and number of VF. It iterates over all
    the interfaces under the PF location and looks for interface that has the
    VF number in the phys_port_name. That interface is the representor for
    the requested VF.
    """

    pf_sw_id = None
    try:
        pf_sw_id = _get_phys_switch_id(pf_ifname)
    except (OSError, IOError):
        raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num)

    pf_subsystem_file = "/sys/class/net/%s/subsystem" % pf_ifname
    try:
        devices = os.listdir(pf_subsystem_file)
    except (OSError, IOError):
        raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num)

    ifname_pf_func = _get_pf_func(pf_ifname)
    if ifname_pf_func is None:
        raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num)

    for device in devices:
        try:
            device_sw_id = _get_phys_switch_id(device)
            if not device_sw_id or device_sw_id != pf_sw_id:
                continue
        except (OSError, IOError):
            continue

        try:
            phys_port_name = _get_phys_port_name(device)
            if phys_port_name is None:
                continue
        except (OSError, IOError):
            continue

        # If the phys_port_name of the VF-rep is of the format pfXvfY
        # (or vfY@pfX), then match "X" (parent PF's func number) with
        # the PCI func number of pf_ifname.
        rep_parent_pf_func = _parse_pf_number(phys_port_name)
        if rep_parent_pf_func is not None:
            if int(rep_parent_pf_func) != int(ifname_pf_func):
                continue

        representor_num = _parse_vf_number(phys_port_name)
        # Note: representor_num can be 0, referring to VF0
        if representor_num is None:
            continue

        # At this point we're confident we have a representor.
        try:
            if int(representor_num) == int(vf_num):
                return device
        except (ValueError):
            continue

    raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num)


def _get_sysfs_netdev_path(pci_addr, pf_interface):
    """Get the sysfs path based on the PCI address of the device.

    Assumes a networking device - will not check for the existence of the path.
    """
    if pf_interface:
        return "/sys/bus/pci/devices/%s/physfn/net" % (pci_addr)
    return "/sys/bus/pci/devices/%s/net" % (pci_addr)


def _is_switchdev(netdev):
    """Returns True if a netdev has a readable phys_switch_id"""
    try:
        phys_switch_id = _get_phys_switch_id(netdev)
        if phys_switch_id != "" and phys_switch_id is not None:
            return True
    except (OSError, IOError):
        return False
    return False


def get_ifname_by_pci_address(pci_addr, pf_interface=False, switchdev=False):
    """Get the interface name based on a VF's pci address

    :param pci_addr: the PCI address of the VF
    :param pf_interface: if True, look for the netdev of the parent PF
    :param switchdev: if True, ensure that phys_switch_id is valid

    :returns: netdev interface name

    The returned interface name is either the parent PF or that of the VF
    itself based on the argument of pf_interface.
    """
    dev_path = _get_sysfs_netdev_path(pci_addr, pf_interface)
    try:
        devices = os.listdir(dev_path)

        # Return the first netdev in case of switchdev=False
        if not switchdev:
            return devices[0]
        elif pf_interface:
            fallback_netdev = None
            for netdev in devices:
                # Return the uplink representor in case of switchdev=True
                if _is_switchdev(netdev):
                    fallback_netdev = netdev if fallback_netdev is None \
                        else fallback_netdev
                    phys_port_name = _get_phys_port_name(netdev)
                    if phys_port_name is not None and \
                            UPLINK_PORT_RE.search(phys_port_name):
                        return netdev

            # Fallback to first switchdev netdev in case of switchdev=True
            if fallback_netdev is not None:
                return fallback_netdev

    except Exception:
        raise exception.PciDeviceNotFoundById(id=pci_addr)
    raise exception.PciDeviceNotFoundById(id=pci_addr)


def get_vf_num_by_pci_address(pci_addr):
    """Get the VF number based on a VF's pci address

    A VF is associated with an VF number, which ip link command uses to
    configure it. This number can be obtained from the PCI device filesystem.
    """
    virtfns_path = "/sys/bus/pci/devices/%s/physfn/virtfn*" % (pci_addr)
    vf_num = None
    try:
        for vf_path in glob.iglob(virtfns_path):
            if re.search(pci_addr, os.readlink(vf_path)):
                t = VIRTFN_RE.search(vf_path)
                vf_num = t.group(1)
                break
    except Exception:
        pass
    if vf_num is None:
        raise exception.PciDeviceNotFoundById(id=pci_addr)
    return vf_num


def get_dpdk_representor_port_name(port_id):
    devname = "vfr" + port_id
    return devname[:NIC_NAME_LEN]


def get_pf_pci_from_vf(vf_pci):
    """Get physical function PCI address of a VF

    :param vf_pci: the PCI address of the VF
    :return: the PCI address of the PF
    """
    physfn_path = os.readlink("/sys/bus/pci/devices/%s/physfn" % vf_pci)
    return os.path.basename(physfn_path)


def _get_phys_port_name(ifname):
    """Get the interface name and return its phys_port_name

    :param ifname: The interface name
    :return: The phys_port_name of the given ifname
    """
    phys_port_name_path = "/sys/class/net/%s/phys_port_name" % ifname

    if not os.path.isfile(phys_port_name_path):
        return None

    with open(phys_port_name_path, 'r') as fd:
        return fd.readline().strip()


def _get_phys_switch_id(ifname):
    """Get the interface name and return its phys_switch_id

    :param ifname: The interface name
    :return: The phys_switch_id of the given ifname
    """
    phys_port_name_path = "/sys/class/net/%s/phys_switch_id" % ifname

    if not os.path.isfile(phys_port_name_path):
        return None

    with open(phys_port_name_path, 'r') as fd:
        return fd.readline().strip()