1#
2# Copyright 2006-2009, 2013 Red Hat, Inc.
3#
4# This work is licensed under the GNU GPLv2 or later.
5# See the COPYING file in the top-level directory.
6
7import os
8import random
9
10from .device import Device
11from ..logger import log
12from ..xmlbuilder import XMLBuilder, XMLChildProperty, XMLProperty
13
14
15def _random_mac(conn):
16    """Generate a random MAC address.
17
18    00-16-3E allocated to xensource
19    52-54-00 used by qemu/kvm
20
21    The OUI list is available at https://standards.ieee.org/regauth/oui/oui.txt.
22
23    The remaining 3 fields are random, with the first bit of the first
24    random field set 0.
25
26    @return: MAC address string
27    """
28
29    if conn.is_qemu():
30        oui = [0x52, 0x54, 0x00]
31    else:
32        # Xen
33        oui = [0x00, 0x16, 0x3E]
34
35    mac = oui + [
36            random.randint(0x00, 0xff),
37            random.randint(0x00, 0xff),
38            random.randint(0x00, 0xff)]
39    return ':'.join(["%02x" % x for x in mac])
40
41
42def _default_route():
43    route_file = "/proc/net/route"
44    if not os.path.exists(route_file):  # pragma: no cover
45        log.debug("route_file=%s does not exist", route_file)
46        return None
47
48    for line in open(route_file):
49        info = line.split()
50        if len(info) != 11:  # pragma: no cover
51            log.debug("Unexpected field count=%s when parsing %s",
52                          len(info), route_file)
53            break
54
55        try:
56            route = int(info[1], 16)
57            if route == 0:
58                return info[0]
59        except ValueError:
60            continue
61
62    return None  # pragma: no cover
63
64
65def _host_default_bridge():
66    dev = _default_route()
67    if not dev:
68        return None  # pragma: no cover
69
70    # New style peth0 == phys dev, eth0 == bridge, eth0 == default route
71    if os.path.exists("/sys/class/net/%s/bridge" % dev):
72        return dev  # pragma: no cover
73
74    # Old style, peth0 == phys dev, eth0 == netloop, xenbr0 == bridge,
75    # vif0.0 == netloop attached, eth0 == default route
76    try:
77        defn = int(dev[-1])
78    except Exception:  # pragma: no cover
79        defn = -1
80
81    if (defn >= 0 and
82        os.path.exists("/sys/class/net/peth%d/brport" % defn) and
83        os.path.exists("/sys/class/net/xenbr%d/bridge" % defn)):
84        return "xenbr%d"  # pragma: no cover
85    return None
86
87
88# Cache the host default bridge lookup. It can change over the lifetime
89# of a virt-manager run, but that should be rare, and this saves us
90# possibly spamming logs if host lookup goes wrong
91_HOST_DEFAULT_BRIDGE = -1
92
93
94def _default_bridge(conn):
95    if conn.is_remote():
96        return None
97
98    global _HOST_DEFAULT_BRIDGE
99    if _HOST_DEFAULT_BRIDGE == -1:
100        try:
101            ret = _host_default_bridge()
102        except Exception:  # pragma: no cover
103            log.debug("Error getting host default bridge", exc_info=True)
104            ret = None
105        _HOST_DEFAULT_BRIDGE = ret
106
107    ret = _HOST_DEFAULT_BRIDGE
108    if conn.in_testsuite():
109        ret = "testsuitebr0"
110    return ret
111
112
113class _VirtualPort(XMLBuilder):
114    XML_NAME = "virtualport"
115
116    type = XMLProperty("./@type")
117    managerid = XMLProperty("./parameters/@managerid", is_int=True)
118    typeid = XMLProperty("./parameters/@typeid", is_int=True)
119    typeidversion = XMLProperty("./parameters/@typeidversion", is_int=True)
120    instanceid = XMLProperty("./parameters/@instanceid")
121    profileid = XMLProperty("./parameters/@profileid")
122    interfaceid = XMLProperty("./parameters/@interfaceid")
123
124
125class DeviceInterface(Device):
126    XML_NAME = "interface"
127
128    TYPE_BRIDGE     = "bridge"
129    TYPE_VIRTUAL    = "network"
130    TYPE_USER       = "user"
131    TYPE_VHOSTUSER  = "vhostuser"
132    TYPE_ETHERNET   = "ethernet"
133    TYPE_DIRECT   = "direct"
134
135    @staticmethod
136    def generate_mac(conn):
137        """
138        Generate a random MAC that doesn't conflict with any VMs on
139        the connection.
140        """
141        if conn.fake_conn_predictable():
142            # Testing hack
143            return "00:11:22:33:44:55"
144
145        for ignore in range(256):
146            mac = _random_mac(conn)
147            try:
148                DeviceInterface.check_mac_in_use(conn, mac)
149                return mac
150            except RuntimeError:  # pragma: no cover
151                continue
152
153        log.debug(  # pragma: no cover
154                "Failed to generate non-conflicting MAC")
155        return None  # pragma: no cover
156
157    @staticmethod
158    def check_mac_in_use(conn, searchmac):
159        """
160        Raise RuntimeError if the passed mac conflicts with a defined VM
161        """
162        if not searchmac:
163            return
164
165        vms = conn.fetch_all_domains()
166        for vm in vms:
167            for nic in vm.devices.interface:
168                nicmac = nic.macaddr or ""
169                if nicmac.lower() == searchmac.lower():
170                    raise RuntimeError(
171                            _("The MAC address '%s' is in use "
172                              "by another virtual machine.") % searchmac)
173
174    @staticmethod
175    def default_bridge(conn):
176        """
177        Return the bridge virt-install would use as a default value,
178        if one is setup on the host
179        """
180        return _default_bridge(conn)
181
182
183    ###############
184    # XML helpers #
185    ###############
186
187    def _get_source(self):
188        """
189        Convenience function, try to return the relevant <source> value
190        per the network type.
191        """
192        if self.type == self.TYPE_VIRTUAL:
193            return self.network
194        if self.type == self.TYPE_BRIDGE:
195            return self.bridge
196        if self.type == self.TYPE_DIRECT:
197            return self.source_dev
198        return None
199    def _set_source(self, newsource):
200        """
201        Convenience function, try to set the relevant <source> value
202        per the network type
203        """
204        self.bridge = None
205        self.network = None
206        self.source_dev = None
207
208        if self.type == self.TYPE_VIRTUAL:
209            self.network = newsource
210        elif self.type == self.TYPE_BRIDGE:
211            self.bridge = newsource
212        elif self.type == self.TYPE_DIRECT:
213            self.source_dev = newsource
214    source = property(_get_source, _set_source)
215
216
217    ##################
218    # XML properties #
219    ##################
220
221    _XML_PROP_ORDER = [
222        "bridge", "network", "source_dev", "source_type", "source_path",
223        "source_mode", "portgroup", "macaddr", "target_dev", "model",
224        "virtualport", "filterref", "rom_bar", "rom_file", "mtu_size"]
225
226    bridge = XMLProperty("./source/@bridge")
227    network = XMLProperty("./source/@network")
228    source_dev = XMLProperty("./source/@dev")
229
230    virtualport = XMLChildProperty(_VirtualPort, is_single=True)
231    type = XMLProperty("./@type")
232    trustGuestRxFilters = XMLProperty("./@trustGuestRxFilters", is_yesno=True)
233
234    macaddr = XMLProperty("./mac/@address")
235
236    source_type = XMLProperty("./source/@type")
237    source_path = XMLProperty("./source/@path")
238    source_mode = XMLProperty("./source/@mode")
239    portgroup = XMLProperty("./source/@portgroup")
240    model = XMLProperty("./model/@type")
241    target_dev = XMLProperty("./target/@dev")
242    filterref = XMLProperty("./filterref/@filter")
243    link_state = XMLProperty("./link/@state")
244
245    driver_name = XMLProperty("./driver/@name")
246    driver_queues = XMLProperty("./driver/@queues", is_int=True)
247
248    rom_bar = XMLProperty("./rom/@bar", is_onoff=True)
249    rom_file = XMLProperty("./rom/@file")
250
251    mtu_size = XMLProperty("./mtu/@size", is_int=True)
252
253
254    #############
255    # Build API #
256    #############
257
258    def set_default_source(self):
259        if self.conn.is_qemu_unprivileged() or self.conn.is_test():
260            self.type = self.TYPE_USER
261            return
262
263        nettype = DeviceInterface.TYPE_BRIDGE
264        source = DeviceInterface.default_bridge(self.conn)
265        if not source:
266            nettype = DeviceInterface.TYPE_VIRTUAL
267            source = "default"
268
269        self.type = nettype
270        self.source = source
271
272
273    ##################
274    # Default config #
275    ##################
276
277    @staticmethod
278    def default_model(guest):
279        if not guest.os.is_hvm():
280            return None
281        if guest.supports_virtionet():
282            return "virtio"
283        if guest.os.is_q35():
284            return "e1000e"
285        if not guest.os.is_x86():
286            return None
287
288        prefs = ["e1000", "rtl8139", "ne2k_pci", "pcnet"]
289        supported_models = guest.osinfo.supported_netmodels()
290        for pref in prefs:
291            if pref in supported_models:
292                return pref
293        return "e1000"
294
295    def set_defaults(self, guest):
296        if not self.type:
297            self.type = self.TYPE_BRIDGE
298        if not self.macaddr:
299            self.macaddr = self.generate_mac(self.conn)
300        if self.type == self.TYPE_BRIDGE and not self.bridge:
301            self.bridge = _default_bridge(self.conn)
302        if not self.model:
303            self.model = self.default_model(guest)
304