335 lines
10 KiB
Python
335 lines
10 KiB
Python
#
|
|
# Copyright 2006-2009, 2013 Red Hat, Inc.
|
|
# Jeremy Katz <katzj@redhat.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; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# 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, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
# MA 02110-1301 USA.
|
|
|
|
import logging
|
|
import os
|
|
import random
|
|
|
|
from . import util
|
|
from .device import VirtualDevice
|
|
from .xmlbuilder import XMLBuilder, XMLChildProperty, XMLProperty
|
|
|
|
|
|
def _random_mac(conn):
|
|
"""Generate a random MAC address.
|
|
|
|
00-16-3E allocated to xensource
|
|
52-54-00 used by qemu/kvm
|
|
|
|
The OUI list is available at http://standards.ieee.org/regauth/oui/oui.txt.
|
|
|
|
The remaining 3 fields are random, with the first bit of the first
|
|
random field set 0.
|
|
|
|
@return: MAC address string
|
|
"""
|
|
|
|
if conn.is_qemu():
|
|
oui = [0x52, 0x54, 0x00]
|
|
else:
|
|
# Xen
|
|
oui = [0x00, 0x16, 0x3E]
|
|
|
|
mac = oui + [
|
|
random.randint(0x00, 0xff),
|
|
random.randint(0x00, 0xff),
|
|
random.randint(0x00, 0xff)]
|
|
return ':'.join(["%02x" % x for x in mac])
|
|
|
|
|
|
def _default_route():
|
|
route_file = "/proc/net/route"
|
|
if not os.path.exists(route_file):
|
|
logging.debug("route_file=%s does not exist", route_file)
|
|
return None
|
|
|
|
for line in open(route_file):
|
|
info = line.split()
|
|
if len(info) != 11:
|
|
logging.debug("Unexpected field count=%s when parsing %s",
|
|
len(info), route_file)
|
|
break
|
|
|
|
try:
|
|
route = int(info[1], 16)
|
|
if route == 0:
|
|
return info[0]
|
|
except ValueError:
|
|
continue
|
|
|
|
return None
|
|
|
|
|
|
def _default_bridge(conn):
|
|
if "VIRTINST_TEST_SUITE" in os.environ:
|
|
return "eth0"
|
|
|
|
if conn.is_remote():
|
|
return None
|
|
|
|
dev = _default_route()
|
|
if not dev:
|
|
return None
|
|
|
|
# New style peth0 == phys dev, eth0 == bridge, eth0 == default route
|
|
if os.path.exists("/sys/class/net/%s/bridge" % dev):
|
|
return dev
|
|
|
|
# Old style, peth0 == phys dev, eth0 == netloop, xenbr0 == bridge,
|
|
# vif0.0 == netloop enslaved, eth0 == default route
|
|
try:
|
|
defn = int(dev[-1])
|
|
except Exception:
|
|
defn = -1
|
|
|
|
if (defn >= 0 and
|
|
os.path.exists("/sys/class/net/peth%d/brport" % defn) and
|
|
os.path.exists("/sys/class/net/xenbr%d/bridge" % defn)):
|
|
return "xenbr%d"
|
|
return None
|
|
|
|
|
|
def _default_network(conn):
|
|
ret = _default_bridge(conn)
|
|
if ret:
|
|
return ["bridge", ret]
|
|
|
|
# FIXME: Check that this exists
|
|
return ["network", "default"]
|
|
|
|
|
|
class VirtualPort(XMLBuilder):
|
|
_XML_ROOT_NAME = "virtualport"
|
|
|
|
type = XMLProperty("./@type")
|
|
managerid = XMLProperty("./parameters/@managerid", is_int=True)
|
|
typeid = XMLProperty("./parameters/@typeid", is_int=True)
|
|
typeidversion = XMLProperty("./parameters/@typeidversion", is_int=True)
|
|
instanceid = XMLProperty("./parameters/@instanceid")
|
|
profileid = XMLProperty("./parameters/@profileid")
|
|
interfaceid = XMLProperty("./parameters/@interfaceid")
|
|
|
|
|
|
class VirtualNetworkInterface(VirtualDevice):
|
|
virtual_device_type = VirtualDevice.VIRTUAL_DEV_NET
|
|
|
|
TYPE_BRIDGE = "bridge"
|
|
TYPE_VIRTUAL = "network"
|
|
TYPE_USER = "user"
|
|
TYPE_VHOSTUSER = "vhostuser"
|
|
TYPE_ETHERNET = "ethernet"
|
|
TYPE_DIRECT = "direct"
|
|
network_types = [TYPE_BRIDGE, TYPE_VIRTUAL, TYPE_USER, TYPE_ETHERNET,
|
|
TYPE_DIRECT]
|
|
|
|
@staticmethod
|
|
def get_network_type_desc(net_type):
|
|
"""
|
|
Return human readable description for passed network type
|
|
"""
|
|
desc = net_type.capitalize()
|
|
|
|
if net_type == VirtualNetworkInterface.TYPE_BRIDGE:
|
|
desc = _("Shared physical device")
|
|
elif net_type == VirtualNetworkInterface.TYPE_VIRTUAL:
|
|
desc = _("Virtual networking")
|
|
elif net_type == VirtualNetworkInterface.TYPE_USER:
|
|
desc = _("Usermode networking")
|
|
|
|
return desc
|
|
|
|
@staticmethod
|
|
def generate_mac(conn):
|
|
"""
|
|
Generate a random MAC that doesn't conflict with any VMs on
|
|
the connection.
|
|
"""
|
|
if conn.fake_conn_predictable():
|
|
# Testing hack
|
|
return "00:11:22:33:44:55"
|
|
|
|
for ignore in range(256):
|
|
mac = _random_mac(conn)
|
|
ret = VirtualNetworkInterface.is_conflict_net(conn, mac)
|
|
if ret[1] is None:
|
|
return mac
|
|
|
|
logging.debug("Failed to generate non-conflicting MAC")
|
|
return None
|
|
|
|
@staticmethod
|
|
def is_conflict_net(conn, searchmac):
|
|
"""
|
|
@returns: a two element tuple:
|
|
first element is True if fatal collision occured
|
|
second element is a string description of the collision.
|
|
|
|
Non fatal collisions (mac addr collides with inactive guest) will
|
|
return (False, "description of collision")
|
|
"""
|
|
if searchmac is None:
|
|
return (False, None)
|
|
|
|
vms = conn.fetch_all_guests()
|
|
for vm in vms:
|
|
for nic in vm.get_devices("interface"):
|
|
nicmac = nic.macaddr or ""
|
|
if nicmac.lower() == searchmac.lower():
|
|
return (True, _("The MAC address '%s' is in use "
|
|
"by another virtual machine.") % searchmac)
|
|
return (False, None)
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
VirtualDevice.__init__(self, *args, **kwargs)
|
|
|
|
self._random_mac = None
|
|
self._default_bridge = None
|
|
|
|
|
|
###############
|
|
# XML helpers #
|
|
###############
|
|
|
|
def _generate_default_bridge(self):
|
|
ret = self._default_bridge
|
|
if ret is None:
|
|
ret = False
|
|
default = _default_bridge(self.conn)
|
|
if default:
|
|
ret = default
|
|
|
|
self._default_bridge = ret
|
|
return ret or None
|
|
|
|
def _get_default_bridge(self):
|
|
if self.type == self.TYPE_BRIDGE:
|
|
return self._generate_default_bridge()
|
|
return None
|
|
|
|
def _default_source_mode(self):
|
|
if self.type == self.TYPE_DIRECT:
|
|
return "vepa"
|
|
return None
|
|
|
|
def _get_default_mac(self):
|
|
if not self._random_mac:
|
|
self._random_mac = self.generate_mac(self.conn)
|
|
return self._random_mac
|
|
def _validate_mac(self, val):
|
|
util.validate_macaddr(val)
|
|
return val
|
|
|
|
def _get_source(self):
|
|
"""
|
|
Convenience function, try to return the relevant <source> value
|
|
per the network type.
|
|
"""
|
|
if self.type == self.TYPE_VIRTUAL:
|
|
return self._network
|
|
if self.type == self.TYPE_BRIDGE:
|
|
return self._bridge
|
|
if self.type == self.TYPE_DIRECT:
|
|
return self._source_dev
|
|
if self.type == self.TYPE_USER or self.type == self.TYPE_ETHERNET:
|
|
return None
|
|
return self._network or self._bridge or self._source_dev
|
|
def _set_source(self, newsource):
|
|
"""
|
|
Convenience function, try to set the relevant <source> value
|
|
per the network type
|
|
"""
|
|
self._bridge = None
|
|
self._network = None
|
|
self._source_dev = None
|
|
|
|
if self.type == self.TYPE_VIRTUAL:
|
|
self._network = newsource
|
|
elif self.type == self.TYPE_BRIDGE:
|
|
self._bridge = newsource
|
|
elif self.type == self.TYPE_DIRECT:
|
|
self._source_dev = newsource
|
|
source = property(_get_source, _set_source)
|
|
|
|
|
|
##################
|
|
# XML properties #
|
|
##################
|
|
|
|
_XML_PROP_ORDER = [
|
|
"_bridge", "_network", "_source_dev", "source_type", "source_path",
|
|
"source_mode", "portgroup", "macaddr", "target_dev", "model",
|
|
"virtualport", "filterref", "rom_bar", "rom_file"]
|
|
|
|
_bridge = XMLProperty("./source/@bridge", default_cb=_get_default_bridge)
|
|
_network = XMLProperty("./source/@network")
|
|
_source_dev = XMLProperty("./source/@dev")
|
|
|
|
virtualport = XMLChildProperty(VirtualPort, is_single=True)
|
|
type = XMLProperty("./@type",
|
|
default_cb=lambda s: s.TYPE_BRIDGE)
|
|
trustGuestRxFilters = XMLProperty("./@trustGuestRxFilters", is_yesno=True)
|
|
|
|
macaddr = XMLProperty("./mac/@address",
|
|
set_converter=_validate_mac,
|
|
default_cb=_get_default_mac)
|
|
|
|
source_type = XMLProperty("./source/@type")
|
|
source_path = XMLProperty("./source/@path")
|
|
source_mode = XMLProperty("./source/@mode",
|
|
default_cb=_default_source_mode)
|
|
portgroup = XMLProperty("./source/@portgroup")
|
|
model = XMLProperty("./model/@type")
|
|
target_dev = XMLProperty("./target/@dev")
|
|
filterref = XMLProperty("./filterref/@filter")
|
|
link_state = XMLProperty("./link/@state")
|
|
|
|
driver_name = XMLProperty("./driver/@name")
|
|
driver_queues = XMLProperty("./driver/@queues", is_int=True)
|
|
|
|
rom_bar = XMLProperty("./rom/@bar", is_onoff=True)
|
|
rom_file = XMLProperty("./rom/@file")
|
|
|
|
|
|
#############
|
|
# Build API #
|
|
#############
|
|
|
|
def setup(self, meter=None):
|
|
ignore = meter
|
|
if not self.macaddr:
|
|
return
|
|
|
|
ret, msg = self.is_conflict_net(self.conn, self.macaddr)
|
|
if msg is None:
|
|
return
|
|
if ret is False:
|
|
logging.warning(msg)
|
|
else:
|
|
raise RuntimeError(msg)
|
|
|
|
def set_default_source(self):
|
|
if (self.conn.is_qemu_session() or self.conn.is_test()):
|
|
self.type = self.TYPE_USER
|
|
else:
|
|
self.type, self.source = _default_network(self.conn)
|
|
|
|
|
|
VirtualNetworkInterface.register_type()
|