1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-28 03:08:14 +00:00

Merge pull request #2415 from GNS3/docker-mac-address

Support for custom MAC addresses in Docker containers
This commit is contained in:
Jeremy Grossmann 2024-09-18 04:05:18 -06:00 committed by GitHub
commit bcc148bbd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 90 additions and 9 deletions

View File

@ -33,7 +33,7 @@ from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
from gns3server.utils.asyncio.raw_command_server import AsyncioRawCommandServer from gns3server.utils.asyncio.raw_command_server import AsyncioRawCommandServer
from gns3server.utils.asyncio import wait_for_file_creation from gns3server.utils.asyncio import wait_for_file_creation
from gns3server.utils.asyncio import monitor_process from gns3server.utils.asyncio import monitor_process
from gns3server.utils.get_resource import get_resource from gns3server.utils import macaddress_to_int, int_to_macaddress
from gns3server.ubridge.ubridge_error import UbridgeError, UbridgeNamespaceError from gns3server.ubridge.ubridge_error import UbridgeError, UbridgeNamespaceError
from ..base_node import BaseNode from ..base_node import BaseNode
@ -83,6 +83,7 @@ class DockerVM(BaseNode):
self._environment = environment self._environment = environment
self._cid = None self._cid = None
self._ethernet_adapters = [] self._ethernet_adapters = []
self._mac_address = ""
self._temporary_directory = None self._temporary_directory = None
self._telnet_servers = [] self._telnet_servers = []
self._vnc_process = None self._vnc_process = None
@ -106,6 +107,8 @@ class DockerVM(BaseNode):
else: else:
self.adapters = adapters self.adapters = adapters
self.mac_address = "" # this will generate a MAC address
log.debug("{module}: {name} [{image}] initialized.".format(module=self.manager.module_name, log.debug("{module}: {name} [{image}] initialized.".format(module=self.manager.module_name,
name=self.name, name=self.name,
image=self._image)) image=self._image))
@ -119,6 +122,7 @@ class DockerVM(BaseNode):
"project_id": self._project.id, "project_id": self._project.id,
"image": self._image, "image": self._image,
"adapters": self.adapters, "adapters": self.adapters,
"mac_address": self.mac_address,
"console": self.console, "console": self.console,
"console_type": self.console_type, "console_type": self.console_type,
"console_resolution": self.console_resolution, "console_resolution": self.console_resolution,
@ -149,6 +153,36 @@ class DockerVM(BaseNode):
def ethernet_adapters(self): def ethernet_adapters(self):
return self._ethernet_adapters return self._ethernet_adapters
@property
def mac_address(self):
"""
Returns the MAC address for this Docker container.
:returns: adapter type (string)
"""
return self._mac_address
@mac_address.setter
def mac_address(self, mac_address):
"""
Sets the MAC address for this Docker container.
:param mac_address: MAC address
"""
if not mac_address:
# use the node UUID to generate a random MAC address
self._mac_address = "02:42:%s:%s:%s:00" % (self.id[2:4], self.id[4:6], self.id[6:8])
else:
self._mac_address = mac_address
log.info('Docker container "{name}" [{id}]: MAC address changed to {mac_addr}'.format(
name=self._name,
id=self._id,
mac_addr=self._mac_address)
)
@property @property
def start_command(self): def start_command(self):
return self._start_command return self._start_command
@ -914,15 +948,33 @@ class DockerVM(BaseNode):
bridge_name = 'bridge{}'.format(adapter_number) bridge_name = 'bridge{}'.format(adapter_number)
await self._ubridge_send('bridge create {}'.format(bridge_name)) await self._ubridge_send('bridge create {}'.format(bridge_name))
self._bridges.add(bridge_name) self._bridges.add(bridge_name)
await self._ubridge_send('bridge add_nio_tap bridge{adapter_number} {hostif}'.format(adapter_number=adapter_number, await self._ubridge_send('bridge add_nio_tap bridge{adapter_number} {hostif}'.format(
hostif=adapter.host_ifc)) adapter_number=adapter_number,
hostif=adapter.host_ifc)
)
mac_address = int_to_macaddress(macaddress_to_int(self._mac_address) + adapter_number)
custom_adapter = self._get_custom_adapter_settings(adapter_number)
custom_mac_address = custom_adapter.get("mac_address")
if custom_mac_address:
mac_address = custom_mac_address
try:
await self._ubridge_send('docker set_mac_addr {ifc} {mac}'.format(ifc=adapter.host_ifc, mac=mac_address))
except UbridgeError:
log.warning("Could not set MAC address %s on interface %s", mac_address, adapter.host_ifc)
log.debug("Move container %s adapter %s to namespace %s", self.name, adapter.host_ifc, self._namespace) log.debug("Move container %s adapter %s to namespace %s", self.name, adapter.host_ifc, self._namespace)
try: try:
await self._ubridge_send('docker move_to_ns {ifc} {ns} eth{adapter}'.format(ifc=adapter.host_ifc, await self._ubridge_send('docker move_to_ns {ifc} {ns} eth{adapter}'.format(
ns=self._namespace, ifc=adapter.host_ifc,
adapter=adapter_number)) ns=self._namespace,
adapter=adapter_number)
)
except UbridgeError as e: except UbridgeError as e:
raise UbridgeNamespaceError(e) raise UbridgeNamespaceError(e)
else:
log.info("Created adapter %s with MAC address %s in namespace %s", adapter_number, mac_address, self._namespace)
if nio: if nio:
await self._connect_nio(adapter_number, nio) await self._connect_nio(adapter_number, nio)

View File

@ -26,8 +26,8 @@ import os
from .compute import ComputeConflict, ComputeError from .compute import ComputeConflict, ComputeError
from .ports.port_factory import PortFactory, StandardPortFactory, DynamipsPortFactory from .ports.port_factory import PortFactory, StandardPortFactory, DynamipsPortFactory
from ..utils.images import images_directories from ..utils.images import images_directories
from ..utils import macaddress_to_int, int_to_macaddress
from ..config import Config from ..config import Config
from ..utils.qt import qt_font_to_style
import logging import logging
@ -663,7 +663,13 @@ class Node:
break break
port_name = "eth{}".format(adapter_number) port_name = "eth{}".format(adapter_number)
port_name = custom_adapter_settings.get("port_name", port_name) port_name = custom_adapter_settings.get("port_name", port_name)
self._ports.append(PortFactory(port_name, 0, adapter_number, 0, "ethernet", short_name=port_name)) mac_address = custom_adapter_settings.get("mac_address")
if not mac_address and "mac_address" in self._properties:
mac_address = int_to_macaddress(macaddress_to_int(self._properties["mac_address"]) + adapter_number)
port = PortFactory(port_name, 0, adapter_number, 0, "ethernet", short_name=port_name)
port.mac_address = mac_address
self._ports.append(port)
elif self._node_type in ("ethernet_switch", "ethernet_hub"): elif self._node_type in ("ethernet_switch", "ethernet_hub"):
# Basic node we don't want to have adapter number # Basic node we don't want to have adapter number
port_number = 0 port_number = 0

View File

@ -317,7 +317,7 @@ class DockerHandler:
props = [ props = [
"name", "console", "aux", "console_type", "console_resolution", "name", "console", "aux", "console_type", "console_resolution",
"console_http_port", "console_http_path", "start_command", "console_http_port", "console_http_path", "start_command",
"environment", "adapters", "extra_hosts", "extra_volumes" "environment", "adapters", "mac_address", "custom_adapters", "extra_hosts", "extra_volumes"
] ]
changed = False changed = False

View File

@ -85,6 +85,12 @@ DOCKER_CREATE_SCHEMA = {
"minimum": 0, "minimum": 0,
"maximum": 99, "maximum": 99,
}, },
"mac_address": {
"description": "Docker container base MAC address",
"type": ["string", "null"],
"minLength": 1,
"pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$"
},
"environment": { "environment": {
"description": "Docker environment variables", "description": "Docker environment variables",
"type": ["string", "null"], "type": ["string", "null"],
@ -187,6 +193,12 @@ DOCKER_OBJECT_SCHEMA = {
"minimum": 0, "minimum": 0,
"maximum": 99, "maximum": 99,
}, },
"mac_address": {
"description": "Docker container base MAC address",
"type": ["string", "null"],
"minLength": 1,
"pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$"
},
"usage": { "usage": {
"description": "How to use the Docker container", "description": "How to use the Docker container",
"type": "string", "type": "string",

View File

@ -38,6 +38,15 @@ DOCKER_TEMPLATE_PROPERTIES = {
"maximum": 99, "maximum": 99,
"default": 1 "default": 1
}, },
"mac_address": {
"description": "Docker container base MAC address",
"type": ["string", "null"],
"anyOf": [
{"pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$"},
{"pattern": "^$"}
],
"default": "",
},
"start_command": { "start_command": {
"description": "Docker CMD entry", "description": "Docker CMD entry",
"type": "string", "type": "string",

View File

@ -48,6 +48,7 @@ async def vm(compute_project, manager):
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest")
vm._cid = "e90e34656842" vm._cid = "e90e34656842"
vm.allocate_aux = False vm.allocate_aux = False
vm.mac_address = '02:42:3d:b7:93:00'
return vm return vm
@ -60,6 +61,7 @@ def test_json(vm, compute_project):
'project_id': compute_project.id, 'project_id': compute_project.id,
'node_id': vm.id, 'node_id': vm.id,
'adapters': 1, 'adapters': 1,
'mac_address': '02:42:3d:b7:93:00',
'console': vm.console, 'console': vm.console,
'console_type': 'telnet', 'console_type': 'telnet',
'console_resolution': '1024x768', 'console_resolution': '1024x768',