Add Trunk Plumber module for Linux Bridge
This adds the module responsible for creating/deleting VLAN sub-interfaces when passed a trunk object. It handles parsing of ip link output to find existing VLAN children and the calls to ip_lib to create/destroy sub-interfaces. This includes unit tests as well as functional tests. Partially-Implements: blueprint vlan-aware-vms Change-Id: I1e3ab69aaff7bca322fa0d738ac74c3dd0dc69b4
This commit is contained in:
parent
06361f7daf
commit
19e4b107f0
|
@ -0,0 +1,176 @@
|
|||
#
|
||||
# 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.
|
||||
import re
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
|
||||
from neutron._i18n import _LW
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.common import utils
|
||||
from neutron.plugins.ml2.drivers.linuxbridge.agent.common import utils as lutil
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Plumber(object):
|
||||
"""Object responsible for VLAN interface CRUD.
|
||||
|
||||
This handles the creation/deletion/listing of VLAN interfaces for
|
||||
a trunk within a namespace.
|
||||
"""
|
||||
|
||||
def __init__(self, namespace=None):
|
||||
self.namespace = namespace
|
||||
|
||||
def trunk_on_host(self, trunk):
|
||||
"""Returns true if trunk device is present else False."""
|
||||
trunk_dev = self._trunk_device_name(trunk)
|
||||
return ip_lib.device_exists(trunk_dev, namespace=self.namespace)
|
||||
|
||||
def ensure_trunk_subports(self, trunk):
|
||||
"""Idempotent wiring for a trunk's subports.
|
||||
|
||||
Given a trunk object, delete any vlan subinterfaces belonging to a
|
||||
trunk that aren't on the object. Create any which are on the object
|
||||
which do not exist.
|
||||
"""
|
||||
trunk_dev = self._trunk_device_name(trunk)
|
||||
with self._trunk_lock(trunk_dev):
|
||||
# lock scoped to trunk device so two diffs don't interleave
|
||||
expected = self._get_subport_devs_and_vlans(trunk.sub_ports)
|
||||
existing = self._get_vlan_children(trunk_dev)
|
||||
to_delete = existing - expected
|
||||
to_create = expected - existing
|
||||
for devname, vlan_id in to_delete:
|
||||
LOG.debug("Deleting subport %(name)s with vlan tag %(tag)s",
|
||||
dict(name=devname, tag=vlan_id))
|
||||
self._safe_delete_device(devname)
|
||||
for devname, vlan_id in to_create:
|
||||
LOG.debug("Creating subport %(name)s with vlan tag %(tag)s",
|
||||
dict(name=devname, tag=vlan_id))
|
||||
self._create_vlan_subint(trunk_dev, devname, vlan_id)
|
||||
|
||||
def delete_trunk_subports(self, trunk):
|
||||
return self.delete_subports_by_port_id(trunk.port_id)
|
||||
|
||||
def delete_subports_by_port_id(self, port_id):
|
||||
device = self._get_tap_device_name(port_id)
|
||||
if not ip_lib.device_exists(device, namespace=self.namespace):
|
||||
LOG.debug("Device %s not present on this host", device)
|
||||
return
|
||||
with self._trunk_lock(device):
|
||||
for subname, vlan_id in self._get_vlan_children(device):
|
||||
LOG.debug("Deleting subport %(name)s with vlan tag %(tag)s",
|
||||
dict(name=subname, tag=vlan_id))
|
||||
self._safe_delete_device(subname)
|
||||
|
||||
def set_port_mac(self, port_id, mac_address):
|
||||
"""Sets mac address of physical device for port_id to mac_address."""
|
||||
dev_name = self._get_tap_device_name(port_id)
|
||||
ipd = ip_lib.IPDevice(dev_name, namespace=self.namespace)
|
||||
try:
|
||||
if mac_address == ipd.link.address:
|
||||
return False
|
||||
LOG.debug("Changing MAC from %(old)s to %(new)s for device "
|
||||
"%(dev)s", dict(old=ipd.link.address, new=mac_address,
|
||||
dev=dev_name))
|
||||
ipd.link.set_down()
|
||||
ipd.link.set_address(mac_address)
|
||||
ipd.link.set_up()
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception() as ectx:
|
||||
ectx.reraise = ip_lib.IPDevice(
|
||||
dev_name, namespace=self.namespace).exists()
|
||||
return True
|
||||
|
||||
def _trunk_lock(self, trunk_dev):
|
||||
lock_name = 'trunk-%s' % trunk_dev
|
||||
return lockutils.lock(lock_name, utils.SYNCHRONIZED_PREFIX)
|
||||
|
||||
def _create_vlan_subint(self, trunk_name, devname, vlan_id):
|
||||
ip_wrap = ip_lib.IPWrapper(namespace=self.namespace)
|
||||
try:
|
||||
dev = ip_wrap.add_vlan(devname, trunk_name, vlan_id)
|
||||
dev.disable_ipv6()
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception() as ectx:
|
||||
ectx.reraise = ip_lib.IPDevice(
|
||||
devname, namespace=self.namespace).exists()
|
||||
|
||||
def _safe_delete_device(self, devname):
|
||||
dev = ip_lib.IPDevice(devname, namespace=self.namespace)
|
||||
try:
|
||||
dev.link.set_down()
|
||||
dev.link.delete()
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception() as ectx:
|
||||
ectx.reraise = dev.exists()
|
||||
|
||||
def _trunk_device_name(self, trunk):
|
||||
return self._get_tap_device_name(trunk.port_id)
|
||||
|
||||
def _get_subport_devs_and_vlans(self, subports):
|
||||
return {(self._get_tap_device_name(s.port_id),
|
||||
s.segmentation_id)
|
||||
for s in subports}
|
||||
|
||||
def _get_tap_device_name(self, devname):
|
||||
return lutil.get_tap_device_name(devname)
|
||||
|
||||
def _get_vlan_children(self, dev):
|
||||
"""Return set of (devname, vlan_id) tuples for children of device."""
|
||||
# TODO(kevinbenton): move into ip-lib after privsep stuff settles
|
||||
ip_wrapper = ip_lib.IPWrapper(namespace=self.namespace)
|
||||
output = ip_wrapper.netns.execute(["ip", "-d", "link", "list"],
|
||||
check_exit_code=True)
|
||||
return {(i.devname, i.vlan_tag)
|
||||
for i in _iter_output_by_interface(output)
|
||||
if i.parent_devname == dev}
|
||||
|
||||
|
||||
def _iter_output_by_interface(output):
|
||||
interface = []
|
||||
for line in output.splitlines():
|
||||
if not line.startswith(' '):
|
||||
# no space indicates new interface info
|
||||
interface_str = ' '.join(interface)
|
||||
if interface_str.strip():
|
||||
yield _InterfaceInfo(interface_str)
|
||||
interface = []
|
||||
interface.append(line)
|
||||
if interface:
|
||||
yield _InterfaceInfo(' '.join(interface))
|
||||
|
||||
|
||||
class _InterfaceInfo(object):
|
||||
def __init__(self, line):
|
||||
try:
|
||||
name_section = line.split(': ')[1]
|
||||
except IndexError:
|
||||
name_section = None
|
||||
LOG.warning(_LW("Bad interface line: %s"), line)
|
||||
if not name_section or '@' not in name_section:
|
||||
self.devname = name_section
|
||||
self.parent_devname = self.vlan_tag = None
|
||||
else:
|
||||
self.devname, self.parent_devname = name_section.split('@')
|
||||
m = re.match(r'.*802\.1Q id (\d+).*', line)
|
||||
self.vlan_tag = int(m.group(1)) if m else None
|
||||
if self.vlan_tag is None:
|
||||
LOG.warning(_LW("Failed to parse VLAN from: %s"), line)
|
||||
|
||||
def __repr__(self):
|
||||
return ('_InterfaceInfo(devname=%s, parent=%s, vlan=%s)' %
|
||||
(self.devname, self.parent_devname, self.vlan_tag))
|
|
@ -14,10 +14,15 @@
|
|||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
import testtools
|
||||
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.common import utils
|
||||
from neutron.objects import trunk
|
||||
from neutron.plugins.ml2.drivers.linuxbridge.agent import \
|
||||
linuxbridge_neutron_agent
|
||||
from neutron.services.trunk.drivers.linuxbridge.agent import trunk_plumber
|
||||
from neutron.tests.functional.agent.linux import test_ip_lib
|
||||
|
||||
lba = linuxbridge_neutron_agent
|
||||
|
@ -54,3 +59,100 @@ class LinuxBridgeAgentTests(test_ip_lib.IpLibTestFramework):
|
|||
self.generate_device_details()._replace(namespace=None,
|
||||
name='br-eth1'))
|
||||
lba.LinuxBridgeManager(mappings, {})
|
||||
|
||||
def test_set_port_mac(self):
|
||||
attr = self.generate_device_details()
|
||||
self.manage_device(attr)
|
||||
plumber = trunk_plumber.Plumber(namespace=attr.namespace)
|
||||
# force it to return name of above
|
||||
plumber._get_tap_device_name = lambda x: attr.name
|
||||
new_mac = utils.get_random_mac('fa:16:3e:00:00:00'.split(':'))
|
||||
self.assertTrue(plumber.set_port_mac('port_id', new_mac))
|
||||
self.assertFalse(plumber.set_port_mac('port_id', new_mac))
|
||||
new_mac = utils.get_random_mac('fa:16:3e:00:00:00'.split(':'))
|
||||
self.assertTrue(plumber.set_port_mac('port_id', new_mac))
|
||||
self.assertFalse(plumber.set_port_mac('port_id', new_mac))
|
||||
|
||||
def test_vlan_subinterfaces(self):
|
||||
attr = self.generate_device_details()
|
||||
device = self.manage_device(attr)
|
||||
devname = device.name
|
||||
plumber = trunk_plumber.Plumber(namespace=attr.namespace)
|
||||
for i in range(20):
|
||||
subname = 'vtest-%s' % i
|
||||
plumber._create_vlan_subint(devname, subname, i)
|
||||
# ensure no addresses were assigned (e.g. ipv6)
|
||||
vlan_int = ip_lib.IPDevice(subname, namespace=attr.namespace)
|
||||
self.assertFalse(vlan_int.addr.list())
|
||||
children = plumber._get_vlan_children(devname)
|
||||
expected = {('vtest-%s' % i, i) for i in range(20)}
|
||||
self.assertEqual(expected, children)
|
||||
|
||||
# delete one
|
||||
plumber._safe_delete_device('vtest-19')
|
||||
children = plumber._get_vlan_children(devname)
|
||||
expected = {('vtest-%s' % i, i) for i in range(19)}
|
||||
self.assertEqual(expected, children)
|
||||
# ensure they are removed by parent removal
|
||||
self._safe_delete_device(device)
|
||||
self.assertFalse(plumber._get_vlan_children(devname))
|
||||
|
||||
def test_vlan_QinQ_subinterfaces(self):
|
||||
# the trunk model does not support this right now, but this is to
|
||||
# the plumber on the agent side doesn't explode in their presense
|
||||
# in case an operator does something fancy or we have a race where
|
||||
# a trunk's parent port is converted to a subport while the agent
|
||||
# is offline.
|
||||
attr = self.generate_device_details()
|
||||
device = self.manage_device(attr)
|
||||
devname = device.name
|
||||
plumber = trunk_plumber.Plumber(namespace=attr.namespace)
|
||||
for i in range(20):
|
||||
plumber._create_vlan_subint(devname, 'vtest-%s' % i, i)
|
||||
plumber._create_vlan_subint('vtest-%s' % i, 'qinq-%s' % i, 2)
|
||||
top_level = {('vtest-%s' % i, i) for i in range(20)}
|
||||
for i in range(20):
|
||||
# as we iterate, we delete a vlan from each dev and ensure it
|
||||
# didn't break the top-level vlans
|
||||
self.assertEqual({('qinq-%s' % i, 2)},
|
||||
plumber._get_vlan_children('vtest-%s' % i))
|
||||
plumber._safe_delete_device('qinq-%s' % i)
|
||||
self.assertEqual(set(), plumber._get_vlan_children('vtest-%i' % i))
|
||||
self.assertEqual(top_level, plumber._get_vlan_children(devname))
|
||||
|
||||
def test_ensure_trunk_subports(self):
|
||||
attr = self.generate_device_details()
|
||||
device = self.manage_device(attr)
|
||||
devname = device.name
|
||||
plumber = trunk_plumber.Plumber(namespace=attr.namespace)
|
||||
plumber._trunk_device_name = lambda x: devname
|
||||
trunk_obj = self._gen_trunk()
|
||||
plumber.ensure_trunk_subports(trunk_obj)
|
||||
# ensure no mutation the second time
|
||||
with mock.patch.object(plumber, '_safe_delete_device',
|
||||
side_effect=RuntimeError()):
|
||||
plumber.ensure_trunk_subports(trunk_obj)
|
||||
|
||||
while trunk_obj.sub_ports:
|
||||
# drain down the sub-ports and make sure it keeps
|
||||
# them equal
|
||||
trunk_obj.sub_ports.pop()
|
||||
plumber.ensure_trunk_subports(trunk_obj)
|
||||
expected = {(plumber._get_tap_device_name(sp.port_id),
|
||||
sp.segmentation_id)
|
||||
for sp in trunk_obj.sub_ports}
|
||||
wired = plumber._get_vlan_children(devname)
|
||||
self.assertEqual(expected, wired)
|
||||
|
||||
def _gen_trunk(self):
|
||||
trunk_obj = trunk.Trunk(id=uuidutils.generate_uuid(),
|
||||
port_id=uuidutils.generate_uuid(),
|
||||
tenant_id=uuidutils.generate_uuid())
|
||||
subports = [trunk.SubPort(id=uuidutils.generate_uuid(),
|
||||
port_id=uuidutils.generate_uuid(),
|
||||
segmentation_type='vlan',
|
||||
trunk_id=trunk_obj.id,
|
||||
segmentation_id=i)
|
||||
for i in range(20, 40)]
|
||||
trunk_obj.sub_ports = subports
|
||||
return trunk_obj
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.objects import trunk
|
||||
from neutron.services.trunk.drivers.linuxbridge.agent import trunk_plumber
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class PlumberTestCase(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
self.plumber = trunk_plumber.Plumber()
|
||||
self.get_tap_device_name = mock.patch.object(
|
||||
self.plumber, '_get_tap_device_name',
|
||||
return_value='devname').start()
|
||||
self.trunk = trunk.Trunk()
|
||||
self.trunk.port_id = uuidutils.generate_uuid()
|
||||
self.trunk.sub_ports = []
|
||||
self.device_exists = mock.patch.object(trunk_plumber.ip_lib,
|
||||
'device_exists').start()
|
||||
self.device_exists.return_value = True
|
||||
ipwrap = mock.patch.object(trunk_plumber.ip_lib, 'IPWrapper').start()
|
||||
ipwrap.return_value.netns.execute.return_value = IP_LINK_OUTPUT
|
||||
super(PlumberTestCase, self).setUp()
|
||||
|
||||
def test_trunk_on_host(self):
|
||||
self.assertTrue(self.plumber.trunk_on_host(self.trunk))
|
||||
self.device_exists.return_value = False
|
||||
self.assertFalse(self.plumber.trunk_on_host(self.trunk))
|
||||
|
||||
def test_ensure_trunk_subports(self):
|
||||
trunk_vals = set([('dev2', 23), ('dev3', 44), ('dev4', 45)])
|
||||
existing_vals = set([('dev1', 21), ('dev2', 23), ('dev3', 45)])
|
||||
mock.patch.object(self.plumber, '_get_subport_devs_and_vlans',
|
||||
return_value=trunk_vals).start()
|
||||
mock.patch.object(self.plumber, '_get_vlan_children',
|
||||
return_value=existing_vals).start()
|
||||
delete = mock.patch.object(self.plumber, '_safe_delete_device').start()
|
||||
create = mock.patch.object(self.plumber, '_create_vlan_subint').start()
|
||||
self.plumber.ensure_trunk_subports(self.trunk)
|
||||
# dev1 is gone and dev3 changed vlans
|
||||
delete.assert_has_calls([mock.call('dev3'), mock.call('dev1')],
|
||||
any_order=True)
|
||||
create.assert_has_calls([mock.call('devname', 'dev4', 45),
|
||||
mock.call('devname', 'dev3', 44)],
|
||||
any_order=True)
|
||||
|
||||
def test_delete_trunk_subports(self):
|
||||
existing_vals = set([('dev1', 21), ('dev2', 23), ('dev3', 45)])
|
||||
mock.patch.object(self.plumber, '_get_vlan_children',
|
||||
return_value=existing_vals).start()
|
||||
delete = mock.patch.object(self.plumber, '_safe_delete_device').start()
|
||||
self.plumber.delete_trunk_subports(self.trunk)
|
||||
delete.assert_has_calls([mock.call('dev3'), mock.call('dev2'),
|
||||
mock.call('dev1')],
|
||||
any_order=True)
|
||||
|
||||
def test_set_port_mac(self):
|
||||
ipd = mock.patch.object(trunk_plumber.ip_lib, 'IPDevice').start()
|
||||
ipdi = ipd.return_value
|
||||
self.plumber.set_port_mac('port_id', mac_address='44')
|
||||
ipdi.link.set_address.assert_called_once_with('44')
|
||||
ipdi.exists.return_value = False
|
||||
ipdi.link.set_address.side_effect = ValueError()
|
||||
# exception suppressed since it no longer 'exists'
|
||||
self.plumber.set_port_mac('port_id', mac_address='44')
|
||||
|
||||
def test__get_vlan_children(self):
|
||||
expected = [('tap47198374-5a', 777),
|
||||
('tap47198374-5b', 2),
|
||||
('tap47198374-5c', 3)]
|
||||
self.assertEqual(set(expected),
|
||||
self.plumber._get_vlan_children('tap34786ac-28'))
|
||||
expected = [('tap39df7d39-c5', 99),
|
||||
('tap39df7d44-b2', 904),
|
||||
('tap11113d44-3f', 777)]
|
||||
self.assertEqual(set(expected),
|
||||
self.plumber._get_vlan_children('tapa962cfc7-9d'))
|
||||
# vlan sub-interface and non-trunk shouldn't have children
|
||||
self.assertEqual(set(),
|
||||
self.plumber._get_vlan_children('tap47198374-5c'))
|
||||
self.assertEqual(set(),
|
||||
self.plumber._get_vlan_children('br-int'))
|
||||
|
||||
def test__iter_output_by_interface(self):
|
||||
iterator = trunk_plumber._iter_output_by_interface(IP_LINK_OUTPUT)
|
||||
names = [i.devname for i in iterator]
|
||||
expected = ['lo', 'eth0', 'bond0', 'ovs-system', 'br-ex',
|
||||
'testb9cfb5d7', 'br-int', 'br-tun', 'tapa962cfc7-9d',
|
||||
'tap39df7d39-c5', 'tap39df7d44-b2', 'tap11113d44-3f',
|
||||
'tap34786ac-28', 'tap47198374-5a', 'tap47198374-5b',
|
||||
'tap47198374-5c']
|
||||
self.assertEqual(expected, names)
|
||||
|
||||
IP_LINK_OUTPUT = """
|
||||
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group
|
||||
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0
|
||||
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFA
|
||||
link/ether 00:0c:29:10:68:04 brd ff:ff:ff:ff:ff:ff promiscuity 0
|
||||
3: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN mode DEFAULT grou
|
||||
link/ether 5e:dc:86:6f:b7:19 brd ff:ff:ff:ff:ff:ff promiscuity 0
|
||||
bond
|
||||
4: ovs-system: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group
|
||||
link/ether 5a:95:a1:b9:42:25 brd ff:ff:ff:ff:ff:ff promiscuity 1
|
||||
5: br-ex: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT gro
|
||||
link/ether be:cc:4f:f7:28:48 brd ff:ff:ff:ff:ff:ff promiscuity 1
|
||||
6: testb9cfb5d7: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFA
|
||||
link/ether 82:90:49:84:32:47 brd ff:ff:ff:ff:ff:ff promiscuity 1
|
||||
7: br-int: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT gr
|
||||
link/ether 5a:5e:7d:02:7c:4d brd ff:ff:ff:ff:ff:ff promiscuity 1
|
||||
8: br-tun: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT gr
|
||||
link/ether 76:d8:a5:16:d7:4a brd ff:ff:ff:ff:ff:ff promiscuity 1
|
||||
10: tapa962cfc7-9d: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT g
|
||||
link/ether 9a:31:1d:cc:b3:86 brd ff:ff:ff:ff:ff:ff promiscuity 0
|
||||
tun
|
||||
11: tap39df7d39-c5@tapa962cfc7-9d: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop sta
|
||||
link/ether 9a:31:1d:cc:b3:86 brd ff:ff:ff:ff:ff:ff promiscuity 0
|
||||
vlan protocol 802.1Q id 99 <REORDER_HDR>
|
||||
12: tap39df7d44-b2@tapa962cfc7-9d: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop sta
|
||||
link/ether 9a:31:1d:cc:b3:86 brd ff:ff:ff:ff:ff:ff promiscuity 0
|
||||
vlan protocol 802.1Q id 904 <REORDER_HDR>
|
||||
13: tap11113d44-3f@tapa962cfc7-9d: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop sta
|
||||
link/ether 9a:31:1d:cc:b3:86 brd ff:ff:ff:ff:ff:ff promiscuity 0
|
||||
vlan protocol 802.1Q id 777 <REORDER_HDR>
|
||||
14: tap34786ac-28: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT gr
|
||||
link/ether f6:07:9f:11:4c:dc brd ff:ff:ff:ff:ff:ff promiscuity 0
|
||||
tun
|
||||
15: tap47198374-5a@tap34786ac-28: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop stat
|
||||
link/ether f6:07:9f:11:4c:dc brd ff:ff:ff:ff:ff:ff promiscuity 0
|
||||
vlan protocol 802.1Q id 777 <REORDER_HDR>
|
||||
16: tap47198374-5b@tap34786ac-28: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop stat
|
||||
link/ether f6:07:9f:11:4c:dc brd ff:ff:ff:ff:ff:ff promiscuity 0
|
||||
vlan protocol 802.1Q id 2 <REORDER_HDR>
|
||||
17: tap47198374-5c@tap34786ac-28: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop stat
|
||||
link/ether f6:07:9f:11:4c:dc brd ff:ff:ff:ff:ff:ff promiscuity 0
|
||||
vlan protocol 802.1Q id 3 <REORDER_HDR>
|
||||
""" # noqa
|
Loading…
Reference in New Issue