Repository: KatharaFramework/Kathara (language: python)

Issue:
Kubernetes startup watch may never terminate if there is a Pod error
The current implementation of the `KubernetesMachine._wait_machines_startup` method continuously loops on watch events from `list_namespaced_pod`. In specific cases, such as critical Pod errors (like CNI errors), no further events are generated.

Consequently, the `for` loop runs forever, causing the program to hang indefinitely.

To resolve this issue, it is necessary to introduce a mechanism that breaks the loop after a defined threshold. Our approach involves utilizing `threading.Timer` to establish a 3-minute timer. This timer will be reset upon receiving each new event. However, if no events occur within the 3-minute interval, the callback will be triggered, signaling an error and terminating the program.


Discussion / hints:

Fix patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index f06a2dff..f9e90b55 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -22,7 +22,7 @@ body:
     attributes:
       label: Kathará Version
       description: "Please provide the Kathará version you are using (`kathara -v`)."
-      placeholder: "3.7.0"
+      placeholder: "3.7.1"
     validations:
       required: true
   - type: textarea
diff --git a/SECURITY.md b/SECURITY.md
index 6d7d1db3..a7440881 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -6,7 +6,7 @@ We release patches for security vulnerabilities only for the last version:
 
 | Version | Supported Versions |
 |---------|--------------------|
-| 3.7.0   | :white_check_mark: |
+| 3.7.1   | :white_check_mark: |
 
 ## Reporting a Vulnerability
 
diff --git a/docs/Makefile b/docs/Makefile
index 34897db2..f2a4b7c3 100755
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -11,7 +11,8 @@ build-%: docker-build-image
 
 docker-build-image:
 	cp ../scripts/Linux-Deb/Docker-Linux-Build/Dockerfile_template ../scripts/Linux-Deb/Docker-Linux-Build/Dockerfile
-	sed -i -e "s|__DISTRO__|focal|g" ../scripts/Linux-Deb/Docker-Linux-Build/Dockerfile
+	sed -i -e "s|__DISTRO__|jammy|g" ../scripts/Linux-Deb/Docker-Linux-Build/Dockerfile
+	sed -i -e "s|__NOKOGIRI__||g"  ../scripts/Linux-Deb/Docker-Linux-Build/Dockerfile
 	cd ../scripts/Linux-Deb/Docker-Linux-Build/ && docker build -t kathara/linux-build-deb .
 	rm -f ../scripts/Linux-Deb/Docker-Linux-Build/Dockerfile
 
diff --git a/docs/footer.txt b/docs/footer.txt
index ebb0ee60..4b6aa8a7 100644
--- a/docs/footer.txt
+++ b/docs/footer.txt
@@ -18,5 +18,5 @@ People involved also include:
 
 ## COPYRIGHT
 
-Copyright © 2017-2021 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
+Copyright © 2017-2024 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
 This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.
diff --git a/docs/kathara-check.1.ronn b/docs/kathara-check.1.ronn
index 4ee627b2..5601e4f1 100644
--- a/docs/kathara-check.1.ronn
+++ b/docs/kathara-check.1.ronn
@@ -23,10 +23,11 @@ Should give an output like this:
 
 	*	Current Manager is: `<your_manager_name>`
 	*	Manager version is: `<your_manager_version>`
-	*	Trying to run `Hello World` container...
-	*	Container run successfully.
 	*	Python version is: `<python_version_used>`
 	*	Kathara version is: `<kathara_version>`
+	*	Operating System version is: `<your_os>`
+	*	Trying to run container with `<your_default_image>` image...
+	*	Container run successfully.
 
 **NOTE:** If you are using the released version, the Python version could be different from the one installed in your system because it is packed into the Kathara binary.
 
diff --git a/docs/kathara-lab.conf.5.ronn b/docs/kathara-lab.conf.5.ronn
index 721af546..015cdf72 100644
--- a/docs/kathara-lab.conf.5.ronn
+++ b/docs/kathara-lab.conf.5.ronn
@@ -8,41 +8,59 @@ The main network scenario configuration file. In this file you can specify the n
 
 This file is a list of `device[arg]=value` assignments, where `arg` can be an integer value or the name of an option (described below).
 
-If `arg` is an integer value, then `value` is the name of the collision domain to which interface eth`arg` of device `device` must be connected (note that the name of the collision domain must not contain spaces (" "), commas (",") and dots ("."). For example, `pc1[0]=CD1` means that interface eth0 of device pc1 will be connected to collision domain CD1.
+In order to establish a uniform convention, comment lines should always start with a hash character (`#`).
 
-If `arg` is an option name, then `device` will be launched with option `arg` set to value `value`.
+## DEVICE INTERFACES
 
-In order to establish a uniform convention, comment lines should always start with a hash character (`#`).
+If `arg` is an integer value, then `value` is the name of the collision domain to which interface eth`arg` of device `device` must be connected. The syntax is as follows:
+
+- `arg`: An integer value representing the interface number (e.g., 0).
+- `CD1`: The name of the collision domain to which the specified interface must be connected. Note that the name of the collision domain must not contain spaces (" "), commas (",") and dots (".").
+- `/MAC_ADDR`: An optional parameter to specify the MAC address of the interface of `device` (MAC address must be in the format `XX:XX:XX:XX:XX:XX`). If `MAC_ADDR` is not provided, Kathara will assign a random one.
+
+### EXAMPLES
+
+1. Connect eth0 of `pc1` to collision domain `CD1` with a random MAC address:
+	```
+	pc1[0]="CD1"
+	```
+
+2. Connect eth1 of `pc1` to collision domain `CD2` with the specified MAC address:
+	```
+	pc1[1]="CD2/02:42:ac:11:00:02"
+	```
 
 ## DEVICE OPTIONS
 
+If `arg` is an option name, then `device` will be launched with option `arg` set to value `value`.
+
 * `image` (string):
-    Docker image used for this device.
+	Docker image used for this device.
 
 * `mem` (string):
-    Set the amount of available RAM inside the device. If you set this option, the minimum allowed value is 4m (4 megabyte).
+	Set the amount of available RAM inside the device. If you set this option, the minimum allowed value is 4m (4 megabyte).
 
-    This option takes a positive integer, followed by a suffix of "b", "k", "m", "g", to indicate bytes, kilobytes, megabytes, or gigabytes.
+	This option takes a positive integer, followed by a suffix of "b", "k", "m", "g", to indicate bytes, kilobytes, megabytes, or gigabytes.
 
 * `cpus` (float):
-    Limit the amount of CPU available for this device.
+	Limit the amount of CPU available for this device.
 
-    This option takes a positive float, ranging from 0 to max number of host logical CPUs. For instance, if the host device has two CPUs and you set `device[cpus]=1.5`, the device is guaranteed at most one and a half of the CPUs.
+	This option takes a positive float, ranging from 0 to max number of host logical CPUs. For instance, if the host device has two CPUs and you set `device[cpus]=1.5`, the device is guaranteed at most one and a half of the CPUs.
 
 * `port` (string):
-    Map localhost port HOST to the internal port GUEST of the device for the specified PROTOCOL. The syntax is [HOST:]GUEST[/PROTOCOL].
+	Map localhost port HOST to the internal port GUEST of the device for the specified PROTOCOL. The syntax is [HOST:]GUEST[/PROTOCOL].
 
-    If HOST port is not specified, default is 3000. If PROTOCOL is not specified, default is `tcp`. Supported PROTOCOL values are: tcp, udp, or sctp.
-    For instance, with this command you can map host's port 8080 to device's port 80 with TCP protocol: `device[port]="8080:80/tcp"`.
+	If HOST port is not specified, default is 3000. If PROTOCOL is not specified, default is `tcp`. Supported PROTOCOL values are: tcp, udp, or sctp.
+	For instance, with this command you can map host's port 8080 to device's port 80 with TCP protocol: `device[port]="8080:80/tcp"`.
 
 * `bridged` (boolean):
-    Connect the device to the host network by adding an additional network interface. This interface will be connected to the host network through a NAT connection.
+	Connect the device to the host network by adding an additional network interface. This interface will be connected to the host network through a NAT connection.
 
 * `ipv6` (boolean):
-    Enable or disable IPv6 on this device.
+	Enable or disable IPv6 on this device.
 
 * `exec` (string):
-    Run a specific shell command inside the device during the startup phase.
+	Run a specific shell command inside the device during the startup phase.
 
 * `sysctl` (string):
 	Set a sysctl option for this device. Only the `net.` namespace is allowed to be set. Can be set multiple times per device, each will add a new entry (unless the same config item is used again).
@@ -51,10 +69,10 @@ In order to establish a uniform convention, comment lines should always start wi
 	Set an environment variable for the device. Can be set multiple times per device, each will add a new entry (unless the same variable is used again). The format is: ENV_NAME=ENV_VALUE.
 
 * `shell` (string):
-    Use the specified shell to connect to the device, e.g., when `kathara connect` is called.
+	Use the specified shell to connect to the device, e.g., when `kathara connect` is called.
 
 * `num_terms` (integer):
-    Choose the number of terminals to open for this device.
+	Choose the number of terminals to open for this device.
 
 ## NETWORK SCENARIO META INFORMATION
 
@@ -69,32 +87,33 @@ It is also possible to provide descriptive information about a network scenario
 
 ## EXAMPLE
 
-        LAB_NAME="Example"
-		LAB_DESCRIPTION="A simple example of lab.conf"
-		LAB_VERSION=1.0
-		LAB_AUTHOR="Kathara Authors"
-		LAB_EMAIL=contact@kathara.org
-		LAB_WEB=http://www.kathara.org/
+Example of a `lab.conf`(5) file.
 
-		r1[0]="A"
-		r1[1]="B"
-		r1[port]="32000"
-		r1[image]="namespace/image_name"
-		r1[sysctl]="net.ipv6.conf.all.forwarding=1"
+	LAB_NAME="Example" 
+	LAB_DESCRIPTION="A simple example of lab.conf"
+	LAB_VERSION=1.0
+	LAB_AUTHOR="Kathara Authors"
+	LAB_EMAIL=contact@kathara.org
+	LAB_WEB=http://www.kathara.org/
 
-		r2[0]="C"
-		r2[1]="B"
-		r2[port]="2000:500/udp"
-		r2[exec]="echo Hi"
+	r1[0]="A"
+	r1[1]="B/02:42:ac:11:00:02" # Specify the MAC address assigned to interface eth1 of r1
+	r1[port]="32000"
+	r1[image]="namespace/image_name"
+	r1[sysctl]="net.ipv6.conf.all.forwarding=1"
 
-		pc1[0]="A"
-		pc1[bridged]="true"
+	r2[0]="C"
+	r2[1]="B"
+	r2[port]="2000:500/udp"
+	r2[exec]="echo Hi"
 
-		pc2[0]="C"
-		pc2[mem]="128m"
-        pc2[shell]="/bin/sh"
+	pc1[0]="A"
+	pc1[bridged]="true"
+
+	pc2[0]="C"
+	pc2[mem]="128m"
+	pc2[shell]="/bin/sh" 
 
-Example of a `lab.conf`(5) file.
 
 m4_include(footer.txt)
 
diff --git a/docs/kathara-lconfig.1.ronn b/docs/kathara-lconfig.1.ronn
index 9e3ec86f..d0d102ec 100644
--- a/docs/kathara-lconfig.1.ronn
+++ b/docs/kathara-lconfig.1.ronn
@@ -4,7 +4,7 @@ kathara-lconfig(1) -- Attach network interfaces to a running Kathara device of a
 
 ## SYNOPSIS
 
-`kathara lconfig` [`-h`] [`-d` <DIRECTORY>] `-n` <DEVICE_NAME> (`--add` <CD> [<CD> ...] \| `--rm` <CD> [<CD> ...])  
+`kathara lconfig` [`-h`] [`-d` <DIRECTORY>] `-n` <DEVICE_NAME> (`--add` <CD[/MAC_ADDR]> [<CD[/MAC_ADDR]> ...] \| `--rm` <CD> [<CD> ...])
 
 ## DESCRIPTION
 
@@ -24,25 +24,34 @@ Manage the network interfaces of a running Kathara device in a network scenario.
 * `-n` <DEVICE_NAME>, `--name` <DEVICE_NAME>:
     Name of the device to configure.
 
-* `--add` <CD> [<CD> ...]:
-	Specify the collision domain to add.
-
-    Equip the device with an additional network interface attached to a (virtual) collision domain whose name is <CD>. The number of the resulting network interface is generated incrementing the number of the last network interface used by the device.
+* `--add` <CD[/MAC_ADDR]> [<CD[/MAC_ADDR]> ...]:
+	Specify the collision domain to be connected to the device:
+    
+    `CD`: The name of the collision domain to which the specified interface must be connected. Note that the name of the collision domain must not contain spaces (" "), commas (",") and dots (".").
+    
+    `/MAC_ADDR`: An optional parameter to specify the MAC address of the interface (MAC address must be in the format `XX:XX:XX:XX:XX:XX`). If `MAC_ADDR` is not provided, Kathara will assign a random one. 
+    
+    Equip the device with an additional network interface attached to a (virtual) collision domain whose name is <CD>. 
+    The number of the resulting network interface is generated incrementing the number of the last network interface used by the device.
 
 * `--rm` <CD> [<CD> ...]:
-	Specify the collision domain to remove.
+	Specify the collision domain to be disconnected from the device.
 
     Disconnect the device from the collision domain whose name is <CD> and remove the corresponding interface.
 
 ## EXAMPLES
 
+Connect `pc1` to collision domain `X` and `Y` (with random MAC addresses):
+    
     kathara lconfig -d path/to/network_scenario -n pc1 --add X Y
 
-Two new interfaces will be added to the device pc1 in the network scenario located in "path/to/network_scenario": the first one will be attached to the collision domain named X, while the other one to the collision domain named Y. Both the interfaces will have to be configured by hand inside the device (for example, by using ifconfig).
-
-    kathara vconfig -n pc1 --rm X
+Connect `pc1` to collision domain `X` with the specified MAC address:
+    
+    kathara lconfig -d path/to/network_scenario -n pc1 --add X/00:00:00:00:00:01
 
-pc1, in the network scenario located in "path/to/network_scenario", will be disconnected from the collision domain named X and the corresponding network interface will be removed.
+Disconnect `pc1` from collision domain `X` and remove the corresponding interface:
+    
+    kathara lconfig -d path/to/network_scenario -n pc1 --rm X
 
 m4_include(footer.txt)
 
diff --git a/docs/kathara-vconfig.1.ronn b/docs/kathara-vconfig.1.ronn
index 3d54987c..142bf2f9 100644
--- a/docs/kathara-vconfig.1.ronn
+++ b/docs/kathara-vconfig.1.ronn
@@ -4,7 +4,7 @@ kathara-vconfig(1) -- Attach network interfaces to a running Kathara device
 
 ## SYNOPSIS
 
-`kathara vconfig` [`-h`] `-n` <DEVICE_NAME> (`--add` <CD> [<CD> ...] \| `--rm` <CD> [<CD> ...])  
+`kathara vconfig` [`-h`] `-n` <DEVICE_NAME> (`--add` <CD[/MAC_ADDR]> [<CD[/MAC_ADDR]> ...] \| `--rm` <CD> [<CD> ...])  
 
 ## DESCRIPTION
 
@@ -18,26 +18,35 @@ Manage the network interfaces of a running Kathara device. The affected device i
 * `-n` <DEVICE_NAME>, `--name` <DEVICE_NAME>:
     Name of the device to manage.
 
-* `--add` <CD> [<CD> ...]:
-	Specify the collision domain to add.
-
-    Equip the device with an additional network interface attached to a (virtual) collision domain whose name is <CD>. The number of the resulting network interface is generated incrementing the number of the last network interface used by the device.
+* `--add` <CD[/MAC_ADDR]> [<CD[/MAC_ADDR]> ...]:
+	Specify the collision domain to be connected to the device:
+    
+    `CD`: The name of the collision domain to which the specified interface must be connected. Note that the name of the collision domain must not contain spaces (" "), commas (",") and dots (".").
+    
+    `/MAC_ADDR`: An optional parameter to specify the MAC address of the interface (MAC address must be in the format `XX:XX:XX:XX:XX:XX`). If `MAC_ADDR` is not provided, Kathara will assign a random one. 
+    
+    Equip the device with an additional network interface attached to a (virtual) collision domain whose name is <CD>. 
+    The number of the resulting network interface is generated incrementing the number of the last network interface used by the device.
 
 * `--rm` <CD> [<CD> ...]:
-	Specify the collision domain to remove.
+	Specify the collision domain to be disconnected from the device.
 
     Disconnect the device from the collision domain whose name is <CD> and remove the corresponding interface.
 
 ## EXAMPLES
 
+Connect `pc1` to collision domain `X` and `Y` (with random MAC addresses):
+    
     kathara vconfig -n pc1 --add X Y
 
-Two new interfaces will be added to the device pc1: the first one will be attached to the collision domain named X, while the other one to the collision domain named Y. Both the interfaces will have to be configured by hand inside the device (for example, by using ifconfig).
+Connect `pc1` to collision domain `X` with the specified MAC address:
+    
+    kathara vconfig -n pc1 --add X/00:00:00:00:00:01
 
+Disconnect `pc1` from collision domain `X` and remove the corresponding interface:
+    
     kathara vconfig -n pc1 --rm X
 
-pc1 will be disconnected from the collision domain named X and the corresponding network interface will be removed.
-
 m4_include(footer.txt)
 
 ## SEE ALSO
diff --git a/docs/kathara-vstart.1.ronn b/docs/kathara-vstart.1.ronn
index ed5566c1..7ac4a0e9 100644
--- a/docs/kathara-vstart.1.ronn
+++ b/docs/kathara-vstart.1.ronn
@@ -6,7 +6,7 @@ kathara-vstart(1) -- Start a new Kathara device
 
 `kathara vstart` `-n` <DEVICE_NAME>  
 [`-h`] [`--noterminals` | `--terminals` | `--privileged` | `--num_terms` <NUM_TERMS>]  
-[`--eth` <N\:CD> [<N\:CD> ...]]  
+[`--eth` <N:CD[/MAC_ADDR]> [<N:CD[/MAC_ADDR]> ...]]  
 [`-e` [<EXEC_COMMANDS> [<EXEC_COMMANDS> ...]]] [`--mem` <MEM>]  
 [`--cpus` <CPUS>] [`-i` <IMAGE>] [`--no-hosthome` \| `--hosthome`]
 [`--xterm` <XTERM>] [`--print`] [`--bridged`]  
@@ -42,9 +42,13 @@ Notice: unless differently stated, command line arguments (DEVICE_NAME) and opti
 * `-n` <DEVICE_NAME>, `--name` <DEVICE_NAME>:
     Name of the device to be started.
 
-* `--eth` <N\:CD> [<N\:CD> ...]:
+* `--eth` <N:CD[/MAC_ADDR]> [<N:CD[/MAC_ADDR]> ...]:
     Set a specific interface on a collision domain.
-
+    
+    `CD`: The name of the collision domain to which the specified interface must be connected. Note that the name of the collision domain must not contain spaces (" "), commas (",") and dots (".").
+    
+    `/MAC_ADDR`: An optional parameter to specify the MAC address of the interface (MAC address must be in the format `XX:XX:XX:XX:XX:XX`). If `MAC_ADDR` is not provided, Kathara will assign a random one. 
+    
     Equip the device with a network interface. `N` is a positive integer starting from 0.
     The network interface will be attached to a (virtual) collision domain whose name is `CD`. Attaching interfaces of different devices to the same collision domain allows them to exchange network traffic. <N> should be declared as a sequential number, starting from 0; if any intermediate number is missing an exception is raised.
 
diff --git a/docs/kathara.conf.5.ronn b/docs/kathara.conf.5.ronn
index 6bb7eb44..1f5b0140 100644
--- a/docs/kathara.conf.5.ronn
+++ b/docs/kathara.conf.5.ronn
@@ -97,10 +97,10 @@ Each Manager specifies additional parameters which are used only when the Manage
 
     Default to `Prompt`.
 
-* `shared_cd` (boolean):
-    This parameter allows to connect devices of different users to the same collision domains.
+* `shared_cds` (integer):
+    This parameter allows to connect devices of different network scenarios and users to the same collision domains.
 
-    Default to `false`.
+    Default to `1` (enum value for `Not Shared`).
 
 * `remote_url` (string):
     This parameter specifies a Remote Docker daemon URL to connect to, instead of a local one.
@@ -154,7 +154,7 @@ Each Manager specifies additional parameters which are used only when the Manage
             "hosthome_mount": false,
             "shared_mount": true,
             "image_update_policy": "Prompt",
-            "shared_cd": false,
+            "shared_cds": 1,
             "remote_url": null,
             "cert_path": null,
             "network_plugin": "kathara/katharanp_vde"
diff --git a/pyproject.toml b/pyproject.toml
index d488af12..6d722cb3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,19 +1,19 @@
 [project]
 name = "kathara"
-version = "3.7.0"
-description = "A lightweight container-based emulation system."
+version = "3.7.1"
+description = "A lightweight container-based network emulation tool."
 readme = "README.md"
 requires-python = ">=3.9"
 license = { file = "LICENSE" }
 keywords = ["NETWORK-EMULATION", "CONTAINERS", "NFV"]
 authors = [
-    { name = "Kathara Framework", email = "contact@kathara.org" } # Optional
+    { name = "Kathara Framework", email = "contact@kathara.org" }
 ]
 
 maintainers = [
-    { name = "Tommaso Caiazzi", email = "contact@kathara.org" }, # Optional
-    { name = "Mariano Scazzariello", email = "contact@kathara.org" }, # Optional
-    { name = "Lorenzo Ariemma", email = "contact@kathara.org" }, # Optional
+    { name = "Tommaso Caiazzi", email = "contact@kathara.org" },
+    { name = "Mariano Scazzariello", email = "contact@kathara.org" },
+    { name = "Lorenzo Ariemma", email = "contact@kathara.org" },
 ]
 
 classifiers = [
@@ -30,7 +30,7 @@ classifiers = [
 
 dependencies = [
     "binaryornot>=0.4.4",
-    "docker>=6.0.1",
+    "docker>=7.0.0",
     "kubernetes>=23.3.0",
     "requests>=2.22.0",
     "coloredlogs>=10.0",
diff --git a/scripts/Linux-Deb/Docker-Linux-Build/Dockerfile_template b/scripts/Linux-Deb/Docker-Linux-Build/Dockerfile_template
index b553e81f..31d0e3ce 100644
--- a/scripts/Linux-Deb/Docker-Linux-Build/Dockerfile_template
+++ b/scripts/Linux-Deb/Docker-Linux-Build/Dockerfile_template
@@ -9,31 +9,46 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
 
 ARG DEBIAN_FRONTEND="noninteractive"
 RUN apt update && \
-    apt upgrade -y && \
+	apt upgrade -y && \
 	apt install -y \
-		apt-utils \
-		debhelper \
-		libxml2-dev \
-		zlib1g-dev \
-		build-essential \
-		lintian \
-		devscripts \
-		git \
-		ruby-full \
-		bash-completion \
-		software-properties-common \
-        patchelf
-
-		
+	apt-utils \
+	bash-completion \
+	build-essential \
+	debhelper \
+	devscripts \
+	git \
+	libxml2-dev \
+	libffi-dev \
+	lintian \
+	patchelf \
+	software-properties-common \
+	zlib1g-dev
+
+RUN apt install -y \
+    curl \
+    autoconf \
+    bison \
+    libssl-dev  \
+    libyaml-dev  \
+    libreadline6-dev  \
+    libncurses5-dev \
+    libgdbm6 \
+    libgdbm-dev \
+    libdb-dev && \
+    curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash && \
+    /root/.rbenv/bin/rbenv install 3.3.0 && \
+    /root/.rbenv/bin/rbenv global 3.3.0
+
+ENV PATH="/root/.rbenv/shims:/root/.rbenv/bin:$PATH"
+
 RUN add-apt-repository ppa:deadsnakes/ppa \
 	&& DEBIAN_FRONTEND=noninteractive apt install -y \
-		python3.10 \
-		python3.10-dev \
-		python3.10-venv \
-		python3-pip \
-		python3.10-distutils
+	python3.11 \
+	python3.11-dev \
+	python3.11-venv \
+	python3-pip \
+	python3.11-distutils
 
-__NOKOGIRI__
 RUN gem install ronn-ng
 RUN mkdir /root/.gnupg && chmod 700 /root/.gnupg
 
diff --git a/scripts/Linux-Deb/Makefile b/scripts/Linux-Deb/Makefile
index 1959de7f..2ef93ca9 100644
--- a/scripts/Linux-Deb/Makefile
+++ b/scripts/Linux-Deb/Makefile
@@ -1,6 +1,6 @@
 #!/usr/bin/make -f
 
-VERSION=3.7.0
+VERSION=3.7.1
 DEBIAN_PACKAGE_VERSION=1
 LAUNCHPAD_NAME=user
 NO_BINARY_PACKAGES=pyroute2|pyuv|deepdiff
@@ -19,11 +19,6 @@ docker-signed_%: docker-build-image_%
 
 docker-build-image_%:
 	cd Docker-Linux-Build && cp Dockerfile_template Dockerfile
-	if [ "$*" = "bionic" ]; then \
-		cd Docker-Linux-Build && sed -i -e "s|__NOKOGIRI__|RUN gem install nokogiri -v 1.12.5|g" Dockerfile; \
-	else \
-		cd Docker-Linux-Build && sed -i -e "s|__NOKOGIRI__||g" Dockerfile; \
-	fi;
 	cd Docker-Linux-Build && sed -i -e "s|__DISTRO__|$*|g" Dockerfile
 	cd Docker-Linux-Build && docker build -t kathara/linux-build-deb:$* .
 	cd Docker-Linux-Build && rm Dockerfile
@@ -51,7 +46,7 @@ copy-debian-folder:
 	sed -i -e 's/__VERSION__/$(VERSION)/g' Output/kathara-$(VERSION)/debian/changelog
 	
 venv:
-	python3.10 -m venv /root/py-build-env
+	python3.11 -m venv /root/py-build-env
 
 download-pip: copy-debian-folder
 	mkdir Output/kathara-$(VERSION)/debian/pythonLibs_amd64
@@ -61,14 +56,18 @@ download-pip: copy-debian-folder
 	/root/py-build-env/bin/pip install --upgrade setuptools
 	cd Output/kathara-$(VERSION)/debian/pythonLibs_amd64 && /root/py-build-env/bin/pip download --platform manylinux2014_x86_64 --only-binary=:all: flit_core
 	cd Output/kathara-$(VERSION)/debian/pythonLibs_amd64 && /root/py-build-env/bin/pip download --platform manylinux2014_x86_64 --only-binary=:all: wheel
+	cd Output/kathara-$(VERSION)/debian/pythonLibs_amd64 && /root/py-build-env/bin/pip download --platform manylinux2014_x86_64 --only-binary=:all: zstandard
 	cd Output/kathara-$(VERSION)/debian/pythonLibs_amd64 && /root/py-build-env/bin/pip download --no-binary=:all: -r ../../requirements-no-binary.txt
 	cd Output/kathara-$(VERSION)/debian/pythonLibs_amd64 && /root/py-build-env/bin/pip download --platform manylinux2014_x86_64 --platform x86_64 --only-binary=:all: -r ../../requirements-binary.txt
 	cd Output/kathara-$(VERSION)/debian/pythonLibs_amd64 && /root/py-build-env/bin/pip download --no-binary=:all: nuitka
 	cd Output/kathara-$(VERSION)/debian/pythonLibs_arm64 && /root/py-build-env/bin/pip download --platform manylinux2014_aarch64 --only-binary=:all: flit_core
 	cd Output/kathara-$(VERSION)/debian/pythonLibs_arm64 && /root/py-build-env/bin/pip download --platform manylinux2014_aarch64 --only-binary=:all: wheel
+	cd Output/kathara-$(VERSION)/debian/pythonLibs_arm64 && /root/py-build-env/bin/pip download --platform manylinux2014_aarch64 --only-binary=:all: zstandard
 	cd Output/kathara-$(VERSION)/debian/pythonLibs_arm64 && /root/py-build-env/bin/pip download --no-binary=:all: -r ../../requirements-no-binary.txt
 	cd Output/kathara-$(VERSION)/debian/pythonLibs_arm64 && /root/py-build-env/bin/pip download --platform manylinux2014_aarch64 --platform arm64 --only-binary=:all: -r ../../requirements-binary.txt
 	cd Output/kathara-$(VERSION)/debian/pythonLibs_arm64 && /root/py-build-env/bin/pip download --no-binary=:all: nuitka
+	rm Output/kathara-$(VERSION)/debian/pythonLibs_arm64/zstandard*.tar.gz
+	rm Output/kathara-$(VERSION)/debian/pythonLibs_amd64/zstandard*.tar.gz
 	rm -Rf Output/kathara-$(VERSION)/requirements-binary.txt Output/kathara-$(VERSION)/requirements-no-binary.txt
 	ls Output/kathara-$(VERSION)/debian/pythonLibs_amd64/ | awk '{print "debian/pythonLibs_amd64/"$$0}' > Output/kathara-$(VERSION)/debian/source/include-binaries
 	ls Output/kathara-$(VERSION)/debian/pythonLibs_arm64/ | awk '{print "debian/pythonLibs_arm64/"$$0}' >> Output/kathara-$(VERSION)/debian/source/include-binaries
diff --git a/scripts/Linux-Deb/debian/changelog b/scripts/Linux-Deb/debian/changelog
index 5be19092..0d1a5cf6 100644
--- a/scripts/Linux-Deb/debian/changelog
+++ b/scripts/Linux-Deb/debian/changelog
@@ -1,7 +1,9 @@
 kathara (__VERSION__-__DEBIAN_PACKAGE_VERSION____UBUNTU_VERSION__) __UBUNTU_VERSION__; urgency=low
 
-    * Add support for the new Kathara Network Plugin based on VDE software switches. It is possible to select the old Network Plugin (based on Linux Bridges) from "kathara settings"
-    * Switch the default image to "kathara/base" for new installations
-    * Fix Docker images fetching in "kathara settings"
+    * It is now possible to specify a MAC Address for a network interface
+    * (Docker) Collision domains are now created per-network-scenario by default
+    * (Docker) If a ".shutdown" file is present in the network scenario, Kathara now correctly waits for the script termination before removing the container
+    * Several fixes of "lconfig" and "vconfig" commands
+    * Minor fixes and improvements
 
  -- Kathara Team <contact@kathara.org>  __DATE__
diff --git a/scripts/Linux-Deb/debian/control b/scripts/Linux-Deb/debian/control
index 63ee2ab6..eced2333 100644
--- a/scripts/Linux-Deb/debian/control
+++ b/scripts/Linux-Deb/debian/control
@@ -4,11 +4,11 @@ Section: misc
 Priority: optional
 Standards-Version: 3.9.8
 Build-Depends: debhelper (>= 10),
-	python3.10 (>= 3.10.0),
+	python3.11 (>= 3.11.0),
 	python3-pip (>= 9.0.0),
-	python3.10-dev (>= 3.10.0),
-	python3.10-venv (>= 3.10.0),
-	python3.10-distutils (>= 3.10.0) | python3-distutils (>= 3.8.0),
+	python3.11-dev (>= 3.11.0),
+	python3.11-venv (>= 3.11.0),
+	python3.11-distutils (>= 3.11.0) | python3-distutils (>= 3.8.0),
 	patchelf,
 	bash-completion,
 	zlib1g-dev
@@ -20,7 +20,7 @@ Package: kathara
 Architecture: any
 Depends: ${shlibs:Depends}, ${misc:Depends}
 Suggests: docker.io (>= 17.03.2) | docker-ce (>= 17.03.2), xterm
-Description: Lightweight network emulation tool.
+Description: A lightweight container-based network emulation tool.
 	It can be really helpful in showing interactive demos/lessons,
 	testing production networks in a sandbox environment, or developing new network protocols.
 
diff --git a/scripts/Linux-Deb/debian/rules b/scripts/Linux-Deb/debian/rules
index 85c072fc..645ad796 100755
--- a/scripts/Linux-Deb/debian/rules
+++ b/scripts/Linux-Deb/debian/rules
@@ -11,11 +11,11 @@ override_dh_auto_clean:
 	find . | grep -E "(__pycache__|\.pyc|\.pyo)$$" | xargs rm -rf
 
 override_dh_auto_configure:
-	python3.10 -m venv $(CURDIR)/venv
-	$(CURDIR)/venv/bin/pip3.10 install --no-index --find-links $(CURDIR)/debian/pythonLibs_$(ARCHITECTURE)/ $(CURDIR)/debian/pythonLibs_$(ARCHITECTURE)/*
+	python3.11 -m venv $(CURDIR)/venv
+	$(CURDIR)/venv/bin/pip3.11 install --no-index --find-links $(CURDIR)/debian/pythonLibs_$(ARCHITECTURE)/ $(CURDIR)/debian/pythonLibs_$(ARCHITECTURE)/*
 
 override_dh_auto_build:
-	$(CURDIR)/venv/bin/python3.10 -m nuitka --lto=no --show-progress --plugin-enable=pylint-warnings \
+	$(CURDIR)/venv/bin/python3.11 -m nuitka --lto=no --show-progress --plugin-enable=pylint-warnings \
 		--plugin-enable=multiprocessing --follow-imports --standalone --include-plugin-directory=Kathara --output-filename=kathara kathara.py
 
 override_dh_auto_install:
diff --git a/scripts/Linux-Pkg/Docker-Linux-Build/Dockerfile b/scripts/Linux-Pkg/Docker-Linux-Build/Dockerfile
index 66ef8778..d1669049 100644
--- a/scripts/Linux-Pkg/Docker-Linux-Build/Dockerfile
+++ b/scripts/Linux-Pkg/Docker-Linux-Build/Dockerfile
@@ -10,12 +10,8 @@ RUN pacman -S --noconfirm git \
 			  			  base-devel \
 			  			  chrpath \
                           patchelf \
-    					  ruby-ronn-ng
-                          
-RUN pacman -S --noconfirm wget && \
-    wget https://archive.archlinux.org/packages/p/python/python-3.10.10-1-x86_64.pkg.tar.zst && \
-    pacman -U --noconfirm python-3.10.10-1-x86_64.pkg.tar.zst && \
-    rm -f python-3.10.10-1-x86_64.pkg.tar.zst
+    					  ruby-ronn-ng \
+                          python
                           
 RUN useradd -m -s /bin/bash builduser
 
diff --git a/scripts/Linux-Pkg/Makefile b/scripts/Linux-Pkg/Makefile
index 65ce93b2..3e5a2a01 100644
--- a/scripts/Linux-Pkg/Makefile
+++ b/scripts/Linux-Pkg/Makefile
@@ -1,6 +1,6 @@
 #!/usr/bin/make -f
 
-VERSION=3.7.0
+VERSION=3.7.1
 PACKAGE_VERSION=1
 AUR_NAME=user
 AUR_MAIL=contact@kathara.org
@@ -34,13 +34,11 @@ build-local: prepare-files
 	mkdir -p /home/builduser/build/src/
 	cp -r /home/builduser/kathara-source/Kathara-$(VERSION) /home/builduser/build/src
 	tar -zcf /home/builduser/build/kathara-$(VERSION).tar.gz /home/builduser/build/src/Kathara-$(VERSION)
-	sed -i -e 's/__PYTHON_DEP__//g' /home/builduser/build/PKGBUILD
 	sed -i -e 's/__SOURCE__/"kathara-$(VERSION).tar.gz"/g' /home/builduser/build/PKGBUILD
 	makepkg
 	mv kathara*.pkg.tar.zst /home/builduser/output/
 
 build-remote: prepare-files
-	sed -i -e "s/__PYTHON_DEP__/'python310'/g" /home/builduser/build/PKGBUILD
 	sed -i -e 's/__SOURCE__/"https:\/\/github.com\/KatharaFramework\/Kathara\/archive\/refs\/tags\/\$$\pkgver.tar.gz"/g' /home/builduser/build/PKGBUILD
 	cp -r /home/builduser/ssh/ /home/builduser/.ssh/ && chmod 600 /home/builduser/.ssh/id_rsa
 	ssh-keyscan aur.archlinux.org > /home/builduser/.ssh/known_hosts
diff --git a/scripts/Linux-Pkg/pkginfo/PKGBUILD b/scripts/Linux-Pkg/pkginfo/PKGBUILD
index 926029f3..e4b47111 100644
--- a/scripts/Linux-Pkg/pkginfo/PKGBUILD
+++ b/scripts/Linux-Pkg/pkginfo/PKGBUILD
@@ -1,14 +1,14 @@
 pkgname=kathara
 pkgver=__VERSION__
 pkgrel=__PACKAGE_VERSION__
-pkgdesc="Lightweight network emulation system based on Docker containers."
+pkgdesc="A lightweight container-based network emulation tool."
 arch=('any')
 url="https://www.kathara.org/"
 license=('GPL3')
 install="kathara.install"
 changelog="kathara.changelog"
 makedepends=(
-	__PYTHON_DEP__
+	'python'
     'chrpath'
     'patchelf'
     'make'
@@ -17,7 +17,7 @@ makedepends=(
 optdepends=(
     'docker: for running network scenarios in a local environment'
     'xterm: for opening devices terminals'
-    'tmux: for opening many devices terminals'
+    'tmux: for devices terminals multiplexing'
 )
 source=(__SOURCE__)
 md5sums=('SKIP')
diff --git a/scripts/Linux-Pkg/pkginfo/kathara.changelog b/scripts/Linux-Pkg/pkginfo/kathara.changelog
index 4bde257a..587afeb0 100644
--- a/scripts/Linux-Pkg/pkginfo/kathara.changelog
+++ b/scripts/Linux-Pkg/pkginfo/kathara.changelog
@@ -1,6 +1,8 @@
 __DATE__ Kathara Team <******@kathara.org>
 
 * Release v__VERSION__
-    * Add support for the new Kathara Network Plugin based on VDE software switches. It is possible to select the old Network Plugin (based on Linux Bridges) from "kathara settings"
-    * Switch the default image to "kathara/base" for new installations
-    * Fix Docker images fetching in "kathara settings"
\ No newline at end of file
+    * It is now possible to specify a MAC Address for a network interface
+    * (Docker) Collision domains are now created per-network-scenario by default
+    * (Docker) If a ".shutdown" file is present in the network scenario, Kathara now correctly waits for the script termination before removing the container
+    * Several fixes of "lconfig" and "vconfig" commands
+    * Minor fixes and improvements
\ No newline at end of file
diff --git a/scripts/Linux-Rpm/Docker-Linux-Build/Dockerfile b/scripts/Linux-Rpm/Docker-Linux-Build/Dockerfile
index 20534255..3fae18c5 100644
--- a/scripts/Linux-Rpm/Docker-Linux-Build/Dockerfile
+++ b/scripts/Linux-Rpm/Docker-Linux-Build/Dockerfile
@@ -14,8 +14,8 @@ RUN dnf install -y rubygem-rails \
     rpmdevtools \
     patchelf \
     pip \
-    python3.10 \
-    python3.10-devel
+    python3.11 \
+    python3.11-devel
 
 RUN gem install ronn-ng
 
diff --git a/scripts/Linux-Rpm/Makefile b/scripts/Linux-Rpm/Makefile
index aeac70da..00690889 100644
--- a/scripts/Linux-Rpm/Makefile
+++ b/scripts/Linux-Rpm/Makefile
@@ -1,6 +1,6 @@
 #!/usr/bin/make -f
 
-VERSION=3.7.0
+VERSION=3.7.1
 PACKAGE_VERSION=1
 
 .PHONY: all clean docker-build-image prepare-source prepare-man-pages prepare-bash-completion pack-source build
diff --git a/scripts/Linux-Rpm/rpm/kathara.spec b/scripts/Linux-Rpm/rpm/kathara.spec
index 7262fd7a..31a7678c 100644
--- a/scripts/Linux-Rpm/rpm/kathara.spec
+++ b/scripts/Linux-Rpm/rpm/kathara.spec
@@ -8,7 +8,7 @@ URL:		    https://www.kathara.org/
 Source:		    %{name}-%{version}.tar.gz
 
 %description
-Lightweight network emulation system based on Docker containers.
+A lightweight container-based network emulation tool.
 
 It can be really helpful in showing interactive demos/lessons,
 testing production networks in a sandbox environment, or developing
@@ -18,15 +18,15 @@ new network protocols.
 
 %prep
 %autosetup
-python3.10 -m venv %{_builddir}/venv
+python3.11 -m venv %{_builddir}/venv
 %{_builddir}/venv/bin/pip install --upgrade pip
 %{_builddir}/venv/bin/pip install -r src/requirements.txt
-%{_builddir}/venv/bin/pip install nuitka==1.7.10
+%{_builddir}/venv/bin/pip install nuitka
 %{_builddir}/venv/bin/pip install pytest
 
 %build
 %{_builddir}/venv/bin/python -m pytest
-cd src && %{_builddir}/venv/bin/python -m nuitka --lto=no --plugin-enable=pylint-warnings --plugin-enable=multiprocessing --follow-imports --standalone --include-plugin-directory=Kathara --output-filename=kathara kathara.py
+cd src && %{_builddir}/venv/bin/python -m nuitka --lto=yes --plugin-enable=pylint-warnings --plugin-enable=multiprocessing --follow-imports --standalone --include-plugin-directory=Kathara --output-filename=kathara kathara.py
 
 %install
 mv %{_builddir}/%{buildsubdir}/src/kathara.dist %{_builddir}/%{buildsubdir}/kathara.dist
@@ -68,6 +68,8 @@ chmod g+s %{_libdir}/kathara/kathara
 
 %changelog
 *  __DATE__ Kathara Team <******@kathara.org> - __VERSION__-__PACKAGE_VERSION__
-- Add support for the new Kathara Network Plugin based on VDE software switches. It is possible to select the old Network Plugin (based on Linux Bridges) from "kathara settings"
-- Switch the default image to "kathara/base" for new installations
-- Fix Docker images fetching in "kathara settings"
\ No newline at end of file
+- It is now possible to specify a MAC Address for a network interface
+- (Docker) Collision domains are now created per-network-scenario by default
+- (Docker) If a ".shutdown" file is present in the network scenario, Kathara now correctly waits for the script termination before removing the container
+- Several fixes of "lconfig" and "vconfig" commands
+- Minor fixes and improvements
\ No newline at end of file
diff --git a/scripts/OSX/Makefile b/scripts/OSX/Makefile
index 2f363ffe..ce5c0f38 100644
--- a/scripts/OSX/Makefile
+++ b/scripts/OSX/Makefile
@@ -1,7 +1,7 @@
 #!/usr/bin/make -s
 
 PRODUCT=Kathara
-VERSION=3.7.0
+VERSION=3.7.1
 TARGET_DIRECTORY=Output
 APPLE_DEVELOPER_CERTIFICATE_ID=FakeID
 ROFF_DIR=../../docs/Roff
@@ -18,13 +18,13 @@ allSigned_arm64: deps binary_arm64 manpages createInstaller_arm64 signProduct_ar
 default: all
 
 py_env:
-	python3.10 -m venv $(VENV_DIR)
+	python3.11 -m venv $(VENV_DIR)
 
 deps: py_env
 	$(VENV_DIR)/bin/pip install pyinstaller
 	$(VENV_DIR)/bin/pip install -r ../../src/requirements.txt
 	$(VENV_DIR)/bin/pip install pytest
-	gem install ronn-ng --user-install
+	$$(brew --prefix ruby)/bin/gem install ronn-ng --user-install
 
 manpages:
 	cd ../../docs && make roff-build;
diff --git a/scripts/OSX/README.md b/scripts/OSX/README.md
index 4979d3d4..3c2d300f 100644
--- a/scripts/OSX/README.md
+++ b/scripts/OSX/README.md
@@ -3,15 +3,16 @@
 1. Install `pkgbuild`, `productbuild` and `make` from Apple developer repository
     - If you have `XCode` installed you probably already have them installed
     - Otherwise, download them from [here](https://developer.apple.com/devcenter/mac/index.action)
-    - **NOTE:** You need Python 3.10 in order to compile the binary ([Quick link](https://www.python.org/downloads/release/python-3107/))
-2. Change the Kathara version number in both `src/Kathara/version.py` and `Makefile` files.
-3. Run `make all_x86` or `make all_arm64` to automatically compile and create the package for the desired architecture.
+    - **NOTE:** You need Python 3.11 in order to compile the binary ([Quick link](https://www.python.org/downloads/release/python-3117/))
+2. Install Ruby from Homebrew: `brew install ruby` 
+3. Change the Kathara version number in both `src/Kathara/version.py` and `Makefile` files.
+4. Run `make all_x86` or `make all_arm64` to automatically compile and create the package for the desired architecture.
 You can compile the `x86` package on a Mac with Intel CPU, and the `arm64` on a Mac with Apple CPU.
     - Run `make deps` to automatically download and install dependencies for your architecture
     - Run `make binary_x86_64` or `make binary_arm64` to automatically compile the package
     - Run `make createInstaller_x86_64` or `make createInstaller_arm64` to create package
     - Run `make clean` to clean all intermediate files
-4. Share the Kathara pkg in `Output` folder :)
+5. Share the Kathara pkg in `Output` folder :)
 
 # Compiling Kathara for Mac OSX (Signed)
 
@@ -19,13 +20,14 @@ You can compile the `x86` package on a Mac with Intel CPU, and the `arm64` on a
 2. Install `pkgbuild`, `productbuild` and `make` from Apple developer repository
     - If you have `XCode` installed you probably already have them installed
     - Otherwise, download them from [here](https://developer.apple.com/devcenter/mac/index.action)
-    - **NOTE:** You need Python 3.10 in order to compile the binary ([Quick link](https://www.python.org/downloads/release/python-3107/))
-3. Change the Kathara version number in both `src/Kathara/version.py` and `Makefile` files.
-4. Run `make allSigned_x86` or `make allSigned_arm64` to automatically compile and create the package for the desired architecture. 
+    - **NOTE:** You need Python 3.11 in order to compile the binary ([Quick link](https://www.python.org/downloads/release/python-3117/))
+3. Install Ruby from Homebrew: `brew install ruby` 
+4. Change the Kathara version number in both `src/Kathara/version.py` and `Makefile` files.
+5. Run `make allSigned_x86` or `make allSigned_arm64` to automatically compile and create the package for the desired architecture. 
 You can compile the `x86` package on a Mac with Intel CPU, and the `arm64` on a Mac with Apple CPU.
     - Run `make deps` to automatically download and install dependencies for your architecture
     - Run `make binary_x86_64` or `make binary_arm64` to automatically compile the package
     - Run `make createInstaller_x86_64` or `make createInstaller_arm64` to create package
     - Run `make signProduct_x86_64` or `make signProduct_arm64` to sign the package
     - Run `make clean` to clean all intermediate files
-5. Share the Kathara pkg signed in `Output` folder :)
\ No newline at end of file
+6. Share the Kathara pkg signed in `Output` folder :)
\ No newline at end of file
diff --git a/scripts/Windows/README.md b/scripts/Windows/README.md
index e5520fbe..44e1d533 100644
--- a/scripts/Windows/README.md
+++ b/scripts/Windows/README.md
@@ -1,10 +1,8 @@
 # Compiling Kathara for Windows
 
 1. Download and Install Inno Setup ([Quick link](http://www.jrsoftware.org/download.php/is.exe))
-2. Download and Install Python 3.10 ([Quick link](https://www.python.org/downloads/release/python-3107/))
-3. Install pyinstaller `python -m pip install pyinstaller` (from an Administrator cmd or Powershell)
-3. Install Kathara python dependencies `python -m pip install -r ..\..\src\requirements.txt` (from an Administrator cmd or Powershell)
-4. Change the Kathara version number in both `src/Kathara/version.py` and `installer.iss` files.
-5. Create binary package running `WindowsBuild.bat`
-6. Compile Inno Script Setup file
-7. Share the `Kathara-setup.exe` in output folder :)
\ No newline at end of file
+2. Download and Install Python 3.11 ([Quick link](https://www.python.org/downloads/release/python-3117/))
+3. Change the Kathara version number in both `src/Kathara/version.py` and `installer.iss` files.
+4. Create binary package running `WindowsBuild.bat`
+5. Compile Inno Script Setup file
+6. Share the `Kathara-setup.exe` in output folder :)
\ No newline at end of file
diff --git a/scripts/Windows/WindowsBuild.bat b/scripts/Windows/WindowsBuild.bat
index c249b1cb..5aaea2cd 100644
--- a/scripts/Windows/WindowsBuild.bat
+++ b/scripts/Windows/WindowsBuild.bat
@@ -2,7 +2,7 @@ set VENV_DIR=%cd%\venv
 
 rmdir /S /Q %VENV_DIR%
 
-python3.10 -m venv %VENV_DIR%
+python -m venv %VENV_DIR%
 if %errorlevel% neq 0 exit /b %errorlevel%
 CALL %VENV_DIR%\Scripts\activate
 if %errorlevel% neq 0 exit /b %errorlevel%
diff --git a/scripts/Windows/installer.iss b/scripts/Windows/installer.iss
index 1134f70a..f74973e8 100644
--- a/scripts/Windows/installer.iss
+++ b/scripts/Windows/installer.iss
@@ -2,7 +2,7 @@
 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
 
 #define MyAppName "Kathara"
-#define MyAppVersion "3.7.0"
+#define MyAppVersion "3.7.1"
 #define MyAppPublisher "Kathara Team"
 #define MyAppURL "https://www.kathara.org"
 #define MyAppExeName "kathara.exe"
@@ -37,7 +37,7 @@ ArchitecturesInstallIn64BitMode=x64
 UninstallDisplayIcon={app}\Kathara.exe
 
 [Messages]
-WelcomeLabel2=Kathara is a lightweight network emulation system based on Docker containers.%n%nThis will install [name/ver] on your computer.%n%nYou will be guided through the steps necessary to install this software.
+WelcomeLabel2=Kathara is a lightweight container-based network emulation tool.%n%nThis will install [name/ver] on your computer.%n%nYou will be guided through the steps necessary to install this software.
 
 [Languages]
 Name: "english"; MessagesFile: "compiler:Default.isl"
diff --git a/scripts/pip-package/Makefile b/scripts/pip-package/Makefile
index 7df99560..c3b8d919 100644
--- a/scripts/pip-package/Makefile
+++ b/scripts/pip-package/Makefile
@@ -4,7 +4,7 @@
 all: clean package upload
 
 package: clean
-	cd ../../ && python3 setup.py sdist --dist-dir='scripts/pip-package/dist'
+	cd ../../ && python3 -m build --outdir='scripts/pip-package/dist'
 
 upload: package
 	python3 -m pip install twine
diff --git a/scripts/pip-package/README.md b/scripts/pip-package/README.md
index d49292ca..10bd31c5 100644
--- a/scripts/pip-package/README.md
+++ b/scripts/pip-package/README.md
@@ -4,6 +4,7 @@
     1. `src/Kathara/version.py`.
     2. `setup.py` (change `version` and `download_url`).
     3. `setup.cfg` (change `version`).
+    4. `pyproject.toml` (change `version`).
 2. Run `make all`. This will:
    1. Create a Kathara Python package.
    2. Upload the packet on PyPI.
diff --git a/setup.cfg b/setup.cfg
index d54c032d..9bb2f565 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,9 +1,9 @@
 [metadata]
 name = kathara
-version = 3.7.0
+version = 3.7.1
 author = Kathara Framework
 author_email = contact@kathara.org
-description = A lightweight container based emulation system
+description = A lightweight container-based network emulation tool.
 long_description = file: README.md
 long_description_content_type = text/markdown
 url = www.kathara.org
diff --git a/setup.py b/setup.py
index bbd25549..c635d861 100644
--- a/setup.py
+++ b/setup.py
@@ -7,17 +7,17 @@
     package_dir={'': 'src'},
     packages=find_packages('src'),
     py_modules=['kathara'],
-    version='3.7.0',
+    version='3.7.1',
     license='gpl-3.0',
-    description='A lightweight container based emulation system.',
+    description='A lightweight container-based network emulation tool.',
     author='Kathara Framework',
     author_email='contact@kathara.org',
     url='https://www.kathara.org',
-    download_url='https://github.com/KatharaFramework/Kathara/archive/refs/tags/3.7.0.tar.gz',
+    download_url='https://github.com/KatharaFramework/Kathara/archive/refs/tags/3.7.1.tar.gz',
     keywords=['NETWORK-EMULATION', 'CONTAINERS', 'NFV'],
     install_requires=[
         "binaryornot>=0.4.4",
-        "docker>=6.0.1",
+        "docker>=7.0.0",
         "kubernetes>=23.3.0",
         "requests>=2.22.0",
         "coloredlogs>=10.0",
diff --git a/src/Kathara/auth/PrivilegeHandler.py b/src/Kathara/auth/PrivilegeHandler.py
index b8b0353c..3a29ab5f 100644
--- a/src/Kathara/auth/PrivilegeHandler.py
+++ b/src/Kathara/auth/PrivilegeHandler.py
@@ -7,7 +7,7 @@
 
 
 class PrivilegeHandler(object):
-    __slots__ = ['user_uid', 'user_gid', 'effective_user_uid', 'effective_user_gid']
+    __slots__ = ['user_uid', 'user_gid', 'effective_user_uid', 'effective_user_gid', '_ref']
 
     __instance: PrivilegeHandler = None
 
@@ -31,10 +31,25 @@ def __init__(self) -> None:
             except AttributeError:
                 pass
 
+            self._ref = 0
+
             PrivilegeHandler.__instance = self
 
     def drop_privileges(self) -> None:
-        logging.debug("Dropping privileges to UID=%d and GID=%d..." % (self.user_uid, self.user_gid))
+        logging.debug("Called `drop_privileges`...")
+
+        logging.debug(f"Reference count is {self._ref}.")
+        if self._ref > 1:
+            self._ref -= 1
+            logging.debug(f"Reference count is {self._ref}, exiting.")
+            return
+
+        if self._ref <= 0:
+            self._ref = 0
+            logging.debug(f"Reference count is {self._ref}, exiting.")
+            return
+
+        logging.debug(f"Dropping privileges to UID={self.user_uid} and GID={self.user_gid}...")
 
         try:
             os.setuid(self.user_uid)
@@ -46,8 +61,19 @@ def drop_privileges(self) -> None:
         except OSError:
             pass
 
+        self._ref = 0
+        logging.debug(f"Reference count is reset to 0.")
+
     def raise_privileges(self) -> None:
-        logging.debug("Raising privileges to UID=%d and GID=%d..." % (self.effective_user_uid, self.effective_user_gid))
+        logging.debug("Called `raise_privileges`...")
+
+        self._ref += 1
+        logging.debug(f"Reference count is now {self._ref}.")
+        if self._ref > 1:
+            logging.debug("Reference count > 1, exiting.")
+            return
+
+        logging.debug(f"Raising privileges to UID={self.effective_user_uid} and GID={self.effective_user_gid}...")
 
         try:
             os.setuid(self.effective_user_uid)
diff --git a/src/Kathara/cli/command/CheckCommand.py b/src/Kathara/cli/command/CheckCommand.py
index 7400ecc0..3ac1544e 100644
--- a/src/Kathara/cli/command/CheckCommand.py
+++ b/src/Kathara/cli/command/CheckCommand.py
@@ -34,15 +34,11 @@ def __init__(self) -> None:
 
     def run(self, current_path: str, argv: List[str]) -> None:
         self.parse_args(argv)
-        args = self.get_args()
-
-        print("*\tCurrent Manager is: %s" % Kathara.get_instance().get_formatted_manager_name())
-
-        print("*\tManager version is: %s" % Kathara.get_instance().get_release_version())
 
+        print(f"*\tCurrent Manager is: {Kathara.get_instance().get_formatted_manager_name()}")
+        print(f"*\tManager version is: {Kathara.get_instance().get_release_version()}")
         print("*\tPython version is: %s" % sys.version.replace("\n", "- "))
-
-        print("*\tKathara version is: %s" % version.CURRENT_VERSION)
+        print(f"*\tKathara version is: {version.CURRENT_VERSION}")
 
         def linux_platform_info():
             info = os.uname()
@@ -51,20 +47,18 @@ def linux_platform_info():
         platform_info = utils.exec_by_platform(
             linux_platform_info, lambda: platform.platform(), lambda: platform.platform()
         )
-        print("*\tOperating System version is: %s" % str(platform_info))
-
-        print("*\tTrying to run `Hello World` container...")
+        print(f"*\tOperating System version is: {str(platform_info)}")
 
+        print(f"*\tTrying to run container with `{Setting.get_instance().image}` image...")
         Setting.get_instance().open_terminals = False
-        args['no_shared'] = False
-        args['no_hosthome'] = False
 
         lab = Lab("kathara_test")
-        lab.get_or_new_machine("hello_world")
+        lab.add_option('hosthome_mount', False)
 
+        machine = lab.get_or_new_machine("hello_world")
         try:
-            Kathara.get_instance().deploy_lab(lab)
+            Kathara.get_instance().deploy_machine(machine)
             print("*\tContainer run successfully.")
-            Kathara.get_instance().undeploy_lab(lab_hash=lab.hash)
+            Kathara.get_instance().undeploy_machine(machine)
         except Exception as e:
-            logging.exception("\t! Running `Hello World` failed: %s" % str(e))
+            logging.exception(f"\t! Running container failed: {str(e)}")
diff --git a/src/Kathara/cli/command/LconfigCommand.py b/src/Kathara/cli/command/LconfigCommand.py
index 42246e4d..38ec2301 100644
--- a/src/Kathara/cli/command/LconfigCommand.py
+++ b/src/Kathara/cli/command/LconfigCommand.py
@@ -2,11 +2,10 @@
 import logging
 from typing import List
 
+from ..ui.utils import alphanumeric, cd_mac
 from ... import utils
-from ..ui.utils import alphanumeric
 from ...foundation.cli.command.Command import Command
 from ...manager.Kathara import Kathara
-from ...model.Lab import Lab
 from ...parser.netkit.LabParser import LabParser
 from ...strings import strings, wiki_description
 
@@ -45,9 +44,9 @@ def __init__(self) -> None:
 
         group.add_argument(
             '--add',
-            type=alphanumeric,
+            type=cd_mac,
             dest='to_add',
-            metavar='CD',
+            metavar='CD/MAC',
             nargs='+',
             help='Specify the collision domain to add.'
         )
@@ -66,26 +65,27 @@ def run(self, current_path: str, argv: List[str]) -> None:
 
         lab_path = args['directory'].replace('"', '').replace("'", '') if args['directory'] else current_path
         lab_path = utils.get_absolute_path(lab_path)
-        try:
-            lab = LabParser.parse(lab_path)
-        except (Exception, IOError):
-            lab = Lab(None, path=lab_path)
+
+        lab = LabParser.parse(lab_path)
 
         Kathara.get_instance().update_lab_from_api(lab)
 
         machine_name = args['name']
-        device = lab.get_or_new_machine(machine_name)
+        device = lab.get_machine(machine_name)
 
         if args['to_add']:
-            for cd in args['to_add']:
+            for cd_name, mac_address in args['to_add']:
                 logging.info(
-                    "Adding interface to device `%s` on collision domain `%s`..." % (machine_name, cd)
+                    f"Adding interface to device `{machine_name}` on collision domain `{cd_name}`" +
+                    (f" with MAC Address {mac_address}" if mac_address else "") +
+                    f"..."
                 )
-                Kathara.get_instance().connect_machine_to_link(device, lab.get_or_new_link(cd))
+                link = lab.get_or_new_link(cd_name)
+                Kathara.get_instance().connect_machine_to_link(device, link, mac_address=mac_address)
 
         if args['to_remove']:
-            for cd in args['to_remove']:
+            for cd_to_remove in args['to_remove']:
                 logging.info(
-                    "Removing interface on collision domain `%s` from device `%s`..." % (cd, machine_name)
+                    "Removing interface on collision domain `%s` from device `%s`..." % (cd_to_remove, machine_name)
                 )
-                Kathara.get_instance().disconnect_machine_from_link(device, lab.get_or_new_link(cd))
+                Kathara.get_instance().disconnect_machine_from_link(device, lab.get_link(cd_to_remove))
diff --git a/src/Kathara/cli/command/VconfigCommand.py b/src/Kathara/cli/command/VconfigCommand.py
index af9c8a0b..4ca9bb4c 100644
--- a/src/Kathara/cli/command/VconfigCommand.py
+++ b/src/Kathara/cli/command/VconfigCommand.py
@@ -2,7 +2,7 @@
 import logging
 from typing import List
 
-from ..ui.utils import alphanumeric
+from ..ui.utils import alphanumeric, cd_mac
 from ...foundation.cli.command.Command import Command
 from ...manager.Kathara import Kathara
 from ...model.Lab import Lab
@@ -37,9 +37,9 @@ def __init__(self) -> None:
 
         group.add_argument(
             '--add',
-            type=alphanumeric,
+            type=cd_mac,
             dest='to_add',
-            metavar='CD',
+            metavar='CD/MAC',
             nargs='+',
             help='Specify the collision domain to add.'
         )
@@ -57,25 +57,28 @@ def run(self, current_path: str, argv: List[str]) -> None:
         args = self.get_args()
 
         lab = Lab("kathara_vlab")
+        Kathara.get_instance().update_lab_from_api(lab)
 
         machine_name = args['name']
-        device = lab.get_or_new_machine(machine_name)
+        device = lab.get_machine(machine_name)
         device.api_object = Kathara.get_instance().get_machine_api_object(machine_name, lab_name=lab.name)
 
         if args['to_add']:
-            for cd in args['to_add']:
+            for cd_name, mac_address in args['to_add']:
                 logging.info(
-                    "Adding interface to device `%s` on collision domain `%s`..." % (machine_name, cd)
+                    f"Adding interface to device `{machine_name}` on collision domain `{cd_name}`" +
+                    (f" with MAC Address {mac_address}" if mac_address else "") +
+                    f"..."
                 )
-                link = lab.get_or_new_link(cd)
-                Kathara.get_instance().connect_machine_to_link(device, link)
+                link = lab.get_or_new_link(cd_name)
+                Kathara.get_instance().connect_machine_to_link(device, link, mac_address=mac_address)
 
         if args['to_remove']:
-            for cd in args['to_remove']:
+            for cd_to_remove in args['to_remove']:
                 logging.info(
-                    "Removing interface on collision domain `%s` from device `%s`..." % (cd, machine_name)
+                    "Removing interface on collision domain `%s` from device `%s`..." % (cd_to_remove, machine_name)
                 )
-                (_, link) = lab.connect_machine_to_link(machine_name, cd)
-                link.api_object = Kathara.get_instance().get_link_api_object(cd, lab_name=lab.name)
+                (_, interface) = lab.connect_machine_to_link(machine_name, cd_to_remove)
+                interface.link.api_object = Kathara.get_instance().get_link_api_object(cd_to_remove, lab_name=lab.name)
 
-                Kathara.get_instance().disconnect_machine_from_link(device, link)
+                Kathara.get_instance().disconnect_machine_from_link(device, interface.link)
diff --git a/src/Kathara/cli/command/VstartCommand.py b/src/Kathara/cli/command/VstartCommand.py
index 00d934c8..46f57766 100644
--- a/src/Kathara/cli/command/VstartCommand.py
+++ b/src/Kathara/cli/command/VstartCommand.py
@@ -3,7 +3,7 @@
 import sys
 from typing import List
 
-from ..ui.utils import format_headers, colon_separated
+from ..ui.utils import format_headers, interface_cd_mac
 from ... import utils
 from ...exceptions import PrivilegeError
 from ...foundation.cli.command.Command import Command
@@ -69,9 +69,9 @@ def __init__(self) -> None:
         )
         self.parser.add_argument(
             '--eth',
-            type=colon_separated,
+            type=interface_cd_mac,
             dest='eths',
-            metavar='N:CD',
+            metavar='N:CD/MAC',
             nargs='+',
             required=False,
             help='Set a specific interface on a collision domain.'
@@ -193,11 +193,14 @@ def run(self, current_path: str, argv: List[str]) -> None:
         device = lab.get_or_new_machine(name, **args)
 
         if args['eths']:
-            for iface_number, cd in args['eths']:
+            for iface_number, cd, mac_address in args['eths']:
                 try:
-                    lab.connect_machine_to_link(device.name, cd, machine_iface_number=int(iface_number))
+                    lab.connect_machine_to_link(device.name, cd,
+                                                machine_iface_number=int(iface_number),
+                                                mac_address=mac_address)
                 except ValueError:
-                    raise SyntaxError("Interface number in `--eth %s:%s` is not a number." % (iface_number, cd))
+                    s = f"{cd}/{mac_address}" if mac_address else f"{cd}"
+                    raise SyntaxError(f"Interface number in `--eth {iface_number}:{s}` is not a number.")
 
         lab.check_integrity()
 
diff --git a/src/Kathara/cli/ui/setting/CommonOptionsHandler.py b/src/Kathara/cli/ui/setting/CommonOptionsHandler.py
index 491d1c6d..d7330740 100644
--- a/src/Kathara/cli/ui/setting/CommonOptionsHandler.py
+++ b/src/Kathara/cli/ui/setting/CommonOptionsHandler.py
@@ -22,115 +22,134 @@ def add_items(self, current_menu: ConsoleMenu, menu_formatter: MenuFormatBuilder
         manager_type = Setting.get_instance().manager_type
 
         choose_manager_string = "Choose default manager"
-        manager_menu = SelectionMenu(strings=[],
-                                     title=choose_manager_string,
-                                     subtitle=lambda: "Current: %s" % managers[manager_type],
-                                     prologue_text="""Manager is the Engine used to run Kathara labs.
-                                                   Default is `%s`.""" % managers[DEFAULTS['manager_type']],
-                                     formatter=menu_formatter
-                                     )
+        manager_menu = SelectionMenu(
+            strings=[],
+            title=choose_manager_string,
+            subtitle=lambda: "Current: %s" % managers[manager_type],
+            prologue_text="""Manager is the Engine used to run Kathara labs.
+            
+                          Default is `%s`.""" % managers[DEFAULTS['manager_type']],
+            formatter=menu_formatter
+        )
 
         for name, formatted_name in managers.items():
-            manager_menu.append_item(FunctionItem(text=formatted_name,
-                                                  function=self.update_manager_value,
-                                                  args=[current_menu, name],
-                                                  should_exit=True
-                                                  )
-                                     )
+            manager_menu.append_item(
+                FunctionItem(
+                    text=formatted_name,
+                    function=self.update_manager_value,
+                    args=[current_menu, name],
+                    should_exit=True
+                )
+            )
 
         manager_item = SubmenuItem(choose_manager_string, manager_menu, current_menu)
 
         # Image Selection Submenu
         image_string = "Choose default image"
-        select_image_menu = SelectionMenu(strings=[],
-                                          title=image_string,
-                                          subtitle=setting_utils.current_string("image"),
-                                          prologue_text="""Default Docker image when you start a network scenario or """
-                                                        """a single Kathara device.
-                                                        Default is `%s`.""" % DEFAULTS['image'],
-                                          formatter=menu_formatter
-                                          )
+        select_image_menu = SelectionMenu(
+            strings=[],
+            title=image_string,
+            subtitle=setting_utils.current_string("image"),
+            prologue_text="""Default Docker image when you start a network scenario or a single Kathara device.
+                          
+                          Default is `%s`.""" % DEFAULTS['image'],
+            formatter=menu_formatter
+        )
 
         try:
             for image_name in DockerHubApi.get_tagged_images():
-                select_image_menu.append_item(FunctionItem(text=image_name,
-                                                           function=setting_utils.update_setting_value,
-                                                           args=['image', image_name],
-                                                           should_exit=True
-                                                           )
-                                              )
+                select_image_menu.append_item(
+                    FunctionItem(
+                        text=image_name,
+                        function=setting_utils.update_setting_value,
+                        args=['image', image_name],
+                        should_exit=True
+                    )
+                )
         except HTTPConnectionError:
             pass
 
-        select_image_menu.append_item(FunctionItem(text="Choose another image",
-                                                   function=setting_utils.read_value,
-                                                   args=['image',
-                                                         ImageValidator(),
-                                                         'Write the name of a Docker image available on Docker Hub:',
-                                                         None
-                                                         ],
-                                                   should_exit=True
-                                                   )
-                                      )
+        select_image_menu.append_item(
+            FunctionItem(
+                text="Choose another image",
+                function=setting_utils.read_value,
+                args=['image',
+                      ImageValidator(),
+                      'Write the name of a Docker image available on Docker Hub:',
+                      None
+                      ],
+                should_exit=True
+            )
+        )
         submenu_item = SubmenuItem(image_string, select_image_menu, current_menu)
 
         # Open Terminals Option
         open_terminals_string = "Automatically open terminals on startup"
-        open_terminals_menu = SelectionMenu(strings=[],
-                                            title=open_terminals_string,
-                                            subtitle=setting_utils.current_bool("open_terminals"),
-                                            prologue_text="""Determines if the device terminal should be opened """
-                                                          """when starting it.
-                                                          Default is %s."""
-                                                          % setting_utils.format_bool(DEFAULTS['open_terminals']),
-                                            formatter=menu_formatter
-                                            )
-
-        open_terminals_menu.append_item(FunctionItem(text="Yes",
-                                                     function=setting_utils.update_setting_value,
-                                                     args=['open_terminals', True],
-                                                     should_exit=True
-                                                     )
-                                        )
-        open_terminals_menu.append_item(FunctionItem(text="No",
-                                                     function=setting_utils.update_setting_value,
-                                                     args=['open_terminals', False],
-                                                     should_exit=True
-                                                     )
-                                        )
+        open_terminals_menu = SelectionMenu(
+            strings=[],
+            title=open_terminals_string,
+            subtitle=setting_utils.current_bool("open_terminals"),
+            prologue_text="""Determines if the device terminal should be opened when starting it.
+            
+                          Default is %s."""
+                          % setting_utils.format_bool(DEFAULTS['open_terminals']),
+            formatter=menu_formatter
+        )
+
+        open_terminals_menu.append_item(
+            FunctionItem(
+                text="Yes",
+                function=setting_utils.update_setting_value,
+                args=['open_terminals', True],
+                should_exit=True
+            )
+        )
+        open_terminals_menu.append_item(
+            FunctionItem(
+                text="No",
+                function=setting_utils.update_setting_value,
+                args=['open_terminals', False],
+                should_exit=True
+            )
+        )
 
         open_terminals_item = SubmenuItem(open_terminals_string, open_terminals_menu, current_menu)
 
         # Machine Shell Submenu
         machine_shell_string = "Choose device shell to be used"
-        machine_shell_menu = SelectionMenu(strings=[],
-                                           title=machine_shell_string,
-                                           subtitle=setting_utils.current_string("device_shell"),
-                                           formatter=menu_formatter,
-                                           prologue_text="""The shell to use inside the device. 
-                                                         **The application must be correctly installed in the Docker 
-                                                         image used for the device!**
-                                                         Default is `%s`, but it depends on the used Docker image.
-                                                         """ % DEFAULTS['device_shell']
-                                           )
+        machine_shell_menu = SelectionMenu(
+            strings=[],
+            title=machine_shell_string,
+            subtitle=setting_utils.current_string("device_shell"),
+            formatter=menu_formatter,
+            prologue_text="""The shell to use inside the device.
+            
+                          **The application must be correctly installed in the Docker image used for the device!**
+                          
+                          Default is `%s`, but it depends on the used Docker image.""" % DEFAULTS['device_shell']
+        )
 
         for shell in SHELLS_HINT:
-            machine_shell_menu.append_item(FunctionItem(text=shell,
-                                                        function=setting_utils.update_setting_value,
-                                                        args=["device_shell", shell],
-                                                        should_exit=True
-                                                        )
-                                           )
-        machine_shell_menu.append_item(FunctionItem(text="Choose another shell",
-                                                    function=setting_utils.read_value,
-                                                    args=['device_shell',
-                                                          RegexValidator(r"^(\w|/)+$"),
-                                                          'Write the name of a shell:',
-                                                          'Shell name is not valid!'
-                                                          ],
-                                                    should_exit=True
-                                                    )
-                                       )
+            machine_shell_menu.append_item(
+                FunctionItem(
+                    text=shell,
+                    function=setting_utils.update_setting_value,
+                    args=["device_shell", shell],
+                    should_exit=True
+                )
+            )
+        machine_shell_menu.append_item(
+            FunctionItem(
+                text="Choose another shell",
+                function=setting_utils.read_value,
+                args=['device_shell',
+                      RegexValidator(r"^(\w|/)+$"),
+                      'Write the name of a shell:',
+                      'Shell name is not valid!'
+                      ],
+                should_exit=True
+            )
+        )
 
         machine_shell_item = SubmenuItem(machine_shell_string, machine_shell_menu, current_menu)
 
@@ -138,69 +157,81 @@ def add_items(self, current_menu: ConsoleMenu, menu_formatter: MenuFormatBuilder
         # Linux Version
         def terminal_emulator_menu_linux():
             terminal_string = "Choose terminal emulator to be used"
-            terminal_menu = SelectionMenu(strings=[],
-                                          title=terminal_string,
-                                          subtitle=setting_utils.current_string("terminal"),
-                                          formatter=menu_formatter,
-                                          prologue_text="""Terminal emulator application to be used for device """
-                                                        """terminals. 
-                                                        **The application must be correctly installed in """
-                                                        """the host system!**
-                                                        Default is `%s`.""" % DEFAULTS['terminal']
-                                          )
-
-            terminal_menu.append_item(FunctionItem(text="/usr/bin/xterm",
-                                                   function=setting_utils.update_setting_value,
-                                                   args=["terminal", "/usr/bin/xterm"],
-                                                   should_exit=True
-                                                   )
-                                      )
-            terminal_menu.append_item(FunctionItem(text="TMUX",
-                                                   function=setting_utils.update_setting_value,
-                                                   args=["terminal", "TMUX"],
-                                                   should_exit=True
-                                                   )
-                                      )
-            terminal_menu.append_item(FunctionItem(text="Choose another terminal emulator",
-                                                   function=setting_utils.read_value,
-                                                   args=['terminal',
-                                                         TerminalValidator(),
-                                                         'Write the path of a terminal emulator:',
-                                                         'Terminal emulator is not valid! Install it before using it.'
-                                                         ],
-                                                   should_exit=True
-                                                   )
-                                      )
+            terminal_menu = SelectionMenu(
+                strings=[],
+                title=terminal_string,
+                subtitle=setting_utils.current_string("terminal"),
+                formatter=menu_formatter,
+                prologue_text="""Terminal emulator application to be used for device terminals.
+                 
+                              **The application must be correctly installed in the host system!**
+                              
+                              Default is `%s`.""" % DEFAULTS['terminal']
+            )
+
+            terminal_menu.append_item(
+                FunctionItem(
+                    text="/usr/bin/xterm",
+                    function=setting_utils.update_setting_value,
+                    args=["terminal", "/usr/bin/xterm"],
+                    should_exit=True
+                )
+            )
+            terminal_menu.append_item(
+                FunctionItem(
+                    text="TMUX",
+                    function=setting_utils.update_setting_value,
+                    args=["terminal", "TMUX"],
+                    should_exit=True
+                )
+            )
+            terminal_menu.append_item(
+                FunctionItem(
+                    text="Choose another terminal emulator",
+                    function=setting_utils.read_value,
+                    args=['terminal',
+                          TerminalValidator(),
+                          'Write the path of a terminal emulator:',
+                          'Terminal emulator is not valid! Install it before using it.'
+                          ],
+                    should_exit=True
+                )
+            )
 
             return SubmenuItem(terminal_string, terminal_menu, current_menu)
 
         # macOS Version
         def terminal_emulator_menu_osx():
             terminal_string = "Choose terminal emulator to be used"
-            terminal_menu = SelectionMenu(strings=[],
-                                          title=terminal_string,
-                                          subtitle=setting_utils.current_string("terminal"),
-                                          formatter=menu_formatter,
-                                          prologue_text="""Terminal emulator application to be used for device """
-                                                        """terminals. 
-                                                        **The application must be correctly installed in """
-                                                        """the host system!**
-                                                        Default is `Terminal`."""
-                                          )
+            terminal_menu = SelectionMenu(
+                strings=[],
+                title=terminal_string,
+                subtitle=setting_utils.current_string("terminal"),
+                formatter=menu_formatter,
+                prologue_text="""Terminal emulator application to be used for device terminals.
+                 
+                              **The application must be correctly installed in the host system!**
+                              
+                              Default is `Terminal`."""
+            )
 
             for terminal in TERMINALS_OSX:
-                terminal_menu.append_item(FunctionItem(text=terminal,
-                                                       function=setting_utils.update_setting_value,
-                                                       args=["terminal", terminal],
-                                                       should_exit=True
-                                                       )
-                                          )
-            terminal_menu.append_item(FunctionItem(text="TMUX",
-                                                   function=setting_utils.update_setting_value,
-                                                   args=["terminal", "TMUX"],
-                                                   should_exit=True
-                                                   )
-                                      )
+                terminal_menu.append_item(
+                    FunctionItem(
+                        text=terminal,
+                        function=setting_utils.update_setting_value,
+                        args=["terminal", terminal],
+                        should_exit=True
+                    )
+                )
+            terminal_menu.append_item(
+                FunctionItem(
+                    text="TMUX",
+                    function=setting_utils.update_setting_value,
+                    args=["terminal", "TMUX"],
+                    should_exit=True
+                )
+            )
 
             return SubmenuItem(terminal_string, terminal_menu, current_menu)
 
@@ -208,108 +239,124 @@ def terminal_emulator_menu_osx():
 
         # Prefixes Submenu
         prefixes_string = "Choose Kathara prefixes"
-        prefixes_menu = SelectionMenu(strings=[],
-                                      title=prefixes_string,
-                                      formatter=menu_formatter,
-                                      prologue_text="""Prefixes assigned to the network and device names when deployed.
-                                                    Default is `%s` and `%s`.""" % (DEFAULTS['net_prefix'],
-                                                                                    DEFAULTS['device_prefix'])
-                                      )
-
-        net_prefix_item = FunctionItem(text=setting_utils.current_string("net_prefix",
-                                                                         text="Insert Kathara networks prefix"),
-                                       function=setting_utils.read_value,
-                                       args=['net_prefix',
-                                             RegexValidator(r"^[a-z]+_?[a-z_]+$"),
-                                             'Write a Kathara networks prefix:',
-                                             'Network Prefix must only contain lowercase letters and underscore.'
-                                             ],
-                                       should_exit=True
-                                       )
+        prefixes_menu = SelectionMenu(
+            strings=[],
+            title=prefixes_string,
+            formatter=menu_formatter,
+            prologue_text="""Prefixes assigned to the network and device names when deployed.
+            
+                          Defaults are `%s` and `%s`.""" % (DEFAULTS['net_prefix'], DEFAULTS['device_prefix'])
+        )
+
+        net_prefix_item = FunctionItem(
+            text=setting_utils.current_string("net_prefix", text="Insert Kathara networks prefix"),
+            function=setting_utils.read_value,
+            args=['net_prefix',
+                  RegexValidator(r"^[a-z]+_?[a-z_]+$"),
+                  'Write a Kathara networks prefix:',
+                  'Network Prefix must only contain lowercase letters and underscore.'
+                  ],
+            should_exit=True
+        )
         prefixes_menu.append_item(net_prefix_item)
 
-        machine_prefix_item = FunctionItem(text=setting_utils.current_string("device_prefix",
-                                                                             text="Insert Kathara devices prefix"),
-                                           function=setting_utils.read_value,
-                                           args=['device_prefix',
-                                                 RegexValidator(r"^[a-z]+_?[a-z_]+$"),
-                                                 'Write a Kathara devices prefix:',
-                                                 'Device Prefix must only contain lowercase letters and underscore.'
-                                                 ],
-                                           should_exit=True
-                                           )
+        machine_prefix_item = FunctionItem(
+            text=setting_utils.current_string("device_prefix", text="Insert Kathara devices prefix"),
+            function=setting_utils.read_value,
+            args=['device_prefix',
+                  RegexValidator(r"^[a-z]+_?[a-z_]+$"),
+                  'Write a Kathara devices prefix:',
+                  'Device Prefix must only contain lowercase letters and underscore.'
+                  ],
+            should_exit=True
+        )
         prefixes_menu.append_item(machine_prefix_item)
 
         prefixes_item = SubmenuItem(prefixes_string, prefixes_menu, current_menu)
 
         # Debug Level Submenu
         debug_level_string = "Choose logging level to be used"
-        debug_level_menu = SelectionMenu(strings=[],
-                                         title=debug_level_string,
-                                         subtitle=setting_utils.current_string("debug_level"),
-                                         formatter=menu_formatter,
-                                         prologue_text="""Logging level of Kathara messages.
-                                                       Default is `%s`.""" % DEFAULTS['debug_level']
-                                         )
+        debug_level_menu = SelectionMenu(
+            strings=[],
+            title=debug_level_string,
+            subtitle=setting_utils.current_string("debug_level"),
+            formatter=menu_formatter,
+            prologue_text="""Logging level of Kathara messages.
+                            
+                          Default is `%s`.""" % DEFAULTS['debug_level']
+        )
 
         for debug_level in AVAILABLE_DEBUG_LEVELS:
-            debug_level_menu.append_item(FunctionItem(text=debug_level,
-                                                      function=setting_utils.update_setting_value,
-                                                      args=["debug_level", debug_level],
-                                                      should_exit=True
-                                                      )
-                                         )
+            debug_level_menu.append_item(
+                FunctionItem(
+                    text=debug_level,
+                    function=setting_utils.update_setting_value,
+                    args=["debug_level", debug_level],
+                    should_exit=True
+                )
+            )
 
         debug_level_item = SubmenuItem(debug_level_string, debug_level_menu, current_menu)
 
         # Print Startup Logs Option
         print_startup_log_string = "Print Startup Logs on device startup"
-        print_startup_log_menu = SelectionMenu(strings=[],
-                                               title=open_terminals_string,
-                                               subtitle=setting_utils.current_bool("print_startup_log"),
-                                               formatter=menu_formatter,
-                                               prologue_text="""When opening a device terminal, print its startup log.
-                                                             Default is %s.""" %
-                                                             setting_utils.format_bool(DEFAULTS['print_startup_log'])
-                                               )
-
-        print_startup_log_menu.append_item(FunctionItem(text="Yes",
-                                                        function=setting_utils.update_setting_value,
-                                                        args=['print_startup_log', True],
-                                                        should_exit=True
-                                                        )
-                                           )
-        print_startup_log_menu.append_item(FunctionItem(text="No",
-                                                        function=setting_utils.update_setting_value,
-                                                        args=['print_startup_log', False],
-                                                        should_exit=True
-                                                        )
-                                           )
+        print_startup_log_menu = SelectionMenu(
+            strings=[],
+            title=open_terminals_string,
+            subtitle=setting_utils.current_bool("print_startup_log"),
+            formatter=menu_formatter,
+            prologue_text="""When opening a device terminal, print its startup log.
+            
+                          Default is %s.""" % setting_utils.format_bool(DEFAULTS['print_startup_log'])
+        )
+
+        print_startup_log_menu.append_item(
+            FunctionItem(
+                text="Yes",
+                function=setting_utils.update_setting_value,
+                args=['print_startup_log', True],
+                should_exit=True
+            )
+        )
+        print_startup_log_menu.append_item(
+            FunctionItem(
+                text="No",
+                function=setting_utils.update_setting_value,
+                args=['print_startup_log', False],
+                should_exit=True
+            )
+        )
 
         print_startup_log_item = SubmenuItem(print_startup_log_string, print_startup_log_menu, current_menu)
 
         # Enable IPv6 Option
         enable_ipv6_string = "Enable IPv6"
-        enable_ipv6_menu = SelectionMenu(strings=[],
-                                         title=enable_ipv6_string,
-                                         subtitle=setting_utils.current_bool("enable_ipv6"),
-                                         formatter=menu_formatter,
-                                         prologue_text="""This option enables IPv6 inside the devices.
-                                                       Default is %s.""" %
-                                                       setting_utils.format_bool(DEFAULTS['enable_ipv6']))
-
-        enable_ipv6_menu.append_item(FunctionItem(text="Yes",
-                                                  function=setting_utils.update_setting_value,
-                                                  args=['enable_ipv6', True],
-                                                  should_exit=True
-                                                  )
-                                     )
-        enable_ipv6_menu.append_item(FunctionItem(text="No",
-                                                  function=setting_utils.update_setting_value,
-                                                  args=['enable_ipv6', False],
-                                                  should_exit=True
-                                                  )
-                                     )
+        enable_ipv6_menu = SelectionMenu(
+            strings=[],
+            title=enable_ipv6_string,
+            subtitle=setting_utils.current_bool("enable_ipv6"),
+            formatter=menu_formatter,
+            prologue_text="""This option enables IPv6 inside the devices.
+            
+                          Default is %s.""" % setting_utils.format_bool(DEFAULTS['enable_ipv6'])
+        )
+
+        enable_ipv6_menu.append_item(
+            FunctionItem(
+                text="Yes",
+                function=setting_utils.update_setting_value,
+                args=['enable_ipv6', True],
+                should_exit=True
+            )
+        )
+        enable_ipv6_menu.append_item(
+            FunctionItem(
+                text="No",
+                function=setting_utils.update_setting_value,
+                args=['enable_ipv6', False],
+                should_exit=True
+            )
+        )
 
         enable_ipv6_item = SubmenuItem(enable_ipv6_string, enable_ipv6_menu, current_menu)
 
diff --git a/src/Kathara/cli/ui/setting/DockerOptionsHandler.py b/src/Kathara/cli/ui/setting/DockerOptionsHandler.py
index 5e62c795..82954726 100644
--- a/src/Kathara/cli/ui/setting/DockerOptionsHandler.py
+++ b/src/Kathara/cli/ui/setting/DockerOptionsHandler.py
@@ -4,6 +4,7 @@
 from ....trdparty.consolemenu import *
 from ....trdparty.consolemenu.items import *
 from ....trdparty.consolemenu.validators.regex import RegexValidator
+from ....types import SharedCollisionDomainsOption
 from ....utils import exec_by_platform
 
 
@@ -11,218 +12,259 @@ class DockerOptionsHandler(OptionsHandler):
     def add_items(self, current_menu: ConsoleMenu, menu_formatter: MenuFormatBuilder) -> None:
         # Network Plugin Option
         network_plugin_string = "Choose Docker Network Plugin version"
-        network_plugin_menu = SelectionMenu(strings=[],
-                                            title=network_plugin_string,
-                                            subtitle=setting_utils.current_string("network_plugin"),
-                                            prologue_text="""Choose Docker Network Plugin version used to create"""
-                                                          """ collision domains.\n"""
-                                                          """`kathara/katharanp` plugin is based on Linux bridges.\n"""
-                                                          """`kathara/katharanp_vde` plugin is based on VDE switches.
-
-                                                          Default is `%s`.""" %
-                                                          DEFAULTS['network_plugin'],
-                                            formatter=menu_formatter
-                                            )
-
-        network_plugin_menu.append_item(FunctionItem(text="kathara/katharanp",
-                                                     function=setting_utils.update_setting_value,
-                                                     args=["network_plugin", "kathara/katharanp"],
-                                                     should_exit=True
-                                                     )
-                                        )
-        network_plugin_menu.append_item(FunctionItem(text="kathara/katharanp_vde",
-                                                     function=setting_utils.update_setting_value,
-                                                     args=["network_plugin", "kathara/katharanp_vde"],
-                                                     should_exit=True
-                                                     )
-                                        )
+        network_plugin_menu = SelectionMenu(
+            strings=[],
+            title=network_plugin_string,
+            subtitle=setting_utils.current_string("network_plugin"),
+            prologue_text="""Choose Docker Network Plugin version for collision domains.
+                          
+                          `kathara/katharanp` plugin is based on Linux bridges.
+                          
+                          `kathara/katharanp_vde` plugin is based on VDE switches.
+
+                          Default is `%s`.""" %
+                          DEFAULTS['network_plugin'],
+            formatter=menu_formatter
+        )
+
+        network_plugin_menu.append_item(
+            FunctionItem(
+                text="kathara/katharanp",
+                function=setting_utils.update_setting_value,
+                args=["network_plugin", "kathara/katharanp"],
+                should_exit=True
+            )
+        )
+        network_plugin_menu.append_item(
+            FunctionItem(
+                text="kathara/katharanp_vde",
+                function=setting_utils.update_setting_value,
+                args=["network_plugin", "kathara/katharanp_vde"],
+                should_exit=True
+            )
+        )
 
         network_plugin_item = SubmenuItem(network_plugin_string, network_plugin_menu, current_menu)
 
         # Hosthome Mount Option
         hosthome_string = "Automatically mount /hosthome on startup"
-        hosthome_menu = SelectionMenu(strings=[],
-                                      title=hosthome_string,
-                                      subtitle=setting_utils.current_bool("hosthome_mount"),
-                                      prologue_text="""The home directory of the current user is made available for """
-                                                    """reading/writing inside the device under the special """
-                                                    """directory `/hosthome`.
-                                                    
-                                                    Default is %s.""" %
-                                                    setting_utils.format_bool(DEFAULTS['hosthome_mount']),
-                                      formatter=menu_formatter
-                                      )
-
-        hosthome_menu.append_item(FunctionItem(text="Yes",
-                                               function=setting_utils.update_setting_value,
-                                               args=["hosthome_mount", True],
-                                               should_exit=True
-                                               )
-                                  )
-        hosthome_menu.append_item(FunctionItem(text="No",
-                                               function=setting_utils.update_setting_value,
-                                               args=["hosthome_mount", False],
-                                               should_exit=True
-                                               )
-                                  )
+        hosthome_menu = SelectionMenu(
+            strings=[],
+            title=hosthome_string,
+            subtitle=setting_utils.current_bool("hosthome_mount"),
+            prologue_text="""The home directory of the current user is made available for """
+                          """reading/writing inside the device under the special directory `/hosthome`.
+                          
+                          Default is %s.""" %
+                          setting_utils.format_bool(DEFAULTS['hosthome_mount']),
+            formatter=menu_formatter
+        )
+
+        hosthome_menu.append_item(
+            FunctionItem(
+                text="Yes",
+                function=setting_utils.update_setting_value,
+                args=["hosthome_mount", True],
+                should_exit=True
+            )
+        )
+        hosthome_menu.append_item(
+            FunctionItem(
+                text="No",
+                function=setting_utils.update_setting_value,
+                args=["hosthome_mount", False],
+                should_exit=True
+            )
+        )
 
         hosthome_item = SubmenuItem(hosthome_string, hosthome_menu, current_menu)
 
         # Shared Mount Option
         shared_string = "Automatically mount /shared on startup"
-        shared_menu = SelectionMenu(strings=[],
-                                    title=shared_string,
-                                    subtitle=setting_utils.current_bool("shared_mount"),
-                                    prologue_text="""The shared directory inside the network scenario folder is """
-                                                  """made available for reading/writing inside the device """
-                                                  """under the special directory `/shared`.
-                                                  
-                                                  Default is %s.""" %
-                                                  setting_utils.format_bool(DEFAULTS['shared_mount']),
-                                    formatter=menu_formatter
-                                    )
-
-        shared_menu.append_item(FunctionItem(text="Yes",
-                                             function=setting_utils.update_setting_value,
-                                             args=["shared_mount", True],
-                                             should_exit=True
-                                             )
-                                )
-        shared_menu.append_item(FunctionItem(text="No",
-                                             function=setting_utils.update_setting_value,
-                                             args=["shared_mount", False],
-                                             should_exit=True
-                                             )
-                                )
+        shared_menu = SelectionMenu(
+            strings=[],
+            title=shared_string,
+            subtitle=setting_utils.current_bool("shared_mount"),
+            prologue_text="""The shared directory inside the network scenario folder is """
+                          """made available for reading/writing inside the device under the special directory `/shared`.
+                          
+                          Default is %s.""" %
+                          setting_utils.format_bool(DEFAULTS['shared_mount']),
+            formatter=menu_formatter
+        )
+
+        shared_menu.append_item(
+            FunctionItem(
+                text="Yes",
+                function=setting_utils.update_setting_value,
+                args=["shared_mount", True],
+                should_exit=True
+            )
+        )
+        shared_menu.append_item(
+            FunctionItem(
+                text="No",
+                function=setting_utils.update_setting_value,
+                args=["shared_mount", False],
+                should_exit=True
+            )
+        )
 
         shared_item = SubmenuItem(shared_string, shared_menu, current_menu)
 
         # Image Update Policy Option
         image_update_policy_string = "Docker Image Update Policy"
-        image_update_policy_menu = SelectionMenu(strings=[],
-                                                 title=image_update_policy_string,
-                                                 subtitle=setting_utils.current_string("image_update_policy"),
-                                                 prologue_text="""Choose the policy when a Docker image update """
-                                                               """is available for a running device.             
-
-                                                               Default is %s.""" % DEFAULTS['image_update_policy'],
-                                                 formatter=menu_formatter
-                                                 )
-
-        image_update_policy_menu.append_item(FunctionItem(text="Prompt",
-                                                          function=setting_utils.update_setting_value,
-                                                          args=["image_update_policy", "Prompt"],
-                                                          should_exit=True
-                                                          )
-                                             )
-        image_update_policy_menu.append_item(FunctionItem(text="Always",
-                                                          function=setting_utils.update_setting_value,
-                                                          args=["image_update_policy", "Always"],
-                                                          should_exit=True
-                                                          )
-                                             )
-        image_update_policy_menu.append_item(FunctionItem(text="Never",
-                                                          function=setting_utils.update_setting_value,
-                                                          args=["image_update_policy", "Never"],
-                                                          should_exit=True
-                                                          )
-                                             )
+        image_update_policy_menu = SelectionMenu(
+            strings=[],
+            title=image_update_policy_string,
+            subtitle=setting_utils.current_string("image_update_policy"),
+            prologue_text="""Choose the policy when a Docker image update is available for a running device.             
+  
+                          \tDefault is %s.""" % DEFAULTS['image_update_policy'],
+            formatter=menu_formatter
+        )
+
+        image_update_policy_menu.append_item(
+            FunctionItem(
+                text="Prompt",
+                function=setting_utils.update_setting_value,
+                args=["image_update_policy", "Prompt"],
+                should_exit=True
+            )
+        )
+        image_update_policy_menu.append_item(
+            FunctionItem(
+                text="Always",
+                function=setting_utils.update_setting_value,
+                args=["image_update_policy", "Always"],
+                should_exit=True
+            )
+        )
+        image_update_policy_menu.append_item(
+            FunctionItem(
+                text="Never",
+                function=setting_utils.update_setting_value,
+                args=["image_update_policy", "Never"],
+                should_exit=True
+            )
+        )
 
         image_update_policy_item = SubmenuItem(image_update_policy_string, image_update_policy_menu, current_menu)
 
-        # Shared Links Option
-        shared_cd_string = "Enable Shared Collision Domains between users"
-        shared_cd_menu = SelectionMenu(strings=[],
-                                       title=shared_cd_string,
-                                       subtitle=setting_utils.current_bool("shared_cd"),
-                                       prologue_text="""This option allows to connect devices of different users to """
-                                                     """the same collision domains.
-                                                     
-                                                     Default is %s.""" %
-                                                     setting_utils.format_bool(DEFAULTS['shared_cd']),
-                                       formatter=menu_formatter
-                                       )
-
-        shared_cd_menu.append_item(FunctionItem(text="Yes",
-                                                function=setting_utils.update_setting_value,
-                                                args=["shared_cd", True],
-                                                should_exit=True
-                                                )
-                                   )
-        shared_cd_menu.append_item(FunctionItem(text="No",
-                                                function=setting_utils.update_setting_value,
-                                                args=["shared_cd", False],
-                                                should_exit=True
-                                                )
-                                   )
-
-        shared_cd_item = SubmenuItem(shared_cd_string, shared_cd_menu, current_menu)
+        # Shared Collision Domains Option
+        shared_cds_string = "Enable Shared Collision Domains"
+        shared_cds_menu = SelectionMenu(
+            strings=[],
+            title=shared_cds_string,
+            subtitle=setting_utils.current_enum("shared_cds", SharedCollisionDomainsOption.to_string),
+            prologue_text="""This option allows sharing collision domains between network scenarios and users.
+                          
+                          Default is: %s.""" % SharedCollisionDomainsOption.to_string(DEFAULTS['shared_cds']),
+            formatter=menu_formatter
+        )
+
+        shared_cds_menu.append_item(
+            FunctionItem(
+                text="Share collision domains between network scenarios",
+                function=setting_utils.update_setting_value,
+                args=["shared_cds", SharedCollisionDomainsOption.LABS],
+                should_exit=True
+            )
+        )
+        shared_cds_menu.append_item(
+            FunctionItem(
+                text="Share collision domains between users",
+                function=setting_utils.update_setting_value,
+                args=["shared_cds", SharedCollisionDomainsOption.USERS],
+                should_exit=True
+            )
+        )
+
+        shared_cds_menu.append_item(
+            FunctionItem(
+                text="Do not share collision domains",
+                function=setting_utils.update_setting_value,
+                args=["shared_cds", SharedCollisionDomainsOption.NOT_SHARED],
+                should_exit=True
+            )
+        )
+
+        shared_cds_item = SubmenuItem(shared_cds_string, shared_cds_menu, current_menu)
 
         # Remote Docker Daemon Option
         def remote_url_unix():
             remote_url_string = "Configure a remote Docker connection"
-            remote_url_menu = SelectionMenu(strings=[],
-                                            title=remote_url_string,
-                                            subtitle=setting_utils.current_string("remote_url"),
-                                            prologue_text="""You can specify a remote Docker Daemon URL.
+            remote_url_menu = SelectionMenu(
+                strings=[],
+                title=remote_url_string,
+                subtitle=setting_utils.current_string("remote_url"),
+                prologue_text="""You can specify a remote Docker Daemon URL.
                                             
-                                                          Default is %s.""" % DEFAULTS['remote_url'],
-                                            formatter=menu_formatter
-                                            )
-
-            remote_url_menu.append_item(FunctionItem(text="Insert a remote Docker Daemon URL",
-                                                     function=setting_utils.read_value,
-                                                     args=['remote_url',
-                                                           RegexValidator(setting_utils.URL_REGEX),
-                                                           'Write a Docker Daemon URL '
-                                                           '(format http[s]://<remote-url>:<remote-port>):',
-                                                           'Docker Daemon URL is not a valid URL (remove '
-                                                           'the trailing slash, if present)'
-                                                           ],
-                                                     should_exit=False
-                                                     )
-                                        )
+                              Default is %s.""" % DEFAULTS['remote_url'],
+                formatter=menu_formatter
+            )
+
+            remote_url_menu.append_item(
+                FunctionItem(
+                    text="Insert a remote Docker Daemon URL",
+                    function=setting_utils.read_value,
+                    args=['remote_url',
+                          RegexValidator(setting_utils.URL_REGEX),
+                          'Write a Docker Daemon URL '
+                          '(format http[s]://<remote-url>:<remote-port>):',
+                          'Docker Daemon URL is not a valid URL (remove '
+                          'the trailing slash, if present)'
+                          ],
+                    should_exit=False
+                )
+            )
 
             remote_url_item = SubmenuItem(remote_url_string, remote_url_menu, current_menu)
 
             # Docker Daemon TLS Path Option
             cert_path_string = "Configure a Docker Daemon TLS Cert Path"
-            cert_path_menu = SelectionMenu(strings=[],
-                                           title=cert_path_string,
-                                           subtitle=setting_utils.current_string("cert_path"),
-                                           prologue_text="""When using a remote Docker Daemon, a TLS Cert could be """
-                                                         """required.
-                                                         
-                                                            Default is %s.""" % DEFAULTS['cert_path'],
-                                           formatter=menu_formatter
-                                           )
-
-            cert_path_menu.append_item(FunctionItem(text="Insert a Docker Daemon TLS Cert Path",
-                                                    function=setting_utils.read_value,
-                                                    args=['cert_path',
-                                                          RegexValidator(r'^.+$'),
-                                                          'Write a TSL Cert Path:',
-                                                          'TLS Cert Path not valid!'
-                                                          ],
-                                                    should_exit=False
-                                                    )
-                                       )
-            cert_path_menu.append_item(FunctionItem(text="Reset value to Empty String",
-                                                    function=setting_utils.update_setting_value,
-                                                    args=["cert_path", None],
-                                                    should_exit=False
-                                                    )
-                                       )
+            cert_path_menu = SelectionMenu(
+                strings=[],
+                title=cert_path_string,
+                subtitle=setting_utils.current_string("cert_path"),
+                prologue_text="""When using a remote Docker Daemon, a TLS Cert could be required.
+                              
+                              Default is %s.""" % DEFAULTS['cert_path'],
+                formatter=menu_formatter
+            )
+
+            cert_path_menu.append_item(
+                FunctionItem(
+                    text="Insert a Docker Daemon TLS Cert Path",
+                    function=setting_utils.read_value,
+                    args=['cert_path',
+                          RegexValidator(r'^.+$'),
+                          'Write a TSL Cert Path:',
+                          'TLS Cert Path not valid!'
+                          ],
+                    should_exit=False
+                )
+            )
+            cert_path_menu.append_item(
+                FunctionItem(
+                    text="Reset value to Empty String",
+                    function=setting_utils.update_setting_value,
+                    args=["cert_path", None],
+                    should_exit=False
+                )
+            )
 
             cert_path_item = SubmenuItem(cert_path_string, cert_path_menu, remote_url_menu)
             remote_url_menu.append_item(cert_path_item)
 
-            remote_url_menu.append_item(FunctionItem(text="Reset remote Docker connection to default",
-                                                     function=setting_utils.update_setting_values,
-                                                     args=[[("remote_url", None), ("cert_path", None)]],
-                                                     should_exit=False
-                                                     )
-                                        )
+            remote_url_menu.append_item(
+                FunctionItem(
+                    text="Reset remote Docker connection to default",
+                    function=setting_utils.update_setting_values,
+                    args=[[("remote_url", None), ("cert_path", None)]],
+                    should_exit=False
+                )
+            )
 
             return remote_url_item
 
@@ -232,6 +274,6 @@ def remote_url_unix():
         current_menu.append_item(hosthome_item)
         current_menu.append_item(shared_item)
         current_menu.append_item(image_update_policy_item)
-        current_menu.append_item(shared_cd_item)
+        current_menu.append_item(shared_cds_item)
         if platform_remote_url_item:
             current_menu.append_item(platform_remote_url_item)
diff --git a/src/Kathara/cli/ui/setting/KubernetesOptionsHandler.py b/src/Kathara/cli/ui/setting/KubernetesOptionsHandler.py
index d8a93a18..0ffedc25 100644
--- a/src/Kathara/cli/ui/setting/KubernetesOptionsHandler.py
+++ b/src/Kathara/cli/ui/setting/KubernetesOptionsHandler.py
@@ -10,122 +10,146 @@ class KubernetesOptionsHandler(OptionsHandler):
     def add_items(self, current_menu: ConsoleMenu, menu_formatter: MenuFormatBuilder) -> None:
         # API URL Option
         api_server_url_string = "Insert a Kubernetes API Server URL"
-        api_server_url_menu = SelectionMenu(strings=[],
-                                            title=api_server_url_string,
-                                            subtitle=setting_utils.current_string("api_server_url"),
-                                            prologue_text="""You can specify a remote Kubernetes API Server URL to """
-                                                          """connect to when Megalos is not used on a Kubernetes """
-                                                          """master.
-                                                          Default is %s.""" % DEFAULTS['api_server_url'],
-                                            formatter=menu_formatter
-                                            )
+        api_server_url_menu = SelectionMenu(
+            strings=[],
+            title=api_server_url_string,
+            subtitle=setting_utils.current_string("api_server_url"),
+            prologue_text="""You can specify a remote Kubernetes API Server URL to """
+                          """connect to when Megalos is not used on a Kubernetes master.
+                          
+                          Default is %s.""" % DEFAULTS['api_server_url'],
+            formatter=menu_formatter
+        )
 
-        api_server_url_menu.append_item(FunctionItem(text=api_server_url_string,
-                                                     function=setting_utils.read_value,
-                                                     args=['api_server_url',
-                                                           RegexValidator(setting_utils.URL_REGEX),
-                                                           'Write a Kubernetes API Server URL:',
-                                                           'Kubernetes API Server URL is not a valid URL (remove '
-                                                           'the trailing slash, if present)'
-                                                           ],
-                                                     should_exit=True
-                                                     )
-                                        )
-        api_server_url_menu.append_item(FunctionItem(text="Reset value to Empty String",
-                                                     function=setting_utils.update_setting_value,
-                                                     args=["api_server_url", None],
-                                                     should_exit=True
-                                                     )
-                                        )
+        api_server_url_menu.append_item(
+            FunctionItem(
+                text=api_server_url_string,
+                function=setting_utils.read_value,
+                args=['api_server_url',
+                      RegexValidator(setting_utils.URL_REGEX),
+                      'Write a Kubernetes API Server URL:',
+                      'Kubernetes API Server URL is not a valid URL (remove '
+                      'the trailing slash, if present)'
+                      ],
+                should_exit=True
+            )
+        )
+        api_server_url_menu.append_item(
+            FunctionItem(
+                text="Reset value to Empty String",
+                function=setting_utils.update_setting_value,
+                args=["api_server_url", None],
+                should_exit=True
+            )
+        )
 
         api_url_item = SubmenuItem(api_server_url_string, api_server_url_menu, current_menu)
 
         # API Token Option
         api_token_string = "Insert a Kubernetes API Token"
-        api_token_menu = SelectionMenu(strings=[],
-                                       title=api_token_string,
-                                       prologue_text="""When using a remote Kubernetes API Server, you must also """
-                                                     """specify the authentication token to use.
-                                                     Default is %s.""" % DEFAULTS['api_token'],
-                                       formatter=menu_formatter
-                                       )
+        api_token_menu = SelectionMenu(
+            strings=[],
+            title=api_token_string,
+            prologue_text="""When using a remote Kubernetes API Server, you must also """
+                          """specify the authentication token to use.
+                          
+                          Default is %s.""" % DEFAULTS['api_token'],
+            formatter=menu_formatter
+        )
 
-        api_token_menu.append_item(FunctionItem(text=api_token_string,
-                                                function=setting_utils.read_value,
-                                                args=['api_token',
-                                                      RegexValidator(r'^.+$'),
-                                                      'Write a Kubernetes API Token:',
-                                                      'Kubernetes API Token not valid!'
-                                                      ],
-                                                should_exit=True
-                                                )
-                                   )
-        api_token_menu.append_item(FunctionItem(text="Reset value to Empty String",
-                                                function=setting_utils.update_setting_value,
-                                                args=["api_token", None],
-                                                should_exit=True
-                                                )
-                                   )
+        api_token_menu.append_item(
+            FunctionItem(
+                text=api_token_string,
+                function=setting_utils.read_value,
+                args=['api_token',
+                      RegexValidator(r'^.+$'),
+                      'Write a Kubernetes API Token:',
+                      'Kubernetes API Token not valid!'
+                      ],
+                should_exit=True
+            )
+        )
+        api_token_menu.append_item(
+            FunctionItem(
+                text="Reset value to Empty String",
+                function=setting_utils.update_setting_value,
+                args=["api_token", None],
+                should_exit=True
+            )
+        )
 
         api_token_item = SubmenuItem(api_token_string, api_token_menu, current_menu)
 
         # Shared Mount Option
         host_shared_string = "Automatically mount /shared on startup"
-        host_shared_menu = SelectionMenu(strings=[],
-                                         title=host_shared_string,
-                                         subtitle=setting_utils.current_bool("host_shared"),
-                                         prologue_text="""Each Kubernetes worker node creates a /home/shared """
-                                                       """directory and it is made available for reading/writing """
-                                                       """inside the device under the special directory `/shared`.
-                                                       Default is %s.""" %
-                                                       setting_utils.format_bool(DEFAULTS['host_shared']),
-                                         formatter=menu_formatter
-                                         )
+        host_shared_menu = SelectionMenu(
+            strings=[],
+            title=host_shared_string,
+            subtitle=setting_utils.current_bool("host_shared"),
+            prologue_text="""Each Kubernetes worker node creates a /home/shared """
+                          """directory and it is made available for reading/writing """
+                          """inside the device under the special directory `/shared`.
+                          
+                          Default is %s.""" %
+                          setting_utils.format_bool(DEFAULTS['host_shared']),
+            formatter=menu_formatter
+        )
 
-        host_shared_menu.append_item(FunctionItem(text="Yes",
-                                                  function=setting_utils.update_setting_value,
-                                                  args=["host_shared", True],
-                                                  should_exit=True
-                                                  )
-                                     )
-        host_shared_menu.append_item(FunctionItem(text="No",
-                                                  function=setting_utils.update_setting_value,
-                                                  args=["host_shared", False],
-                                                  should_exit=True
-                                                  )
-                                     )
+        host_shared_menu.append_item(
+            FunctionItem(
+                text="Yes",
+                function=setting_utils.update_setting_value,
+                args=["host_shared", True],
+                should_exit=True
+            )
+        )
+        host_shared_menu.append_item(
+            FunctionItem(
+                text="No",
+                function=setting_utils.update_setting_value,
+                args=["host_shared", False],
+                should_exit=True
+            )
+        )
 
         host_shared_item = SubmenuItem(host_shared_string, host_shared_menu, current_menu)
 
         # Image Pull Policy Option
         image_pull_policy_string = "Image Pull Policy"
-        image_pull_policy_menu = SelectionMenu(strings=[],
-                                               title=image_pull_policy_string,
-                                               subtitle=setting_utils.current_string("image_pull_policy"),
-                                               prologue_text="""Specify the image pull policy for Docker images """
-                                                             """used by devices. 
-                                                             Default is %s.""" % DEFAULTS['image_pull_policy'],
-                                               formatter=menu_formatter
-                                               )
+        image_pull_policy_menu = SelectionMenu(
+            strings=[],
+            title=image_pull_policy_string,
+            subtitle=setting_utils.current_string("image_pull_policy"),
+            prologue_text="""Specify the image pull policy for Docker images used by devices.
+             
+                          Default is `%s`.""" % DEFAULTS['image_pull_policy'],
+            formatter=menu_formatter
+        )
 
-        image_pull_policy_menu.append_item(FunctionItem(text="Always",
-                                                        function=setting_utils.update_setting_value,
-                                                        args=["image_pull_policy", "Always"],
-                                                        should_exit=True
-                                                        )
-                                           )
-        image_pull_policy_menu.append_item(FunctionItem(text="If Not Present",
-                                                        function=setting_utils.update_setting_value,
-                                                        args=["image_pull_policy", "IfNotPresent"],
-                                                        should_exit=True
-                                                        )
-                                           )
-        image_pull_policy_menu.append_item(FunctionItem(text="Never",
-                                                        function=setting_utils.update_setting_value,
-                                                        args=["image_pull_policy", "Never"],
-                                                        should_exit=True
-                                                        )
-                                           )
+        image_pull_policy_menu.append_item(
+            FunctionItem(
+                text="Always",
+                function=setting_utils.update_setting_value,
+                args=["image_pull_policy", "Always"],
+                should_exit=True
+            )
+        )
+        image_pull_policy_menu.append_item(
+            FunctionItem(
+                text="If Not Present",
+                function=setting_utils.update_setting_value,
+                args=["image_pull_policy", "IfNotPresent"],
+                should_exit=True
+            )
+        )
+        image_pull_policy_menu.append_item(
+            FunctionItem(
+                text="Never",
+                function=setting_utils.update_setting_value,
+                args=["image_pull_policy", "Never"],
+                should_exit=True
+            )
+        )
 
         image_pull_policy_item = SubmenuItem(image_pull_policy_string, image_pull_policy_menu, current_menu)
 
diff --git a/src/Kathara/cli/ui/setting/SettingsMenuFactory.py b/src/Kathara/cli/ui/setting/SettingsMenuFactory.py
index 6fa0f24e..1aff7df7 100644
--- a/src/Kathara/cli/ui/setting/SettingsMenuFactory.py
+++ b/src/Kathara/cli/ui/setting/SettingsMenuFactory.py
@@ -9,7 +9,8 @@ class SettingsMenuFactory(object):
     __slots__ = ['menu_formatter']
 
     def __init__(self) -> None:
-        self.menu_formatter: MenuFormatBuilder = MenuFormatBuilder().set_title_align('center') \
+        self.menu_formatter: MenuFormatBuilder = MenuFormatBuilder() \
+            .set_title_align('center') \
             .set_subtitle_align('center') \
             .set_prologue_text_align('center') \
             .set_border_style_type(MenuBorderStyleType.DOUBLE_LINE_BORDER) \
diff --git a/src/Kathara/cli/ui/setting/utils.py b/src/Kathara/cli/ui/setting/utils.py
index 7896e48d..efaa82d7 100644
--- a/src/Kathara/cli/ui/setting/utils.py
+++ b/src/Kathara/cli/ui/setting/utils.py
@@ -1,3 +1,4 @@
+from enum import Enum
 from typing import Optional, Callable, Any, Tuple, List
 
 from ....exceptions import SettingsError
@@ -35,6 +36,13 @@ def current_string(attribute_name: str, text: Optional[str] = None) -> Callable[
                                         )
 
 
+def current_enum(attribute_name: str, to_string: Callable[[int], str], text: Optional[str] = None) -> Callable[[], str]:
+    return lambda: "%sCurrent: %s%s" % (text + " (" if text else "",
+                                        to_string(getattr(Setting.get_instance(), attribute_name)),
+                                        ")" if text else ""
+                                        )
+
+
 def update_setting_value(attribute_name: str, value: Any, stdout: bool = True) -> None:
     reload = False
 
diff --git a/src/Kathara/cli/ui/utils.py b/src/Kathara/cli/ui/utils.py
index 2240ee85..a31a8855 100644
--- a/src/Kathara/cli/ui/utils.py
+++ b/src/Kathara/cli/ui/utils.py
@@ -13,6 +13,7 @@
 from ...foundation.manager.stats.IMachineStats import IMachineStats
 from ...setting.Setting import Setting
 from ...trdparty.consolemenu import PromptUtils, Screen
+from ...utils import parse_cd_mac_address
 
 FORBIDDEN_TABLE_COLUMNS = ["container_name"]
 
@@ -158,10 +159,25 @@ def alphanumeric(value, pat=re.compile(r"^\w+$")):
     return value
 
 
-def colon_separated(value):
+def interface_cd_mac(value):
+    n, cd, mac = None, None, None
     try:
-        (v1, v2) = value.split(':')
+        parts = value.split('/')
+        (n, cd) = parts[0].split(':')
+        if len(parts) == 2:
+            if parts[1]:
+                mac = parts[1]
+            else:
+                raise ValueError
     except ValueError:
-        raise argparse.ArgumentTypeError("invalid colon-separated value: %s" % value)
+        raise argparse.ArgumentTypeError("invalid interface definition: %s" % value)
 
-    return v1, v2
+    if not re.search(r"^\w+$", cd):
+        raise argparse.ArgumentTypeError(f"invalid interface definition, "
+                                         f"collision domain `{cd}` contains non-alphanumeric characters")
+
+    return n, cd, mac
+
+
+def cd_mac(value):
+    return parse_cd_mac_address(value)
diff --git a/src/Kathara/exceptions.py b/src/Kathara/exceptions.py
index cd56dbf7..87d7aaea 100644
--- a/src/Kathara/exceptions.py
+++ b/src/Kathara/exceptions.py
@@ -95,6 +95,11 @@ class MachineNotFoundError(Exception):
     pass
 
 
+class MachineNotRunningError(Exception):
+    def __init__(self, machine_name: str) -> None:
+        super().__init__(f"Device `{machine_name}` is not running.")
+
+
 class MachineNotReadyError(Exception):
     def __init__(self, machine_name: str) -> None:
         super().__init__(f"Device `{machine_name}` is not ready.")
@@ -111,6 +116,14 @@ def __str__(self):
         return f"Binary `{self.binary}` not found in device `{self.machine_name}`."
 
 
+# Interface Exceptions
+class InterfaceMacAddressError(Exception):
+    def __init__(self, mac_address: str, interface_num: int, machine_name: str) -> None:
+        super().__init__(
+            f"MAC address {mac_address} on interface `{interface_num}` of device `{machine_name}` is invalid."
+        )
+
+
 # Link Exceptions
 class LinkNotFoundError(Exception):
     pass
diff --git a/src/Kathara/foundation/manager/IManager.py b/src/Kathara/foundation/manager/IManager.py
index 15f41703..0d637b4f 100644
--- a/src/Kathara/foundation/manager/IManager.py
+++ b/src/Kathara/foundation/manager/IManager.py
@@ -56,12 +56,13 @@ def deploy_lab(self, lab: Lab, selected_machines: Set[str] = None) -> None:
         raise NotImplementedError("You must implement `deploy_lab` method.")
 
     @abstractmethod
-    def connect_machine_to_link(self, machine: Machine, link: Link) -> None:
+    def connect_machine_to_link(self, machine: Machine, link: Link, mac_address: Optional[str] = None) -> None:
         """Connect a Kathara device to a collision domain.
 
         Args:
             machine (Kathara.model.Machine): A Kathara machine object.
             link (Kathara.model.Link): A Kathara collision domain object.
+            mac_address (Optional[str]): The MAC address to assign to the interface.
 
         Returns:
             None
@@ -157,13 +158,18 @@ def wipe(self, all_users: bool = False) -> None:
 
     @abstractmethod
     def connect_tty(self, machine_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
-                    shell: str = None, logs: bool = False, wait: Union[bool, Tuple[int, float]] = True) -> None:
+                    lab: Optional[Lab] = None, shell: str = None, logs: bool = False,
+                    wait: Union[bool, Tuple[int, float]] = True) -> None:
         """Connect to a device in a running network scenario, using the specified shell.
 
         Args:
             machine_name (str): The name of the device to connect.
-            lab_hash (str): The hash of the network scenario where the device is deployed.
-            lab_name (str): The name of the network scenario where the device is deployed.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             shell (str): The name of the shell to use for connecting.
             logs (bool): If True, print startup logs on stdout.
             wait (Union[bool, Tuple[int, float]]): If True, wait indefinitely until the end of the startup commands
@@ -181,15 +187,19 @@ def connect_tty(self, machine_name: str, lab_hash: Optional[str] = None, lab_nam
 
     @abstractmethod
     def exec(self, machine_name: str, command: Union[List[str], str], lab_hash: Optional[str] = None,
-             lab_name: Optional[str] = None, wait: Union[bool, Tuple[int, float]] = False) \
+             lab_name: Optional[str] = None, lab: Optional[Lab] = None, wait: Union[bool, Tuple[int, float]] = False) \
             -> Generator[Tuple[bytes, bytes], None, None]:
         """Exec a command on a device in a running network scenario.
 
         Args:
             machine_name (str): The name of the device to connect.
             command (Union[List[str], str]): The command to exec on the device.
-            lab_hash (Optional[str]): The hash of the network scenario where the device is deployed.
-            lab_name (Optional[str]): The name of the network scenario where the device is deployed.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             wait (Union[bool, Tuple[int, float]]): If True, wait indefinitely until the end of the startup commands
                 execution before executing the command. If a tuple is provided, the first value indicates the
                 number of retries before stopping waiting and the second value indicates the time interval to wait
@@ -218,16 +228,18 @@ def copy_files(self, machine: Machine, guest_to_host: Dict[str, io.IOBase]) -> N
         raise NotImplementedError("You must implement `copy_files` method.")
 
     @abstractmethod
-    def get_machine_api_object(self, machine_name: str, lab_hash: str = None, lab_name: str = None,
-                               all_users: bool = False) -> Any:
+    def get_machine_api_object(self, machine_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                               lab: Optional[Lab] = None, all_users: bool = False) -> Any:
         """Return the corresponding API object of a running device in a network scenario.
 
         Args:
             machine_name (str): The name of the device.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_hash should be set.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             all_users (bool): If True, return information about devices of all users.
 
         Returns:
@@ -240,13 +252,17 @@ def get_machine_api_object(self, machine_name: str, lab_hash: str = None, lab_na
         raise NotImplementedError("You must implement `get_machine_api_object` method.")
 
     @abstractmethod
-    def get_machines_api_objects(self, lab_hash: str = None, lab_name: str = None, all_users: bool = False) \
-            -> List[Any]:
+    def get_machines_api_objects(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                                 lab: Optional[Lab] = None, all_users: bool = False) -> List[Any]:
         """Return API objects of running devices.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
             all_users (bool): If True, return information about devices of all users.
 
         Returns:
@@ -255,16 +271,18 @@ def get_machines_api_objects(self, lab_hash: str = None, lab_name: str = None, a
         raise NotImplementedError("You must implement `get_machines_api_objects` method.")
 
     @abstractmethod
-    def get_link_api_object(self, link_name: str, lab_hash: str = None, lab_name: str = None,
-                            all_users: bool = False) -> Any:
+    def get_link_api_object(self, link_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                            lab: Optional[Lab] = None, all_users: bool = False) -> Any:
         """Return the corresponding API object of a collision domain in a network scenario.
 
         Args:
             link_name (str): The name of the collision domain.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_hash should be set.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             all_users (bool): If True, return information about collision domains of all users.
 
         Returns:
@@ -277,12 +295,17 @@ def get_link_api_object(self, link_name: str, lab_hash: str = None, lab_name: st
         raise NotImplementedError("You must implement `get_link_api_object` method.")
 
     @abstractmethod
-    def get_links_api_objects(self, lab_hash: str = None, lab_name: str = None, all_users: bool = False) -> List[Any]:
+    def get_links_api_objects(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                              lab: Optional[Lab] = None, all_users: bool = False) -> List[Any]:
         """Return API objects of collision domains in a network scenario.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
             all_users (bool): If True, return information about collision domains of all users.
 
         Returns:
@@ -291,12 +314,14 @@ def get_links_api_objects(self, lab_hash: str = None, lab_name: str = None, all_
         raise NotImplementedError("You must implement `get_links_api_objects` method.")
 
     @abstractmethod
-    def get_lab_from_api(self, lab_hash: str = None, lab_name: str = None) -> Lab:
+    def get_lab_from_api(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None) -> Lab:
         """Return the network scenario (specified by the hash or name), building it from API objects.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name. If None, lab_name should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash. If None, lab_hash should be set.
 
         Returns:
             Lab: The built network scenario.
@@ -316,13 +341,18 @@ def update_lab_from_api(self, lab: Lab) -> None:
         raise NotImplementedError("You must implement `update_lab_from_api` method.")
 
     @abstractmethod
-    def get_machines_stats(self, lab_hash: str = None, lab_name: str = None, machine_name: str = None,
-                           all_users: bool = False) -> Generator[Dict[str, IMachineStats], None, None]:
+    def get_machines_stats(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                           lab: Optional[Lab] = None, machine_name: str = None, all_users: bool = False) \
+            -> Generator[Dict[str, IMachineStats], None, None]:
         """Return information about the running devices.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
             machine_name (str): If specified return all the devices with machine_name.
             all_users (bool): If True, return information about the device of all users.
 
@@ -333,16 +363,18 @@ def get_machines_stats(self, lab_hash: str = None, lab_name: str = None, machine
         raise NotImplementedError("You must implement `get_machines_stats` method.")
 
     @abstractmethod
-    def get_machine_stats(self, machine_name: str, lab_hash: str = None,
-                          lab_name: str = None, all_users: bool = False) -> Generator[IMachineStats, None, None]:
+    def get_machine_stats(self, machine_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                          lab: Optional[Lab] = None, all_users: bool = False) -> Generator[IMachineStats, None, None]:
         """Return information of the specified device in a specified network scenario.
 
         Args:
             machine_name (str): The device name.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
-                If None, lab_hash should be set.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             all_users (bool): If True, search the device among all the users devices.
 
         Returns:
@@ -354,13 +386,17 @@ def get_machine_stats(self, machine_name: str, lab_hash: str = None,
         raise NotImplementedError("You must implement `get_machine_stats` method.")
 
     @abstractmethod
-    def get_links_stats(self, lab_hash: str = None, lab_name: str = None, link_name: str = None,
-                        all_users: bool = False) -> Generator[Dict[str, ILinkStats], None, None]:
+    def get_links_stats(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None, lab: Optional[Lab] = None,
+                        link_name: str = None, all_users: bool = False) -> Generator[Dict[str, ILinkStats], None, None]:
         """Return information about deployed networks.
 
         Args:
-           lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-           lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
            link_name (str): If specified return all the networks with link_name.
            all_users (bool): If True, return information about the networks of all users.
 
@@ -371,17 +407,19 @@ def get_links_stats(self, lab_hash: str = None, lab_name: str = None, link_name:
         raise NotImplementedError("You must implement `get_links_stats` method.")
 
     @abstractmethod
-    def get_link_stats(self, link_name: str, lab_hash: str = None, lab_name: str = None, all_users: bool = False) -> \
-            Generator[ILinkStats, None, None]:
+    def get_link_stats(self, link_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                       lab: Optional[Lab] = None, all_users: bool = False) -> Generator[ILinkStats, None, None]:
         """Return information of the specified deployed network in a specified network scenario.
 
         Args:
-           link_name (str): If specified return all the networks with link_name.
-           lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-           lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
-                If None, lab_hash should be set.
-           all_users (bool): If True, return information about the networks of all users.
+            link_name (str): If specified return all the networks with link_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
+            all_users (bool): If True, return information about the networks of all users.
 
         Returns:
              Generator[Dict[str, ILinkStats], None, None]: A generator containing dicts that has API Object
diff --git a/src/Kathara/foundation/model/FilesystemMixin.py b/src/Kathara/foundation/model/FilesystemMixin.py
index 9926ca41..e986dfe9 100644
--- a/src/Kathara/foundation/model/FilesystemMixin.py
+++ b/src/Kathara/foundation/model/FilesystemMixin.py
@@ -2,7 +2,9 @@
 import os.path
 from typing import Optional, List, BinaryIO, TextIO, Union
 
+from fs import open_fs
 from fs.base import FS
+from fs.copy import copy_dir
 
 from ...exceptions import InvocationError
 
@@ -73,7 +75,7 @@ def update_file_from_string(self, content: str, dst_path: str) -> None:
             fs.errors.ResourceNotFound: If the path is not found in the fs.
         """
         if not self.fs:
-            raise InvocationError("There is no filesystem associated to this network scenario.")
+            raise InvocationError("There is no filesystem associated to this object.")
 
         with self.fs.open(dst_path, "a") as dst_file:
             dst_file.write(content)
@@ -93,7 +95,7 @@ def create_file_from_list(self, lines: List[str], dst_path: str) -> None:
             fs.errors.ResourceNotFound: If the path is not found in the fs.
         """
         if not self.fs:
-            raise InvocationError("There is no filesystem associated to this network scenario.")
+            raise InvocationError("There is no filesystem associated to this object.")
 
         directory = os.path.dirname(dst_path)
         self.fs.makedirs(directory, recreate=True)
@@ -116,7 +118,7 @@ def update_file_from_list(self, lines: List[str], dst_path: str) -> None:
             fs.errors.ResourceNotFound: If the path is not found in the fs.
         """
         if not self.fs:
-            raise InvocationError("There is no filesystem associated to this network scenario.")
+            raise InvocationError("There is no filesystem associated to this object.")
 
         with self.fs.open(dst_path, "a") as dst_file:
             dst_file.writelines(line + '\n' for line in lines)
@@ -136,7 +138,7 @@ def create_file_from_path(self, src_path: str, dst_path: str) -> None:
             fs.errors.ResourceNotFound: If the path is not found in the fs.
         """
         if not self.fs:
-            raise InvocationError("There is no filesystem associated to this network scenario.")
+            raise InvocationError("There is no filesystem associated to this object.")
 
         directory = os.path.dirname(dst_path)
         self.fs.makedirs(directory, recreate=True)
@@ -160,7 +162,7 @@ def create_file_from_stream(self, stream: Union[BinaryIO, TextIO], dst_path: str
             fs.errors.ResourceNotFound: If the path is not found in the fs.
         """
         if not self.fs:
-            raise InvocationError("There is no filesystem associated to this network scenario.")
+            raise InvocationError("There is no filesystem associated to this object.")
 
         directory = os.path.dirname(dst_path)
         self.fs.makedirs(directory, recreate=True)
@@ -174,6 +176,25 @@ def create_file_from_stream(self, stream: Union[BinaryIO, TextIO], dst_path: str
         except io.UnsupportedOperation:
             raise io.UnsupportedOperation("To create a file from stream, you must open it with read permissions.")
 
+    def copy_directory_from_path(self, src_path: str, dst_path: str) -> None:
+        """Copy a directory from a src_path in the host filesystem into a dst_path in this fs.
+
+        Args:
+             src_path (str): The source path of the directory to copy.
+             dst_path (str): The destination path where to copy the directory.
+
+        Returns:
+            None
+
+        Raises:
+            InvocationError: If the fs is None.
+        """
+        if not self.fs:
+            raise InvocationError("There is no filesystem associated to this object.")
+
+        directory_fs = open_fs(f"osfs://{os.path.abspath(src_path)}")
+        copy_dir(directory_fs, ".", self.fs, dst_path)
+
     def write_line_before(self, file_path: str, line_to_add: str, searched_line: str, first_occurrence: bool = False) \
             -> int:
         """Write a new line before a specific line in a file.
@@ -194,7 +215,7 @@ def write_line_before(self, file_path: str, line_to_add: str, searched_line: str
             LineNotFoundError: If the searched line is not found in the file.
         """
         if not self.fs:
-            raise InvocationError("There is no filesystem associated to this network scenario.")
+            raise InvocationError("There is no filesystem associated to this object.")
 
         n_added = 0
         with self.fs.open(file_path, "r+") as file:
@@ -232,7 +253,7 @@ def write_line_after(self, file_path: str, line_to_add: str, searched_line: str,
             LineNotFoundError: If the searched line is not found in the file.
         """
         if not self.fs:
-            raise InvocationError("There is no filesystem associated to this network scenario.")
+            raise InvocationError("There is no filesystem associated to this object.")
 
         n_added = 0
         with self.fs.open(file_path, "r+") as file:
@@ -269,7 +290,7 @@ def delete_line(self, file_path: str, line_to_delete: str, first_occurrence: boo
             LineNotFoundError: If the searched line is not found in the file.
         """
         if not self.fs:
-            raise InvocationError("There is no filesystem associated to this network scenario.")
+            raise InvocationError("There is no filesystem associated to this object.")
 
         n_deleted = 0
         with self.fs.open(file_path, "r+") as file:
diff --git a/src/Kathara/manager/Kathara.py b/src/Kathara/manager/Kathara.py
index f86412bd..1df6cb6b 100644
--- a/src/Kathara/manager/Kathara.py
+++ b/src/Kathara/manager/Kathara.py
@@ -87,12 +87,13 @@ def deploy_lab(self, lab: Lab, selected_machines: Set[str] = None) -> None:
         """
         self.manager.deploy_lab(lab, selected_machines)
 
-    def connect_machine_to_link(self, machine: Machine, link: Link) -> None:
+    def connect_machine_to_link(self, machine: Machine, link: Link, mac_address: Optional[str] = None) -> None:
         """Connect a Kathara device to a collision domain.
 
         Args:
             machine (Kathara.model.Machine): A Kathara machine object.
             link (Kathara.model.Link): A Kathara collision domain object.
+            mac_address (Optional[str]): The MAC address to assign to the interface.
 
         Returns:
             None
@@ -102,7 +103,7 @@ def connect_machine_to_link(self, machine: Machine, link: Link) -> None:
             LabNotFoundError: If the collision domain is not associated to any network scenario.
             MachineCollisionDomainConflictError: If the device is already connected to the collision domain.
         """
-        self.manager.connect_machine_to_link(machine, link)
+        self.manager.connect_machine_to_link(machine, link, mac_address)
 
     def disconnect_machine_from_link(self, machine: Machine, link: Link) -> None:
         """Disconnect a Kathara device from a collision domain.
@@ -183,13 +184,18 @@ def wipe(self, all_users: bool = False) -> None:
         self.manager.wipe(all_users)
 
     def connect_tty(self, machine_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
-                    shell: str = None, logs: bool = False, wait: Union[bool, Tuple[int, float]] = True) -> None:
+                    lab: Optional[Lab] = None, shell: str = None, logs: bool = False,
+                    wait: Union[bool, Tuple[int, float]] = True) -> None:
         """Connect to a device in a running network scenario, using the specified shell.
 
         Args:
             machine_name (str): The name of the device to connect.
-            lab_hash (str): The hash of the network scenario where the device is deployed.
-            lab_name (str): The name of the network scenario where the device is deployed.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             shell (str): The name of the shell to use for connecting.
             logs (bool): If True, print startup logs on stdout.
             wait (Union[bool, Tuple[int, float]]): If True, wait indefinitely until the end of the startup commands
@@ -203,18 +209,22 @@ def connect_tty(self, machine_name: str, lab_hash: Optional[str] = None, lab_nam
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        self.manager.connect_tty(machine_name, lab_hash, lab_name, shell, logs, wait)
+        self.manager.connect_tty(machine_name, lab_hash, lab_name, lab, shell, logs, wait)
 
     def exec(self, machine_name: str, command: Union[List[str], str], lab_hash: Optional[str] = None,
-             lab_name: Optional[str] = None, wait: Union[bool, Tuple[int, float]] = False) \
+             lab_name: Optional[str] = None, lab: Optional[Lab] = None, wait: Union[bool, Tuple[int, float]] = False) \
             -> Generator[Tuple[bytes, bytes], None, None]:
         """Exec a command on a device in a running network scenario.
 
         Args:
             machine_name (str): The name of the device to connect.
             command (Union[List[str], str]): The command to exec on the device.
-            lab_hash (Optional[str]): The hash of the network scenario where the device is deployed.
-            lab_name (Optional[str]): The name of the network scenario where the device is deployed.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             wait (Union[bool, Tuple[int, float]]): If True, wait indefinitely until the end of the startup commands
                 execution before executing the command. If a tuple is provided, the first value indicates the
                 number of retries before stopping waiting and the second value indicates the time interval to wait
@@ -226,7 +236,7 @@ def exec(self, machine_name: str, command: Union[List[str], str], lab_hash: Opti
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        return self.manager.exec(machine_name, command, lab_hash, lab_name, wait)
+        return self.manager.exec(machine_name, command, lab_hash, lab_name, lab, wait)
 
     def copy_files(self, machine: Machine, guest_to_host: Dict[str, io.IOBase]) -> None:
         """Copy files on a running device in the specified paths.
@@ -241,16 +251,18 @@ def copy_files(self, machine: Machine, guest_to_host: Dict[str, io.IOBase]) -> N
         """
         self.manager.copy_files(machine, guest_to_host)
 
-    def get_machine_api_object(self, machine_name: str, lab_hash: str = None, lab_name: str = None,
-                               all_users: bool = False) -> Any:
+    def get_machine_api_object(self, machine_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                               lab: Optional[Lab] = None, all_users: bool = False) -> Any:
         """Return the corresponding API object of a running device in a network scenario.
 
         Args:
             machine_name (str): The name of the device.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_hash should be set.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             all_users (bool): If True, return information about devices of all users.
 
         Returns:
@@ -260,32 +272,38 @@ def get_machine_api_object(self, machine_name: str, lab_hash: str = None, lab_na
             InvocationError: If a running network scenario hash or name is not specified.
             MachineNotFoundError: If the specified device is not found.
         """
-        return self.manager.get_machine_api_object(machine_name, lab_hash, lab_name, all_users)
+        return self.manager.get_machine_api_object(machine_name, lab_hash, lab_name, lab, all_users)
 
-    def get_machines_api_objects(self, lab_hash: str = None, lab_name: str = None, all_users: bool = False) \
-            -> List[Any]:
+    def get_machines_api_objects(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                                 lab: Optional[Lab] = None, all_users: bool = False) -> List[Any]:
         """Return API objects of running devices.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
             all_users (bool): If True, return information about devices of all users.
 
         Returns:
             List[Any]: API objects of devices, specific for the current manager.
         """
-        return self.manager.get_machines_api_objects(lab_hash, lab_name, all_users)
+        return self.manager.get_machines_api_objects(lab_hash, lab_name, lab, all_users)
 
-    def get_link_api_object(self, link_name: str, lab_hash: str = None, lab_name: str = None,
-                            all_users: bool = False) -> Any:
+    def get_link_api_object(self, link_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                            lab: Optional[Lab] = None, all_users: bool = False) -> Any:
         """Return the corresponding API object of a collision domain in a network scenario.
 
         Args:
             link_name (str): The name of the collision domain.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_hash should be set.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             all_users (bool): If True, return information about collision domains of all users.
 
         Returns:
@@ -295,27 +313,34 @@ def get_link_api_object(self, link_name: str, lab_hash: str = None, lab_name: st
             InvocationError: If a running network scenario hash or name is not specified.
             LinkNotFoundError: If the collision domain is not found.
         """
-        return self.manager.get_link_api_object(link_name, lab_hash, lab_name, all_users)
+        return self.manager.get_link_api_object(link_name, lab_hash, lab_name, lab, all_users)
 
-    def get_links_api_objects(self, lab_hash: str = None, lab_name: str = None, all_users: bool = False) -> List[Any]:
+    def get_links_api_objects(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                              lab: Optional[Lab] = None, all_users: bool = False) -> List[Any]:
         """Return API objects of collision domains in a network scenario.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
             all_users (bool): If True, return information about collision domains of all users.
 
         Returns:
             List[Any]: API objects of collision domains, specific for the current manager.
         """
-        return self.manager.get_links_api_objects(lab_hash, lab_name, all_users)
+        return self.manager.get_links_api_objects(lab_hash, lab_name, lab, all_users)
 
-    def get_lab_from_api(self, lab_hash: str = None, lab_name: str = None) -> Lab:
+    def get_lab_from_api(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None) -> Lab:
         """Return the network scenario (specified by the hash or name), building it from API objects.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name. If None, lab_name should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash. If None, lab_hash should be set.
 
         Returns:
             Lab: The built network scenario.
@@ -333,13 +358,18 @@ def update_lab_from_api(self, lab: Lab) -> None:
         """
         self.manager.update_lab_from_api(lab)
 
-    def get_machines_stats(self, lab_hash: str = None, lab_name: str = None, machine_name: str = None,
-                           all_users: bool = False) -> Generator[Dict[str, IMachineStats], None, None]:
+    def get_machines_stats(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                           lab: Optional[Lab] = None, machine_name: str = None, all_users: bool = False) \
+            -> Generator[Dict[str, IMachineStats], None, None]:
         """Return information about the running devices.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
             machine_name (str): If specified return all the devices with machine_name.
             all_users (bool): If True, return information about the device of all users.
 
@@ -347,18 +377,20 @@ def get_machines_stats(self, lab_hash: str = None, lab_name: str = None, machine
               Generator[Dict[str, IMachineStats], None, None]: A generator containing dicts that has API Object
               identifier as keys and IMachineStats objects as values.
         """
-        return self.manager.get_machines_stats(lab_hash, lab_name, machine_name, all_users)
+        return self.manager.get_machines_stats(lab_hash, lab_name, lab, machine_name, all_users)
 
-    def get_machine_stats(self, machine_name: str, lab_hash: str = None,
-                          lab_name: str = None, all_users: bool = False) -> Generator[IMachineStats, None, None]:
+    def get_machine_stats(self, machine_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                          lab: Optional[Lab] = None, all_users: bool = False) -> Generator[IMachineStats, None, None]:
         """Return information of the specified device in a specified network scenario.
 
         Args:
             machine_name (str): The device name.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
-                If None, lab_hash should be set.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             all_users (bool): If True, search the device among all the users devices.
 
         Returns:
@@ -367,15 +399,19 @@ def get_machine_stats(self, machine_name: str, lab_hash: str = None,
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        return self.manager.get_machine_stats(machine_name, lab_hash, lab_name, all_users)
+        return self.manager.get_machine_stats(machine_name, lab_hash, lab_name, lab, all_users)
 
-    def get_links_stats(self, lab_hash: str = None, lab_name: str = None, link_name: str = None,
-                        all_users: bool = False) -> Generator[Dict[str, ILinkStats], None, None]:
+    def get_links_stats(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None, lab: Optional[Lab] = None,
+                        link_name: str = None, all_users: bool = False) -> Generator[Dict[str, ILinkStats], None, None]:
         """Return information about deployed networks.
 
         Args:
-           lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-           lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
            link_name (str): If specified return all the networks with link_name.
            all_users (bool): If True, return information about the networks of all users.
 
@@ -383,19 +419,21 @@ def get_links_stats(self, lab_hash: str = None, lab_name: str = None, link_name:
              Generator[Dict[str, ILinkStats], None, None]: A generator containing dicts that has API Object
              identifier as keys and ILinksStats objects as values.
         """
-        return self.manager.get_links_stats(lab_hash, lab_name, link_name, all_users)
+        return self.manager.get_links_stats(lab_hash, lab_name, lab, link_name, all_users)
 
-    def get_link_stats(self, link_name: str, lab_hash: str = None, lab_name: str = None, all_users: bool = False) -> \
-            Generator[ILinkStats, None, None]:
+    def get_link_stats(self, link_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                       lab: Optional[Lab] = None, all_users: bool = False) -> Generator[ILinkStats, None, None]:
         """Return information of the specified deployed network in a specified network scenario.
 
         Args:
-           link_name (str): If specified return all the networks with link_name.
-           lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-           lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
-                If None, lab_hash should be set.
-           all_users (bool): If True, return information about the networks of all users.
+            link_name (str): If specified return all the networks with link_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
+            all_users (bool): If True, return information about the networks of all users.
 
         Returns:
              Generator[Dict[str, ILinkStats], None, None]: A generator containing dicts that has API Object
@@ -404,7 +442,7 @@ def get_link_stats(self, link_name: str, lab_hash: str = None, lab_name: str = N
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        return self.manager.get_link_stats(link_name, lab_hash, lab_name, all_users)
+        return self.manager.get_link_stats(link_name, lab_hash, lab_name, lab, all_users)
 
     def check_image(self, image_name: str) -> None:
         """Check if the specified image is valid.
@@ -416,7 +454,8 @@ def check_image(self, image_name: str) -> None:
             None
 
         Raises:
-            ConnectionError: If the image is not locally available and there is no connection to a remote image repository.
+            ConnectionError: If the image is not locally available and there is no connection to a
+                remote image repository.
             ImageNotFoundError: If the image is not found.
         """
         self.manager.check_image(image_name)
diff --git a/src/Kathara/manager/docker/DockerLink.py b/src/Kathara/manager/docker/DockerLink.py
index c6a29290..d8f6bd04 100644
--- a/src/Kathara/manager/docker/DockerLink.py
+++ b/src/Kathara/manager/docker/DockerLink.py
@@ -20,6 +20,7 @@
 from ...model.Link import BRIDGE_LINK_NAME, Link
 from ...os.Networking import Networking
 from ...setting.Setting import Setting
+from ...types import SharedCollisionDomainsOption
 
 
 class DockerLink(object):
@@ -95,25 +96,38 @@ def create(self, link: Link) -> None:
             return
 
         # If a network with the same name exists, return it instead of creating a new one.
-        link_name = self.get_network_name(link.name)
-        networks = self.get_links_api_objects_by_filters(link_name=link_name)
+        filter_lab_hash = None
+        filter_user = None
+        if Setting.get_instance().shared_cds == SharedCollisionDomainsOption.NOT_SHARED:
+            filter_lab_hash = link.lab.hash
+        if Setting.get_instance().shared_cds != SharedCollisionDomainsOption.USERS:
+            filter_user = utils.get_current_user_name()
+
+        networks = self.get_links_api_objects_by_filters(
+            link_name=link.name, lab_hash=filter_lab_hash, user=filter_user
+        )
         if networks:
             link.api_object = networks.pop()
         else:
             network_ipam_config = docker.types.IPAMConfig(driver='null')
 
-            user_label = "shared_cd" if Setting.get_instance().shared_cd else utils.get_current_user_name()
+            link_name = self.get_network_name(link)
+            additional_labels = {}
+            if Setting.get_instance().shared_cds != SharedCollisionDomainsOption.USERS:
+                additional_labels["user"] = utils.get_current_user_name()
+            if Setting.get_instance().shared_cds == SharedCollisionDomainsOption.NOT_SHARED:
+                additional_labels["lab_hash"] = link.lab.hash
+
             link.api_object = self.client.networks.create(
                 name=link_name,
                 driver=f"{Setting.get_instance().network_plugin}:{utils.get_architecture()}",
                 check_duplicate=True,
                 ipam=network_ipam_config,
                 labels={
-                    "lab_hash": link.lab.hash,
                     "name": link.name,
-                    "user": user_label,
                     "app": "kathara",
-                    "external": ";".join([x.get_full_name() for x in link.external])
+                    "external": ";".join([x.get_full_name() for x in link.external]),
+                    **additional_labels
                 }
             )
 
@@ -161,7 +175,7 @@ def wipe(self, user: str = None) -> None:
         Returns:
             None
         """
-        user_label = "shared_cd" if Setting.get_instance().shared_cd else user
+        user_label = user if Setting.get_instance().shared_cds != SharedCollisionDomainsOption.USERS else None
         networks = self.get_links_api_objects_by_filters(user=user_label)
         for item in networks:
             item.reload()
@@ -211,11 +225,11 @@ def get_links_api_objects_by_filters(self, lab_hash: str = None, link_name: str
         """
         filters = {"label": ["app=kathara"]}
         if user:
-            filters["label"].append("user=%s" % user)
+            filters["label"].append(f"user={user}")
         if lab_hash:
-            filters["label"].append("lab_hash=%s" % lab_hash)
+            filters["label"].append(f"lab_hash={lab_hash}")
         if link_name:
-            filters["name"] = link_name
+            filters["label"].append(f"name={link_name}")
 
         return self.client.networks.list(filters=filters)
 
@@ -350,18 +364,22 @@ def _get_bridge_name(network: docker.models.networks.Network) -> str:
         Returns:
             str: The name of the Docker bridge in the format "kt-<network.id[:12]>".
         """
-        return "kt-%s" % network.id[:12]
+        return f"kt-{network.id[:12]}"
 
     @staticmethod
-    def get_network_name(name: str) -> str:
+    def get_network_name(link: Link) -> str:
         """Return the name of a Docker network.
 
         Args:
-            name (str): The name of a Kathara collision domain.
+            link (Kathara.model.Link): A Kathara collision domain.
 
         Returns:
             str: The name of the Docker network in the format "|net_prefix|_|username_prefix|_|name|".
                 If shared collision domains, the format is: "|net_prefix|_|lab_hash|".
         """
-        username_prefix = "_%s" % utils.get_current_user_name() if not Setting.get_instance().shared_cd else ""
-        return "%s%s_%s" % (Setting.get_instance().net_prefix, username_prefix, name)
+        if Setting.get_instance().shared_cds == SharedCollisionDomainsOption.LABS:
+            return f"{Setting.get_instance().net_prefix}_{utils.get_current_user_name()}_{link.name}"
+        elif Setting.get_instance().shared_cds == SharedCollisionDomainsOption.USERS:
+            return f"{Setting.get_instance().net_prefix}_{link.name}"
+        elif Setting.get_instance().shared_cds == SharedCollisionDomainsOption.NOT_SHARED:
+            return f"{Setting.get_instance().net_prefix}_{utils.get_current_user_name()}_{link.name}_{link.lab.hash}"
diff --git a/src/Kathara/manager/docker/DockerMachine.py b/src/Kathara/manager/docker/DockerMachine.py
index 3508f2e5..eb583c36 100644
--- a/src/Kathara/manager/docker/DockerMachine.py
+++ b/src/Kathara/manager/docker/DockerMachine.py
@@ -17,7 +17,8 @@
 from ... import utils
 from ...event.EventDispatcher import EventDispatcher
 from ...exceptions import MountDeniedError, MachineAlreadyExistsError, MachineNotFoundError, DockerPluginError, \
-    MachineBinaryError
+    MachineBinaryError, MachineNotRunningError
+from ...model.Interface import Interface
 from ...model.Lab import Lab
 from ...model.Link import Link, BRIDGE_LINK_NAME
 from ...model.Machine import Machine, MACHINE_CAPABILITIES
@@ -219,19 +220,21 @@ def create(self, machine: Machine) -> None:
         # Get the first network object, if defined.
         # This should be used in container create function
         first_network = None
+        first_machine_iface = None
         if machine.interfaces:
-            first_network = machine.interfaces[0].api_object
+            first_machine_iface = machine.interfaces[0]
+            first_network = first_machine_iface.link.api_object
 
         # If no interfaces are declared in machine, but bridged mode is required, get bridge as first link.
         # Flag that bridged is already connected (because there's another check in `start`).
-        if first_network is None and machine.is_bridged():
+        if first_machine_iface is None and machine.is_bridged():
             first_network = machine.lab.get_or_new_link(BRIDGE_LINK_NAME).api_object
             machine.add_meta("bridge_connected", True)
 
         # Sysctl params to pass to the container creation
         sysctl_parameters = {RP_FILTER_NAMESPACE % x: 0 for x in ["all", "default", "lo"]}
 
-        if first_network:
+        if first_machine_iface:
             sysctl_parameters[RP_FILTER_NAMESPACE % "eth0"] = 0
 
         sysctl_parameters["net.ipv4.ip_forward"] = 1
@@ -264,6 +267,17 @@ def create(self, machine: Machine) -> None:
             privileged = False
             logging.warning("Privileged flag is ignored with a remote Docker connection.")
 
+        networking_config = None
+        if first_machine_iface:
+            driver_opt = {'kathara.mac_addr': first_machine_iface.mac_address} \
+                if first_machine_iface.mac_address else None
+
+            networking_config = {
+                first_network.name: self.client.api.create_endpoint_config(
+                    driver_opt=driver_opt
+                )
+            }
+
         container_name = self.get_container_name(machine.name, machine.lab.hash)
 
         try:
@@ -274,6 +288,7 @@ def create(self, machine: Machine) -> None:
                                                               privileged=privileged,
                                                               network=first_network.name if first_network else None,
                                                               network_mode="bridge" if first_network else "none",
+                                                              networking_config=networking_config,
                                                               environment=machine.meta['envs'],
                                                               sysctls=sysctl_parameters,
                                                               mem_limit=memory,
@@ -303,12 +318,12 @@ def create(self, machine: Machine) -> None:
         machine.api_object = machine_container
 
     @staticmethod
-    def connect_to_link(machine: Machine, link: Link) -> None:
+    def connect_interface(machine: Machine, interface: Interface) -> None:
         """Connect the Docker container representing the machine to a specified collision domain.
 
         Args:
-            machine (Kathara.model.Machine): A Kathara device.
-            link (Kathara.model.Link): A Kathara collision domain object.
+            machine (Kathara.model.Machine.Machine): A Kathara device.
+            interface (Kathara.model.Interface.Interface): A Kathara interface object.
 
         Returns:
             None
@@ -320,9 +335,14 @@ def connect_to_link(machine: Machine, link: Link) -> None:
         machine.api_object.reload()
         attached_networks = machine.api_object.attrs["NetworkSettings"]["Networks"]
 
-        if link.api_object.name not in attached_networks:
+        if interface.link.api_object.name not in attached_networks:
             try:
-                link.api_object.connect(machine.api_object)
+                driver_opt = {'kathara.mac_addr': interface.mac_address} if interface.mac_address else None
+
+                interface.link.api_object.connect(
+                    machine.api_object,
+                    driver_opt=driver_opt
+                )
             except APIError as e:
                 if e.response.status_code == 500 and \
                         ("network does not exist" in e.explanation or "endpoint does not exist" in e.explanation):
@@ -380,14 +400,18 @@ def start(self, machine: Machine) -> None:
         # Connect the container to its networks (starting from the second, the first is already connected in `create`)
         # This should be done after the container start because Docker causes a non-deterministic order when attaching
         # networks before container startup.
-        for (iface_num, machine_link) in islice(machine.interfaces.items(), 1, None):
-            logging.debug("Connecting device `%s` to collision domain `%s` on interface %d..." % (machine.name,
-                                                                                                  machine_link.name,
-                                                                                                  iface_num
-                                                                                                  )
-                          )
+        for (iface_num, machine_iface) in islice(machine.interfaces.items(), 1, None):
+            logging.debug(
+                f"Connecting device `{machine.name}` to collision domain `{machine_iface.link.name}` "
+                f"on interface {iface_num}..."
+            )
             try:
-                machine_link.api_object.connect(machine.api_object)
+                driver_opt = {'kathara.mac_addr': machine_iface.mac_address} if machine_iface.mac_address else None
+
+                machine_iface.link.api_object.connect(
+                    machine.api_object,
+                    driver_opt=driver_opt
+                )
             except APIError as e:
                 if e.response.status_code == 500 and \
                         ("network does not exist" in e.explanation or "endpoint does not exist" in e.explanation):
@@ -516,12 +540,12 @@ def connect(self, lab_hash: str, machine_name: str, user: str = None, shell: str
             None
 
         Raises:
-            MachineNotFoundError: If the specified device is not running.
+            MachineNotRunningError: If the specified device is not running.
             ValueError: If the wait values is neither a boolean nor a tuple, or an invalid tuple.
         """
         containers = self.get_machines_api_objects_by_filters(lab_hash=lab_hash, machine_name=machine_name, user=user)
         if not containers:
-            raise MachineNotFoundError("The specified device `%s` is not running." % machine_name)
+            raise MachineNotRunningError(machine_name)
         container = containers.pop()
 
         if not shell:
@@ -619,14 +643,14 @@ def exec(self, lab_hash: str, machine_name: str, command: Union[str, List], user
             Generator[Tuple[bytes, bytes]]: A generator of tuples containing the stdout and stderr in bytes.
 
         Raises:
-            MachineNotFoundError: If the specified device is not running.
+            MachineNotRunningError: If the specified device is not running.
             ValueError: If the wait values is neither a boolean nor a tuple, or an invalid tuple.
         """
         logging.debug("Executing command `%s` to device with name: %s" % (command, machine_name))
 
         containers = self.get_machines_api_objects_by_filters(lab_hash=lab_hash, machine_name=machine_name, user=user)
         if not containers:
-            raise MachineNotFoundError("The specified device `%s` is not running." % machine_name)
+            raise MachineNotRunningError(machine_name)
         container = containers.pop()
 
         if isinstance(wait, tuple):
@@ -818,11 +842,11 @@ def get_machines_api_objects_by_filters(self, lab_hash: str = None, machine_name
         """
         filters = {"label": ["app=kathara"]}
         if user:
-            filters["label"].append("user=%s" % user)
+            filters["label"].append(f"user={user}")
         if lab_hash:
-            filters["label"].append("lab_hash=%s" % lab_hash)
+            filters["label"].append(f"lab_hash={lab_hash}")
         if machine_name:
-            filters["label"].append("name=%s" % machine_name)
+            filters["label"].append(f"name={machine_name}")
 
         return self.client.containers.list(all=True, filters=filters)
 
@@ -900,15 +924,16 @@ def _delete_machine(self, container: docker.models.containers.Container) -> None
         # Build the shutdown command string
         shutdown_commands_string = "; ".join(SHUTDOWN_COMMANDS).format(machine_name=container.labels["name"])
 
+        logging.debug(f"Executing shutdown commands on `{container.labels['name']}`: {shutdown_commands_string}")
         # Execute the shutdown commands inside the container (only if it's running)
         if container.status == "running":
             try:
                 self._exec_run(container,
                                cmd=[container.labels['shell'], '-c', shutdown_commands_string],
-                               stdout=False,
+                               stdout=True,
                                stderr=False,
                                privileged=True,
-                               detach=True
+                               detach=False
                                )
             except MachineBinaryError as e:
                 logging.warning(f"Shell `{e.binary}` not found in "
diff --git a/src/Kathara/manager/docker/DockerManager.py b/src/Kathara/manager/docker/DockerManager.py
index 62b18294..f92341ba 100644
--- a/src/Kathara/manager/docker/DockerManager.py
+++ b/src/Kathara/manager/docker/DockerManager.py
@@ -16,14 +16,16 @@
 from ... import utils
 from ...decorators import privileged
 from ...exceptions import DockerDaemonConnectionError, LinkNotFoundError, MachineCollisionDomainError, \
-    InvocationError, LabNotFoundError
+    InvocationError, LabNotFoundError, MachineNotRunningError
 from ...exceptions import MachineNotFoundError
 from ...foundation.manager.IManager import IManager
 from ...model.Lab import Lab
 from ...model.Link import Link
 from ...model.Machine import Machine
 from ...setting.Setting import Setting
-from ...utils import pack_files_for_tar, import_pywintypes
+from ...types import SharedCollisionDomainsOption
+from ...utils import pack_files_for_tar, import_pywintypes, \
+    check_required_single_not_none_var, check_single_not_none_var
 
 pywintypes = import_pywintypes()
 
@@ -89,7 +91,7 @@ def deploy_machine(self, machine: Machine) -> None:
         if not machine.lab:
             raise LabNotFoundError("Device `%s` is not associated to a network scenario." % machine.name)
 
-        self.docker_link.deploy_links(machine.lab, selected_links={x.name for x in machine.interfaces.values()})
+        self.docker_link.deploy_links(machine.lab, selected_links={x.link.name for x in machine.interfaces.values()})
         self.docker_machine.deploy_machines(machine.lab, selected_machines={machine.name})
 
     @privileged
@@ -139,24 +141,29 @@ def deploy_lab(self, lab: Lab, selected_machines: Set[str] = None) -> None:
         self.docker_machine.deploy_machines(lab, selected_machines=selected_machines)
 
     @privileged
-    def connect_machine_to_link(self, machine: Machine, link: Link) -> None:
-        """Connect a Kathara device to a collision domain.
+    def connect_machine_to_link(self, machine: Machine, link: Link, mac_address: Optional[str] = None) -> None:
+        """Create a new interface and connect a Kathara device to a collision domain.
 
         Args:
             machine (Kathara.model.Machine): A Kathara machine object.
             link (Kathara.model.Link): A Kathara collision domain object.
+            mac_address (Optional[str]): The MAC address to assign to the interface.
 
         Returns:
             None
 
         Raises:
             LabNotFoundError: If the device specified is not associated to any network scenario.
+            MachineNotRunningError: If the specified device is not running.
             LabNotFoundError: If the collision domain is not associated to any network scenario.
             MachineCollisionDomainConflictError: If the device is already connected to the collision domain.
         """
         if not machine.lab:
             raise LabNotFoundError("Device `%s` is not associated to a network scenario." % machine.name)
 
+        if not machine.api_object or machine.api_object.status != "running":
+            raise MachineNotRunningError(machine.name)
+
         if not link.lab:
             raise LabNotFoundError(f"Collision domain `{link.name}` is not associated to a network scenario.")
 
@@ -165,10 +172,10 @@ def connect_machine_to_link(self, machine: Machine, link: Link) -> None:
                 f"Device `{machine.name}` is already connected to collision domain `{link.name}`."
             )
 
-        machine.add_interface(link)
+        interface = machine.add_interface(link, mac_address=mac_address)
 
         self.deploy_link(link)
-        self.docker_machine.connect_to_link(machine, link)
+        self.docker_machine.connect_interface(machine, interface)
 
     @privileged
     def disconnect_machine_from_link(self, machine: Machine, link: Link) -> None:
@@ -219,7 +226,7 @@ def undeploy_machine(self, machine: Machine) -> None:
             raise LabNotFoundError(f"Device `{machine.name}` is not associated to a network scenario.")
 
         self.docker_machine.undeploy(machine.lab.hash, selected_machines={machine.name})
-        self.docker_link.undeploy(machine.lab.hash, selected_links={x.name for x in machine.interfaces.values()})
+        self.docker_link.undeploy(machine.lab.hash, selected_links={x.link.name for x in machine.interfaces.values()})
 
     @privileged
     def undeploy_link(self, link: Link) -> None:
@@ -259,9 +266,7 @@ def undeploy_lab(self, lab_hash: Optional[str] = None, lab_name: Optional[str] =
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        if not lab_hash and not lab_name and not lab:
-            raise InvocationError("You must specify a running network scenario hash, name or object.")
-
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
         if lab:
             lab_hash = lab.hash
         elif lab_name:
@@ -295,13 +300,18 @@ def wipe(self, all_users: bool = False) -> None:
 
     @privileged
     def connect_tty(self, machine_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
-                    shell: str = None, logs: bool = False, wait: Union[bool, Tuple[int, float]] = True) -> None:
+                    lab: Optional[Lab] = None, shell: str = None, logs: bool = False,
+                    wait: Union[bool, Tuple[int, float]] = True) -> None:
         """Connect to a device in a running network scenario, using the specified shell.
 
         Args:
             machine_name (str): The name of the device to connect.
-            lab_hash (str): The hash of the network scenario where the device is deployed.
-            lab_name (str): The name of the network scenario where the device is deployed.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             shell (str): The name of the shell to use for connecting.
             logs (bool): If True, print startup logs on stdout.
             wait (Union[bool, Tuple[int, float]]): If True, wait indefinitely until the end of the startup commands
@@ -315,10 +325,10 @@ def connect_tty(self, machine_name: str, lab_hash: Optional[str] = None, lab_nam
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        if not lab_hash and not lab_name:
-            raise InvocationError("You must specify a running network scenario hash or name.")
-
-        if lab_name:
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
         user_name = utils.get_current_user_name()
@@ -333,15 +343,19 @@ def connect_tty(self, machine_name: str, lab_hash: Optional[str] = None, lab_nam
 
     @privileged
     def exec(self, machine_name: str, command: Union[List[str], str], lab_hash: Optional[str] = None,
-             lab_name: Optional[str] = None, wait: Union[bool, Tuple[int, float]] = False) \
+             lab_name: Optional[str] = None, lab: Optional[Lab] = None, wait: Union[bool, Tuple[int, float]] = False) \
             -> Generator[Tuple[bytes, bytes], None, None]:
         """Exec a command on a device in a running network scenario.
 
         Args:
             machine_name (str): The name of the device to connect.
             command (Union[List[str], str]): The command to exec on the device.
-            lab_hash (Optional[str]): The hash of the network scenario where the device is deployed.
-            lab_name (Optional[str]): The name of the network scenario where the device is deployed.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             wait (Union[bool, Tuple[int, float]]): If True, wait indefinitely until the end of the startup commands
                 execution before executing the command. If a tuple is provided, the first value indicates the
                 number of retries before stopping waiting and the second value indicates the time interval to wait
@@ -353,13 +367,13 @@ def exec(self, machine_name: str, command: Union[List[str], str], lab_hash: Opti
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        if not lab_hash and not lab_name:
-            raise InvocationError("You must specify a running network scenario hash or name.")
-
-        user_name = utils.get_current_user_name()
-        if lab_name:
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
+        user_name = utils.get_current_user_name()
         return self.docker_machine.exec(lab_hash, machine_name, command, user=user_name, tty=False, wait=wait)
 
     @privileged
@@ -382,16 +396,19 @@ def copy_files(self, machine: Machine, guest_to_host: Dict[str, io.IOBase]) -> N
                                        )
 
     @privileged
-    def get_machine_api_object(self, machine_name: str, lab_hash: str = None, lab_name: str = None,
-                               all_users: bool = False) -> docker.models.containers.Container:
+    def get_machine_api_object(self, machine_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                               lab: Optional[Lab] = None, all_users: bool = False) \
+            -> docker.models.containers.Container:
         """Return the corresponding API object of a running device in a network scenario.
 
         Args:
             machine_name (str): The name of the device.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_hash should be set.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             all_users (bool): If True, return information about devices of all users.
 
         Returns:
@@ -401,13 +418,13 @@ def get_machine_api_object(self, machine_name: str, lab_hash: str = None, lab_na
             InvocationError: If a running network scenario hash or name is not specified.
             MachineNotFoundError: If the specified device is not found.
         """
-        if not lab_hash and not lab_name:
-            raise InvocationError("You must specify a running network scenario hash or name.")
-
-        user_name = utils.get_current_user_name() if not all_users else None
-        if lab_name:
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
+        user_name = utils.get_current_user_name() if not all_users else None
         containers = self.docker_machine.get_machines_api_objects_by_filters(
             lab_hash=lab_hash, machine_name=machine_name, user=user_name
         )
@@ -416,34 +433,46 @@ def get_machine_api_object(self, machine_name: str, lab_hash: str = None, lab_na
 
         raise MachineNotFoundError(f"Device `{machine_name}` not found.")
 
-    def get_machines_api_objects(self, lab_hash: str = None, lab_name: str = None, all_users: bool = False) -> \
-            List[docker.models.containers.Container]:
+    @privileged
+    def get_machines_api_objects(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                                 lab: Optional[Lab] = None, all_users: bool = False) \
+            -> List[docker.models.containers.Container]:
         """Return API objects of running devices.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
             all_users (bool): If True, return information about devices of all users.
 
         Returns:
             List[docker.models.containers.Container]: Docker API objects of devices.
         """
-        user_name = utils.get_current_user_name() if not all_users else None
-        if lab_name:
+        check_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
+        user_name = utils.get_current_user_name() if not all_users else None
         return self.docker_machine.get_machines_api_objects_by_filters(lab_hash=lab_hash, user=user_name)
 
-    def get_link_api_object(self, link_name: str, lab_hash: str = None, lab_name: str = None,
-                            all_users: bool = False) -> docker.models.networks.Network:
+    @privileged
+    def get_link_api_object(self, link_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                            lab: Optional[Lab] = None, all_users: bool = False) -> docker.models.networks.Network:
         """Return the corresponding API object of a collision domain in a network scenario.
 
         Args:
             link_name (str): The name of the collision domain.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_hash should be set.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             all_users (bool): If True, return information about collision domains of all users.
 
         Returns:
@@ -453,13 +482,13 @@ def get_link_api_object(self, link_name: str, lab_hash: str = None, lab_name: st
             InvocationError: If a running network scenario hash or name is not specified.
             LinkNotFoundError: If the collision domain is not found.
         """
-        if not lab_hash and not lab_name:
-            raise InvocationError("You must specify a running network scenario hash or name.")
-
-        user_name = utils.get_current_user_name() if not all_users else None
-        if lab_name:
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
+        user_name = utils.get_current_user_name() if not all_users else None
         networks = self.docker_link.get_links_api_objects_by_filters(
             lab_hash=lab_hash, link_name=link_name, user=user_name
         )
@@ -468,31 +497,42 @@ def get_link_api_object(self, link_name: str, lab_hash: str = None, lab_name: st
 
         raise LinkNotFoundError(f"Collision Domain `{link_name}` not found.")
 
-    def get_links_api_objects(self, lab_hash: str = None, lab_name: str = None, all_users: bool = False) -> \
-            List[docker.models.networks.Network]:
+    @privileged
+    def get_links_api_objects(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                              lab: Optional[Lab] = None, all_users: bool = False) \
+            -> List[docker.models.networks.Network]:
         """Return API objects of collision domains in a network scenario.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
             all_users (bool): If True, return information about collision domains of all users.
 
         Returns:
             List[docker.models.networks.Network]: Docker API objects of networks.
         """
-        user_name = utils.get_current_user_name() if not all_users else None
-        if lab_name:
+        check_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
+        user_name = utils.get_current_user_name() if not all_users else None
         return self.docker_link.get_links_api_objects_by_filters(lab_hash=lab_hash, user=user_name)
 
     @privileged
-    def get_lab_from_api(self, lab_hash: str = None, lab_name: str = None) -> Lab:
+    def get_lab_from_api(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None) -> Lab:
         """Return the network scenario (specified by the hash or name), building it from API objects.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name. If None, lab_name should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash. If None, lab_hash should be set.
 
         Returns:
             Lab: The built network scenario.
@@ -511,7 +551,11 @@ def get_lab_from_api(self, lab_hash: str = None, lab_name: str = None) -> Lab:
 
         lab_containers = self.get_machines_api_objects(lab_hash=reconstructed_lab.hash)
         lab_networks = dict(
-            map(lambda x: (x.name, x), self.get_links_api_objects(lab_hash=reconstructed_lab.hash))
+            map(lambda x: (x.name, x), self.get_links_api_objects(
+                lab_hash=reconstructed_lab.hash \
+                    if Setting.get_instance().shared_cds == SharedCollisionDomainsOption.NOT_SHARED else None,
+                all_users=Setting.get_instance().shared_cds == SharedCollisionDomainsOption.USERS
+            ))
         )
 
         for container in lab_containers:
@@ -547,7 +591,7 @@ def get_lab_from_api(self, lab_hash: str = None, lab_name: str = None) -> Lab:
             device.meta["sysctls"] = container.attrs["HostConfig"]["Sysctls"]
 
             if "none" not in container.attrs["NetworkSettings"]["Networks"]:
-                for network_name in container.attrs["NetworkSettings"]["Networks"]:
+                for network_name, network_options in container.attrs["NetworkSettings"]["Networks"].items():
                     if network_name == "bridge":
                         device.add_meta("bridged", True)
                         continue
@@ -555,7 +599,13 @@ def get_lab_from_api(self, lab_hash: str = None, lab_name: str = None) -> Lab:
                     network = lab_networks[network_name]
                     link = reconstructed_lab.get_or_new_link(network.attrs["Labels"]["name"])
                     link.api_object = network
-                    device.add_interface(link)
+
+                    iface_mac_addr = None
+                    if network_options["DriverOpts"] is not None:
+                        if "kathara.mac_addr" in network_options["DriverOpts"]:
+                            iface_mac_addr = network_options["DriverOpts"]["kathara.mac_addr"]
+
+                    device.add_interface(link, mac_address=iface_mac_addr)
 
         return reconstructed_lab
 
@@ -569,13 +619,17 @@ def update_lab_from_api(self, lab: Lab) -> None:
         running_containers = self.get_machines_api_objects(lab_hash=lab.hash)
 
         deployed_networks = dict(
-            map(lambda x: (x.name, x), self.get_links_api_objects(lab_hash=lab.hash))
+            map(lambda x: (x.name, x), self.get_links_api_objects(
+                lab_hash=lab.hash \
+                    if Setting.get_instance().shared_cds == SharedCollisionDomainsOption.NOT_SHARED else None,
+                all_users=Setting.get_instance().shared_cds == SharedCollisionDomainsOption.USERS
+            ))
         )
         for network in deployed_networks.values():
             network.reload()
 
         deployed_networks_by_link_name = dict(
-            map(lambda x: (x.attrs["Labels"]["name"], x), self.get_links_api_objects(lab_hash=lab.hash))
+            map(lambda x: (x.attrs["Labels"]["name"], x), deployed_networks.values())
         )
 
         for container in running_containers:
@@ -584,12 +638,16 @@ def update_lab_from_api(self, lab: Lab) -> None:
             device.api_object = container
 
             # Collision domains declared in the network scenario
-            static_links = set(device.interfaces.values())
+            static_links = set([x.link for x in device.interfaces.values()])
+            # Interfaces currently attached to the device
+            current_ifaces = [
+                (lab.get_or_new_link(deployed_networks[name].attrs["Labels"]["name"]), options)
+                for name, options in container.attrs["NetworkSettings"]["Networks"].items()
+                if name != "bridge"
+            ]
+
             # Collision domains currently attached to the device
-            current_links = set(
-                map(lambda x: lab.get_or_new_link(deployed_networks[x].attrs["Labels"]["name"]),
-                    filter(lambda x: x != "bridge", container.attrs["NetworkSettings"]["Networks"]))
-            )
+            current_links = set(map(lambda x: x[0], current_ifaces))
             # Collision domains attached at runtime to the device
             dynamic_links = current_links - static_links
             # Static collision domains detached at runtime from the device
@@ -599,21 +657,33 @@ def update_lab_from_api(self, lab: Lab) -> None:
                 if link.name in deployed_networks_by_link_name:
                     link.api_object = deployed_networks_by_link_name[link.name]
 
+            current_ifaces = dict([(x[0].name, x[1]) for x in current_ifaces])
             for link in dynamic_links:
                 link.api_object = deployed_networks_by_link_name[link.name]
-                device.add_interface(link)
+                iface_options = current_ifaces[link.name]
+                iface_mac_addr = None
+                if iface_options["DriverOpts"] is not None:
+                    if "kathara.mac_addr" in iface_options["DriverOpts"]:
+                        iface_mac_addr = iface_options["DriverOpts"]["kathara.mac_addr"]
+
+                device.add_interface(link, mac_address=iface_mac_addr)
 
             for link in deleted_links:
                 device.remove_interface(link)
 
     @privileged
-    def get_machines_stats(self, lab_hash: str = None, lab_name: str = None, machine_name: str = None,
-                           all_users: bool = False) -> Generator[Dict[str, DockerMachineStats], None, None]:
+    def get_machines_stats(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                           lab: Optional[Lab] = None, machine_name: str = None, all_users: bool = False) \
+            -> Generator[Dict[str, DockerMachineStats], None, None]:
         """Return information about the running devices.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
             machine_name (str): If specified return all the devices with machine_name.
             all_users (bool): If True, return information about the device of all users.
 
@@ -621,24 +691,30 @@ def get_machines_stats(self, lab_hash: str = None, lab_name: str = None, machine
               Generator[Dict[str, DockerMachineStats], None, None]: A generator containing dicts that has API Object
               identifier as keys and DockerMachineStats objects as values.
         """
-        user_name = utils.get_current_user_name() if not all_users else None
-        if lab_name:
+        check_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
+        user_name = utils.get_current_user_name() if not all_users else None
         return self.docker_machine.get_machines_stats(lab_hash=lab_hash, machine_name=machine_name,
                                                       user=user_name)
 
     @privileged
-    def get_machine_stats(self, machine_name: str, lab_hash: str = None,
-                          lab_name: str = None, all_users: bool = False) -> Generator[DockerMachineStats, None, None]:
+    def get_machine_stats(self, machine_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                          lab: Optional[Lab] = None, all_users: bool = False) \
+            -> Generator[DockerMachineStats, None, None]:
         """Return information of the specified device in a specified network scenario.
 
-        Args:
+         Args:
             machine_name (str): The device name.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
-                If None, lab_hash should be set.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             all_users (bool): If True, search the device among all the users devices.
 
         Returns:
@@ -648,10 +724,10 @@ def get_machine_stats(self, machine_name: str, lab_hash: str = None,
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        if not lab_hash and not lab_name:
-            raise InvocationError("You must specify a running network scenario hash or name.")
-
-        if lab_name:
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
         machines_stats = self.get_machines_stats(lab_hash=lab_hash, machine_name=machine_name, all_users=all_users)
@@ -659,13 +735,19 @@ def get_machine_stats(self, machine_name: str, lab_hash: str = None,
 
         yield machine_stats
 
-    def get_links_stats(self, lab_hash: str = None, lab_name: str = None, link_name: str = None,
-                        all_users: bool = False) -> Generator[Dict[str, DockerLinkStats], None, None]:
-        """Return information about deployed Docker networks.
+    @privileged
+    def get_links_stats(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None, lab: Optional[Lab] = None,
+                        link_name: str = None, all_users: bool = False) \
+            -> Generator[Dict[str, DockerLinkStats], None, None]:
+        """Return information about deployed networks.
 
         Args:
-           lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-           lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
            link_name (str): If specified return all the networks with link_name.
            all_users (bool): If True, return information about the networks of all users.
 
@@ -673,23 +755,29 @@ def get_links_stats(self, lab_hash: str = None, lab_name: str = None, link_name:
              Generator[Dict[str, DockerLinkStats], None, None]: A generator containing dicts that has API Object
                 identifier as keys and DockerLinksStats objects as values.
         """
-        user_name = utils.get_current_user_name() if not all_users else None
-        if lab_name:
+        check_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
+        user_name = utils.get_current_user_name() if not all_users else None
         return self.docker_link.get_links_stats(lab_hash=lab_hash, link_name=link_name, user=user_name)
 
-    def get_link_stats(self, link_name: str, lab_hash: str = None, lab_name: str = None, all_users: bool = False) -> \
-            Generator[DockerLinkStats, None, None]:
+    @privileged
+    def get_link_stats(self, link_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                       lab: Optional[Lab] = None, all_users: bool = False) -> Generator[DockerLinkStats, None, None]:
         """Return information of the specified deployed network in a specified network scenario.
 
         Args:
-            link_name (str): The link name.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
-                If None, lab_hash should be set.
-            all_users (bool): If True, search the network among all the users networks.
+            link_name (str): If specified return all the networks with link_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
+            all_users (bool): If True, return information about the networks of all users.
 
         Returns:
             Generator[DockerLinkStats, None, None]: A generator containing DockerLinkStats objects with the network
@@ -698,10 +786,10 @@ def get_link_stats(self, link_name: str, lab_hash: str = None, lab_name: str = N
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        if not lab_hash and not lab_name:
-            raise InvocationError("You must specify a running network scenario hash or name.")
-
-        if lab_name:
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
         links_stats = self.get_links_stats(lab_hash=lab_hash, link_name=link_name, all_users=all_users)
diff --git a/src/Kathara/manager/kubernetes/KubernetesLink.py b/src/Kathara/manager/kubernetes/KubernetesLink.py
index 26bf3c19..a4cedd29 100644
--- a/src/Kathara/manager/kubernetes/KubernetesLink.py
+++ b/src/Kathara/manager/kubernetes/KubernetesLink.py
@@ -197,7 +197,7 @@ def get_links_api_objects_by_filters(self, lab_hash: str = None, link_name: str
         """
         filters = ["app=kathara"]
         if link_name:
-            filters.append("name=%s" % link_name)
+            filters.append(f"name={link_name}")
 
         # Get all Kathara namespaces if lab_hash is None
         namespaces = list(map(lambda x: x.metadata.name, self.kubernetes_namespace.get_all())) \
diff --git a/src/Kathara/manager/kubernetes/KubernetesMachine.py b/src/Kathara/manager/kubernetes/KubernetesMachine.py
index affb2ebf..1f69f661 100644
--- a/src/Kathara/manager/kubernetes/KubernetesMachine.py
+++ b/src/Kathara/manager/kubernetes/KubernetesMachine.py
@@ -1,9 +1,12 @@
 import hashlib
 import json
 import logging
+import os
 import re
 import shlex
+import signal
 import sys
+import threading
 import uuid
 from multiprocessing.dummy import Pool
 from typing import Optional, Set, List, Union, Generator, Tuple, Dict
@@ -21,13 +24,14 @@
 from .stats.KubernetesMachineStats import KubernetesMachineStats
 from ... import utils
 from ...event.EventDispatcher import EventDispatcher
-from ...exceptions import MachineAlreadyExistsError, MachineNotFoundError, MachineNotReadyError
+from ...exceptions import MachineAlreadyExistsError, MachineNotFoundError, MachineNotReadyError, MachineNotRunningError
 from ...model.Lab import Lab
 from ...model.Machine import Machine
 from ...setting.Setting import Setting
 
 RP_FILTER_NAMESPACE = "net.ipv4.conf.%s.rp_filter"
 MAX_RESTART_COUNT = 3
+MAX_TIME_ERROR = 180
 
 # Known commands that each container should execute
 # Run order: shared.startup, machine.startup and machine.meta['exec_commands']
@@ -41,15 +45,17 @@
     # Removes /etc/bind already existing configuration from k8s internal DNS
     "rm -Rf /etc/bind/*",
 
-    # Unmount the /etc/resolv.conf and /etc/hosts files, automatically mounted by Docker inside the container.
+    # Unmount the /etc/resolv.conf and /etc/hosts files, automatically mounted by Kubernetes inside the container.
     # In this way, they can be overwritten by custom user files.
     "umount /etc/resolv.conf",
     "umount /etc/hosts",
 
-    # Parse hostlab.b64
+    # Parse hostlab.b64 (if present)
+    "if [ -f \"/tmp/kathara/hostlab.b64\" ]; then "
     "base64 -d /tmp/kathara/hostlab.b64 > /hostlab.tar.gz",
     # Extract hostlab.tar.gz data into /
     "tar xmfz /hostlab.tar.gz -C /; rm -f hostlab.tar.gz",
+    "fi",
 
     # Copy the machine folder (if present) from the hostlab directory into the root folder of the container
     # In this way, files are all replaced in the container root folder
@@ -95,7 +101,7 @@
     "/hostlab/{machine_name}.startup &> /var/log/startup.log; fi",
 
     # Remove the Kubernetes' default gateway which points to the eth0 interface and causes problems sometimes.
-    "route del default dev eth0 || true",
+    "ip route del default dev eth0 || true",
 
     # Placeholder for user commands
     "{machine_commands}",
@@ -184,8 +190,27 @@ def _wait_machines_startup(self, lab: Lab, selected_machines: Set[str]) -> None:
         machines_ready = 0
         machines_failed = 0
 
+        # Create a timer to raise an exception if the execution gets stuck
+        def raise_timeout_error():
+            logging.error(
+                f"Network scenario startup is not responding for over {MAX_TIME_ERROR} seconds, exiting. "
+                f"To check devices status, use the following command:\n\t"
+                f"kubectl -n {lab.hash} get pods"
+            )
+
+            # We should send a SIGINT to the main thread to exit the w.stream
+            os.kill(os.getpid(), signal.SIGINT)
+
+        timer = threading.Timer(MAX_TIME_ERROR, raise_timeout_error)
+        timer.start()
+
         w = watch.Watch()
         for event in w.stream(self.kubernetes_namespace.client.list_namespaced_pod, namespace=lab.hash):
+            # Every new event, cancel and create the timer
+            timer.cancel()
+            timer = threading.Timer(MAX_TIME_ERROR, raise_timeout_error)
+            timer.start()
+
             machine_name = event['object'].metadata.labels['name']
 
             if not selected_machines or machine_name in selected_machines:
@@ -214,6 +239,9 @@ def _wait_machines_startup(self, lab: Lab, selected_machines: Set[str]) -> None:
                             logging.warning(f"Device `{machine_name}` has been restarted {restart_count} times.")
 
             if machines_ready + machines_failed == len(machines):
+                # Finished watching, cancel the last timer
+                timer.cancel()
+
                 w.stop()
 
         if machines_ready == len(machines):
@@ -342,11 +370,10 @@ def _build_definition(self, machine: Machine, config_map: client.V1ConfigMap) ->
         # to execute custom commands coming from .startup file and "exec" option
         # Build the final startup commands string
         sysctl_commands = "; ".join(["sysctl -w -q %s=%d" % item for item in machine.meta["sysctls"].items()])
+        machine_commands = "; ".join(machine.meta['exec_commands']) if machine.meta['exec_commands'] else ":"
+
         startup_commands_string = "; ".join(STARTUP_COMMANDS) \
-            .format(machine_name=machine.name,
-                    sysctl_commands=sysctl_commands,
-                    machine_commands="; ".join(machine.meta['exec_commands'])
-                    )
+            .format(machine_name=machine.name, sysctl_commands=sysctl_commands, machine_commands=machine_commands)
 
         post_start = client.V1LifecycleHandler(
             _exec=client.V1ExecAction(
@@ -377,11 +404,16 @@ def _build_definition(self, machine: Machine, config_map: client.V1ConfigMap) ->
 
         pod_annotations = {}
         network_interfaces = []
-        for (idx, machine_link) in machine.interfaces.items():
+        for (idx, interface) in machine.interfaces.items():
+            additional_data = {}
+            if interface.mac_address:
+                additional_data["mac"] = interface.mac_address
+
             network_interfaces.append({
-                "name": machine_link.api_object["metadata"]["name"],
+                "name": interface.link.api_object["metadata"]["name"],
                 "namespace": machine.lab.hash,
-                "interface": "net%d" % idx
+                "interface": "net%d" % idx,
+                **additional_data
             })
         pod_annotations["k8s.v1.cni.cncf.io/networks"] = json.dumps(network_interfaces)
 
@@ -578,11 +610,12 @@ def connect(self, lab_hash: str, machine_name: str, shell: Union[str, List[str]]
             None
 
         Raises:
+            MachineNotRunningError: If the specified device is not running.
             MachineNotReadyError: If the device is not ready.
         """
         pods = self.get_machines_api_objects_by_filters(lab_hash=lab_hash, machine_name=machine_name)
         if not pods:
-            raise MachineNotFoundError("The specified device `%s` is not running." % machine_name)
+            raise MachineNotRunningError(machine_name)
         deployment = pods.pop()
 
         if 'Running' not in deployment.status.phase:
@@ -676,8 +709,11 @@ def exec(self, lab_hash: str, machine_name: str, command: Union[str, List], tty:
 
         Returns:
             Generator[Tuple[bytes, bytes]]: A generator of tuples containing the stdout and stderr in bytes.
+
+        Raises:
+            MachineNotRunningError: If the specified device is not running.
         """
-        command = shlex.split(command) if type(command) == str else command
+        command = shlex.split(command) if command is str else command
 
         logging.debug("Executing command `%s` to device with name: %s" % (command, machine_name))
 
@@ -685,7 +721,7 @@ def exec(self, lab_hash: str, machine_name: str, command: Union[str, List], tty:
             # Retrieve the pod of current Deployment
             pods = self.get_machines_api_objects_by_filters(lab_hash=lab_hash, machine_name=machine_name)
             if not pods:
-                raise MachineNotFoundError("The specified device `%s` is not running." % machine_name)
+                raise MachineNotRunningError(machine_name)
             pod = pods.pop()
 
             response = stream(self.core_client.connect_get_namespaced_pod_exec,
@@ -772,7 +808,7 @@ def get_machines_api_objects_by_filters(self, lab_hash: str = None, machine_name
         """
         filters = ["app=kathara"]
         if machine_name:
-            filters.append("name=%s" % machine_name)
+            filters.append(f"name={machine_name}")
 
         # Get all Kathara namespaces if lab_hash is None
         namespaces = list(map(lambda x: x.metadata.name, self.kubernetes_namespace.get_all())) \
diff --git a/src/Kathara/manager/kubernetes/KubernetesManager.py b/src/Kathara/manager/kubernetes/KubernetesManager.py
index 9fe81efa..e46c42e1 100644
--- a/src/Kathara/manager/kubernetes/KubernetesManager.py
+++ b/src/Kathara/manager/kubernetes/KubernetesManager.py
@@ -19,7 +19,7 @@
 from ...model.Lab import Lab
 from ...model.Link import Link
 from ...model.Machine import Machine
-from ...utils import pack_files_for_tar
+from ...utils import pack_files_for_tar, check_required_single_not_none_var, check_single_not_none_var
 
 
 class KubernetesManager(IManager):
@@ -52,7 +52,7 @@ def deploy_machine(self, machine: Machine) -> None:
         machine.lab.hash = machine.lab.hash.lower()
 
         self.k8s_namespace.create(machine.lab)
-        self.k8s_link.deploy_links(machine.lab, selected_links={x.name for x in machine.interfaces.values()})
+        self.k8s_link.deploy_links(machine.lab, selected_links={x.link.name for x in machine.interfaces.values()})
         self.k8s_machine.deploy_machines(machine.lab, selected_machines={machine.name})
 
     def deploy_link(self, link: Link) -> None:
@@ -113,12 +113,13 @@ def deploy_lab(self, lab: Lab, selected_machines: Set[str] = None) -> None:
             else:
                 raise e
 
-    def connect_machine_to_link(self, machine: Machine, link: Link) -> None:
+    def connect_machine_to_link(self, machine: Machine, link: Link, mac_address: Optional[str] = None) -> None:
         """Connect a Kathara device to a collision domain.
 
         Args:
             machine (Kathara.model.Machine): A Kathara machine object.
             link (Kathara.model.Link): A Kathara collision domain object.
+            mac_address (Optional[str]): The MAC address to assign to the interface.
 
         Returns:
             None
@@ -173,7 +174,7 @@ def undeploy_machine(self, machine: Machine) -> None:
             running_networks.update([net['name'] for net in network_annotation])
 
         # Difference between all networks of the machine to undeploy, and attached networks are the ones to delete
-        machine_networks = {self.k8s_link.get_network_name(x.name) for x in machine.interfaces.values()}
+        machine_networks = {self.k8s_link.get_network_name(x.link.name) for x in machine.interfaces.values()}
         networks_to_delete = machine_networks - running_networks
 
         self.k8s_machine.undeploy(machine.lab.hash, selected_machines={machine.name})
@@ -236,9 +237,7 @@ def undeploy_lab(self, lab_hash: Optional[str] = None, lab_name: Optional[str] =
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        if not lab_hash and not lab_name and not lab:
-            raise InvocationError("You must specify a running network scenario hash, name or object.")
-
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
         if lab:
             lab_hash = lab.hash
         elif lab_name:
@@ -301,19 +300,24 @@ def wipe(self, all_users: bool = False) -> None:
         self.k8s_namespace.wipe()
 
     def connect_tty(self, machine_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
-                    shell: str = None, logs: bool = False, wait: Union[bool, Tuple[int, float]] = True) -> None:
+                    lab: Optional[Lab] = None, shell: str = None, logs: bool = False,
+                    wait: Union[bool, Tuple[int, float]] = True) -> None:
         """Connect to a device in a running network scenario, using the specified shell.
 
         Args:
             machine_name (str): The name of the device to connect.
-            lab_hash (str): The hash of the network scenario where the device is deployed.
-            lab_name (str): The name of the network scenario where the device is deployed.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             shell (str): The name of the shell to use for connecting.
             logs (bool): If True, print startup logs on stdout.
             wait (Union[bool, Tuple[int, float]]): If True, wait indefinitely until the end of the startup commands
                 execution before connecting. If a tuple is provided, the first value indicates the number of retries
                 before stopping waiting and the second value indicates the time interval to wait for each retry.
-                Default is True. No effect on Kubernetes.
+                Default is True.
 
         Returns:
             None
@@ -321,10 +325,10 @@ def connect_tty(self, machine_name: str, lab_hash: Optional[str] = None, lab_nam
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        if not lab_hash and not lab_name:
-            raise InvocationError("You must specify a running network scenario hash or name.")
-
-        if lab_name:
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
         lab_hash = lab_hash.lower()
@@ -336,19 +340,23 @@ def connect_tty(self, machine_name: str, lab_hash: Optional[str] = None, lab_nam
                                  )
 
     def exec(self, machine_name: str, command: Union[List[str], str], lab_hash: Optional[str] = None,
-             lab_name: Optional[str] = None, wait: Union[bool, Tuple[int, float]] = False) \
+             lab_name: Optional[str] = None, lab: Optional[Lab] = None, wait: Union[bool, Tuple[int, float]] = False) \
             -> Generator[Tuple[bytes, bytes], None, None]:
         """Exec a command on a device in a running network scenario.
 
         Args:
             machine_name (str): The name of the device to connect.
             command (Union[List[str], str]): The command to exec on the device.
-            lab_hash (Optional[str]): The hash of the network scenario where the device is deployed.
-            lab_name (Optional[str]): The name of the network scenario where the device is deployed.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             wait (Union[bool, Tuple[int, float]]): If True, wait indefinitely until the end of the startup commands
                 execution before executing the command. If a tuple is provided, the first value indicates the
                 number of retries before stopping waiting and the second value indicates the time interval to wait
-                for each retry. Default is False. No effect on Kubernetes.
+                for each retry. Default is False.
 
         Returns:
             Generator[Tuple[bytes, bytes]]: A generator of tuples containing the stdout and stderr in bytes.
@@ -356,17 +364,17 @@ def exec(self, machine_name: str, command: Union[List[str], str], lab_hash: Opti
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        if not lab_hash and not lab_name:
-            raise InvocationError("You must specify a running network scenario hash or name.")
-
-        if wait:
-            logging.warning("Wait option has no effect on Megalos.")
-
-        if lab_name:
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
         lab_hash = lab_hash.lower()
 
+        if wait:
+            logging.warning("Wait option has no effect on Megalos.")
+
         return self.k8s_machine.exec(lab_hash, machine_name, command, stderr=True, tty=False, is_stream=True)
 
     def copy_files(self, machine: Machine, guest_to_host: Dict[str, io.IOBase]) -> None:
@@ -384,16 +392,18 @@ def copy_files(self, machine: Machine, guest_to_host: Dict[str, io.IOBase]) -> N
 
         self.k8s_machine.copy_files(machine.api_object, path="/", tar_data=tar_data)
 
-    def get_machine_api_object(self, machine_name: str, lab_hash: str = None, lab_name: str = None,
-                               all_users: bool = False) -> client.V1Pod:
+    def get_machine_api_object(self, machine_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                               lab: Optional[Lab] = None, all_users: bool = False) -> client.V1Pod:
         """Return the corresponding API object of a running device in a network scenario.
 
         Args:
             machine_name (str): The name of the device.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_hash should be set.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             all_users (bool): If True, return information about devices of all users.
 
         Returns:
@@ -403,55 +413,64 @@ def get_machine_api_object(self, machine_name: str, lab_hash: str = None, lab_na
             InvocationError: If a running network scenario hash or name is not specified.
             MachineNotFoundError: If the device is not found.
         """
-        if all_users:
-            logging.warning("User-specific options have no effect on Megalos.")
-
-        if not lab_hash and not lab_name:
-            raise InvocationError("You must specify a running network scenario hash or name.")
-
-        if lab_name:
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
         lab_hash = lab_hash.lower()
 
+        if all_users:
+            logging.warning("User-specific options have no effect on Megalos.")
+
         pods = self.k8s_machine.get_machines_api_objects_by_filters(lab_hash=lab_hash, machine_name=machine_name)
         if pods:
             return pods.pop()
 
         raise MachineNotFoundError(f"Device {machine_name} not found.")
 
-    def get_machines_api_objects(self, lab_hash: str = None, lab_name: str = None, all_users: bool = False) -> \
-            List[client.V1Pod]:
+    def get_machines_api_objects(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                                 lab: Optional[Lab] = None, all_users: bool = False) -> List[client.V1Pod]:
         """Return API objects of running devices.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
             all_users (bool): If True, return information about devices of all users.
 
         Returns:
             List[client.V1Pod]: Kubernetes Pod objects of devices.
         """
-        if all_users:
-            logging.warning("User-specific options have no effect on Megalos.")
-
-        if lab_name:
+        check_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
         lab_hash = lab_hash.lower() if lab_hash else None
 
+        if all_users:
+            logging.warning("User-specific options have no effect on Megalos.")
+
         return self.k8s_machine.get_machines_api_objects_by_filters(lab_hash=lab_hash)
 
-    def get_link_api_object(self, link_name: str, lab_hash: str = None, lab_name: str = None,
-                            all_users: bool = False) -> Any:
+    def get_link_api_object(self, link_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                            lab: Optional[Lab] = None, all_users: bool = False) -> Any:
         """Return the corresponding API object of a collision domain in a network scenario.
 
         Args:
             link_name (str): The name of the collision domain.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_hash should be set.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             all_users (bool): If True, return information about collision domains of all users.
 
         Returns:
@@ -461,68 +480,51 @@ def get_link_api_object(self, link_name: str, lab_hash: str = None, lab_name: st
             InvocationError: If a running network scenario hash or name is not specified.
             LinkNotFoundError: If the collision domain is not found.
         """
-        if all_users:
-            logging.warning("User-specific options have no effect on Megalos.")
-
-        if not lab_hash and not lab_name:
-            raise InvocationError("You must specify a running network scenario hash or name.")
-
-        if lab_name:
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
         lab_hash = lab_hash.lower()
 
+        if all_users:
+            logging.warning("User-specific options have no effect on Megalos.")
+
         networks = self.k8s_link.get_links_api_objects_by_filters(lab_hash=lab_hash, link_name=link_name)
         if networks:
             return networks.pop()
 
         raise LinkNotFoundError(f"Collision Domain {link_name} not found.")
 
-    def get_links_api_objects(self, lab_hash: str = None, lab_name: str = None, all_users: bool = False) -> \
-            List[Any]:
+    def get_links_api_objects(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                              lab: Optional[Lab] = None, all_users: bool = False) -> List[Any]:
         """Return API objects of collision domains in a network scenario.
 
         Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
             all_users (bool): If True, return information about collision domains of all users.
 
         Returns:
             List[Any]: Kubernetes API objects of networks.
         """
-        if all_users:
-            logging.warning("User-specific options have no effect on Megalos.")
-
-        if lab_name:
+        check_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
         lab_hash = lab_hash.lower() if lab_hash else None
 
-        return self.k8s_link.get_links_api_objects_by_filters(lab_hash=lab_hash)
-
-    def get_machines_stats(self, lab_hash: str = None, lab_name: str = None, machine_name: str = None,
-                           all_users: bool = False) -> Generator[Dict[str, KubernetesMachineStats], None, None]:
-        """Return information about the running devices.
-
-        Args:
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
-            machine_name (str): If specified return all the devices with machine_name.
-            all_users (bool): If True, return information about the device of all users.
-
-        Returns:
-              Generator[Dict[str, KubernetesMachineStats], None, None]: A generator containing dicts that has API Object
-                identifier as keys and KubernetesMachineStats objects as values.
-        """
         if all_users:
             logging.warning("User-specific options have no effect on Megalos.")
 
-        if lab_name:
-            lab_hash = utils.generate_urlsafe_hash(lab_name)
-
-        lab_hash = lab_hash.lower() if lab_hash else None
-
-        return self.k8s_machine.get_machines_stats(lab_hash=lab_hash, machine_name=machine_name)
+        return self.k8s_link.get_links_api_objects_by_filters(lab_hash=lab_hash)
 
     def get_lab_from_api(self, lab_hash: str = None, lab_name: str = None) -> Lab:
         """Return the network scenario (specified by the hash or name), building it from API objects.
@@ -582,7 +584,12 @@ def get_lab_from_api(self, lab_hash: str = None, lab_name: str = None) -> Lab:
                 network = lab_networks[network_conf['name']]
                 link = reconstructed_lab.get_or_new_link(network['metadata']['labels']['name'])
                 link.api_object = network
-                device.add_interface(link)
+
+                iface_mac_addr = None
+                if "mac" in network_conf:
+                    iface_mac_addr = network_conf['mac']
+
+                device.add_interface(link, mac_address=iface_mac_addr)
 
         return reconstructed_lab
 
@@ -597,16 +604,51 @@ def update_lab_from_api(self, lab: Lab) -> None:
         """
         raise NotSupportedError("Unable to update a running network scenario.")
 
-    def get_machine_stats(self, machine_name: str, lab_hash: str = None, lab_name: str = None,
-                          all_users: bool = False) -> Generator[KubernetesMachineStats, None, None]:
+    def get_machines_stats(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                           lab: Optional[Lab] = None, machine_name: str = None, all_users: bool = False) \
+            -> Generator[Dict[str, KubernetesMachineStats], None, None]:
+        """Return information about the running devices.
+
+        Args:
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
+            machine_name (str): If specified return all the devices with machine_name.
+            all_users (bool): If True, return information about the device of all users.
+
+        Returns:
+              Generator[Dict[str, KubernetesMachineStats], None, None]: A generator containing dicts that has API Object
+                identifier as keys and KubernetesMachineStats objects as values.
+        """
+        check_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
+            lab_hash = utils.generate_urlsafe_hash(lab_name)
+
+        lab_hash = lab_hash.lower() if lab_hash else None
+
+        if all_users:
+            logging.warning("User-specific options have no effect on Megalos.")
+
+        return self.k8s_machine.get_machines_stats(lab_hash=lab_hash, machine_name=machine_name)
+
+    def get_machine_stats(self, machine_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                          lab: Optional[Lab] = None, all_users: bool = False) \
+            -> Generator[KubernetesMachineStats, None, None]:
         """Return information of the specified device in a specified network scenario.
 
         Args:
             machine_name (str): The device name.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
-                If None, lab_hash should be set.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
             all_users (bool): If True, search the device among all the users devices.
 
         Returns:
@@ -615,10 +657,10 @@ def get_machine_stats(self, machine_name: str, lab_hash: str = None, lab_name: s
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        if not lab_hash and not lab_name:
-            raise InvocationError("You must specify a running network scenario hash or name.")
-
-        if lab_name:
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
         lab_hash = lab_hash.lower()
@@ -628,13 +670,18 @@ def get_machine_stats(self, machine_name: str, lab_hash: str = None, lab_name: s
 
         yield machine_stats
 
-    def get_links_stats(self, lab_hash: str = None, lab_name: str = None, link_name: str = None,
-                        all_users: bool = False) -> Generator[Dict[str, KubernetesLinkStats], None, None]:
-        """Return information about deployed Kubernetes networks.
+    def get_links_stats(self, lab_hash: Optional[str] = None, lab_name: Optional[str] = None, lab: Optional[Lab] = None,
+                        link_name: str = None, all_users: bool = False) \
+            -> Generator[Dict[str, KubernetesLinkStats], None, None]:
+        """Return information about deployed networks.
 
         Args:
-           lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-           lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name.
            link_name (str): If specified return all the networks with link_name.
            all_users (bool): If True, return information about the networks of all users.
 
@@ -642,27 +689,33 @@ def get_links_stats(self, lab_hash: str = None, lab_name: str = None, link_name:
              Generator[Dict[str, KubernetesLinkStats], None, None]: A generator containing dicts that has API Object
                 identifier as keys and KubernetesLinkStats objects as values.
         """
-        if all_users:
-            logging.warning("User-specific options have no effect on Megalos.")
-
-        if lab_name:
+        check_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
         lab_hash = lab_hash.lower() if lab_hash else None
 
+        if all_users:
+            logging.warning("User-specific options have no effect on Megalos.")
+
         return self.k8s_link.get_links_stats(lab_hash=lab_hash, link_name=link_name)
 
-    def get_link_stats(self, link_name: str, lab_hash: str = None, lab_name: str = None, all_users: bool = False) -> \
-            Generator[KubernetesLinkStats, None, None]:
+    def get_link_stats(self, link_name: str, lab_hash: Optional[str] = None, lab_name: Optional[str] = None,
+                       lab: Optional[Lab] = None, all_users: bool = False) \
+            -> Generator[KubernetesLinkStats, None, None]:
         """Return information of the specified deployed network in a specified network scenario.
 
         Args:
-            link_name (str): The link name.
-            lab_hash (str): The hash of the network scenario. Can be used as an alternative to lab_name.
-                If None, lab_name should be set.
-            lab_name (str): The name of the network scenario. Can be used as an alternative to lab_hash.
-                If None, lab_hash should be set.
-            all_users (bool): If True, search the network among all the users networks.
+            link_name (str): If specified return all the networks with link_name.
+            lab_hash (Optional[str]): The hash of the network scenario.
+                Can be used as an alternative to lab_name and lab. If None, lab_name or lab should be set.
+            lab_name (Optional[str]): The name of the network scenario.
+                Can be used as an alternative to lab_hash and lab. If None, lab_hash or lab should be set.
+            lab (Optional[Kathara.model.Lab]): The network scenario object.
+                Can be used as an alternative to lab_hash and lab_name. If None, lab_hash or lab_name should be set.
+            all_users (bool): If True, return information about the networks of all users.
 
         Returns:
             Generator[KubernetesLinkStats, None, None]: A generator containing KubernetesLinkStats objects with
@@ -671,10 +724,10 @@ def get_link_stats(self, link_name: str, lab_hash: str = None, lab_name: str = N
         Raises:
             InvocationError: If a running network scenario hash or name is not specified.
         """
-        if not lab_hash and not lab_name:
-            raise InvocationError("You must specify a running network scenario hash or name.")
-
-        if lab_name:
+        check_required_single_not_none_var(lab_hash=lab_hash, lab_name=lab_name, lab=lab)
+        if lab:
+            lab_hash = lab.hash
+        elif lab_name:
             lab_hash = utils.generate_urlsafe_hash(lab_name)
 
         lab_hash = lab_hash.lower()
diff --git a/src/Kathara/model/Interface.py b/src/Kathara/model/Interface.py
new file mode 100644
index 00000000..3ab55d16
--- /dev/null
+++ b/src/Kathara/model/Interface.py
@@ -0,0 +1,34 @@
+import re
+from typing import Optional
+
+from . import Link as LinkPackage
+from . import Machine as MachinePackage
+from ..exceptions import InterfaceMacAddressError
+
+MAC_ADDRESS_REGEX = re.compile(r"^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$")
+
+
+class Interface(object):
+    """Interface object associated to a Machine network interface.
+
+    Attributes:
+        machine (Kathara.model.Machine.Machine): The machine associated to this interface.
+        link (Kathara.model.Link.Link): The collision domain associated to this interface.
+        num (int): The interface number.
+        mac_address (Optional[str]): The MAC address of the interface. If None, a generated MAC address
+            is associated when the Machine is started.
+    """
+    __slots__ = ['machine', 'link', 'num', 'mac_address']
+
+    def __init__(self, machine: 'MachinePackage.Machine', link: 'LinkPackage.Link',
+                 num: int, mac_address: Optional[str] = None) -> None:
+        self.machine: 'MachinePackage.Machine' = machine
+        self.link: 'LinkPackage.Link' = link
+        self.num: int = num
+        self.mac_address: Optional[str] = mac_address
+
+        if self.mac_address and not MAC_ADDRESS_REGEX.match(self.mac_address):
+            raise InterfaceMacAddressError(self.mac_address, self.num, machine.name)
+
+    def __repr__(self) -> str:
+        return "Interface(%s, %d, %s)" % (self.machine.name, self.num, self.mac_address)
diff --git a/src/Kathara/model/Lab.py b/src/Kathara/model/Lab.py
index 83005fee..4caab092 100644
--- a/src/Kathara/model/Lab.py
+++ b/src/Kathara/model/Lab.py
@@ -5,9 +5,10 @@
 from fs import open_fs
 from fs.base import FS
 
+from . import Interface as InterfacePackage
+from . import Link as LinkPackage
 from . import Machine as MachinePackage
 from .ExternalLink import ExternalLink
-from .Link import Link
 from .. import utils
 from ..exceptions import LinkNotFoundError, MachineNotFoundError, MachineAlreadyExistsError, LinkAlreadyExistsError
 from ..foundation.model.FilesystemMixin import FilesystemMixin
@@ -58,7 +59,7 @@ def __init__(self, name: Optional[str], path: Optional[str] = None) -> None:
         self.web: Optional[str] = None
 
         self.machines: Dict[str, 'MachinePackage.Machine'] = {}
-        self.links: Dict[str, Link] = {}
+        self.links: Dict[str, 'LinkPackage.Link'] = {}
 
         self.general_options: Dict[str, Any] = {}
 
@@ -82,8 +83,9 @@ def name(self, value):
         self._name = value
         self.hash = utils.generate_urlsafe_hash(value)
 
-    def connect_machine_to_link(self, machine_name: str, link_name: str, machine_iface_number: int = None) \
-            -> Tuple['MachinePackage.Machine', Link]:
+    def connect_machine_to_link(self, machine_name: str, link_name: str,
+                                machine_iface_number: int = None, mac_address: Optional[str] = None) \
+            -> Tuple['MachinePackage.Machine', 'InterfacePackage.Interface']:
         """Connect the specified device to the specified collision domain.
 
         Args:
@@ -91,10 +93,11 @@ def connect_machine_to_link(self, machine_name: str, link_name: str, machine_ifa
             link_name (str): The collision domain name.
             machine_iface_number (int): The number of the device interface to connect. If it is None, the first free
                 number is used.
+            mac_address (Optional[str]): The MAC address to assign to the interface.
 
         Returns:
-            Tuple[Kathara.model.Machine, Kathara.model.Link]: A tuple containing the Kathara device and collision domain
-                specified by their names.
+            Tuple[Kathara.model.Machine.Machine, Kathara.model.Interface.Interface]: A tuple containing the Kathara
+                device and the interface object specified by their names.
 
         Raises:
             Exception: If an already used interface number is specified.
@@ -102,12 +105,13 @@ def connect_machine_to_link(self, machine_name: str, link_name: str, machine_ifa
         machine = self.get_or_new_machine(machine_name)
         link = self.get_or_new_link(link_name)
 
-        machine.add_interface(link, number=machine_iface_number)
+        interface = machine.add_interface(link, number=machine_iface_number, mac_address=mac_address)
 
-        return machine, link
+        return machine, interface
 
-    def connect_machine_obj_to_link(self, machine: 'MachinePackage.Machine',
-                                    link_name: str, machine_iface_number: int = None) -> Tuple[Link, Optional[int]]:
+    def connect_machine_obj_to_link(self, machine: 'MachinePackage.Machine', link_name: str,
+                                    machine_iface_number: int = None, mac_address: Optional[str] = None) \
+            -> 'InterfacePackage.Interface':
         """Connect the specified device object to the specified collision domain.
 
         Args:
@@ -117,17 +121,16 @@ def connect_machine_obj_to_link(self, machine: 'MachinePackage.Machine',
                 number is used.
 
         Returns:
-            Tuple[Kathara.model.Link, Optional[int]]: A tuple containing the collision domain and
-                the assigned interface number (if machine_iface_number is None).
+            Kathara.model.Interface.Interface: The interface object associated to the new interface..
 
         Raises:
             Exception: If an already used interface number is specified.
         """
         link = self.get_or_new_link(link_name)
 
-        machine_iface_number = machine.add_interface(link, number=machine_iface_number)
+        interface = machine.add_interface(link, number=machine_iface_number, mac_address=mac_address)
 
-        return link, machine_iface_number
+        return interface
 
     def assign_meta_to_machine(self, machine_name: str, meta_name: str, meta_value: str) -> Optional[Any]:
         """Assign meta information to the specified device.
@@ -192,7 +195,7 @@ def get_links_from_machines(self, machines: Union[List[str], Set[str]]) -> Set[s
 
         # Get only selected machines Link objects.
         selected_links = set(chain.from_iterable([machine.interfaces.values() for machine in machines]))
-        selected_links = {link.name for link in selected_links}
+        selected_links = {interface.link.name for interface in selected_links}
 
         return selected_links
 
@@ -214,7 +217,7 @@ def get_links_from_machine_objs(self,
 
         # Get only selected machines Link objects.
         selected_links = set(chain.from_iterable([machine.interfaces.values() for machine in machines]))
-        selected_links = {link.name for link in selected_links}
+        selected_links = {interface.link.name for interface in selected_links}
 
         return selected_links
 
@@ -291,7 +294,7 @@ def get_or_new_machine(self, name: str, **kwargs: Dict[str, Any]) -> 'MachinePac
 
         return self.machines[name]
 
-    def get_link(self, name: str) -> Link:
+    def get_link(self, name: str) -> 'LinkPackage.Link':
         """Get the specified collision domain.
 
         Args:
@@ -308,7 +311,7 @@ def get_link(self, name: str) -> Link:
 
         return self.links[name]
 
-    def new_link(self, name: str) -> Link:
+    def new_link(self, name: str) -> 'LinkPackage.Link':
         """Create the collision domain and add it to the collision domains list.
 
         Args:
@@ -323,11 +326,11 @@ def new_link(self, name: str) -> Link:
         if name in self.links:
             raise LinkAlreadyExistsError(f"Collision domain {name} is already the network scenario.")
 
-        self.links[name] = Link(self, name)
+        self.links[name] = LinkPackage.Link(self, name)
 
         return self.links[name]
 
-    def get_or_new_link(self, name: str) -> Link:
+    def get_or_new_link(self, name: str) -> 'LinkPackage.Link':
         """Get the specified collision domain. If it not exists, create and add it to the collision domains list.
 
         Args:
@@ -337,7 +340,7 @@ def get_or_new_link(self, name: str) -> Link:
             Kathara.model.Link: A Kathara collision domain.
         """
         if name not in self.links:
-            self.links[name] = Link(self, name)
+            self.links[name] = LinkPackage.Link(self, name)
 
         return self.links[name]
 
diff --git a/src/Kathara/model/Machine.py b/src/Kathara/model/Machine.py
index 9dbd5730..00b37c74 100644
--- a/src/Kathara/model/Machine.py
+++ b/src/Kathara/model/Machine.py
@@ -13,9 +13,9 @@
 from fs.tempfs import TempFS
 from fs.walk import Walker
 
+from . import Interface as InterfacePackage
 from . import Lab as LabPackage
 from . import Link as LinkPackage
-from .Link import Link
 from .. import utils
 from ..exceptions import NonSequentialMachineInterfaceError, MachineOptionError, MachineCollisionDomainError
 from ..foundation.model.FilesystemMixin import FilesystemMixin
@@ -61,7 +61,7 @@ def __init__(self, lab: 'LabPackage.Lab', name: str, **kwargs) -> None:
         self.lab: LabPackage.Lab = lab
         self.name: str = name
 
-        self.interfaces: OrderedDict[int, Link] = collections.OrderedDict()
+        self.interfaces: OrderedDict[int, 'InterfacePackage.Interface'] = collections.OrderedDict()
 
         self.meta: Dict[str, Any] = {
             'exec_commands': [],
@@ -77,24 +77,25 @@ def __init__(self, lab: 'LabPackage.Lab', name: str, **kwargs) -> None:
 
         self.update_meta(kwargs)
 
-    def add_interface(self, link: 'LinkPackage.Link', number: int = None) -> Optional[int]:
+    def add_interface(self, link: 'LinkPackage.Link', number: int = None, mac_address: str = None) \
+            -> 'InterfacePackage.Interface':
         """Add an interface to the device attached to the specified collision domain.
 
         Args:
             link (Kathara.model.Link): The Kathara collision domain to attach.
             number (int): The number of the new interface. If it is None, the first free number is selected.
+            mac_address (str): The MAC address of the interface. If None, a generated MAC address
+                is associated when the Machine is started.
 
         Returns:
-            Optional[int]: The number of the assigned interface if not passed, else None.
+            Interface: The object associated to this interface.
 
         Raises:
             MachineCollisionDomainConflictError: If the interface number specified is already used on the device.
             MachineCollisionDomainConflictError: If the device is already connected to the collision domain.
         """
-        had_number = True
         if number is None:
             number = len(self.interfaces.keys())
-            had_number = False
 
         if number in self.interfaces:
             raise MachineCollisionDomainError(f"Interface {number} already set on device `{self.name}`.")
@@ -104,10 +105,11 @@ def add_interface(self, link: 'LinkPackage.Link', number: int = None) -> Optiona
                 f"Device `{self.name}` is already connected to collision domain `{link.name}`."
             )
 
-        self.interfaces[number] = link
+        interface = InterfacePackage.Interface(self, link, number, mac_address)
+        self.interfaces[number] = interface
         link.machines[self.name] = self
 
-        return number if not had_number else None
+        return interface
 
     def remove_interface(self, link: 'LinkPackage.Link') -> None:
         """Disconnect the device from the specified collision domain.
@@ -127,11 +129,14 @@ def remove_interface(self, link: 'LinkPackage.Link') -> None:
             )
 
         self.interfaces = collections.OrderedDict(
-            map(lambda x: x if x[1] is not None and x[1].name != link.name else (x[0], None), self.interfaces.items())
+            map(
+                lambda x: x if x[1] is not None and x[1].link.name != link.name else (x[0], None),
+                self.interfaces.items()
+            )
         )
         link.machines.pop(self.name)
 
-    def add_meta(self, name: str, value: Any) -> Optional[Any]:
+    def add_meta(self, name: str, value: Any) -> None:
         """Add a meta property to the device.
 
         Args:
@@ -154,7 +159,7 @@ def add_meta(self, name: str, value: Any) -> Optional[Any]:
             return old_value
 
         if name == "sysctl":
-            matches = re.search(r"^(?P<key>net\.([\w-]+\.)+[\w-]+)=(?P<value>-?\w+)$", value)
+            matches = re.search(r"^(?P<key>net\.([\w-]+\.)+[\w-]+)=(?P<value>[^=]*)$", value)
 
             # Check for valid kv-pair
             if matches:
@@ -164,7 +169,7 @@ def add_meta(self, name: str, value: Any) -> Optional[Any]:
                 old_value = self.meta['sysctls'][key] if key in self.meta['sysctls'] else None
 
                 # Convert to int if possible
-                self.meta['sysctls'][key] = int(val) if val.strip('-').isnumeric() else val
+                self.meta['sysctls'][key] = int(val) if val.lstrip('-').isnumeric() else val
             else:
                 raise MachineOptionError(
                     "Invalid sysctl value (`%s`) on `%s`, missing `=` or value not in `net.` namespace."
@@ -173,7 +178,7 @@ def add_meta(self, name: str, value: Any) -> Optional[Any]:
             return old_value
 
         if name == "env":
-            matches = re.search(r"^(?P<key>\w+)=(?P<value>\S+)$", value)
+            matches = re.search(r"^(?P<key>\w+)=(?P<value>[^=]*)$", value)
 
             # Check for valid kv-pair
             if matches:
@@ -595,6 +600,21 @@ def create_file_from_stream(self, stream: Union[BinaryIO, TextIO], dst_path: str
 
         super().create_file_from_stream(stream, dst_path)
 
+    def copy_directory_from_path(self, src_path: str, dst_path: str) -> None:
+        """Copy a directory from a src_path in the host filesystem into a dst_path in the fs of the device.
+
+        Args:
+             src_path (str): The source path of the directory to copy.
+             dst_path (str): The destination path on the device where to copy the directory.
+
+        Returns:
+            None
+        """
+        if not self.fs:
+            self.fs = self.lab.fs.makedir(self.name, recreate=True)
+
+        super().copy_directory_from_path(src_path, dst_path)
+
     def write_line_before(self, file_path: str, line_to_add: str, searched_line: str, first_occurrence: bool = False) \
             -> int:
         """Write a new line before a specific line in a file.
@@ -671,11 +691,14 @@ def __str__(self) -> str:
 
         if self.interfaces:
             formatted_machine += "\nInterfaces: "
-            for (iface_num, link) in self.interfaces.items():
-                if link:
-                    formatted_machine += f"\n\t- {iface_num}: {link.name}"
-
-        formatted_machine += f"\nBridged Connection: {self.meta['bridged']}"
+            for (iface_num, interface) in self.interfaces.items():
+                if interface:
+                    formatted_machine += f"\n\t- {iface_num}: {interface.link.name}"
+                    if interface.mac_address:
+                        formatted_machine += f" (MAC Address: {interface.mac_address})"
+
+        if 'bridged' in self.meta:
+            formatted_machine += f"\nBridged Connection: {self.meta['bridged']}"
 
         if self.meta["sysctls"]:
             formatted_machine += "\nSysctls:"
diff --git a/src/Kathara/parser/netkit/DepParser.py b/src/Kathara/parser/netkit/DepParser.py
index b574243e..3213da39 100644
--- a/src/Kathara/parser/netkit/DepParser.py
+++ b/src/Kathara/parser/netkit/DepParser.py
@@ -39,7 +39,7 @@ def parse(path: str) -> Optional[List[str]]:
 
         dependencies = {}
 
-        # Reads lab.dep in memory so it is faster.
+        # Reads lab.dep in memory, so it is faster.
         try:
             with open(lab_dep_path, 'r') as dep_file:
                 dep_mem_file = mmap.mmap(dep_file.fileno(), 0, access=mmap.ACCESS_READ)
diff --git a/src/Kathara/parser/netkit/LabParser.py b/src/Kathara/parser/netkit/LabParser.py
index 9b4fd6d1..884e13b3 100644
--- a/src/Kathara/parser/netkit/LabParser.py
+++ b/src/Kathara/parser/netkit/LabParser.py
@@ -4,36 +4,37 @@
 import re
 
 from ...model.Lab import Lab, LAB_METADATA
-from ...utils import RESERVED_MACHINE_NAMES
+from ...utils import parse_cd_mac_address, RESERVED_MACHINE_NAMES
 
 
 class LabParser(object):
     """Class responsible for parsing the lab.conf file."""
 
     @staticmethod
-    def parse(path: str) -> Lab:
-        """Parse the lab.conf and return the corresponding Kathara network scenario.
+    def parse(path: str, conf_name: str = "lab.conf") -> Lab:
+        """Parse the lab configuration identified by conf_name and return the corresponding Kathara network scenario.
 
         Args:
-            path (str): The path to lab.conf file.
+            path (str): The path to the directory containing the configuration file.
+            conf_name (str): The name of the network scenario configuration file (default is 'lab.conf').
 
         Returns:
             Kathara.model.Lab.Lab: A Kathara network scenario.
         """
-        lab_conf_path = os.path.join(path, 'lab.conf')
+        lab_conf_path = os.path.join(path, conf_name)
 
         if not os.path.exists(lab_conf_path):
-            raise IOError("No lab.conf in given directory.")
+            raise IOError(f"No {conf_name} in given directory.")
 
         if os.stat(lab_conf_path).st_size == 0:
-            raise IOError("lab.conf file is empty.")
+            raise IOError(f"{conf_name} file is empty.")
 
-        # Reads lab.conf in memory so it is faster.
+        # Reads lab.conf in memory, so it is faster.
         try:
             with open(lab_conf_path, 'r') as lab_file:
                 lab_mem_file = mmap.mmap(lab_file.fileno(), 0, access=mmap.ACCESS_READ)
         except Exception:
-            raise IOError("Cannot open lab.conf file.")
+            raise IOError(f"Cannot open {conf_name} file.")
 
         lab = Lab(None, path=path)
 
@@ -51,29 +52,35 @@ def parse(path: str) -> Lab:
                 value = matches.group("value").replace('"', '').replace("'", '')
 
                 if key in RESERVED_MACHINE_NAMES:
-                    raise ValueError(f"In lab.conf - Line {line_number}: "
+                    raise ValueError(f"In {conf_name} - Line {line_number}: "
                                      f"`{key}` is a reserved name, you can not use it for a device.")
 
                 try:
                     # It's an interface, handle it.
                     interface_number = int(arg)
 
-                    if re.search(r"^\w+$", value):
-                        lab.connect_machine_to_link(key, value, machine_iface_number=interface_number)
+                    try:
+                        cd_name, mac_address = parse_cd_mac_address(value)
+                    except SyntaxError as e:
+                        raise SyntaxError(f"In {conf_name} - Line {line_number}: {str(e)}")
+
+                    if re.search(r"^\w+$", cd_name):
+                        lab.connect_machine_to_link(key, cd_name,
+                                                    machine_iface_number=interface_number, mac_address=mac_address)
                     else:
-                        raise SyntaxError(f"In lab.conf - Line {line_number}: "
+                        raise SyntaxError(f"In {conf_name} - Line {line_number}: "
                                           f"Collision domain `{value}` contains non-alphanumeric characters.")
                 except ValueError:
                     # Not an interface, add it to the machine metas.
                     if lab.assign_meta_to_machine(key, arg, value) is not None:
-                        logging.warning(f"In lab.conf - Line {line_number}: "
+                        logging.warning(f"In {conf_name} - Line {line_number}: "
                                         f"Device `{key}` already has a value assigned to meta `{arg}`. "
                                         f"Previous value has been overwritten with `{value}`.")
             else:
                 if not line.startswith('#') and \
                         line.strip():
                     if not any([line.startswith(f"{x}=") for x in LAB_METADATA]):
-                        raise SyntaxError(f"In lab.conf - Line {line_number}: `{line}`.")
+                        raise SyntaxError(f"In {conf_name} - Line {line_number}: `{line}`.")
                     else:
                         (key, value) = line.split("=")
                         key = key.replace("LAB_", "").lower()
diff --git a/src/Kathara/setting/addon/DockerSettingsAddon.py b/src/Kathara/setting/addon/DockerSettingsAddon.py
index 9da87411..e988ce5b 100644
--- a/src/Kathara/setting/addon/DockerSettingsAddon.py
+++ b/src/Kathara/setting/addon/DockerSettingsAddon.py
@@ -1,12 +1,13 @@
 from typing import Optional, Dict, Any
 
 from ...foundation.setting.SettingsAddon import SettingsAddon
+from ...types import SharedCollisionDomainsOption
 
 DEFAULTS = {
     "hosthome_mount": False,
     "shared_mount": True,
     "image_update_policy": "Prompt",
-    "shared_cd": False,
+    "shared_cds": SharedCollisionDomainsOption.NOT_SHARED,
     "remote_url": None,
     "cert_path": None,
     "network_plugin": "kathara/katharanp_vde"
@@ -14,14 +15,14 @@
 
 
 class DockerSettingsAddon(SettingsAddon):
-    __slots__ = ['hosthome_mount', 'shared_mount', 'image_update_policy', 'shared_cd', 'remote_url', 'cert_path',
-                 'network_plugin']
+    __slots__ = ['hosthome_mount', 'shared_mount', 'image_update_policy', 'shared_cds',
+                 'remote_url', 'cert_path', 'network_plugin']
 
     def __init__(self) -> None:
         self.hosthome_mount: bool = False
         self.shared_mount: bool = True
         self.image_update_policy: str = 'Prompt'
-        self.shared_cd: bool = False
+        self.shared_cds: int = SharedCollisionDomainsOption.NOT_SHARED
         self.remote_url: Optional[str] = None
         self.cert_path: Optional[str] = None
         self.network_plugin: Optional[str] = "kathara/katharanp_vde"
@@ -31,7 +32,7 @@ def _to_dict(self) -> Dict[str, Any]:
             'hosthome_mount': self.hosthome_mount,
             'shared_mount': self.shared_mount,
             'image_update_policy': self.image_update_policy,
-            'shared_cd': self.shared_cd,
+            'shared_cds': self.shared_cds,
             'remote_url': self.remote_url,
             'cert_path': self.cert_path,
             'network_plugin': self.network_plugin
diff --git a/src/Kathara/types.py b/src/Kathara/types.py
new file mode 100644
index 00000000..00dea9f5
--- /dev/null
+++ b/src/Kathara/types.py
@@ -0,0 +1,25 @@
+from enum import IntEnum
+
+
+class SharedCollisionDomainsOption(IntEnum):
+    """Enum representing options for shared collision domains option.
+
+    Attributes:
+        NOT_SHARED (int): Represents the option for not sharing collision domains (value: 1).
+        LABS (int): Represents the option for sharing collision domains among network scenarios of the same user
+            (value: 2).
+        USERS (int): Represents the option for sharing collision domains among network scenarios of different users
+            (value: 3).
+    """
+    NOT_SHARED = 1
+    LABS = 2
+    USERS = 3
+
+    @staticmethod
+    def to_string(value):
+        if value == 1:
+            return "Not Shared"
+        elif value == 2:
+            return "Share collision domains between network scenarios"
+        elif value == 3:
+            return "Share collision domains between users"
diff --git a/src/Kathara/utils.py b/src/Kathara/utils.py
index b8904bb2..c9552e46 100644
--- a/src/Kathara/utils.py
+++ b/src/Kathara/utils.py
@@ -16,12 +16,12 @@
 from platform import node, machine
 from sys import platform as _platform
 from types import ModuleType
-from typing import Any, Optional, Match, Generator, List, Callable, Union, Dict, Iterable
+from typing import Any, Optional, Match, Generator, List, Callable, Union, Dict, Iterable, Tuple
 
 from binaryornot.check import is_binary
 from slug import slug
 
-from .exceptions import HostArchitectureError
+from .exceptions import HostArchitectureError, InvocationError
 
 # Platforms constants definition.
 MAC_OS: str = "darwin"
@@ -109,6 +109,22 @@ def get_pool_size() -> int:
     return cpu_count()
 
 
+def check_single_not_none_var(**kwargs) -> None:
+    not_none = [x for x in kwargs.values() if x is not None]
+
+    if len(not_none) > 1:
+        raise InvocationError(f"You must specify only a parameter among {', '.join(kwargs.keys())}")
+
+
+def check_required_single_not_none_var(**kwargs) -> None:
+    not_none = [x for x in kwargs.values() if x is not None]
+
+    if len(not_none) == 0:
+        raise InvocationError(f"You must specify a parameter among {', '.join(kwargs.keys())}")
+    elif len(not_none) > 1:
+        raise InvocationError(f"You must specify only a parameter among {', '.join(kwargs.keys())}")
+
+
 # Platform Specific Functions
 def is_platform(desired_platform: str) -> bool:
     return _platform == desired_platform
@@ -154,26 +170,6 @@ def wait_user_input_windows() -> bool:
     return b'\r' in msvcrt.getch() if msvcrt.kbhit() else False
 
 
-# Architecture Test
-def get_architecture() -> str:
-    architecture = machine().lower()
-
-    logging.debug("Machine architecture is `%s`." % architecture)
-
-    if architecture in ["x86_64", "amd64"]:
-        return "amd64"
-    elif architecture == "i686":
-        return "386"
-    elif architecture in ["arm64", "aarch64"]:
-        return "arm64"
-    elif architecture == "armv7l":
-        return "armv7"
-    elif architecture == "armv6l":
-        return "armv6"
-    else:
-        raise HostArchitectureError(architecture)
-
-
 def convert_win_2_linux(filename: str, write: bool = False) -> Optional[bytes]:
     if not is_binary(filename):
         file_obj = None
@@ -260,6 +256,26 @@ def passwd_info():
     return exec_by_platform(passwd_info, lambda: None, passwd_info)
 
 
+# Architecture Test
+def get_architecture() -> str:
+    architecture = machine().lower()
+
+    logging.debug("Machine architecture is `%s`." % architecture)
+
+    if architecture in ["x86_64", "amd64"]:
+        return "amd64"
+    elif architecture == "i686":
+        return "386"
+    elif architecture in ["arm64", "aarch64"]:
+        return "arm64"
+    elif architecture == "armv7l":
+        return "armv7"
+    elif architecture == "armv6l":
+        return "armv6"
+    else:
+        raise HostArchitectureError(architecture)
+
+
 # Formatting Functions
 def human_readable_bytes(size_bytes: int) -> str:
     if size_bytes == 0:
@@ -306,3 +322,18 @@ def pack_files_for_tar(guest_to_host: Dict) -> bytes:
         tar_data = temp_file.read()
 
     return tar_data
+
+
+def parse_cd_mac_address(value) -> Tuple[str, str]:
+    if '/' in value:
+        parts = [x for x in value.split('/') if x]
+
+        if len(parts) != 2:
+            raise SyntaxError(f"Invalid interface definition: `{value}`.")
+
+        cd_name = parts[0]
+        mac_address = parts[1]
+    else:
+        (cd_name, mac_address) = value, None
+
+    return cd_name, mac_address
diff --git a/src/Kathara/version.py b/src/Kathara/version.py
index 47dd420e..55c7761f 100644
--- a/src/Kathara/version.py
+++ b/src/Kathara/version.py
@@ -1,6 +1,6 @@
 from typing import Tuple
 
-CURRENT_VERSION = "3.7.0"
+CURRENT_VERSION = "3.7.1"
 
 
 def parse(version: str) -> Tuple:
diff --git a/src/requirements.txt b/src/requirements.txt
index da5a96b4..486999fe 100644
--- a/src/requirements.txt
+++ b/src/requirements.txt
@@ -1,5 +1,5 @@
 binaryornot>=0.4.4;
-docker>=6.0.1;
+docker>=7.0.0;
 kubernetes>=23.3.0;
 requests>=2.22.0;
 coloredlogs>=10.0;


Test patch:
diff --git a/tests/cli/lconfig_command_test.py b/tests/cli/lconfig_command_test.py
index 4b2fa8ee..75a220cc 100644
--- a/tests/cli/lconfig_command_test.py
+++ b/tests/cli/lconfig_command_test.py
@@ -10,6 +10,7 @@
 from src.Kathara.model.Machine import Machine
 from src.Kathara.model.Link import Link
 from src.Kathara.model.Lab import Lab
+from src.Kathara.exceptions import MachineNotFoundError
 
 
 @pytest.fixture()
@@ -26,51 +27,148 @@ def test_lab():
 @mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
 @mock.patch("src.Kathara.parser.netkit.LabParser.LabParser.parse")
-def test_run_add_link(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
+def test_run_add_interface(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
+    test_lab.connect_machine_to_link("pc1", "A")
+
     mock_parse_lab.return_value = test_lab
     mock_manager_get_instance.return_value = mock_docker_manager
     command = LconfigCommand()
     command.run('.', ['-n', 'pc1', '--add', 'A'])
     mock_parse_lab.assert_called_once_with(os.getcwd())
     mock_docker_manager.update_lab_from_api.assert_called_once_with(test_lab)
-    mock_docker_manager.connect_machine_to_link.assert_called_once_with(test_lab.get_or_new_machine('pc1'),
-                                                                        test_lab.get_or_new_link('A'))
+    mock_docker_manager.connect_machine_to_link.assert_called_once_with(
+        test_lab.get_machine('pc1'),
+        test_lab.get_machine("pc1").interfaces[0].link,
+        mac_address=None
+    )
+
+
+@mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
+@mock.patch("src.Kathara.parser.netkit.LabParser.LabParser.parse")
+def test_run_add_interface_with_mac_address(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
+    test_lab.connect_machine_to_link("pc1", "A")
+
+    mock_parse_lab.return_value = test_lab
+    mock_manager_get_instance.return_value = mock_docker_manager
+    command = LconfigCommand()
+    command.run('.', ['-n', 'pc1', '--add', 'A/00:00:00:00:00:01'])
+    mock_parse_lab.assert_called_once_with(os.getcwd())
+    mock_docker_manager.update_lab_from_api.assert_called_once_with(test_lab)
+    mock_docker_manager.connect_machine_to_link.assert_called_once_with(
+        test_lab.get_machine('pc1'),
+        test_lab.get_machine('pc1').interfaces[0].link,
+        mac_address='00:00:00:00:00:01'
+    )
+
+
+@mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
+@mock.patch("src.Kathara.parser.netkit.LabParser.LabParser.parse")
+def test_run_add_interface_invalid_interface_definition(mock_parse_lab, mock_docker_manager, mock_manager_get_instance,
+                                                        test_lab):
+    test_lab.connect_machine_to_link("pc1", "A")
+
+    mock_parse_lab.return_value = test_lab
+    mock_manager_get_instance.return_value = mock_docker_manager
+    command = LconfigCommand()
+    with pytest.raises(SyntaxError):
+        command.run('.', ['-n', 'pc1', '--add', 'B/00:/00:00:00:00:01'])
 
 
 @mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
 @mock.patch("src.Kathara.parser.netkit.LabParser.LabParser.parse")
-def test_run_add_link_with_directory(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
+def test_run_add_interface_with_directory(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
+    test_lab.connect_machine_to_link("pc1", "A")
+
     mock_parse_lab.return_value = test_lab
     mock_manager_get_instance.return_value = mock_docker_manager
     command = LconfigCommand()
     command.run('.', ['-d', os.path.join('/test', 'path'), '-n', 'pc1', '--add', 'A'])
     mock_parse_lab.assert_called_once_with(os.path.abspath(os.path.join('/test', 'path')))
     mock_docker_manager.update_lab_from_api.assert_called_once_with(test_lab)
-    mock_docker_manager.connect_machine_to_link.assert_called_once_with(test_lab.get_or_new_machine('pc1'),
-                                                                        test_lab.get_or_new_link('A'))
+    mock_docker_manager.connect_machine_to_link.assert_called_once_with(
+        test_lab.get_machine('pc1'),
+        test_lab.get_machine("pc1").interfaces[0].link,
+        mac_address=None
+    )
 
 
 @mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
 @mock.patch("src.Kathara.parser.netkit.LabParser.LabParser.parse")
-def test_run_add_two_links(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
+def test_run_add_two_interfaces(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
+    test_lab.connect_machine_to_link("pc1", "A")
+    test_lab.connect_machine_to_link("pc1", "B")
+
     mock_parse_lab.return_value = test_lab
     mock_manager_get_instance.return_value = mock_docker_manager
     command = LconfigCommand()
     command.run('.', ['-n', 'pc1', '--add', 'A', 'B'])
     mock_parse_lab.assert_called_once_with(os.getcwd())
     mock_docker_manager.update_lab_from_api.assert_called_once_with(test_lab)
-    mock_docker_manager.connect_machine_to_link.assert_any_call(test_lab.get_or_new_machine('pc1'),
-                                                                test_lab.get_or_new_link('A'))
-    mock_docker_manager.connect_machine_to_link.assert_any_call(test_lab.get_or_new_machine('pc1'),
-                                                                test_lab.get_or_new_link('B'))
+    mock_docker_manager.connect_machine_to_link.assert_any_call(
+        test_lab.get_machine('pc1'),
+        test_lab.get_machine("pc1").interfaces[0].link,
+        mac_address=None
+    )
+    mock_docker_manager.connect_machine_to_link.assert_any_call(
+        test_lab.get_machine('pc1'),
+        test_lab.get_machine("pc1").interfaces[1].link,
+        mac_address=None
+    )
+
+
+@mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
+@mock.patch("src.Kathara.parser.netkit.LabParser.LabParser.parse")
+def test_run_add_two_interfaces_one_mac_address(mock_parse_lab, mock_docker_manager, mock_manager_get_instance,
+                                                test_lab):
+    test_lab.connect_machine_to_link("pc1", "A")
+    test_lab.connect_machine_to_link("pc1", "B")
+
+    mock_parse_lab.return_value = test_lab
+    mock_manager_get_instance.return_value = mock_docker_manager
+    command = LconfigCommand()
+    command.run('.', ['-n', 'pc1', '--add', 'A', 'B/00:00:00:00:00:01'])
+    mock_parse_lab.assert_called_once_with(os.getcwd())
+    mock_docker_manager.update_lab_from_api.assert_called_once_with(test_lab)
+    mock_docker_manager.connect_machine_to_link.assert_any_call(
+        test_lab.get_machine('pc1'),
+        test_lab.get_machine("pc1").interfaces[0].link,
+        mac_address=None
+    )
+    mock_docker_manager.connect_machine_to_link.assert_any_call(
+        test_lab.get_machine('pc1'),
+        test_lab.get_machine("pc1").interfaces[1].link,
+        mac_address='00:00:00:00:00:01'
+    )
+
+
+@mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
+@mock.patch("src.Kathara.parser.netkit.LabParser.LabParser.parse")
+def test_run_add_interface_with_directory(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
+    test_lab.connect_machine_to_link("pc1", "A")
+
+    mock_parse_lab.return_value = test_lab
+    mock_manager_get_instance.return_value = mock_docker_manager
+    command = LconfigCommand()
+    command.run('.', ['-d', os.path.join('/test', 'path'), '-n', 'pc1', '--add', 'A'])
+    mock_parse_lab.assert_called_once_with(os.path.abspath(os.path.join('/test', 'path')))
+    mock_docker_manager.update_lab_from_api.assert_called_once_with(test_lab)
+    mock_docker_manager.connect_machine_to_link.assert_called_once_with(
+        test_lab.get_machine('pc1'),
+        test_lab.get_machine("pc1").interfaces[0].link,
+        mac_address=None
+    )
 
 
 @mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
 @mock.patch("src.Kathara.parser.netkit.LabParser.LabParser.parse")
-def test_run_remove_link(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
+def test_run_remove_interface(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
     mock_parse_lab.return_value = test_lab
     mock_manager_get_instance.return_value = mock_docker_manager
     command = LconfigCommand()
@@ -84,8 +182,9 @@ def test_run_remove_link(mock_parse_lab, mock_docker_manager, mock_manager_get_i
 @mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
 @mock.patch("src.Kathara.parser.netkit.LabParser.LabParser.parse")
-def test_run_remove_link_with_directory_absolute_path(mock_parse_lab, mock_docker_manager, mock_manager_get_instance,
-                                                      test_lab):
+def test_run_remove_interface_with_directory_absolute_path(mock_parse_lab, mock_docker_manager,
+                                                           mock_manager_get_instance,
+                                                           test_lab):
     mock_parse_lab.return_value = test_lab
     mock_manager_get_instance.return_value = mock_docker_manager
     command = LconfigCommand()
@@ -99,8 +198,9 @@ def test_run_remove_link_with_directory_absolute_path(mock_parse_lab, mock_docke
 @mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
 @mock.patch("src.Kathara.parser.netkit.LabParser.LabParser.parse")
-def test_run_remove_link_with_directory_relative_path(mock_parse_lab, mock_docker_manager, mock_manager_get_instance,
-                                                      test_lab):
+def test_run_remove_interface_with_directory_relative_path(mock_parse_lab, mock_docker_manager,
+                                                           mock_manager_get_instance,
+                                                           test_lab):
     mock_parse_lab.return_value = test_lab
     mock_manager_get_instance.return_value = mock_docker_manager
     command = LconfigCommand()
@@ -114,7 +214,7 @@ def test_run_remove_link_with_directory_relative_path(mock_parse_lab, mock_docke
 @mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
 @mock.patch("src.Kathara.parser.netkit.LabParser.LabParser.parse")
-def test_run_remove_two_links(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
+def test_run_remove_two_interfaces(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
     mock_parse_lab.return_value = test_lab
     mock_manager_get_instance.return_value = mock_docker_manager
     command = LconfigCommand()
@@ -127,6 +227,19 @@ def test_run_remove_two_links(mock_parse_lab, mock_docker_manager, mock_manager_
                                                                      test_lab.get_or_new_link('B'))
 
 
+@mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
+@mock.patch("src.Kathara.parser.netkit.LabParser.LabParser.parse")
+def test_run_machine_not_found_error(mock_parse_lab, mock_docker_manager, mock_manager_get_instance, test_lab):
+    mock_parse_lab.return_value = test_lab
+    mock_manager_get_instance.return_value = mock_docker_manager
+    command = LconfigCommand()
+    with pytest.raises(MachineNotFoundError):
+        command.run('.', ['-n', 'pc10', '--add', 'A'])
+    mock_parse_lab.assert_called_once_with(os.getcwd())
+    mock_docker_manager.update_lab_from_api.assert_called_once_with(test_lab)
+
+
 def test_run_system_exit_error():
     command = LconfigCommand()
     with pytest.raises(SystemExit):
diff --git a/tests/cli/vconfig_command_test.py b/tests/cli/vconfig_command_test.py
index 6b83782b..b48a187e 100644
--- a/tests/cli/vconfig_command_test.py
+++ b/tests/cli/vconfig_command_test.py
@@ -1,24 +1,65 @@
 import sys
 from unittest import mock
 
+import pytest
+
 sys.path.insert(0, './')
 
 from src.Kathara.cli.command.VconfigCommand import VconfigCommand
+from src.Kathara.model.Lab import Lab
 
-
+@mock.patch("src.Kathara.model.Lab.Lab.get_or_new_link")
+@mock.patch("src.Kathara.model.Lab.Lab.get_machine")
 @mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
-def test_run_add_link(mock_docker_manager, mock_manager_get_instance):
+def test_run_add_interface(mock_docker_manager, mock_manager_get_instance, mock_get_machine, mock_get_or_new_link):
+    lab = Lab('kathara_vlab')
+    pc1 = lab.new_machine("pc1")
+    link_a = lab.get_or_new_link("A")
+    mock_get_or_new_link.return_value = link_a
+    mock_get_machine.return_value = pc1
     mock_manager_get_instance.return_value = mock_docker_manager
     command = VconfigCommand()
     command.run('.', ['-n', 'pc1', '--add', 'A'])
     mock_docker_manager.get_machine_api_object.assert_called_once_with('pc1', lab_name='kathara_vlab')
-    mock_docker_manager.connect_machine_to_link.assert_called_once()
+    mock_docker_manager.connect_machine_to_link.assert_called_once_with(pc1, link_a, mac_address=None)
+
+
+@mock.patch("src.Kathara.model.Lab.Lab.get_or_new_link")
+@mock.patch("src.Kathara.model.Lab.Lab.get_machine")
+@mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
+def test_run_add_interface_with_mac_address(mock_docker_manager, mock_manager_get_instance, mock_get_machine,
+                                            mock_get_or_new_link):
+    lab = Lab('kathara_vlab')
+    pc1 = lab.new_machine("pc1")
+    link_a = lab.get_or_new_link("A")
+    mock_get_machine.return_value = pc1
+    mock_get_or_new_link.return_value = link_a
+    mock_manager_get_instance.return_value = mock_docker_manager
+    command = VconfigCommand()
+    command.run('.', ['-n', 'pc1', '--add', 'A/00:00:00:00:00:01'])
+    mock_docker_manager.get_machine_api_object.assert_called_once_with('pc1', lab_name='kathara_vlab')
+    mock_docker_manager.connect_machine_to_link.assert_called_once_with(pc1, link_a, mac_address='00:00:00:00:00:01')
+
+
+@mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
+def test_run_add_interface_syntax_error(mock_docker_manager, mock_manager_get_instance):
+    mock_manager_get_instance.return_value = mock_docker_manager
+    command = VconfigCommand()
+    with pytest.raises(SyntaxError):
+        command.run('.', ['-n', 'pc1', '--add', 'A/00/:00:00:00:00:01'])
 
 
+@mock.patch("src.Kathara.model.Lab.Lab.get_machine")
 @mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
-def test_run_remove_link(mock_docker_manager, mock_manager_get_instance):
+def test_run_remove_interface(mock_docker_manager, mock_manager_get_instance, mock_get_machine):
+    lab = Lab('kathara_vlab')
+    pc1 = lab.new_machine("pc1")
+    lab.new_link("A")
+    mock_get_machine.return_value = pc1
     mock_manager_get_instance.return_value = mock_docker_manager
     command = VconfigCommand()
     command.run('.', ['-n', 'pc1', '--rm', 'A'])
diff --git a/tests/cli/vstart_command_test.py b/tests/cli/vstart_command_test.py
index 4558db0c..6cd43f54 100644
--- a/tests/cli/vstart_command_test.py
+++ b/tests/cli/vstart_command_test.py
@@ -194,7 +194,7 @@ def test_run_with_one_interface(mock_docker_manager, mock_manager_get_instance,
     mock_manager_get_instance.return_value = mock_docker_manager
     mock_setting_get_instance.return_value = mock_setting
     mock_get_or_new_machine.return_value = Machine(test_lab, 'pc1')
-    default_device_args['eths'] = [('0', 'A')]
+    default_device_args['eths'] = [('0', 'A', None)]
     command = VstartCommand()
     with mock.patch.object(Lab, "add_option") as mock_add_option:
         command.run('.', ['-n', 'pc1', '--eth', '0:A'])
@@ -205,7 +205,35 @@ def test_run_with_one_interface(mock_docker_manager, mock_manager_get_instance,
         mock_add_option.assert_any_call('shared_mount', False)
         mock_add_option.assert_any_call('privileged_machines', None)
         mock_get_or_new_machine.assert_called_once_with('pc1', **default_device_args)
-        mock_connect_machine_to_link.assert_called_once_with('pc1', 'A', machine_iface_number=0)
+        mock_connect_machine_to_link.assert_called_once_with('pc1', 'A', machine_iface_number=0, mac_address=None)
+        mock_docker_manager.deploy_lab.assert_called_once()
+
+
+@mock.patch("src.Kathara.model.Lab.Lab.connect_machine_to_link")
+@mock.patch("src.Kathara.model.Lab.Lab.get_or_new_machine")
+@mock.patch("src.Kathara.setting.Setting.Setting.get_instance")
+@mock.patch("src.Kathara.manager.Kathara.Kathara.get_instance")
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager")
+def test_run_with_one_interface_and_mac_address(mock_docker_manager, mock_manager_get_instance,
+                                                mock_setting_get_instance,
+                                                mock_get_or_new_machine, mock_connect_machine_to_link, test_lab,
+                                                mock_setting, default_device_args):
+    mock_manager_get_instance.return_value = mock_docker_manager
+    mock_setting_get_instance.return_value = mock_setting
+    mock_get_or_new_machine.return_value = Machine(test_lab, 'pc1')
+    default_device_args['eths'] = [('0', 'A', '00:00:00:00:00:01')]
+    command = VstartCommand()
+    with mock.patch.object(Lab, "add_option") as mock_add_option:
+        command.run('.', ['-n', 'pc1', '--eth', '0:A/00:00:00:00:00:01'])
+        assert mock_setting.open_terminals
+        assert mock_setting.terminal == '/usr/bin/xterm'
+        assert mock_setting.device_shell == '/usr/bin/bash'
+        mock_add_option.assert_any_call('hosthome_mount', None)
+        mock_add_option.assert_any_call('shared_mount', False)
+        mock_add_option.assert_any_call('privileged_machines', None)
+        mock_get_or_new_machine.assert_called_once_with('pc1', **default_device_args)
+        mock_connect_machine_to_link.assert_called_once_with('pc1', 'A', machine_iface_number=0,
+                                                             mac_address='00:00:00:00:00:01')
         mock_docker_manager.deploy_lab.assert_called_once()
 
 
@@ -220,7 +248,7 @@ def test_run_with_two_interfaces(mock_docker_manager, mock_manager_get_instance,
     mock_manager_get_instance.return_value = mock_docker_manager
     mock_setting_get_instance.return_value = mock_setting
     mock_get_or_new_machine.return_value = Machine(test_lab, 'pc1')
-    default_device_args['eths'] = [('0', 'A'), ('1', 'B')]
+    default_device_args['eths'] = [('0', 'A', None), ('1', 'B', None)]
     command = VstartCommand()
     with mock.patch.object(Lab, "add_option") as mock_add_option:
         command.run('.', ['-n', 'pc1', '--eth', '0:A', '1:B'])
@@ -231,8 +259,8 @@ def test_run_with_two_interfaces(mock_docker_manager, mock_manager_get_instance,
         mock_add_option.assert_any_call('shared_mount', False)
         mock_add_option.assert_any_call('privileged_machines', None)
         mock_get_or_new_machine.assert_called_once_with('pc1', **default_device_args)
-        mock_connect_machine_to_link.assert_any_call('pc1', 'A', machine_iface_number=0)
-        mock_connect_machine_to_link.assert_any_call('pc1', 'B', machine_iface_number=1)
+        mock_connect_machine_to_link.assert_any_call('pc1', 'A', machine_iface_number=0, mac_address=None)
+        mock_connect_machine_to_link.assert_any_call('pc1', 'B', machine_iface_number=1, mac_address=None)
         mock_docker_manager.deploy_lab.assert_called_once()
 
 
diff --git a/tests/manager/docker/docker_link_test.py b/tests/manager/docker/docker_link_test.py
index 40bceb7f..c56413fd 100644
--- a/tests/manager/docker/docker_link_test.py
+++ b/tests/manager/docker/docker_link_test.py
@@ -8,9 +8,11 @@
 sys.path.insert(0, './')
 
 from src.Kathara.model.Lab import Lab
+from src.Kathara.model.Link import BRIDGE_LINK_NAME
 from src.Kathara.manager.docker.DockerLink import DockerLink
 from src.Kathara import utils
 from src.Kathara.exceptions import LinkNotFoundError
+from src.Kathara.types import SharedCollisionDomainsOption
 
 
 #
@@ -19,7 +21,17 @@
 @pytest.fixture()
 def default_link():
     from src.Kathara.model.Link import Link
-    return Link(Lab("default_scenario"), "A")
+    lab = Lab("default_scenario")
+    lab.hash = "lab-hash"
+    return Link(lab, "A")
+
+
+@pytest.fixture()
+def bridged_link():
+    from src.Kathara.model.Link import Link
+    lab = Lab("default_scenario")
+    lab.hash = "lab-hash"
+    return Link(lab, BRIDGE_LINK_NAME)
 
 
 @pytest.fixture()
@@ -47,32 +59,47 @@ def docker_link(mock_docker_client, mock_docker_plugin):
 #
 @mock.patch("src.Kathara.setting.Setting.Setting.get_instance")
 @mock.patch("src.Kathara.utils.get_current_user_name")
-def test_get_network_name(mock_get_current_user_name, mock_setting_get_instance):
+def test_get_network_name(mock_get_current_user_name, mock_setting_get_instance, default_link):
     mock_get_current_user_name.return_value = 'user'
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': False,
+        'shared_cds': SharedCollisionDomainsOption.NOT_SHARED,
         'net_prefix': 'kathara',
         'remote_url': None,
     })
     mock_setting_get_instance.return_value = setting_mock
-    link_name = DockerLink.get_network_name("A")
+    link_name = DockerLink.get_network_name(default_link)
+    assert link_name == "kathara_user_A_lab-hash"
+
+
+@mock.patch("src.Kathara.setting.Setting.Setting.get_instance")
+@mock.patch("src.Kathara.utils.get_current_user_name")
+def test_get_network_name_shared_cds_between_labs(mock_get_current_user_name, mock_setting_get_instance, default_link):
+    mock_get_current_user_name.return_value = 'user'
+    setting_mock = Mock()
+    setting_mock.configure_mock(**{
+        'shared_cds': SharedCollisionDomainsOption.LABS,
+        'net_prefix': 'kathara',
+        'remote_url': None
+    })
+    mock_setting_get_instance.return_value = setting_mock
+    link_name = DockerLink.get_network_name(default_link)
     assert link_name == "kathara_user_A"
 
 
 @mock.patch("src.Kathara.setting.Setting.Setting.get_instance")
 @mock.patch("src.Kathara.utils.get_current_user_name")
-def test_get_network_name_shared_cd(mock_get_current_user_name, mock_setting_get_instance):
+def test_get_network_name_shared_cds_between_users(mock_get_current_user_name, mock_setting_get_instance, default_link):
     mock_get_current_user_name.return_value = 'user'
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': True,
-        'net_prefix': 'CUSTOM_PREFIX',
+        'shared_cds': SharedCollisionDomainsOption.USERS,
+        'net_prefix': 'kathara',
         'remote_url': None
     })
     mock_setting_get_instance.return_value = setting_mock
-    link_name = DockerLink.get_network_name("A")
-    assert link_name == "CUSTOM_PREFIX_A"
+    link_name = DockerLink.get_network_name(default_link)
+    assert link_name == "kathara_A"
 
 
 #
@@ -86,7 +113,7 @@ def test_create(mock_get_current_user_name, mock_setting_get_instance, docker_li
     mock_get_current_user_name.return_value = 'user'
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': False,
+        'shared_cds': SharedCollisionDomainsOption.NOT_SHARED,
         'net_prefix': 'kathara',
         'remote_url': None,
         'network_plugin': 'kathara/katharanp'
@@ -94,14 +121,47 @@ def test_create(mock_get_current_user_name, mock_setting_get_instance, docker_li
     mock_setting_get_instance.return_value = setting_mock
     docker_link.create(default_link)
     docker_link.client.networks.create.assert_called_once_with(
-        name="kathara_user_A",
+        name="kathara_user_A_lab-hash",
         driver=f"{setting_mock.network_plugin}:{utils.get_architecture()}",
         check_duplicate=True,
         ipam=docker.types.IPAMConfig(driver='null'),
         labels={
-            "lab_hash": utils.generate_urlsafe_hash("default_scenario"),
             "name": "A",
+            "app": "kathara",
+            "external": "",
             "user": "user",
+            "lab_hash": default_link.lab.hash,
+        }
+    )
+
+
+def test_create_bridge_link(docker_link, bridged_link):
+    assert not docker_link.create(bridged_link)
+
+
+@mock.patch("src.Kathara.setting.Setting.Setting.get_instance")
+@mock.patch("src.Kathara.utils.get_current_user_name")
+def test_create_shared_cds_between_users(mock_get_current_user_name, mock_setting_get_instance, docker_link,
+                                         default_link):
+    docker_link.client.networks.list.return_value = []
+
+    mock_get_current_user_name.return_value = 'user'
+    setting_mock = Mock()
+    setting_mock.configure_mock(**{
+        'shared_cds': SharedCollisionDomainsOption.USERS,
+        'net_prefix': 'kathara',
+        'remote_url': None,
+        'network_plugin': 'kathara/katharanp'
+    })
+    mock_setting_get_instance.return_value = setting_mock
+    docker_link.create(default_link)
+    docker_link.client.networks.create.assert_called_once_with(
+        name="kathara_A",
+        driver=f"{setting_mock.network_plugin}:{utils.get_architecture()}",
+        check_duplicate=True,
+        ipam=docker.types.IPAMConfig(driver='null'),
+        labels={
+            "name": "A",
             "app": "kathara",
             "external": ""
         }
@@ -110,13 +170,14 @@ def test_create(mock_get_current_user_name, mock_setting_get_instance, docker_li
 
 @mock.patch("src.Kathara.setting.Setting.Setting.get_instance")
 @mock.patch("src.Kathara.utils.get_current_user_name")
-def test_create_shared_cd(mock_get_current_user_name, mock_setting_get_instance, docker_link, default_link):
+def test_create_shared_cds_between_labs(mock_get_current_user_name, mock_setting_get_instance, docker_link,
+                                        default_link):
     docker_link.client.networks.list.return_value = []
 
     mock_get_current_user_name.return_value = 'user'
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': True,
+        'shared_cds': SharedCollisionDomainsOption.LABS,
         'net_prefix': 'kathara',
         'remote_url': None,
         'network_plugin': 'kathara/katharanp'
@@ -124,14 +185,13 @@ def test_create_shared_cd(mock_get_current_user_name, mock_setting_get_instance,
     mock_setting_get_instance.return_value = setting_mock
     docker_link.create(default_link)
     docker_link.client.networks.create.assert_called_once_with(
-        name="kathara_A",
+        name="kathara_user_A",
         driver=f"{setting_mock.network_plugin}:{utils.get_architecture()}",
         check_duplicate=True,
         ipam=docker.types.IPAMConfig(driver='null'),
         labels={
-            "lab_hash": utils.generate_urlsafe_hash("default_scenario"),
             "name": "A",
-            "user": "shared_cd",
+            "user": "user",
             "app": "kathara",
             "external": ""
         }
@@ -147,6 +207,10 @@ def test_deploy_link(mock_create, docker_link, default_link):
     mock_create.called_once_with(default_link)
 
 
+def test_deploy_link_bridge(docker_link, bridged_link):
+    assert not docker_link._deploy_link((bridged_link.name, bridged_link))
+
+
 #
 # TEST: deploy_links
 #
@@ -194,7 +258,7 @@ def test_undeploy_link(mock_undeploy_link, docker_link, docker_network):
 @mock.patch("docker.models.networks.list")
 def test_get_links_by_filters(mock_network_list, docker_link):
     docker_link.get_links_api_objects_by_filters("lab_hash_value", "link_name_value", "user_name_value")
-    filters = {"label": ["app=kathara", "lab_hash=lab_hash_value", "user=user_name_value"], "name": "link_name_value"}
+    filters = {"label": ["app=kathara", "lab_hash=lab_hash_value", "user=user_name_value", "name=link_name_value"]}
     mock_network_list.called_once_with(filters=filters)
 
 
@@ -215,7 +279,7 @@ def test_get_links_by_filters_only_lab_hash(mock_network_list, docker_link):
 @mock.patch("docker.models.networks.list")
 def test_get_links_by_filters_only_link_name(mock_network_list, docker_link):
     docker_link.get_links_api_objects_by_filters(None, "link_name_value")
-    filters = {"label": ["app=kathara"], "name": "link_name_value"}
+    filters = {"label": ["app=kathara", "name=link_name_value"]}
     mock_network_list.called_once_with(filters=filters)
 
 
diff --git a/tests/manager/docker/docker_machine_test.py b/tests/manager/docker/docker_machine_test.py
index ce8efabf..56456beb 100644
--- a/tests/manager/docker/docker_machine_test.py
+++ b/tests/manager/docker/docker_machine_test.py
@@ -11,6 +11,7 @@
 from src.Kathara.model.Machine import Machine
 from src.Kathara.manager.docker.DockerMachine import DockerMachine
 from src.Kathara.exceptions import MachineNotFoundError, DockerPluginError, MachineBinaryError
+from src.Kathara.types import SharedCollisionDomainsOption
 
 
 #
@@ -55,6 +56,14 @@ def default_link_b(default_device):
     return link
 
 
+@pytest.fixture()
+def default_link_c(default_device):
+    link = Link(default_device.lab, "C")
+    link.api_object = Mock()
+    link.api_object.connect = Mock(return_value=True)
+    return link
+
+
 #
 # TEST: create
 #
@@ -69,7 +78,7 @@ def test_create(mock_get_current_user_name, mock_setting_get_instance, mock_copy
 
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': False,
+        'shared_cds': SharedCollisionDomainsOption.NOT_SHARED,
         'device_prefix': 'dev_prefix',
         "device_shell": '/bin/bash',
         'enable_ipv6': False,
@@ -87,6 +96,7 @@ def test_create(mock_get_current_user_name, mock_setting_get_instance, mock_copy
         privileged=False,
         network=None,
         network_mode='none',
+        networking_config=None,
         sysctls={'net.ipv4.conf.all.rp_filter': 0,
                  'net.ipv4.conf.default.rp_filter': 0,
                  'net.ipv4.conf.lo.rp_filter': 0,
@@ -119,7 +129,7 @@ def test_create_ipv6(mock_get_current_user_name, mock_setting_get_instance, mock
 
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': False,
+        'shared_cds': SharedCollisionDomainsOption.NOT_SHARED,
         'device_prefix': 'dev_prefix',
         "device_shell": '/bin/bash',
         'enable_ipv6': True,
@@ -137,6 +147,7 @@ def test_create_ipv6(mock_get_current_user_name, mock_setting_get_instance, mock
         privileged=False,
         network=None,
         network_mode='none',
+        networking_config=None,
         sysctls={'net.ipv4.conf.all.rp_filter': 0,
                  'net.ipv4.conf.default.rp_filter': 0,
                  'net.ipv4.conf.lo.rp_filter': 0,
@@ -175,7 +186,7 @@ def test_create_privileged(mock_get_current_user_name, mock_setting_get_instance
     mock_get_current_user_name.return_value = "test-user"
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': False,
+        'shared_cds': SharedCollisionDomainsOption.NOT_SHARED,
         'device_prefix': 'dev_prefix',
         "device_shell": '/bin/bash',
         'enable_ipv6': True,
@@ -193,6 +204,7 @@ def test_create_privileged(mock_get_current_user_name, mock_setting_get_instance
         privileged=True,
         network=None,
         network_mode='none',
+        networking_config=None,
         sysctls={'net.ipv4.conf.all.rp_filter': 0,
                  'net.ipv4.conf.default.rp_filter': 0,
                  'net.ipv4.conf.lo.rp_filter': 0,
@@ -217,6 +229,137 @@ def test_create_privileged(mock_get_current_user_name, mock_setting_get_instance
     assert not mock_copy_files.called
 
 
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_api_objects_by_filters")
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.copy_files")
+@mock.patch("src.Kathara.setting.Setting.Setting.get_instance")
+@mock.patch("src.Kathara.utils.get_current_user_name")
+def test_create_interface(mock_get_current_user_name, mock_setting_get_instance, mock_copy_files,
+                          mock_get_machines_api_objects_by_filters, docker_machine, default_device):
+    class LinkApiObj:
+        def __init__(self, name):
+            self.name = name
+
+    link = Link(default_device.lab, "A")
+    link.api_object = LinkApiObj("link_a")
+
+    default_device.add_interface(link, 0)
+
+    docker_machine.client.api.create_endpoint_config.return_value = {}
+    mock_get_machines_api_objects_by_filters.return_value = []
+    mock_get_current_user_name.return_value = "test-user"
+
+    setting_mock = Mock()
+    setting_mock.configure_mock(**{
+        'shared_cds': SharedCollisionDomainsOption.NOT_SHARED,
+        'device_prefix': 'dev_prefix',
+        "device_shell": '/bin/bash',
+        'enable_ipv6': False,
+        'remote_url': None,
+        'hosthome_mount': False,
+        'shared_mount': False
+    })
+    mock_setting_get_instance.return_value = setting_mock
+    docker_machine.create(default_device)
+    docker_machine.client.containers.create.assert_called_once_with(
+        image='kathara/test',
+        name='dev_prefix_test-user_test_device_9pe3y6IDMwx4PfOPu5mbNg',
+        hostname='test_device',
+        cap_add=['NET_ADMIN', 'NET_RAW', 'NET_BROADCAST', 'NET_BIND_SERVICE', 'SYS_ADMIN'],
+        privileged=False,
+        network="link_a",
+        network_mode='bridge',
+        networking_config={"link_a": {}},
+        sysctls={'net.ipv4.conf.all.rp_filter': 0,
+                 'net.ipv4.conf.default.rp_filter': 0,
+                 'net.ipv4.conf.lo.rp_filter': 0,
+                 'net.ipv4.conf.eth0.rp_filter': 0,
+                 'net.ipv4.ip_forward': 1,
+                 'net.ipv4.icmp_ratelimit': 0,
+                 },
+        environment={},
+        mem_limit='64m',
+        nano_cpus=2000000000,
+        ports=None,
+        tty=True,
+        stdin_open=True,
+        detach=True,
+        volumes={},
+        labels={'name': 'test_device', 'lab_hash': '9pe3y6IDMwx4PfOPu5mbNg', 'user': 'test-user', 'app': 'kathara',
+                'shell': '/bin/bash'}
+    )
+
+    assert not mock_copy_files.called
+
+
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_api_objects_by_filters")
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.copy_files")
+@mock.patch("src.Kathara.setting.Setting.Setting.get_instance")
+@mock.patch("src.Kathara.utils.get_current_user_name")
+def test_create_interface_mac_addr(mock_get_current_user_name, mock_setting_get_instance, mock_copy_files,
+                                   mock_get_machines_api_objects_by_filters, docker_machine, default_device):
+    class LinkApiObj:
+        def __init__(self, name):
+            self.name = name
+
+    link = Link(default_device.lab, "A")
+    link.api_object = LinkApiObj("link_a")
+
+    expected_mac_addr = "00:00:00:00:ff:ff"
+    default_device.add_interface(link, 0, expected_mac_addr)
+
+    docker_machine.client.api.create_endpoint_config.return_value = {
+        'driver_opt': {'kathara.mac_addr': expected_mac_addr}
+    }
+    mock_get_machines_api_objects_by_filters.return_value = []
+    mock_get_current_user_name.return_value = "test-user"
+
+    setting_mock = Mock()
+    setting_mock.configure_mock(**{
+        'shared_cds': SharedCollisionDomainsOption.NOT_SHARED,
+        'device_prefix': 'dev_prefix',
+        "device_shell": '/bin/bash',
+        'enable_ipv6': False,
+        'remote_url': None,
+        'hosthome_mount': False,
+        'shared_mount': False
+    })
+    mock_setting_get_instance.return_value = setting_mock
+    docker_machine.create(default_device)
+
+    docker_machine.client.api.create_endpoint_config.assert_called_once_with(
+        driver_opt={'kathara.mac_addr': expected_mac_addr}
+    )
+    docker_machine.client.containers.create.assert_called_once_with(
+        image='kathara/test',
+        name='dev_prefix_test-user_test_device_9pe3y6IDMwx4PfOPu5mbNg',
+        hostname='test_device',
+        cap_add=['NET_ADMIN', 'NET_RAW', 'NET_BROADCAST', 'NET_BIND_SERVICE', 'SYS_ADMIN'],
+        privileged=False,
+        network="link_a",
+        network_mode='bridge',
+        networking_config={"link_a": {'driver_opt': {'kathara.mac_addr': expected_mac_addr}}},
+        sysctls={'net.ipv4.conf.all.rp_filter': 0,
+                 'net.ipv4.conf.default.rp_filter': 0,
+                 'net.ipv4.conf.lo.rp_filter': 0,
+                 'net.ipv4.conf.eth0.rp_filter': 0,
+                 'net.ipv4.ip_forward': 1,
+                 'net.ipv4.icmp_ratelimit': 0,
+                 },
+        environment={},
+        mem_limit='64m',
+        nano_cpus=2000000000,
+        ports=None,
+        tty=True,
+        stdin_open=True,
+        detach=True,
+        volumes={},
+        labels={'name': 'test_device', 'lab_hash': '9pe3y6IDMwx4PfOPu5mbNg', 'user': 'test-user', 'app': 'kathara',
+                'shell': '/bin/bash'}
+    )
+
+    assert not mock_copy_files.called
+
+
 #
 # TEST: start
 #
@@ -239,6 +382,58 @@ def test_start(docker_machine, default_device, default_link, default_link_b):
     default_link_b.api_object.connect.assert_called_once()
 
 
+def test_start_one_mac_addr(docker_machine, default_device, default_link, default_link_b):
+    expected_mac_addr = "00:00:00:00:ee:ee"
+
+    default_device.add_interface(default_link)
+    default_device.add_interface(default_link_b, mac_address=expected_mac_addr)
+    default_device.add_meta("num_terms", 3)
+    docker_machine.client.api.exec_create.return_value = {"Id": "1234"}
+    docker_machine.client.api.exec_start.return_value = ("cmd_stdout", "cmd_stderr")
+    docker_machine.client.api.exec_inspect.return_value = {"ExitCode": 0}
+    default_device.api_object.start.return_value = True
+
+    docker_machine.start(default_device)
+
+    default_device.api_object.start.assert_called_once()
+    docker_machine.client.api.exec_create.assert_called_once()
+    docker_machine.client.api.exec_start.assert_called_once()
+    docker_machine.client.api.exec_inspect.assert_called_once()
+    default_link_b.api_object.connect.assert_called_once_with(
+        default_device.api_object,
+        driver_opt={'kathara.mac_addr': expected_mac_addr}
+    )
+
+
+def test_start_two_mac_addr(docker_machine, default_device, default_link, default_link_b, default_link_c):
+    expected_mac_addr_1 = "00:00:00:00:ee:ee"
+    expected_mac_addr_2 = "00:00:00:00:ee:ee"
+
+    default_device.add_interface(default_link)
+    default_device.add_interface(default_link_b, mac_address=expected_mac_addr_1)
+    default_device.add_interface(default_link_c, mac_address=expected_mac_addr_2)
+    default_device.add_meta("num_terms", 3)
+    docker_machine.client.api.exec_create.return_value = {"Id": "1234"}
+    docker_machine.client.api.exec_start.return_value = ("cmd_stdout", "cmd_stderr")
+    docker_machine.client.api.exec_inspect.return_value = {"ExitCode": 0}
+    default_device.api_object.start.return_value = True
+
+    docker_machine.start(default_device)
+
+    default_device.api_object.start.assert_called_once()
+    docker_machine.client.api.exec_create.assert_called_once()
+    docker_machine.client.api.exec_start.assert_called_once()
+    docker_machine.client.api.exec_inspect.assert_called_once()
+    default_link_b.api_object.connect.assert_called_once_with(
+        default_device.api_object,
+        driver_opt={'kathara.mac_addr': expected_mac_addr_1}
+    )
+    default_link_c.api_object.connect.assert_called_once_with(
+        default_device.api_object,
+        driver_opt={'kathara.mac_addr': expected_mac_addr_2}
+    )
+
+
 def test_start_plugin_error_endpoint_start(default_device, docker_machine):
     default_device.api_object.start.side_effect = DockerPluginError("endpoint does not exists")
     with pytest.raises(DockerPluginError):
@@ -289,7 +484,7 @@ def test_deploy_and_start_machine(mock_create, mock_start, docker_machine, defau
 def test_deploy_machines(mock_deploy_and_start, mock_setting_get_instance, docker_machine):
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': False,
+        'shared_cds': SharedCollisionDomainsOption.NOT_SHARED,
         'device_prefix': 'dev_prefix',
         "device_shell": '/bin/bash',
         'enable_ipv6': False,
@@ -310,34 +505,52 @@ def test_deploy_machines(mock_deploy_and_start, mock_setting_get_instance, docke
 
 
 #
-# TEST: connect_to_link
+# TEST: connect_interface
 #
-def test_connect_to_link(docker_machine, default_device, default_link, default_link_b):
+def test_connect_interface(docker_machine, default_device, default_link, default_link_b):
     default_device.api_object.attrs["NetworkSettings"] = {}
     default_device.api_object.attrs["NetworkSettings"]["Networks"] = ["A"]
     default_link.api_object.name = "A"
 
     default_device.add_interface(default_link)
-    default_device.add_interface(default_link_b)
+    interface = default_device.add_interface(default_link_b)
 
-    docker_machine.connect_to_link(default_device, default_link_b)
+    docker_machine.connect_interface(default_device, interface)
 
     assert not default_link.api_object.connect.called
     default_link_b.api_object.connect.assert_called_once()
 
 
-def test_connect_to_link_plugin_error_network(default_device, default_link, docker_machine):
+def test_connect_interface_mac_addr(docker_machine, default_device, default_link, default_link_b):
+    default_device.api_object.attrs["NetworkSettings"] = {}
+    default_device.api_object.attrs["NetworkSettings"]["Networks"] = ["A"]
+    default_link.api_object.name = "A"
+
+    expected_mac_addr = "00:00:00:00:ff:ff"
     default_device.add_interface(default_link)
+    interface = default_device.add_interface(default_link_b, mac_address=expected_mac_addr)
+
+    docker_machine.connect_interface(default_device, interface)
+
+    assert not default_link.api_object.connect.called
+    default_link_b.api_object.connect.assert_called_once_with(
+        default_device.api_object,
+        driver_opt={'kathara.mac_addr': expected_mac_addr}
+    )
+
+
+def test_connect_interface_plugin_error_network(default_device, default_link, docker_machine):
+    interface = default_device.add_interface(default_link)
     default_link.api_object.connect.side_effect = DockerPluginError("network does not exists")
     with pytest.raises(DockerPluginError):
-        docker_machine.connect_to_link(default_device, default_link)
+        docker_machine.connect_interface(default_device, interface)
 
 
-def test_connect_to_link_plugin_error_endpoint(default_device, default_link, docker_machine):
-    default_device.add_interface(default_link)
+def test_connect_interface_plugin_error_endpoint(default_device, default_link, docker_machine):
+    interface = default_device.add_interface(default_link)
     default_link.api_object.connect.side_effect = DockerPluginError("endpoint does not exists")
     with pytest.raises(DockerPluginError):
-        docker_machine.connect_to_link(default_device, default_link)
+        docker_machine.connect_interface(default_device, interface)
 
 
 #
@@ -449,7 +662,7 @@ def test_exec(mock_get_machines_api_objects_by_filters, mock_setting_get_instanc
 
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': False,
+        'shared_cds': SharedCollisionDomainsOption.NOT_SHARED,
         'device_prefix': 'dev_prefix',
         "device_shell": '/bin/bash',
         'enable_ipv6': False,
@@ -722,7 +935,7 @@ def test_get_container_name_lab_hash(mock_get_current_user_name, mock_setting_ge
 
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': False,
+        'shared_cds': SharedCollisionDomainsOption.NOT_SHARED,
         'device_prefix': 'dev_prefix'
     })
     mock_setting_get_instance.return_value = setting_mock
@@ -732,12 +945,25 @@ def test_get_container_name_lab_hash(mock_get_current_user_name, mock_setting_ge
 
 @mock.patch("src.Kathara.setting.Setting.Setting.get_instance")
 @mock.patch("src.Kathara.utils.get_current_user_name")
-def test_get_container_name_lab_hash_shared_cd(mock_get_current_user_name, mock_setting_get_instance):
+def test_get_container_name_lab_hash_shared_cd_lab(mock_get_current_user_name, mock_setting_get_instance):
+    mock_get_current_user_name.return_value = "kathara-user"
+
+    setting_mock = Mock()
+    setting_mock.configure_mock(**{
+        'shared_cd': SharedCollisionDomainsOption.LABS,
+        'device_prefix': 'dev_prefix'
+    })
+    mock_setting_get_instance.return_value = setting_mock
+
+
+@mock.patch("src.Kathara.setting.Setting.Setting.get_instance")
+@mock.patch("src.Kathara.utils.get_current_user_name")
+def test_get_container_name_lab_hash_shared_cd_user(mock_get_current_user_name, mock_setting_get_instance):
     mock_get_current_user_name.return_value = "kathara-user"
 
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': True,
+        'shared_cd': SharedCollisionDomainsOption.USERS,
         'device_prefix': 'dev_prefix'
     })
     mock_setting_get_instance.return_value = setting_mock
diff --git a/tests/manager/docker/docker_manager_test.py b/tests/manager/docker/docker_manager_test.py
index a65c7cd7..a531b3dc 100644
--- a/tests/manager/docker/docker_manager_test.py
+++ b/tests/manager/docker/docker_manager_test.py
@@ -13,7 +13,9 @@
 from src.Kathara.utils import generate_urlsafe_hash
 from src.Kathara.manager.docker.stats.DockerLinkStats import DockerLinkStats
 from src.Kathara.manager.docker.stats.DockerMachineStats import DockerMachineStats
-from src.Kathara.exceptions import MachineNotFoundError, LabNotFoundError, InvocationError, LinkNotFoundError
+from src.Kathara.exceptions import MachineNotFoundError, LabNotFoundError, InvocationError, LinkNotFoundError, \
+    MachineNotRunningError
+from src.Kathara.types import SharedCollisionDomainsOption
 
 
 #
@@ -51,6 +53,7 @@ def default_device(mock_docker_container):
     device.add_meta("image", "kathara/test")
     device.add_meta("bridged", False)
     device.api_object = mock_docker_container
+    mock_docker_container.status = "running"
     return device
 
 
@@ -136,8 +139,11 @@ def docker_container(mock_container):
         "Networks": {
             "kathara_user_hash_test_network": {
                 "Links": None,
+                "DriverOpts": {
+                    "kathara.mac_addr": "00:00:00:00:00:01"
+                }
             },
-            "bridge": {}
+            "bridge": {},
         }
     }}
 
@@ -251,31 +257,49 @@ def test_deploy_link_no_lab(docker_manager, default_link):
 # TEST: connect_machine_to_link
 #
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.deploy_link")
-@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.connect_to_link")
-def test_connect_machine_to_link_one_link(mock_connect_to_link_machine, mock_deploy_link, docker_manager,
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.connect_interface")
+def test_connect_machine_to_link_one_link(mock_connect_interface_machine, mock_deploy_link, docker_manager,
                                           default_device, default_link):
     docker_manager.connect_machine_to_link(default_device, default_link)
 
+    interface = default_device.interfaces[0]
+
     mock_deploy_link.assert_called_once_with(default_link)
-    mock_connect_to_link_machine.assert_called_once_with(default_device, default_link)
+    mock_connect_interface_machine.assert_called_once_with(default_device, interface)
 
 
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.deploy_link")
-@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.connect_to_link")
-def test_connect_machine_to_link_two_links(mock_connect_to_link_machine, mock_deploy_link, docker_manager,
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.connect_interface")
+def test_connect_machine_to_link_two_links(mock_connect_interface_machine, mock_deploy_link, docker_manager,
                                            default_device, default_link, default_link_b):
     docker_manager.connect_machine_to_link(default_device, default_link)
+    interface_a = default_device.interfaces[0]
 
     mock_deploy_link.assert_called_with(default_link)
-    mock_connect_to_link_machine.assert_called_with(default_device, default_link)
+    mock_connect_interface_machine.assert_called_with(default_device, interface_a)
 
     docker_manager.connect_machine_to_link(default_device, default_link_b)
+    interface_b = default_device.interfaces[1]
 
     mock_deploy_link.assert_called_with(default_link_b)
-    mock_connect_to_link_machine.assert_called_with(default_device, default_link_b)
+    mock_connect_interface_machine.assert_called_with(default_device, interface_b)
 
     assert mock_deploy_link.call_count == 2
-    assert mock_connect_to_link_machine.call_count == 2
+    assert mock_connect_interface_machine.call_count == 2
+
+
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.deploy_link")
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.connect_interface")
+def test_connect_machine_to_link_mac_address(mock_connect_interface_machine, mock_deploy_link, docker_manager,
+                                             default_device, default_link):
+    expected_mac_address = "00:00:00:00:ff:ff"
+    docker_manager.connect_machine_to_link(default_device, default_link, mac_address=expected_mac_address)
+
+    interface = default_device.interfaces[0]
+
+    assert interface.mac_address == expected_mac_address
+    mock_deploy_link.assert_called_once_with(default_link)
+    mock_connect_interface_machine.assert_called_once_with(default_device, interface)
 
 
 def test_connect_machine_to_link_no_machine_lab(docker_manager, default_device, default_link):
@@ -292,6 +316,20 @@ def test_connect_machine_to_link_no_link_lab(docker_manager, default_device, def
         docker_manager.connect_machine_to_link(default_device, default_link)
 
 
+def test_connect_machine_to_link_machine_not_running_error(docker_manager, default_device, default_link):
+    default_device.api_object = None
+
+    with pytest.raises(MachineNotRunningError):
+        docker_manager.connect_machine_to_link(default_device, default_link)
+
+
+def test_connect_machine_to_link_machine_exited_error(docker_manager, default_device, default_link):
+    default_device.api_object.status = "exited"
+
+    with pytest.raises(MachineNotRunningError):
+        docker_manager.connect_machine_to_link(default_device, default_link)
+
+
 #
 # TEST: disconnect_machine_from_link
 #
@@ -364,7 +402,7 @@ def test_undeploy_machine(mock_machine_undeploy, mock_link_undeploy, docker_mana
 @mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.undeploy")
 def test_undeploy_machine_two_machines(mock_machine_undeploy, mock_link_undeploy, docker_manager, two_device_scenario):
     device_1 = two_device_scenario.get_or_new_machine('pc1')
-    device_1_links = {x.name for x in device_1.interfaces.values()}
+    device_1_links = {x.link.name for x in device_1.interfaces.values()}
 
     docker_manager.undeploy_machine(device_1)
 
@@ -460,6 +498,11 @@ def test_undeploy_lab_lab_obj_selected_machines(mock_undeploy_machine, mock_unde
     mock_undeploy_link.assert_called_once_with(expected_hash)
 
 
+def test_undeploy_lab_lab_hash_lab_obj(docker_manager, two_device_scenario):
+    with pytest.raises(InvocationError):
+        docker_manager.undeploy_lab(lab_hash=two_device_scenario.hash, lab=two_device_scenario)
+
+
 #
 # TEST: wipe
 #
@@ -482,7 +525,7 @@ def test_wipe_all_users(mock_setting_get_instance, mock_get_current_user_name, m
                         docker_manager):
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': False,
+        'shared_cds': SharedCollisionDomainsOption.NOT_SHARED,
         'remote_url': None
     })
     mock_setting_get_instance.return_value = setting_mock
@@ -497,11 +540,31 @@ def test_wipe_all_users(mock_setting_get_instance, mock_get_current_user_name, m
 @mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.wipe")
 @mock.patch("src.Kathara.utils.get_current_user_name")
 @mock.patch("src.Kathara.setting.Setting.Setting.get_instance")
-def test_wipe_all_users_and_shared_cd(mock_setting_get_instance, mock_get_current_user_name, mock_wipe_machines,
-                                      mock_wipe_links, docker_manager):
+def test_wipe_all_users_and_shared_cd_lab(mock_setting_get_instance, mock_get_current_user_name, mock_wipe_machines,
+                                          mock_wipe_links, docker_manager):
     setting_mock = Mock()
     setting_mock.configure_mock(**{
-        'shared_cd': True,
+        'shared_cd': SharedCollisionDomainsOption.LABS,
+        'remote_url': None
+    })
+    mock_setting_get_instance.return_value = setting_mock
+    mock_get_current_user_name.return_value = "kathara_user"
+
+    docker_manager.wipe(all_users=True)
+    assert not mock_get_current_user_name.called
+    mock_wipe_machines.assert_called_once_with(user=None)
+    mock_wipe_links.assert_called_once_with(user=None)
+
+
+@mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.wipe")
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.wipe")
+@mock.patch("src.Kathara.utils.get_current_user_name")
+@mock.patch("src.Kathara.setting.Setting.Setting.get_instance")
+def test_wipe_all_users_and_shared_cd_user(mock_setting_get_instance, mock_get_current_user_name, mock_wipe_machines,
+                                           mock_wipe_links, docker_manager):
+    setting_mock = Mock()
+    setting_mock.configure_mock(**{
+        'shared_cd': SharedCollisionDomainsOption.USERS,
         'remote_url': None
     })
     mock_setting_get_instance.return_value = setting_mock
@@ -548,6 +611,30 @@ def test_connect_tty_lab_name(mock_connect, mock_get_current_user_name, docker_m
                                          wait=True)
 
 
+@mock.patch("src.Kathara.utils.get_current_user_name")
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.connect")
+def test_connect_tty_lab_obj(mock_connect, mock_get_current_user_name, docker_manager, default_device,
+                             two_device_scenario):
+    mock_get_current_user_name.return_value = "kathara_user"
+
+    docker_manager.connect_tty(default_device.name,
+                               lab=two_device_scenario)
+
+    mock_connect.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                         machine_name=default_device.name,
+                                         user="kathara_user",
+                                         shell=None,
+                                         logs=False,
+                                         wait=True)
+
+
+def test_connect_tty_lab_hash_lab_obj(docker_manager, default_device, two_device_scenario):
+    with pytest.raises(InvocationError):
+        docker_manager.connect_tty(default_device.name,
+                                   lab_hash=two_device_scenario.hash,
+                                   lab=two_device_scenario)
+
+
 @mock.patch("src.Kathara.utils.get_current_user_name")
 @mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.connect")
 def test_connect_tty_with_custom_shell(mock_connect, mock_get_current_user_name, docker_manager, default_device):
@@ -630,6 +717,29 @@ def test_exec_lab_name(mock_exec, mock_get_current_user_name, docker_manager, de
     )
 
 
+@mock.patch("src.Kathara.utils.get_current_user_name")
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.exec")
+def test_exec_lab_obj(mock_exec, mock_get_current_user_name, docker_manager, default_device, two_device_scenario):
+    mock_get_current_user_name.return_value = "kathara_user"
+
+    docker_manager.exec(default_device.name, ["test", "command"], lab=two_device_scenario)
+
+    mock_exec.assert_called_once_with(
+        two_device_scenario.hash,
+        default_device.name,
+        ["test", "command"],
+        user="kathara_user",
+        tty=False,
+        wait=False
+    )
+
+
+def test_exec_lab_hash_lab_obj(docker_manager, default_device, two_device_scenario):
+    with pytest.raises(InvocationError):
+        docker_manager.exec(default_device.name, ["test", "command"],
+                            lab_hash=two_device_scenario.hash, lab=two_device_scenario)
+
+
 @mock.patch("src.Kathara.utils.get_current_user_name")
 @mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.exec")
 def test_exec_wait(mock_exec, mock_get_current_user_name, docker_manager, default_device):
@@ -699,8 +809,35 @@ def test_get_machine_api_object_lab_name_no_user(mock_get_machines_api_objects,
                                                           machine_name="test_device", user=None)
 
 
+@mock.patch("src.Kathara.utils.get_current_user_name")
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_api_objects_by_filters")
+def test_get_machine_api_object_lab_obj_user(mock_get_machines_api_objects, mock_get_current_user_name,
+                                             docker_manager, default_device, two_device_scenario):
+    mock_get_machines_api_objects.return_value = [default_device.api_object]
+    mock_get_current_user_name.return_value = "kathara_user"
+    docker_manager.get_machine_api_object("test_device", lab=two_device_scenario, all_users=False)
+    mock_get_machines_api_objects.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                          machine_name="test_device", user="kathara_user")
+
+
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_api_objects_by_filters")
+def test_get_machine_api_object_lab_obj_no_user(mock_get_machines_api_objects, docker_manager, default_device,
+                                                two_device_scenario):
+    mock_get_machines_api_objects.return_value = [default_device.api_object]
+    docker_manager.get_machine_api_object("test_device", lab=two_device_scenario, all_users=True)
+    mock_get_machines_api_objects.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                          machine_name="test_device", user=None)
+
+
+def test_get_machine_api_object_lab_hash_lab_obj(docker_manager, default_device, two_device_scenario):
+    with pytest.raises(InvocationError):
+        docker_manager.get_machine_api_object("test_device",
+                                              lab_hash=two_device_scenario.hash,
+                                              lab=two_device_scenario, all_users=False)
+
+
 @mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_api_objects_by_filters")
-def test_get_machine_api_object_no_name_no_hash(mock_get_machines_api_objects, docker_manager, default_device):
+def test_get_machine_api_object_invocation_error(mock_get_machines_api_objects, docker_manager, default_device):
     with pytest.raises(InvocationError):
         docker_manager.get_machine_api_object("test_device", all_users=True)
     assert not mock_get_machines_api_objects.called
@@ -753,8 +890,33 @@ def test_get_machines_api_objects_lab_name_no_user(mock_get_machines_api_objects
     mock_get_machines_api_objects.assert_called_once_with(lab_hash=generate_urlsafe_hash("lab_name"), user=None)
 
 
+@mock.patch("src.Kathara.utils.get_current_user_name")
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_api_objects_by_filters")
+def test_get_machines_api_objects_lab_obj_user(mock_get_machines_api_objects, mock_get_current_user_name,
+                                               docker_manager, default_device, two_device_scenario):
+    mock_get_machines_api_objects.return_value = [default_device.api_object]
+    mock_get_current_user_name.return_value = "kathara_user"
+    docker_manager.get_machines_api_objects(lab=two_device_scenario, all_users=False)
+    mock_get_machines_api_objects.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                          user="kathara_user")
+
+
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_api_objects_by_filters")
+def test_get_machines_api_objects_lab_obj_no_user(mock_get_machines_api_objects, docker_manager, default_device,
+                                                  two_device_scenario):
+    mock_get_machines_api_objects.return_value = [default_device.api_object]
+    docker_manager.get_machines_api_objects(lab=two_device_scenario, all_users=True)
+    mock_get_machines_api_objects.assert_called_once_with(lab_hash=two_device_scenario.hash, user=None)
+
+
+def test_get_machines_api_objects_lab_hash_lab_obj(docker_manager, default_device, two_device_scenario):
+    with pytest.raises(InvocationError):
+        docker_manager.get_machines_api_objects(lab_hash=two_device_scenario.hash,
+                                                lab=two_device_scenario, all_users=False)
+
+
 @mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_api_objects_by_filters")
-def test_get_machines_api_objects_no_name_no_hash(mock_get_machines_api_objects, docker_manager):
+def test_get_machines_api_objects_no_labs(mock_get_machines_api_objects, docker_manager):
     docker_manager.get_machines_api_objects(all_users=True)
     mock_get_machines_api_objects.assert_called_once_with(lab_hash=None, user=None)
 
@@ -810,8 +972,35 @@ def test_get_link_api_object_lab_name_no_user(mock_get_links_api_objects,
                                                        link_name="test_link", user=None)
 
 
+@mock.patch("src.Kathara.utils.get_current_user_name")
+@mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.get_links_api_objects_by_filters")
+def test_get_link_api_object_lab_obj_user(mock_get_links_api_objects, mock_get_current_user_name,
+                                          docker_manager, docker_network, two_device_scenario):
+    mock_get_links_api_objects.return_value = [docker_network]
+    mock_get_current_user_name.return_value = "kathara_user"
+    docker_manager.get_link_api_object("test_link", lab=two_device_scenario, all_users=False)
+    mock_get_links_api_objects.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                       link_name="test_link", user="kathara_user")
+
+
+@mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.get_links_api_objects_by_filters")
+def test_get_link_api_object_lab_obj_no_user(mock_get_links_api_objects, docker_manager, docker_network,
+                                             two_device_scenario):
+    mock_get_links_api_objects.return_value = [docker_network]
+    docker_manager.get_link_api_object("test_link", lab=two_device_scenario, all_users=True)
+    mock_get_links_api_objects.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                       link_name="test_link", user=None)
+
+
+def test_get_link_api_object_lab_hash_lab_obj(docker_manager, docker_network, two_device_scenario):
+    with pytest.raises(InvocationError):
+        docker_manager.get_link_api_object("test_link",
+                                           lab_hash=two_device_scenario.hash,
+                                           lab=two_device_scenario, all_users=True)
+
+
 @mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.get_links_api_objects_by_filters")
-def test_get_link_api_object_no_name_no_hash(mock_get_links_api_objects, docker_manager):
+def test_get_link_api_object_invocation_error(mock_get_links_api_objects, docker_manager):
     with pytest.raises(InvocationError):
         docker_manager.get_link_api_object("test_link", all_users=True)
     assert not mock_get_links_api_objects.called
@@ -868,8 +1057,34 @@ def test_get_links_api_objects_lab_name_no_user(mock_get_links_api_objects,
                                                        user=None)
 
 
+@mock.patch("src.Kathara.utils.get_current_user_name")
+@mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.get_links_api_objects_by_filters")
+def test_get_links_api_objects_lab_obj_user(mock_get_links_api_objects, mock_get_current_user_name,
+                                            docker_manager, docker_network, two_device_scenario):
+    mock_get_links_api_objects.return_value = [docker_network]
+    mock_get_current_user_name.return_value = "kathara_user"
+    docker_manager.get_links_api_objects(lab=two_device_scenario, all_users=False)
+    mock_get_links_api_objects.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                       user="kathara_user")
+
+
+@mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.get_links_api_objects_by_filters")
+def test_get_links_api_objects_lab_obj_no_user(mock_get_links_api_objects, docker_manager, docker_network,
+                                               two_device_scenario):
+    mock_get_links_api_objects.return_value = [docker_network]
+    docker_manager.get_links_api_objects(lab=two_device_scenario, all_users=True)
+    mock_get_links_api_objects.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                       user=None)
+
+
+def test_get_links_api_objects_lab_hash_lab_obj(docker_manager, docker_network, two_device_scenario):
+    with pytest.raises(InvocationError):
+        docker_manager.get_links_api_objects(lab_hash=two_device_scenario.hash,
+                                             lab=two_device_scenario, all_users=True)
+
+
 @mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.get_links_api_objects_by_filters")
-def test_get_links_api_objects_no_name_no_hash(mock_get_links_api_objects, docker_manager):
+def test_get_links_api_objects_no_labs(mock_get_links_api_objects, docker_manager):
     docker_manager.get_links_api_objects(all_users=True)
     mock_get_links_api_objects.assert_called_once_with(lab_hash=None, user=None)
 
@@ -903,6 +1118,8 @@ def test_get_lab_from_api_lab_name_all_info(mock_get_links_api_objects, mock_get
     assert reconstructed_device.meta["envs"]["test"] == "path"
     assert reconstructed_device.meta["ports"][(3000, "udp")] == 55
     assert reconstructed_device.meta["sysctls"]["sysctl.test"] == "0"
+    assert reconstructed_device.meta["bridged"]
+    assert reconstructed_device.interfaces[0].mac_address == "00:00:00:00:00:01"
     assert len(lab.links) == 1
     assert docker_network.attrs["Labels"]["name"] in lab.links
 
@@ -928,6 +1145,7 @@ def test_get_lab_from_api_lab_hash_all_info(mock_get_links_api_objects, mock_get
     assert reconstructed_device.meta["ports"][(3000, "udp")] == 55
     assert reconstructed_device.meta["sysctls"]["sysctl.test"] == "0"
     assert reconstructed_device.meta["bridged"]
+    assert reconstructed_device.interfaces[0].mac_address == "00:00:00:00:00:01"
     assert len(lab.links) == 1
     assert docker_network.attrs["Labels"]["name"] in lab.links
 
@@ -967,18 +1185,31 @@ def test_update_lab_from_api_add_link(mock_get_links_api_objects, mock_get_machi
                                       docker_network, docker_network_b, docker_manager):
     lab = Lab("test")
     device = lab.get_or_new_machine(docker_container.labels["name"])
-    link = Link(lab, docker_network.attrs["Labels"]["name"])
+    link = lab.new_link(docker_network.attrs["Labels"]["name"])
     device.add_interface(link)
     mock_get_machines_api_objects.return_value = [docker_container]
     mock_get_links_api_objects.return_value = [docker_network, docker_network_b]
-    docker_container.attrs["NetworkSettings"]["Networks"] = ["kathara_user_hash_test_network",
-                                                             "kathara_user_hash_test_network_b"]
+    docker_container.attrs["NetworkSettings"]["Networks"] = {
+        "kathara_user_hash_test_network": {
+            "Links": None,
+            "DriverOpts": None
+        },
+        "kathara_user_hash_test_network_b": {
+            "Links": None,
+            "DriverOpts": {
+                "kathara.mac_addr": "00:00:00:00:00:02"
+            }
+        }
+    }
+
     docker_manager.update_lab_from_api(lab)
     assert len(lab.machines) == 1
     assert docker_container.labels["name"] in lab.machines
     assert len(lab.links) == 2
     assert docker_network.attrs["Labels"]["name"] in lab.links
     assert docker_network_b.attrs["Labels"]["name"] in lab.links
+    assert lab.machines[docker_container.labels["name"]].interfaces[0].mac_address is None
+    assert lab.machines[docker_container.labels["name"]].interfaces[1].mac_address == "00:00:00:00:00:02"
 
 
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_machines_api_objects")
@@ -987,16 +1218,18 @@ def test_update_lab_from_api_remove_link(mock_get_links_api_objects, mock_get_ma
                                          docker_network, docker_manager):
     lab = Lab("test")
     device = lab.get_or_new_machine(docker_container.labels["name"])
-    link = Link(lab, docker_network.attrs["Labels"]["name"])
+    link = lab.new_link(docker_network.attrs["Labels"]["name"])
     device.add_interface(link)
     mock_get_machines_api_objects.return_value = [docker_container]
     mock_get_links_api_objects.return_value = []
-    docker_container.attrs["NetworkSettings"]["Networks"] = []
+    docker_container.attrs["NetworkSettings"]["Networks"] = {}
 
     docker_manager.update_lab_from_api(lab)
     assert len(lab.machines) == 1
     assert docker_container.labels["name"] in lab.machines
-    assert len(lab.links) == 0
+    assert len(device.interfaces) == 1
+    assert device.interfaces[0] is None
+    assert docker_container.labels["name"] not in link.machines
 
 
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_machines_api_objects")
@@ -1011,11 +1244,19 @@ def test_update_lab_from_api_add_remove_link(mock_get_links_api_objects, mock_ge
 
     mock_get_machines_api_objects.return_value = [docker_container]
     mock_get_links_api_objects.return_value = [docker_network_b]
-    docker_container.attrs["NetworkSettings"]["Networks"] = ["kathara_user_hash_test_network_b"]
+    docker_container.attrs["NetworkSettings"]["Networks"] = {
+        "kathara_user_hash_test_network_b": {
+            "Links": None,
+            "DriverOpts": {
+                "kathara.mac_addr": "00:00:00:00:00:02"
+            }
+        }
+    }
 
     docker_manager.update_lab_from_api(lab)
     assert len(lab.machines) == 1
     assert docker_container.labels["name"] in lab.machines
+    assert lab.machines[docker_container.labels["name"]].interfaces[1].mac_address == "00:00:00:00:00:02"
     assert len(lab.links) == 1
     assert docker_network_b.attrs["Labels"]["name"] in lab.links
 
@@ -1030,6 +1271,14 @@ def test_get_machines_stats_lab_hash_no_user(mock_get_machines_stats, docker_man
                                                     user=None)
 
 
+@mock.patch("src.Kathara.utils.get_current_user_name")
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_stats")
+def test_get_machines_stats_lab_hash_user(mock_get_machines_stats, mock_get_current_user_name, docker_manager):
+    mock_get_current_user_name.return_value = "kathara-user"
+    docker_manager.get_machines_stats(lab_hash="lab_hash")
+    mock_get_machines_stats.assert_called_once_with(lab_hash="lab_hash", machine_name=None, user="kathara-user")
+
+
 @mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_stats")
 def test_get_machines_stats_lab_name_no_user(mock_get_machines_stats, docker_manager):
     docker_manager.get_machines_stats(lab_name="lab_name", all_users=True)
@@ -1039,14 +1288,37 @@ def test_get_machines_stats_lab_name_no_user(mock_get_machines_stats, docker_man
 
 @mock.patch("src.Kathara.utils.get_current_user_name")
 @mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_stats")
-def test_get_machines_stats_lab_hash_user(mock_get_machines_stats, mock_get_current_user_name, docker_manager):
+def test_get_machines_stats_lab_name_user(mock_get_machines_stats, mock_get_current_user_name, docker_manager):
     mock_get_current_user_name.return_value = "kathara-user"
-    docker_manager.get_machines_stats(lab_hash="lab_hash")
-    mock_get_machines_stats.assert_called_once_with(lab_hash="lab_hash", machine_name=None, user="kathara-user")
+    docker_manager.get_machines_stats(lab_name="lab_name")
+    mock_get_machines_stats.assert_called_once_with(lab_hash=generate_urlsafe_hash("lab_name"),
+                                                    machine_name=None, user="kathara-user")
+
+
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_stats")
+def test_get_machines_stats_lab_obj_no_user(mock_get_machines_stats, docker_manager, two_device_scenario):
+    docker_manager.get_machines_stats(lab=two_device_scenario, all_users=True)
+    mock_get_machines_stats.assert_called_once_with(lab_hash=two_device_scenario.hash, machine_name=None,
+                                                    user=None)
 
 
+@mock.patch("src.Kathara.utils.get_current_user_name")
 @mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_stats")
-def test_get_machines_stats_no_name_no_hash(mock_get_machines_stats, docker_manager):
+def test_get_machines_stats_lab_obj_user(mock_get_machines_stats, mock_get_current_user_name, docker_manager,
+                                         two_device_scenario):
+    mock_get_current_user_name.return_value = "kathara-user"
+    docker_manager.get_machines_stats(lab=two_device_scenario)
+    mock_get_machines_stats.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                    machine_name=None, user="kathara-user")
+
+
+def test_get_machines_stats_lab_hash_lab_obj(docker_manager, two_device_scenario):
+    with pytest.raises(InvocationError):
+        docker_manager.get_machines_stats(lab_hash=two_device_scenario.hash, lab=two_device_scenario)
+
+
+@mock.patch("src.Kathara.manager.docker.DockerMachine.DockerMachine.get_machines_stats")
+def test_get_machines_stats_no_labs(mock_get_machines_stats, docker_manager):
     docker_manager.get_machines_stats(all_users=True)
     mock_get_machines_stats.assert_called_once_with(lab_hash=None, machine_name=None, user=None)
 
@@ -1062,6 +1334,15 @@ def test_get_machine_stats_lab_hash_no_user(mock_get_machines_stats, default_dev
                                                     all_users=True)
 
 
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_machines_stats")
+def test_get_machine_stats_lab_hash_user(mock_get_machines_stats, default_device, docker_manager):
+    mock_get_machines_stats.return_value = iter([{"test_device": DockerMachineStats(default_device.api_object)}])
+    next(docker_manager.get_machine_stats(machine_name="test_device", lab_hash="lab_hash"))
+    mock_get_machines_stats.assert_called_once_with(lab_hash="lab_hash",
+                                                    machine_name="test_device",
+                                                    all_users=False)
+
+
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_machines_stats")
 def test_get_machine_stats_lab_name_no_user(mock_get_machines_stats, default_device, docker_manager):
     mock_get_machines_stats.return_value = iter([{"test_device": DockerMachineStats(default_device.api_object)}])
@@ -1072,16 +1353,41 @@ def test_get_machine_stats_lab_name_no_user(mock_get_machines_stats, default_dev
 
 
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_machines_stats")
-def test_get_machine_stats_lab_hash_user(mock_get_machines_stats, default_device, docker_manager):
+def test_get_machine_stats_lab_name_user(mock_get_machines_stats, default_device, docker_manager):
     mock_get_machines_stats.return_value = iter([{"test_device": DockerMachineStats(default_device.api_object)}])
-    next(docker_manager.get_machine_stats(machine_name="test_device", lab_hash="lab_hash"))
-    mock_get_machines_stats.assert_called_once_with(lab_hash="lab_hash",
+    next(docker_manager.get_machine_stats(machine_name="test_device", lab_name="lab_name"))
+    mock_get_machines_stats.assert_called_once_with(lab_hash=generate_urlsafe_hash("lab_name"),
                                                     machine_name="test_device",
                                                     all_users=False)
 
 
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_machines_stats")
-def test_get_machine_stats_no_name_no_hash(mock_get_machines_stats, docker_manager):
+def test_get_machine_stats_lab_obj_no_user(mock_get_machines_stats, default_device, docker_manager,
+                                           two_device_scenario):
+    mock_get_machines_stats.return_value = iter([{"test_device": DockerMachineStats(default_device.api_object)}])
+    next(docker_manager.get_machine_stats(machine_name="test_device", lab=two_device_scenario, all_users=True))
+    mock_get_machines_stats.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                    machine_name="test_device",
+                                                    all_users=True)
+
+
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_machines_stats")
+def test_get_machine_stats_lab_obj_user(mock_get_machines_stats, default_device, docker_manager, two_device_scenario):
+    mock_get_machines_stats.return_value = iter([{"test_device": DockerMachineStats(default_device.api_object)}])
+    next(docker_manager.get_machine_stats(machine_name="test_device", lab=two_device_scenario))
+    mock_get_machines_stats.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                    machine_name="test_device",
+                                                    all_users=False)
+
+
+def test_get_machine_stats_lab_hash_lab_obj(default_device, docker_manager, two_device_scenario):
+    with pytest.raises(InvocationError):
+        next(docker_manager.get_machine_stats(machine_name="test_device", lab_hash=two_device_scenario.hash,
+                                              lab=two_device_scenario))
+
+
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_machines_stats")
+def test_get_machine_stats_no_labs(mock_get_machines_stats, docker_manager):
     with pytest.raises(InvocationError):
         next(docker_manager.get_machine_stats(machine_name="test_device", all_users=True))
     assert not mock_get_machines_stats.called
@@ -1096,32 +1402,54 @@ def test_get_links_stats_lab_hash_no_user(mock_get_links_stats, docker_manager):
     mock_get_links_stats.assert_called_once_with(lab_hash="lab_hash", link_name=None, user=None)
 
 
+@mock.patch("src.Kathara.utils.get_current_user_name")
+@mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.get_links_stats")
+def test_get_links_stats_lab_hash_user(mock_get_links_stats, mock_get_current_user_name, docker_manager):
+    mock_get_current_user_name.return_value = "kathara-user"
+    docker_manager.get_links_stats(lab_hash="lab_hash")
+    mock_get_links_stats.assert_called_once_with(lab_hash="lab_hash", link_name=None, user="kathara-user")
+
+
 @mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.get_links_stats")
 def test_get_links_stats_lab_name_no_user(mock_get_links_stats, docker_manager):
     docker_manager.get_links_stats(lab_name="lab_name", all_users=True)
     mock_get_links_stats.assert_called_once_with(lab_hash=generate_urlsafe_hash("lab_name"), link_name=None, user=None)
 
 
+@mock.patch("src.Kathara.utils.get_current_user_name")
 @mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.get_links_stats")
-def test_get_links_stats_no_hash_no_user(mock_get_links_stats, docker_manager):
-    docker_manager.get_links_stats(all_users=True)
-    mock_get_links_stats.assert_called_once_with(lab_hash=None, link_name=None, user=None)
+def test_get_links_stats_lab_name_user(mock_get_links_stats, mock_get_current_user_name, docker_manager):
+    mock_get_current_user_name.return_value = "kathara-user"
+    docker_manager.get_links_stats(lab_name="lab_name")
+    mock_get_links_stats.assert_called_once_with(lab_hash=generate_urlsafe_hash("lab_name"),
+                                                 link_name=None, user="kathara-user")
 
 
-@mock.patch("src.Kathara.utils.get_current_user_name")
 @mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.get_links_stats")
-def test_get_links_stats_no_hash_user(mock_get_links_stats, mock_get_current_user_name, docker_manager):
-    mock_get_current_user_name.return_value = "kathara-user"
-    docker_manager.get_links_stats()
-    mock_get_links_stats.assert_called_once_with(lab_hash=None, link_name=None, user="kathara-user")
+def test_get_links_stats_lab_obj_no_user(mock_get_links_stats, docker_manager, two_device_scenario):
+    docker_manager.get_links_stats(lab=two_device_scenario, all_users=True)
+    mock_get_links_stats.assert_called_once_with(lab_hash=two_device_scenario.hash, link_name=None, user=None)
 
 
 @mock.patch("src.Kathara.utils.get_current_user_name")
 @mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.get_links_stats")
-def test_get_links_stats_lab_hash_user(mock_get_links_stats, mock_get_current_user_name, docker_manager):
+def test_get_links_stats_lab_obj_user(mock_get_links_stats, mock_get_current_user_name, docker_manager,
+                                      two_device_scenario):
     mock_get_current_user_name.return_value = "kathara-user"
-    docker_manager.get_links_stats(lab_hash="lab_hash")
-    mock_get_links_stats.assert_called_once_with(lab_hash="lab_hash", link_name=None, user="kathara-user")
+    docker_manager.get_links_stats(lab=two_device_scenario)
+    mock_get_links_stats.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                 link_name=None, user="kathara-user")
+
+
+def test_get_links_stats_lab_hash_lab_obj(docker_manager, two_device_scenario):
+    with pytest.raises(InvocationError):
+        docker_manager.get_links_stats(lab_hash=two_device_scenario.hash, lab=two_device_scenario)
+
+
+@mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.get_links_stats")
+def test_get_links_stats_no_labs(mock_get_links_stats, docker_manager):
+    docker_manager.get_links_stats(all_users=True)
+    mock_get_links_stats.assert_called_once_with(lab_hash=None, link_name=None, user=None)
 
 
 #
@@ -1134,6 +1462,13 @@ def test_get_link_stats_lab_hash_no_user(mock_get_links_stats, docker_network, d
     mock_get_links_stats.assert_called_once_with(lab_hash="lab_hash", link_name="test_network", all_users=True)
 
 
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_links_stats")
+def test_get_link_stats_lab_hash_user(mock_get_links_stats, docker_network, docker_manager):
+    mock_get_links_stats.return_value = iter([{"test_network": DockerLinkStats(docker_network)}])
+    next(docker_manager.get_link_stats(link_name="test_network", lab_hash="lab_hash"))
+    mock_get_links_stats.assert_called_once_with(lab_hash="lab_hash", link_name="test_network", all_users=False)
+
+
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_links_stats")
 def test_get_link_stats_lab_name_no_user(mock_get_links_stats, docker_network, docker_manager):
     mock_get_links_stats.return_value = iter([{"test_network": DockerLinkStats(docker_network)}])
@@ -1143,14 +1478,37 @@ def test_get_link_stats_lab_name_no_user(mock_get_links_stats, docker_network, d
 
 
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_links_stats")
-def test_get_link_stats_lab_hash_user(mock_get_links_stats, docker_network, docker_manager):
+def test_get_link_stats_lab_name_user(mock_get_links_stats, docker_network, docker_manager):
     mock_get_links_stats.return_value = iter([{"test_network": DockerLinkStats(docker_network)}])
-    next(docker_manager.get_link_stats(link_name="test_network", lab_hash="lab_hash"))
-    mock_get_links_stats.assert_called_once_with(lab_hash="lab_hash", link_name="test_network", all_users=False)
+    next(docker_manager.get_link_stats(link_name="test_network", lab_name="lab_name"))
+    mock_get_links_stats.assert_called_once_with(lab_hash=generate_urlsafe_hash("lab_name"),
+                                                 link_name="test_network", all_users=False)
+
+
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_links_stats")
+def test_get_link_stats_lab_obj_no_user(mock_get_links_stats, docker_network, docker_manager, two_device_scenario):
+    mock_get_links_stats.return_value = iter([{"test_network": DockerLinkStats(docker_network)}])
+    next(docker_manager.get_link_stats(link_name="test_network", lab=two_device_scenario, all_users=True))
+    mock_get_links_stats.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                 link_name="test_network", all_users=True)
+
+
+@mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_links_stats")
+def test_get_link_stats_lab_obj_user(mock_get_links_stats, docker_network, docker_manager, two_device_scenario):
+    mock_get_links_stats.return_value = iter([{"test_network": DockerLinkStats(docker_network)}])
+    next(docker_manager.get_link_stats(link_name="test_network", lab=two_device_scenario))
+    mock_get_links_stats.assert_called_once_with(lab_hash=two_device_scenario.hash,
+                                                 link_name="test_network", all_users=False)
+
+
+def test_get_link_stats_lab_hash_lab_obj(docker_network, docker_manager, two_device_scenario):
+    with pytest.raises(InvocationError):
+        next(docker_manager.get_link_stats(link_name="test_network", lab_hash=two_device_scenario.hash,
+                                           lab=two_device_scenario))
 
 
 @mock.patch("src.Kathara.manager.docker.DockerManager.DockerManager.get_links_stats")
-def test_get_link_stats_no_lab_hash_and_no_name(mock_get_links_stats, docker_network, docker_manager):
+def test_get_link_stats_invocation_error(mock_get_links_stats, docker_network, docker_manager):
     mock_get_links_stats.return_value = iter([{"test_network": DockerLinkStats(docker_network)}])
     with pytest.raises(InvocationError):
         next(docker_manager.get_link_stats(link_name="test_network"))
diff --git a/tests/manager/kubernetes/kubernetes_machine_test.py b/tests/manager/kubernetes/kubernetes_machine_test.py
index 7953b655..6111f78e 100644
--- a/tests/manager/kubernetes/kubernetes_machine_test.py
+++ b/tests/manager/kubernetes/kubernetes_machine_test.py
@@ -427,8 +427,9 @@ def test_deploy_machine(mock_create, kubernetes_machine, default_device):
 #
 # TEST: deploy_machines
 #
+@mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine._wait_machines_startup")
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine._deploy_machine")
-def test_deploy_machines(mock_deploy, kubernetes_machine):
+def test_deploy_machines(mock_deploy, mock_wait_machines_startup, kubernetes_machine):
     lab = Lab("Default scenario")
 
     lab.get_or_new_machine("pc1", **{'image': 'kathara/test1'})
@@ -439,15 +440,17 @@ def test_deploy_machines(mock_deploy, kubernetes_machine):
     kubernetes_machine.deploy_machines(lab)
 
     assert mock_deploy.call_count == 2
+    mock_wait_machines_startup.assert_called_once_with(lab, None)
 
 
 #
 # TEST: undeploy
 #
+@mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine._wait_machines_shutdown")
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine._undeploy_machine")
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine.get_machines_api_objects_by_filters")
-def test_undeploy_one_device(mock_get_machines_api_objects_by_filters, mock_undeploy_machine, kubernetes_machine,
-                             default_device):
+def test_undeploy_one_device(mock_get_machines_api_objects_by_filters, mock_undeploy_machine,
+                             mock_wait_machines_shutdown, kubernetes_machine, default_device):
     default_device.api_object.metadata.labels = {'name': "test_device"}
     mock_get_machines_api_objects_by_filters.return_value = [default_device.api_object]
     mock_undeploy_machine.return_value = None
@@ -456,12 +459,14 @@ def test_undeploy_one_device(mock_get_machines_api_objects_by_filters, mock_unde
 
     mock_get_machines_api_objects_by_filters.assert_called_once()
     mock_undeploy_machine.assert_called_once_with(default_device.api_object)
+    mock_wait_machines_shutdown.assert_called_once_with("lab_hash", {default_device.name})
 
 
+@mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine._wait_machines_shutdown")
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine._undeploy_machine")
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine.get_machines_api_objects_by_filters")
-def test_undeploy_three_devices(mock_get_machines_api_objects_by_filters, mock_undeploy_machine, kubernetes_machine,
-                                default_device):
+def test_undeploy_three_devices(mock_get_machines_api_objects_by_filters, mock_undeploy_machine,
+                                mock_wait_machines_shutdown, kubernetes_machine, default_device):
     default_device.api_object.metadata.labels = {'name': "test_device"}
     # fill the list with more devices
     mock_get_machines_api_objects_by_filters.return_value = [default_device.api_object,
@@ -472,11 +477,14 @@ def test_undeploy_three_devices(mock_get_machines_api_objects_by_filters, mock_u
 
     mock_get_machines_api_objects_by_filters.assert_called_once()
     assert mock_undeploy_machine.call_count == 3
+    mock_wait_machines_shutdown.assert_called_once_with("lab_hash", {"test_device"})
 
 
+@mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine._wait_machines_shutdown")
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine._undeploy_machine")
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine.get_machines_api_objects_by_filters")
-def test_undeploy_no_devices(mock_get_machines_api_objects_by_filters, mock_undeploy_machine, kubernetes_machine):
+def test_undeploy_no_devices(mock_get_machines_api_objects_by_filters, mock_undeploy_machine,
+                             mock_wait_machines_shutdown, kubernetes_machine):
     mock_get_machines_api_objects_by_filters.return_value = []
     mock_undeploy_machine.return_value = None
 
@@ -484,6 +492,7 @@ def test_undeploy_no_devices(mock_get_machines_api_objects_by_filters, mock_unde
 
     mock_get_machines_api_objects_by_filters.assert_called_once()
     assert not mock_undeploy_machine.called
+    assert not mock_wait_machines_shutdown.called
 
 
 #
diff --git a/tests/manager/kubernetes/kubernetes_manager_test.py b/tests/manager/kubernetes/kubernetes_manager_test.py
index d614ec47..f29e8b4f 100644
--- a/tests/manager/kubernetes/kubernetes_manager_test.py
+++ b/tests/manager/kubernetes/kubernetes_manager_test.py
@@ -477,6 +477,18 @@ def test_connect_tty_lab_name(mock_connect, kubernetes_manager, default_device):
                                          logs=False)
 
 
+@mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine.connect")
+def test_connect_tty_lab_obj(mock_connect, kubernetes_manager, default_device,
+                             two_device_scenario):
+    kubernetes_manager.connect_tty(default_device.name,
+                                   lab=two_device_scenario)
+
+    mock_connect.assert_called_once_with(lab_hash=two_device_scenario.hash.lower(),
+                                         machine_name=default_device.name,
+                                         shell=None,
+                                         logs=False)
+
+
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine.connect")
 def test_connect_tty_custom_shell(mock_connect, kubernetes_manager, default_device):
     kubernetes_manager.connect_tty(default_device.name,
@@ -540,6 +552,20 @@ def test_exec_lab_name(mock_exec, kubernetes_manager, default_device):
     )
 
 
+@mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine.exec")
+def test_exec_lab_obj(mock_exec, kubernetes_manager, default_device, two_device_scenario):
+    kubernetes_manager.exec(default_device.name, ["test", "command"], lab=two_device_scenario)
+
+    mock_exec.assert_called_once_with(
+        two_device_scenario.hash.lower(),
+        default_device.name,
+        ["test", "command"],
+        stderr=True,
+        tty=False,
+        is_stream=True
+    )
+
+
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine.exec")
 def test_exec_wait(mock_exec, kubernetes_manager, default_device):
     kubernetes_manager.exec(default_device.name, ["test", "command"], lab_hash=default_device.lab.hash, wait=True)
@@ -583,16 +609,29 @@ def test_get_machine_api_object_lab_name(mock_get_machines_api_objects, kubernet
 
 
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine.get_machines_api_objects_by_filters")
-def test_get_machine_api_object_lab_hash_and_name(mock_get_machines_api_objects, kubernetes_manager, default_device):
+def test_get_machine_api_object_lab_obj(mock_get_machines_api_objects, kubernetes_manager, default_device,
+                                        two_device_scenario):
     default_device.api_object.name = "default_device"
     mock_get_machines_api_objects.return_value = [default_device.api_object]
-    kubernetes_manager.get_machine_api_object(machine_name="default_device", lab_name="lab_name", lab_hash="lab_hash")
+    kubernetes_manager.get_machine_api_object(machine_name="default_device", lab=two_device_scenario)
     mock_get_machines_api_objects.assert_called_once_with(machine_name="default_device",
-                                                          lab_hash=generate_urlsafe_hash("lab_name").lower())
+                                                          lab_hash=two_device_scenario.hash.lower())
+
+
+def test_get_machine_api_object_lab_hash_and_name(kubernetes_manager, default_device):
+    with pytest.raises(InvocationError):
+        kubernetes_manager.get_machine_api_object(machine_name="default_device", lab_name="lab_name",
+                                                  lab_hash="lab_hash")
+
+
+def test_get_machine_api_object_lab_hash_and_name_and_obj(kubernetes_manager, default_device, two_device_scenario):
+    with pytest.raises(InvocationError):
+        kubernetes_manager.get_machine_api_object(machine_name="default_device", lab_name="lab_name",
+                                                  lab_hash="lab_hash", lab=two_device_scenario)
 
 
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine.get_machines_api_objects_by_filters")
-def test_get_machine_api_object_no_hash_no_name(mock_get_machines_api_objects, kubernetes_manager, default_device):
+def test_get_machine_api_object_invocation_error(mock_get_machines_api_objects, kubernetes_manager, default_device):
     default_device.api_object.name = "default_device"
     mock_get_machines_api_objects.return_value = [default_device.api_object]
     with pytest.raises(InvocationError):
@@ -625,6 +664,14 @@ def test_get_machines_api_objects_lab_name(mock_get_machines_api_objects, kubern
     mock_get_machines_api_objects.assert_called_once_with(lab_hash=generate_urlsafe_hash("lab_name").lower())
 
 
+@mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine.get_machines_api_objects_by_filters")
+def test_get_machines_api_objects_lab_obj(mock_get_machines_api_objects, kubernetes_manager, default_device,
+                                          two_device_scenario):
+    mock_get_machines_api_objects.return_value = [default_device.api_object]
+    kubernetes_manager.get_machines_api_objects(lab=two_device_scenario)
+    mock_get_machines_api_objects.assert_called_once_with(lab_hash=two_device_scenario.hash.lower())
+
+
 #
 # TEST: get_links_api_objects
 #
@@ -642,6 +689,14 @@ def test_get_links_api_objects_lab_name(mock_get_links_api_objects, kubernetes_m
     mock_get_links_api_objects.assert_called_once_with(lab_hash=generate_urlsafe_hash("lab_name").lower())
 
 
+@mock.patch("src.Kathara.manager.kubernetes.KubernetesLink.KubernetesLink.get_links_api_objects_by_filters")
+def test_get_links_api_objects_lab_obj(mock_get_links_api_objects, kubernetes_manager, kubernetes_network,
+                                       two_device_scenario):
+    mock_get_links_api_objects.return_value = [kubernetes_network]
+    kubernetes_manager.get_links_api_objects(lab=two_device_scenario)
+    mock_get_links_api_objects.assert_called_once_with(lab_hash=two_device_scenario.hash.lower())
+
+
 #
 # TEST: get_link_api_object
 #
@@ -661,15 +716,22 @@ def test_get_link_api_object_lab_name(mock_get_links_api_objects, kubernetes_man
 
 
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesLink.KubernetesLink.get_links_api_objects_by_filters")
-def test_get_link_api_object_lab_hash_and_name(mock_get_links_api_objects, kubernetes_manager, kubernetes_network):
+def test_get_link_api_object_lab_obj(mock_get_links_api_objects, kubernetes_manager, kubernetes_network,
+                                     two_device_scenario):
     mock_get_links_api_objects.return_value = [kubernetes_network]
-    kubernetes_manager.get_link_api_object(link_name="test_network", lab_name="lab_name", lab_hash="lab_hash")
+    kubernetes_manager.get_link_api_object(link_name="test_network", lab=two_device_scenario)
     mock_get_links_api_objects.assert_called_once_with(link_name="test_network",
-                                                       lab_hash=generate_urlsafe_hash("lab_name").lower())
+                                                       lab_hash=two_device_scenario.hash.lower())
+
+
+def test_get_link_api_object_lab_hash_and_name_and_obj(kubernetes_manager, kubernetes_network, two_device_scenario):
+    with pytest.raises(InvocationError):
+        kubernetes_manager.get_link_api_object(link_name="test_network", lab_name="lab_name", lab_hash="lab_hash",
+                                               lab=two_device_scenario)
 
 
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesLink.KubernetesLink.get_links_api_objects_by_filters")
-def test_get_link_api_object_no_hash_no_name(mock_get_links_api_objects, kubernetes_manager, kubernetes_network):
+def test_get_link_api_object_invocation_error(mock_get_links_api_objects, kubernetes_manager, kubernetes_network):
     mock_get_links_api_objects.return_value = [kubernetes_network]
     with pytest.raises(InvocationError):
         kubernetes_manager.get_link_api_object(link_name="test_network")
@@ -792,7 +854,14 @@ def test_get_machines_stats_lab_name(mock_get_machines_stats, kubernetes_manager
 
 
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine.get_machines_stats")
-def test_get_machines_stats_no_hash_no_name(mock_get_machines_stats, kubernetes_manager):
+def test_get_machines_stats_lab_obj(mock_get_machines_stats, kubernetes_manager, two_device_scenario):
+    kubernetes_manager.get_machines_stats(lab=two_device_scenario)
+    mock_get_machines_stats.assert_called_once_with(lab_hash=two_device_scenario.hash.lower(),
+                                                    machine_name=None)
+
+
+@mock.patch("src.Kathara.manager.kubernetes.KubernetesMachine.KubernetesMachine.get_machines_stats")
+def test_get_machines_stats_no_labs(mock_get_machines_stats, kubernetes_manager):
     kubernetes_manager.get_machines_stats(all_users=True)
     mock_get_machines_stats.assert_called_once_with(lab_hash=None, machine_name=None)
 
@@ -816,7 +885,15 @@ def test_get_machine_stats_lab_name(mock_get_machines_stats, default_device, kub
 
 
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesManager.KubernetesManager.get_machines_stats")
-def test_get_machine_stats_no_hash_no_name(mock_get_machines_stats, kubernetes_manager):
+def test_get_machine_stats_lab_obj(mock_get_machines_stats, default_device, kubernetes_manager, two_device_scenario):
+    mock_get_machines_stats.return_value = iter([{"test_device": KubernetesMachineStats(default_device.api_object)}])
+    next(kubernetes_manager.get_machine_stats(machine_name="test_device", lab=two_device_scenario))
+    mock_get_machines_stats.assert_called_once_with(lab_hash=two_device_scenario.hash.lower(),
+                                                    machine_name="test_device")
+
+
+@mock.patch("src.Kathara.manager.kubernetes.KubernetesManager.KubernetesManager.get_machines_stats")
+def test_get_machine_stats_invocation_error(mock_get_machines_stats, kubernetes_manager):
     mock_get_machines_stats.return_value = iter([])
     with pytest.raises(InvocationError):
         next(kubernetes_manager.get_machine_stats(machine_name="test_device"))
@@ -839,7 +916,13 @@ def test_get_links_stats_lab_name(mock_get_links_stats, kubernetes_manager):
 
 
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesLink.KubernetesLink.get_links_stats")
-def test_get_links_stats_no_lab_hash(mock_get_links_stats, kubernetes_manager):
+def test_get_links_stats_lab_obj(mock_get_links_stats, kubernetes_manager, two_device_scenario):
+    kubernetes_manager.get_links_stats(lab=two_device_scenario)
+    mock_get_links_stats.assert_called_once_with(lab_hash=two_device_scenario.hash.lower(), link_name=None)
+
+
+@mock.patch("src.Kathara.manager.kubernetes.KubernetesLink.KubernetesLink.get_links_stats")
+def test_get_links_stats_no_labs(mock_get_links_stats, kubernetes_manager):
     kubernetes_manager.get_links_stats()
     mock_get_links_stats.assert_called_once_with(lab_hash=None, link_name=None)
 
@@ -863,7 +946,15 @@ def test_get_link_stats_lab_name(mock_get_links_stats, kubernetes_network, kuber
 
 
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesLink.KubernetesLink.get_links_stats")
-def test_get_link_stats_no_lab_hash_and_no_name(mock_get_links_stats, kubernetes_manager):
+def test_get_link_stats_lab_obj(mock_get_links_stats, kubernetes_network, kubernetes_manager, two_device_scenario):
+    mock_get_links_stats.return_value = iter([{"test_network": KubernetesLinkStats(kubernetes_network)}])
+    next(kubernetes_manager.get_links_stats(link_name="test_network", lab=two_device_scenario))
+    mock_get_links_stats.assert_called_once_with(lab_hash=two_device_scenario.hash.lower(),
+                                                 link_name="test_network")
+
+
+@mock.patch("src.Kathara.manager.kubernetes.KubernetesLink.KubernetesLink.get_links_stats")
+def test_get_link_stats_invocation_error(mock_get_links_stats, kubernetes_manager):
     with pytest.raises(InvocationError):
         next(kubernetes_manager.get_link_stats(link_name="test_network"))
     assert not mock_get_links_stats.called
diff --git a/tests/model/filesystem_mixin_test.py b/tests/model/filesystem_mixin_test.py
index da3053de..810e6ac1 100644
--- a/tests/model/filesystem_mixin_test.py
+++ b/tests/model/filesystem_mixin_test.py
@@ -186,6 +186,26 @@ def test_create_file_from_stream_byte():
         mock_fs.upload.assert_called_once_with("/", stream)
 
 
+#
+# TEST: copy_directory_from_path
+#
+def test_copy_directory_from_path():
+    filesystem = FilesystemMixin()
+    filesystem.fs = fs.open_fs(f"mem://")
+    with mock.patch("src.Kathara.foundation.model.FilesystemMixin.open_fs") as mock_open_fs:
+        with mock.patch("src.Kathara.foundation.model.FilesystemMixin.copy_dir") as mock_copy_dir:
+            mock_open_fs.return_value = "directory"
+            filesystem.copy_directory_from_path("src_path", "dst_path")
+            mock_open_fs.assert_called_once()
+            mock_copy_dir.assert_called_once_with("directory", ".", filesystem.fs, "dst_path")
+
+
+def test_copy_directory_from_path_invocation_error():
+    filesystem = FilesystemMixin()
+    with pytest.raises(InvocationError):
+        filesystem.copy_directory_from_path("src_path", "dst_path")
+
+
 #
 # TEST: write_line_before
 #
diff --git a/tests/model/interface_test.py b/tests/model/interface_test.py
new file mode 100644
index 00000000..521d098d
--- /dev/null
+++ b/tests/model/interface_test.py
@@ -0,0 +1,84 @@
+import sys
+
+import pytest
+
+sys.path.insert(0, './')
+
+from src.Kathara.exceptions import InterfaceMacAddressError
+from src.Kathara.model.Machine import Machine
+from src.Kathara.model.Lab import Lab
+from src.Kathara.model.Interface import Interface
+from src.Kathara.model.Link import Link
+
+sys.path.insert(0, './src')
+
+
+@pytest.fixture()
+def default_device():
+    return Machine(Lab("test_lab"), "test_machine")
+
+
+@pytest.fixture()
+def default_link(default_device):
+    return Link(default_device.lab, "A")
+
+
+def test_normal_iface(default_device: Machine, default_link: Link):
+    interface = Interface(default_device, default_link, 0)
+
+    assert interface.machine == default_device
+    assert interface.link == default_link
+    assert interface.num == 0
+    assert interface.mac_address is None
+
+
+def test_iface_num(default_device: Machine, default_link: Link):
+    interface = Interface(default_device, default_link, 2)
+
+    assert interface.machine == default_device
+    assert interface.link == default_link
+    assert interface.num == 2
+    assert interface.mac_address is None
+
+
+def test_iface_mac_address(default_device: Machine, default_link: Link):
+    interface = Interface(default_device, default_link, 0, "00:00:00:00:00:01")
+
+    assert interface.machine == default_device
+    assert interface.link == default_link
+    assert interface.num == 0
+    assert interface.mac_address == "00:00:00:00:00:01"
+
+
+def test_iface_num_and_mac_address(default_device: Machine, default_link: Link):
+    interface = Interface(default_device, default_link, 2, "00:00:00:00:00:01")
+
+    assert interface.machine == default_device
+    assert interface.link == default_link
+    assert interface.num == 2
+    assert interface.mac_address == "00:00:00:00:00:01"
+
+
+def test_iface_mac_address_error_short(default_device: Machine, default_link: Link):
+    with pytest.raises(InterfaceMacAddressError):
+        Interface(default_device, default_link, 0, "00:00:00:00:00")
+
+
+def test_iface_mac_address_error_long(default_device: Machine, default_link: Link):
+    with pytest.raises(InterfaceMacAddressError):
+        Interface(default_device, default_link, 0, "00:00:00:00:00:00:00")
+
+
+def test_iface_mac_address_error_invalid_sep(default_device: Machine, default_link: Link):
+    with pytest.raises(InterfaceMacAddressError):
+        Interface(default_device, default_link, 0, "00:00-00:00:00:00:00")
+
+
+def test_iface_mac_address_invalid_val(default_device: Machine, default_link: Link):
+    with pytest.raises(InterfaceMacAddressError):
+        Interface(default_device, default_link, 0, "gg:00:00:00:00:00:00")
+
+
+def test_iface_mac_address_invalid_format(default_device: Machine, default_link: Link):
+    with pytest.raises(InterfaceMacAddressError):
+        Interface(default_device, default_link, 0, "f:00:00:00:00:00:00")
diff --git a/tests/model/lab_test.py b/tests/model/lab_test.py
index 234bc90f..54f9ccea 100644
--- a/tests/model/lab_test.py
+++ b/tests/model/lab_test.py
@@ -159,110 +159,170 @@ def test_get_or_new_link_two_cd(default_scenario: Lab):
 
 
 def test_connect_one_machine_to_link(default_scenario: Lab):
-    result_1 = default_scenario.connect_machine_to_link("pc1", "A")
+    (machine, interface) = default_scenario.connect_machine_to_link("pc1", "A")
     assert len(default_scenario.machines) == 1
     assert default_scenario.machines['pc1']
     assert len(default_scenario.links) == 1
     assert default_scenario.links['A']
-    assert default_scenario.machines['pc1'].interfaces[0].name == 'A'
-    assert result_1 == (default_scenario.machines['pc1'], default_scenario.links['A'])
+    assert default_scenario.machines['pc1'].interfaces[0].link.name == 'A'
+    assert machine == default_scenario.machines['pc1']
+    assert interface == default_scenario.machines['pc1'].interfaces[0]
 
 
 def test_connect_two_machine_to_link(default_scenario: Lab):
-    result_1 = default_scenario.connect_machine_to_link("pc1", "A")
+    (machine_a, interface_a) = default_scenario.connect_machine_to_link("pc1", "A")
     assert len(default_scenario.machines) == 1
     assert default_scenario.machines['pc1']
     assert len(default_scenario.links) == 1
     assert default_scenario.links['A']
-    result_2 = default_scenario.connect_machine_to_link("pc2", "A")
+    (machine_b, interface_b) = default_scenario.connect_machine_to_link("pc2", "A")
     assert len(default_scenario.machines) == 2
     assert default_scenario.machines['pc2']
     assert len(default_scenario.links) == 1
     assert default_scenario.links['A']
-    assert default_scenario.machines['pc1'].interfaces[0].name == 'A'
-    assert default_scenario.machines['pc2'].interfaces[0].name == 'A'
-    assert result_1 == (default_scenario.machines['pc1'], default_scenario.links['A'])
-    assert result_2 == (default_scenario.machines['pc2'], default_scenario.links['A'])
+    assert default_scenario.machines['pc1'].interfaces[0].link.name == 'A'
+    assert default_scenario.machines['pc2'].interfaces[0].link.name == 'A'
+    assert machine_a == default_scenario.machines['pc1']
+    assert interface_a == default_scenario.machines['pc1'].interfaces[0]
+    assert machine_b == default_scenario.machines['pc2']
+    assert interface_b == default_scenario.machines['pc2'].interfaces[0]
 
 
 def test_connect_machine_to_two_links(default_scenario: Lab):
-    result_1 = default_scenario.connect_machine_to_link("pc1", "A")
-    result_2 = default_scenario.connect_machine_to_link("pc1", "B")
+    (machine_a1, interface_a1) = default_scenario.connect_machine_to_link("pc1", "A")
+    (machine_a2, interface_a2) = default_scenario.connect_machine_to_link("pc1", "B")
     assert len(default_scenario.machines) == 1
     assert default_scenario.machines['pc1']
     assert len(default_scenario.links) == 2
     assert default_scenario.links['A']
     assert default_scenario.links['B']
-    assert default_scenario.machines['pc1'].interfaces[0].name == 'A'
-    assert default_scenario.machines['pc1'].interfaces[1].name == 'B'
-    assert result_1 == (default_scenario.machines['pc1'], default_scenario.links['A'])
-    assert result_2 == (default_scenario.machines['pc1'], default_scenario.links['B'])
+    assert default_scenario.machines['pc1'].interfaces[0].link.name == 'A'
+    assert default_scenario.machines['pc1'].interfaces[1].link.name == 'B'
+    assert machine_a1 == default_scenario.machines['pc1']
+    assert interface_a1 == default_scenario.machines['pc1'].interfaces[0]
+    assert machine_a2 == default_scenario.machines['pc1']
+    assert interface_a2 == default_scenario.machines['pc1'].interfaces[1]
 
 
 def test_connect_machine_to_link_iface_numbers(default_scenario: Lab):
-    result_1 = default_scenario.connect_machine_to_link("pc1", "A", machine_iface_number=2)
+    (machine_a, interface_a) = default_scenario.connect_machine_to_link("pc1", "A",
+                                                                        machine_iface_number=2)
     assert len(default_scenario.machines) == 1
     assert default_scenario.machines['pc1']
     assert len(default_scenario.links) == 1
     assert default_scenario.links['A']
-    assert default_scenario.machines['pc1'].interfaces[2].name == 'A'
-    assert result_1 == (default_scenario.machines['pc1'], default_scenario.links['A'])
+    assert machine_a == default_scenario.machines['pc1']
+    assert interface_a == default_scenario.machines['pc1'].interfaces[2]
+
+
+def test_connect_machine_to_link_mac_address(default_scenario: Lab):
+    (machine_a, interface_a) = default_scenario.connect_machine_to_link(
+        "pc1", "A", mac_address="00:00:00:00:00:01"
+    )
+    assert len(default_scenario.machines) == 1
+    assert default_scenario.machines['pc1']
+    assert len(default_scenario.links) == 1
+    assert default_scenario.links['A']
+    assert machine_a == default_scenario.machines['pc1']
+    assert interface_a == default_scenario.machines['pc1'].interfaces[0]
+    assert interface_a.mac_address == "00:00:00:00:00:01"
+
+
+def test_connect_machine_to_link_iface_num_mac_address(default_scenario: Lab):
+    (machine_a, interface_a) = default_scenario.connect_machine_to_link(
+        "pc1", "A", machine_iface_number=2, mac_address="00:00:00:00:00:01"
+    )
+    assert len(default_scenario.machines) == 1
+    assert default_scenario.machines['pc1']
+    assert len(default_scenario.links) == 1
+    assert default_scenario.links['A']
+    assert machine_a == default_scenario.machines['pc1']
+    assert interface_a == default_scenario.machines['pc1'].interfaces[2]
+    assert interface_a.mac_address == "00:00:00:00:00:01"
 
 
 def test_connect_one_machine_obj_to_link(default_scenario: Lab):
     pc1 = default_scenario.new_machine("pc1")
-    result_1 = default_scenario.connect_machine_obj_to_link(pc1, "A")
+    interface = default_scenario.connect_machine_obj_to_link(pc1, "A")
     assert len(default_scenario.machines) == 1
     assert default_scenario.machines['pc1']
     assert len(default_scenario.links) == 1
     assert default_scenario.links['A']
-    assert default_scenario.machines['pc1'].interfaces[0].name == 'A'
-    assert result_1 == (default_scenario.links['A'], 0)
+    assert default_scenario.machines['pc1'].interfaces[0].link.name == 'A'
+    assert interface == default_scenario.machines['pc1'].interfaces[0]
 
 
 def test_connect_two_machine_obj_to_link(default_scenario: Lab):
     pc1 = default_scenario.new_machine("pc1")
-    result_1 = default_scenario.connect_machine_obj_to_link(pc1, "A")
+    interface_a = default_scenario.connect_machine_obj_to_link(pc1, "A")
     assert len(default_scenario.machines) == 1
     assert default_scenario.machines['pc1']
     assert len(default_scenario.links) == 1
     assert default_scenario.links['A']
     pc2 = default_scenario.new_machine("pc2")
-    result_2 = default_scenario.connect_machine_obj_to_link(pc2, "A")
+    interface_b = default_scenario.connect_machine_obj_to_link(pc2, "A")
     assert len(default_scenario.machines) == 2
     assert default_scenario.machines['pc2']
     assert len(default_scenario.links) == 1
     assert default_scenario.links['A']
-    assert default_scenario.machines['pc1'].interfaces[0].name == 'A'
-    assert default_scenario.machines['pc2'].interfaces[0].name == 'A'
-    assert result_1 == (default_scenario.links['A'], 0)
-    assert result_2 == (default_scenario.links['A'], 0)
+    assert default_scenario.machines['pc1'].interfaces[0].link.name == 'A'
+    assert default_scenario.machines['pc2'].interfaces[0].link.name == 'A'
+    assert interface_a == default_scenario.machines['pc1'].interfaces[0]
+    assert interface_b == default_scenario.machines['pc2'].interfaces[0]
 
 
 def test_connect_machine_obj_to_two_links(default_scenario: Lab):
     pc1 = default_scenario.new_machine("pc1")
-    result_1 = default_scenario.connect_machine_obj_to_link(pc1, "A")
-    result_2 = default_scenario.connect_machine_obj_to_link(pc1, "B")
+    interface_a = default_scenario.connect_machine_obj_to_link(pc1, "A")
+    interface_b = default_scenario.connect_machine_obj_to_link(pc1, "B")
     assert len(default_scenario.machines) == 1
     assert default_scenario.machines['pc1']
     assert len(default_scenario.links) == 2
     assert default_scenario.links['A']
     assert default_scenario.links['B']
-    assert default_scenario.machines['pc1'].interfaces[0].name == 'A'
-    assert default_scenario.machines['pc1'].interfaces[1].name == 'B'
-    assert result_1 == (default_scenario.links['A'], 0)
-    assert result_2 == (default_scenario.links['B'], 1)
+    assert default_scenario.machines['pc1'].interfaces[0].link.name == 'A'
+    assert default_scenario.machines['pc1'].interfaces[1].link.name == 'B'
+    assert interface_a == default_scenario.machines['pc1'].interfaces[0]
+    assert interface_b == default_scenario.machines['pc1'].interfaces[1]
 
 
 def test_connect_machine_obj_to_link_iface_numbers(default_scenario: Lab):
     pc1 = default_scenario.new_machine("pc1")
-    result_1 = default_scenario.connect_machine_obj_to_link(pc1, "A", machine_iface_number=2)
+    interface = default_scenario.connect_machine_obj_to_link(pc1, "A", machine_iface_number=2)
+    assert len(default_scenario.machines) == 1
+    assert default_scenario.machines['pc1']
+    assert len(default_scenario.links) == 1
+    assert default_scenario.links['A']
+    assert default_scenario.machines['pc1'].interfaces[2].link.name == 'A'
+    assert interface == default_scenario.machines['pc1'].interfaces[2]
+
+
+def test_connect_machine_obj_to_link_mac_address(default_scenario: Lab):
+    pc1 = default_scenario.new_machine("pc1")
+    interface_a = default_scenario.connect_machine_obj_to_link(
+        pc1, "A", mac_address="00:00:00:00:00:01"
+    )
+    assert len(default_scenario.machines) == 1
+    assert default_scenario.machines['pc1']
+    assert len(default_scenario.links) == 1
+    assert default_scenario.links['A']
+    assert pc1 == default_scenario.machines['pc1']
+    assert interface_a == default_scenario.machines['pc1'].interfaces[0]
+    assert interface_a.mac_address == "00:00:00:00:00:01"
+
+
+def test_connect_machine_obj_to_link_iface_num_mac_address(default_scenario: Lab):
+    pc1 = default_scenario.new_machine("pc1")
+    interface_a = default_scenario.connect_machine_obj_to_link(
+        pc1, "A", machine_iface_number=2, mac_address="00:00:00:00:00:01"
+    )
     assert len(default_scenario.machines) == 1
     assert default_scenario.machines['pc1']
     assert len(default_scenario.links) == 1
     assert default_scenario.links['A']
-    assert default_scenario.machines['pc1'].interfaces[2].name == 'A'
-    assert result_1 == (default_scenario.links['A'], None)
+    assert pc1 == default_scenario.machines['pc1']
+    assert interface_a == default_scenario.machines['pc1'].interfaces[2]
+    assert interface_a.mac_address == "00:00:00:00:00:01"
 
 
 def test_assign_meta_to_machine(default_scenario: Lab):
diff --git a/tests/model/machine_test.py b/tests/model/machine_test.py
index 360a75b4..e646740a 100644
--- a/tests/model/machine_test.py
+++ b/tests/model/machine_test.py
@@ -39,17 +39,34 @@ def test_default_device_parameters(default_device: Machine):
 # TEST: add_interface
 #
 def test_add_interface(default_device: Machine):
-    result = default_device.add_interface(Link(default_device.lab, "A"))
+    interface = default_device.add_interface(Link(default_device.lab, "A"))
     assert len(default_device.interfaces) == 1
-    assert default_device.interfaces[0].name == "A"
-    assert result == 0
+    assert default_device.interfaces[0].link.name == "A"
+    assert interface == default_device.interfaces[0]
 
 
 def test_add_interface_with_number(default_device: Machine):
-    result = default_device.add_interface(Link(default_device.lab, "A"), number=2)
+    interface = default_device.add_interface(Link(default_device.lab, "A"), number=2)
     assert len(default_device.interfaces) == 1
-    assert default_device.interfaces[2].name == "A"
-    assert result is None
+    assert default_device.interfaces[2].link.name == "A"
+    assert interface == default_device.interfaces[2]
+
+
+def test_add_interface_with_mac_address(default_device: Machine):
+    interface = default_device.add_interface(Link(default_device.lab, "A"), mac_address="00:00:00:00:00:01")
+    assert len(default_device.interfaces) == 1
+    assert default_device.interfaces[0].link.name == "A"
+    assert interface == default_device.interfaces[0]
+    assert interface.mac_address == "00:00:00:00:00:01"
+
+
+def test_add_interface_with_number_and_mac_address(default_device: Machine):
+    interface = default_device.add_interface(Link(default_device.lab, "A"),
+                                             number=2, mac_address="00:00:00:00:00:01")
+    assert len(default_device.interfaces) == 1
+    assert default_device.interfaces[2].link.name == "A"
+    assert interface == default_device.interfaces[2]
+    assert interface.mac_address == "00:00:00:00:00:01"
 
 
 def test_add_interface_exception(default_device: Machine):
@@ -73,7 +90,7 @@ def test_remove_interface(default_device: Machine):
     link = Link(default_device.lab, "A")
     default_device.add_interface(link)
     assert len(default_device.interfaces) == 1
-    assert default_device.interfaces[0].name == "A"
+    assert default_device.interfaces[0].link.name == "A"
     assert default_device.name in link.machines
     default_device.remove_interface(link)
     assert len(default_device.interfaces) == 1
@@ -89,14 +106,14 @@ def test_remove_interface_one(default_device: Machine):
     default_device.add_interface(link_b)
     default_device.add_interface(link_c)
     assert len(default_device.interfaces) == 3
-    assert default_device.interfaces[0].name == "A"
-    assert default_device.interfaces[1].name == "B"
-    assert default_device.interfaces[2].name == "C"
+    assert default_device.interfaces[0].link.name == "A"
+    assert default_device.interfaces[1].link.name == "B"
+    assert default_device.interfaces[2].link.name == "C"
     default_device.remove_interface(link_a)
     assert len(default_device.interfaces) == 3
     assert default_device.interfaces[0] is None
-    assert default_device.interfaces[1].name == "B"
-    assert default_device.interfaces[2].name == "C"
+    assert default_device.interfaces[1].link.name == "B"
+    assert default_device.interfaces[2].link.name == "C"
     assert default_device.name not in link_a.machines
 
 
@@ -110,8 +127,8 @@ def test_add_remove_add_interface(default_device: Machine):
     link = Link(default_device.lab, "A")
     default_device.add_interface(link)
     default_device.remove_interface(link)
-    default_device.add_interface(link)
-    assert default_device.interfaces[1] == link
+    interface = default_device.add_interface(link)
+    assert default_device.interfaces[1] == interface
 
 
 def test_add_remove_three_interfaces(default_device: Machine):
@@ -126,8 +143,8 @@ def test_add_remove_three_interfaces(default_device: Machine):
 
     default_device.remove_interface(link_c)
 
-    default_device.add_interface(link_d)
-    assert default_device.interfaces[3] == link_d
+    interface = default_device.add_interface(link_d)
+    assert default_device.interfaces[3] == interface
 
 
 #
@@ -159,8 +176,18 @@ def test_add_meta_sysctl_negative_number(default_device: Machine):
 
 
 def test_add_meta_sysctl_negative_number_not_format(default_device: Machine):
+    default_device.add_meta("sysctl", "net.test_sysctl.negative=-1-")
+    assert default_device.meta['sysctls']['net.test_sysctl.negative'] == "-1-"
+
+
+def test_add_meta_sysctl_with_spaces(default_device: Machine):
+    default_device.add_meta("sysctl", "net.ipv4.tcp_rmem=4096 87380 33554432")
+    assert default_device.meta['sysctls']['net.ipv4.tcp_rmem'] == "4096 87380 33554432"
+
+
+def test_add_meta_sysctl_double_equal(default_device: Machine):
     with pytest.raises(MachineOptionError):
-        default_device.add_meta("sysctl", "net.test_sysctl.negative=-1-")
+        default_device.add_meta("sysctl", "net.test_sysctl.text=test=again")
 
 
 def test_add_meta_env(default_device: Machine):
@@ -173,6 +200,16 @@ def test_add_meta_env_number(default_device: Machine):
     assert default_device.meta['envs']['MY_ENV_VAR'] == "1"
 
 
+def test_add_meta_env_with_spaces(default_device: Machine):
+    default_device.add_meta("env", "MY_ENV_VAR=spaced value")
+    assert default_device.meta['envs']['MY_ENV_VAR'] == "spaced value"
+
+
+def test_add_meta_env_double_equal(default_device: Machine):
+    with pytest.raises(MachineOptionError):
+        default_device.add_meta("env", "MY_ENV_VAR=test=not valid")
+
+
 def test_add_meta_env_not_format_exception(default_device: Machine):
     with pytest.raises(MachineOptionError):
         default_device.add_meta("env", "MY_ENV_VAR")
diff --git a/tests/parser/lab_parser_test.py b/tests/parser/lab_parser_test.py
index e1ef9db6..f93e30d0 100644
--- a/tests/parser/lab_parser_test.py
+++ b/tests/parser/lab_parser_test.py
@@ -2,20 +2,18 @@
 
 import pytest
 
+from src.Kathara.exceptions import InterfaceMacAddressError
 from src.Kathara.exceptions import MachineCollisionDomainError
 from src.Kathara.parser.netkit.LabParser import LabParser
 
-sys.path.insert(0, './')
-
-
 def test_one_device():
     lab = LabParser.parse("tests/parser/labconf/one_device")
     assert len(lab.machines) == 1
     assert len(lab.links) == 2
     assert lab.machines['pc1']
     assert len(lab.machines['pc1'].interfaces) == 2
-    assert lab.machines['pc1'].interfaces[0].name == 'A'
-    assert lab.machines['pc1'].interfaces[1].name == 'B'
+    assert lab.machines['pc1'].interfaces[0].link.name == 'A'
+    assert lab.machines['pc1'].interfaces[1].link.name == 'B'
     assert lab.machines['pc1'].meta['privileged']
     assert (1000, 'udp') in lab.machines['pc1'].meta['ports']
     assert lab.machines['pc1'].meta['ports'][(1000, 'udp')] == 2000
@@ -56,10 +54,10 @@ def test_two_device_one_cd():
     assert len(lab.links) == 1
     assert lab.machines['pc1']
     assert len(lab.machines['pc1'].interfaces) == 1
-    assert lab.machines['pc1'].interfaces[0].name == 'A'
+    assert lab.machines['pc1'].interfaces[0].link.name == 'A'
     assert lab.machines['pc2']
     assert len(lab.machines['pc2'].interfaces) == 1
-    assert lab.machines['pc2'].interfaces[0].name == 'A'
+    assert lab.machines['pc2'].interfaces[0].link.name == 'A'
 
 
 def test_inline_comment():
@@ -68,7 +66,7 @@ def test_inline_comment():
     assert len(lab.links) == 1
     assert lab.machines['pc1']
     assert len(lab.machines['pc1'].interfaces) == 1
-    assert lab.machines['pc1'].interfaces[0].name == 'A'
+    assert lab.machines['pc1'].interfaces[0].link.name == 'A'
 
 
 def test_inline_comment_error():
@@ -84,3 +82,23 @@ def test_unmatched_quotes():
 def test_unclosed_quotes():
     with pytest.raises(SyntaxError):
         LabParser.parse("tests/parser/labconf/unclosed_quotes")
+
+
+def test_mac_address():
+    lab = LabParser.parse("tests/parser/labconf/mac_address")
+
+    assert lab.machines["pc1"].interfaces[0].mac_address == "00:00:00:00:00:01"
+    assert lab.machines["pc1"].interfaces[0].link.name == "A"
+
+    assert lab.machines["pc2"].interfaces[0].mac_address is None
+    assert lab.machines["pc2"].interfaces[0].link.name == "A"
+
+
+def test_mac_address_error():
+    with pytest.raises(InterfaceMacAddressError):
+        LabParser.parse("tests/parser/labconf/mac_address_error")
+
+
+def test_mac_address_parse_error():
+    with pytest.raises(SyntaxError):
+        LabParser.parse("tests/parser/labconf/mac_address_parse_error")
diff --git a/tests/parser/labconf/mac_address/lab.conf b/tests/parser/labconf/mac_address/lab.conf
new file mode 100644
index 00000000..17e1ae26
--- /dev/null
+++ b/tests/parser/labconf/mac_address/lab.conf
@@ -0,0 +1,3 @@
+pc1[0]="A/00:00:00:00:00:01"
+
+pc2[0]="A"
\ No newline at end of file
diff --git a/tests/parser/labconf/mac_address_error/lab.conf b/tests/parser/labconf/mac_address_error/lab.conf
new file mode 100644
index 00000000..12621ee4
--- /dev/null
+++ b/tests/parser/labconf/mac_address_error/lab.conf
@@ -0,0 +1,3 @@
+pc1[0]="A/00:00:00:00:00"
+
+pc2[0]="A"
\ No newline at end of file
diff --git a/tests/parser/labconf/mac_address_parse_error/lab.conf b/tests/parser/labconf/mac_address_parse_error/lab.conf
new file mode 100644
index 00000000..93f38ae7
--- /dev/null
+++ b/tests/parser/labconf/mac_address_parse_error/lab.conf
@@ -0,0 +1,3 @@
+pc1[0]="A/0/0"
+
+pc2[0]="A"
\ No newline at end of file
diff --git a/tests/types_test.py b/tests/types_test.py
new file mode 100644
index 00000000..ad71324f
--- /dev/null
+++ b/tests/types_test.py
@@ -0,0 +1,11 @@
+import sys
+
+sys.path.insert(0, './')
+
+from src.Kathara.types import SharedCollisionDomainsOption
+
+
+def test_shared_collision_domains_option_to_string():
+    assert SharedCollisionDomainsOption.to_string(1) == 'Not Shared'
+    assert SharedCollisionDomainsOption.to_string(2) == 'Share collision domains between network scenarios'
+    assert SharedCollisionDomainsOption.to_string(3) == 'Share collision domains between users'