Compare commits

...

2 Commits
fabian-3 ... f1

Author SHA1 Message Date
baiobelfer
3a18edc2d5 added veth 2025-05-15 10:28:20 +02:00
baiobelfer
1bdefab786 add files.py 2025-05-15 09:40:42 +02:00
18 changed files with 718 additions and 29 deletions

Binary file not shown.

View File

@ -19,6 +19,33 @@
stringstyle=\color{red} stringstyle=\color{red}
} }
% Define YAML language for listings
\lstdefinelanguage{yaml}{
keywords={true,false,null,yaml,network,version,ethernets,dhcp4,addresses,routes,to,via,nameservers},
keywordstyle=\color{blue}\bfseries,
basicstyle=\ttfamily\small,
sensitive=false,
comment=[l]{\#},
commentstyle=\color{gray}\itshape,
stringstyle=\color{red},
morestring=[b]{"},
morestring=[b]{'}
}
% Define Python language for listings
\lstdefinelanguage{python}{
keywords={def,class,import,from,as,try,except,with,return,raise,if,elif,else,for,in,while,break,continue},
keywordstyle=\color{blue}\bfseries,
basicstyle=\ttfamily\small,
sensitive=true,
comment=[l]{\#},
commentstyle=\color{gray}\itshape,
stringstyle=\color{red},
morestring=[b]{"},
morestring=[b]{'},
identifierstyle=\color{black}
}
\begin{document} \begin{document}
\title{Basic Configuration of dnsmasq in an Incus Container on Debian with Netplan} \title{Basic Configuration of dnsmasq in an Incus Container on Debian with Netplan}
@ -27,13 +54,14 @@
\maketitle \maketitle
\section{Introduction} \section{Introduction}
This guide provides step-by-step instructions for setting up \texttt{dnsmasq} as a DNS and DHCP server in an Incus container running Debian. The network configuration is managed using Netplan to ensure proper network integration. This guide provides step-by-step instructions for setting up \texttt{dnsmasq} as a DNS and DHCP server in an Incus container running Debian. The network configuration is managed using Netplan and a custom Python script to create virtual Ethernet (veth) pairs and bridges, ensuring proper network integration.
\section{Prerequisites} \section{Prerequisites}
Before proceeding, ensure the following: Before proceeding, ensure the following:
\begin{itemize} \begin{itemize}
\item Incus is installed on the host system (\texttt{sudo apt install incus}). \item Incus is installed on the host system (\texttt{sudo apt install incus}).
\item A Debian-based container is created in Incus. \item A Debian-based container is created in Incus.
\item Python 3 and the \texttt{pyroute2} package are installed on the host (\texttt{sudo apt install python3 python3-pyroute2}).
\item Basic knowledge of Linux networking and container management. \item Basic knowledge of Linux networking and container management.
\item Root or sudo access to the host and container. \item Root or sudo access to the host and container.
\end{itemize} \end{itemize}
@ -41,34 +69,105 @@ Before proceeding, ensure the following:
\section{Step-by-Step Configuration} \section{Step-by-Step Configuration}
\subsection{Creating and Setting Up the Incus Container} \subsection{Creating and Setting Up the Incus Container}
% Creating the Incus container Create a Debian container named \texttt{dnsmasq-container} using the following commands on the host:
Create a Debian container named \texttt{dnsmasq-container} using the following command on the host:
\begin{lstlisting}[language=bash] \begin{lstlisting}[language=bash]
incus create images:debian/12 dnsmasq-container incus create images:debian/12 dnsmasq-container
incus config set dnsmasq-container security.syscalls.intercept.mount true incus config set dnsmasq-container security.syscalls.intercept.mount true
incus config set dnsmasq-container security.nesting true
incus config set dnsmasq-container security.privileged true
incus start dnsmasq-container incus start dnsmasq-container
\end{lstlisting} \end{lstlisting}
The \texttt{security.syscalls.intercept.mount} setting is required for \texttt{dnsmasq} to function correctly in the container. The \texttt{security.syscalls.intercept.mount}, \texttt{security.nesting}, and \texttt{security.privileged} settings are required for \texttt{dnsmasq} and Docker to function correctly in the container.
% Accessing the container \subsection{Installing Additional Packages}
Access the container: Install the necessary packages inside the container:
\begin{lstlisting}[language=bash]
incus exec dnsmasq-container -- apt update
incus exec dnsmasq-container -- apt install -y \
netplan.io \
sudo vim nano git tmux mc zip unzip curl wget htop lynx \
iproute2 termshark bridge-utils \
python3 python3-ipython python3-pyroute2 python3-scapy \
docker.io docker-compose
\end{lstlisting}
\subsection{Configuring Users and Permissions}
Configure user access and permissions within the container.
\subsubsection{Changing the Root Password}
Set the root password to "passroot":
\begin{lstlisting}[language=bash]
incus exec dnsmasq-container -- bash -c 'echo "root:passroot" | chpasswd'
\end{lstlisting}
\subsubsection{Adding a New User}
Add a new user named "user" with the password "pass" and add them to the "sudo" and "docker" groups:
\begin{lstlisting}[language=bash]
incus exec dnsmasq-container -- useradd -m -s /bin/bash user
incus exec dnsmasq-container -- bash -c 'echo "user:pass" | chpasswd'
incus exec dnsmasq-container -- usermod -aG sudo user
incus exec dnsmasq-container -- usermod -aG docker user
\end{lstlisting}
\subsection{Accessing the Container}
Access the container's shell:
\begin{lstlisting}[language=bash] \begin{lstlisting}[language=bash]
incus exec dnsmasq-container -- bash incus exec dnsmasq-container -- bash
\end{lstlisting} \end{lstlisting}
\subsection{Installing dnsmasq} \subsection{Installing dnsmasq}
% Installing dnsmasq and dependencies
Update the package list and install \texttt{dnsmasq}: Update the package list and install \texttt{dnsmasq}:
\begin{lstlisting}[language=bash] \begin{lstlisting}[language=bash]
apt update incus exec dnsmasq-container -- apt update
apt install dnsmasq -y incus exec dnsmasq-container -- apt install dnsmasq -y
\end{lstlisting} \end{lstlisting}
\subsection{Configuring the Network with Netplan} \subsection{Configuring Network with Veth Pairs and Netplan}
% Setting up the network interface To enable advanced networking, use the provided Python script (\texttt{link.py}) to create a virtual Ethernet (veth) pair connecting the container to the host's network namespace, with an optional bridge for network integration. Save the following script as \texttt{link.py} on the host:
Configure the container's network using Netplan to assign a static IP address. Edit the Netplan configuration file (e.g., \texttt{/etc/netplan/01-netcfg.yaml}):
\begin{lstlisting}[language=python]
import argparse
import os
import subprocess
import sys
from pyroute2 import IPRoute, NetNS
# ... (rest of the link.py script as provided) ...
\end{lstlisting}
Run the script to create a veth pair, move one end to the container's network namespace, and attach it to a bridge on the host. First, identify the container's name or ID:
\begin{lstlisting}[language=bash] \begin{lstlisting}[language=bash]
nano /etc/netplan/01-netcfg.yaml incus list
\end{lstlisting}
Assuming the container name is \texttt{dnsmasq-container}, execute the script with sudo privileges:
\begin{lstlisting}[language=bash]
sudo python3 link.py --namespace1 dnsmasq-container --namespace2 1 \
--name1 veth-container --name2 veth-host \
--bridge2 br0 --type1 incus
\end{lstlisting}
\textbf{Explanation:}
\begin{itemize}
\item \texttt{--namespace1 dnsmasq-container}: Specifies the container's network namespace (Incus container).
\item \texttt{--namespace2 1}: Specifies the default (host) namespace.
\item \texttt{--name1 veth-container}: Names the veth interface inside the container.
\item \texttt{--name2 veth-host}: Names the veth interface on the host.
\item \texttt{--bridge2 br0}: Attaches the host's veth interface to a bridge named \texttt{br0}.
\item \texttt{--type1 incus}: Indicates that \texttt{namespace1} is an Incus container.
\end{itemize}
Before running the script, ensure the bridge \texttt{br0} exists on the host. Create it if necessary:
\begin{lstlisting}[language=bash]
sudo ip link add name br0 type bridge
sudo ip link set br0 up
\end{lstlisting}
The script exposes the container's network namespace, creates the veth pair, moves \texttt{veth-container} to the container's namespace, attaches \texttt{veth-host} to \texttt{br0}, and brings both interfaces up.
\subsection{Configuring the Network with Netplan}
Configure the container's network using Netplan to assign a static IP address to the \texttt{veth-container} interface (aliased as \texttt{eth0} for simplicity). Create or edit the Netplan configuration file at \texttt{/etc/netplan/01-netcfg.yaml} inside the container:
\begin{lstlisting}[language=bash]
incus exec dnsmasq-container -- nano /etc/netplan/01-netcfg.yaml
\end{lstlisting} \end{lstlisting}
Add the following configuration: Add the following configuration:
\begin{lstlisting}[language=yaml] \begin{lstlisting}[language=yaml]
@ -76,23 +175,42 @@ network:
version: 2 version: 2
ethernets: ethernets:
eth0: eth0:
match:
name: veth-container
dhcp4: no dhcp4: no
addresses: addresses:
- 192.168.1.10/24 - 192.168.1.10/24
gateway4: 192.168.1.1 routes:
- to: default
via: 192.168.1.1
nameservers: nameservers:
addresses: [8.8.8.8, 8.8.4.4] addresses: [8.8.8.8, 8.8.4.4]
\end{lstlisting} \end{lstlisting}
\textbf{Explanation:}
\begin{itemize}
\item \texttt{match: name: veth-container}: Matches the \texttt{veth-container} interface created by the script, aliased as \texttt{eth0}.
\item \texttt{dhcp4: no}: Disables DHCP to use a static IP.
\item \texttt{addresses}: Assigns the static IP \texttt{192.168.1.10/24}.
\item \texttt{routes}: Sets the default gateway to \texttt{192.168.1.1}.
\item \texttt{nameservers}: Specifies Google's DNS servers.
\end{itemize}
Apply the configuration: Apply the configuration:
\begin{lstlisting}[language=bash] \begin{lstlisting}[language=bash]
netplan apply incus exec dnsmasq-container -- netplan apply
\end{lstlisting}
Verify the network configuration:
\begin{lstlisting}[language=bash]
incus exec dnsmasq-container -- ip a show eth0
incus exec dnsmasq-container -- ping 8.8.8.8
\end{lstlisting} \end{lstlisting}
\subsection{Configuring dnsmasq} \subsection{Configuring dnsmasq}
% Configuring dnsmasq for DNS and DHCP
Edit the \texttt{dnsmasq} configuration file at \texttt{/etc/dnsmasq.conf}: Edit the \texttt{dnsmasq} configuration file at \texttt{/etc/dnsmasq.conf}:
\begin{lstlisting}[language=bash] \begin{lstlisting}[language=bash]
nano /etc/dnsmasq.conf incus exec dnsmasq-container -- nano /etc/dnsmasq.conf
\end{lstlisting} \end{lstlisting}
Add or modify the following settings to enable DNS and DHCP: Add or modify the following settings to enable DNS and DHCP:
\begin{lstlisting} \begin{lstlisting}
@ -123,34 +241,33 @@ dhcp-option=6,8.8.8.8,8.8.4.4
\end{itemize} \end{itemize}
\subsection{Starting and Enabling dnsmasq} \subsection{Starting and Enabling dnsmasq}
% Starting the dnsmasq service Restart and enable the \texttt{dnsmasq} service:
Restart the \texttt{dnsmasq} service to apply the configuration:
\begin{lstlisting}[language=bash] \begin{lstlisting}[language=bash]
systemctl restart dnsmasq incus exec dnsmasq-container -- systemctl restart dnsmasq
systemctl enable dnsmasq incus exec dnsmasq-container -- systemctl enable dnsmasq
\end{lstlisting} \end{lstlisting}
Verify that \texttt{dnsmasq} is running: Verify that \texttt{dnsmasq} is running:
\begin{lstlisting}[language=bash] \begin{lstlisting}[language=bash]
systemctl status dnsmasq incus exec dnsmasq-container -- systemctl status dnsmasq
\end{lstlisting} \end{lstlisting}
\subsection{Testing the Configuration} \subsection{Testing the Configuration}
% Testing DNS and DHCP Test DNS resolution from within the container:
Test the DNS resolution from within the container:
\begin{lstlisting}[language=bash] \begin{lstlisting}[language=bash]
nslookup example.local 192.168.1.10 incus exec dnsmasq-container -- nslookup example.local 192.168.1.10
\end{lstlisting} \end{lstlisting}
To test DHCP, connect a client device to the same network and verify that it receives an IP address in the range \texttt{192.168.1.100--192.168.1.200}. To test DHCP, connect a client device to the same network (via the \texttt{br0} bridge) and verify that it receives an IP address in the range \texttt{192.168.1.100--192.168.1.200}.
\section{Troubleshooting} \section{Troubleshooting}
If \texttt{dnsmasq} fails to start: If \texttt{dnsmasq} fails to start:
\begin{itemize} \begin{itemize}
\item Check the logs: \texttt{journalctl -u dnsmasq}. \item Check the logs: \texttt{incus exec dnsmasq-container -- journalctl -u dnsmasq}.
\item Ensure no other service is using port 53 (DNS) or 67 (DHCP). \item Ensure no other service is using port 53 (DNS) or 67 (DHCP).
\item Verify the network configuration with \texttt{ip a} and \texttt{ping 8.8.8.8}. \item Verify the network configuration with \texttt{incus exec dnsmasq-container -- ip a} and \texttt{incus exec dnsmasq-container -- ping 8.8.8.8}.
\item Confirm the veth pair and bridge setup: \texttt{ip link show} on the host and \texttt{incus exec dnsmasq-container -- ip link show}.
\end{itemize} \end{itemize}
\section{Conclusion} \section{Conclusion}
This guide configures \texttt{dnsmasq} as a DNS and DHCP server in an Incus container on Debian. The Netplan configuration ensures proper network setup. For advanced configurations, refer to the \texttt{dnsmasq} documentation (\texttt{man dnsmasq}). This guide configures \texttt{dnsmasq} as a DNS and DHCP server in an Incus container on Debian. The \texttt{link.py} script and Netplan configuration ensure a robust network setup with veth pairs and static IP addressing. For advanced configurations, refer to the \texttt{dnsmasq} documentation (\texttt{man dnsmasq}) and \texttt{pyroute2} documentation.
\end{document} \end{document}

91
py/files.py Normal file
View File

@ -0,0 +1,91 @@
import re
from pathlib import Path
def get_file_extension_and_default_name(attributes: str, code: str) -> tuple[str, str]:
"""Determine file extension and default name based on attributes and content."""
# Extract language from attributes (e.g., language=bash)
language_match = re.search(r'language=(\w+)', attributes)
language = language_match.group(1).lower() if language_match else 'unknown'
if language == 'bash' or 'incus' in code.lower():
return '.sh', 'incus_script'
elif language == 'yaml':
return '.yaml', 'netplan_config'
elif language == 'python':
return '.py', 'python_script'
else:
# Assume .conf for blocks without language or resembling config
return '.conf', 'dnsmasq_config'
def generate_unique_filename(base_dir: Path, base_name: str, extension: str) -> Path:
"""Generate a unique filename by appending a number if the file exists."""
counter = 1
while True:
filename = f"{base_name}_{counter}{extension}"
file_path = base_dir / filename
if not file_path.exists():
return file_path
counter += 1
def extract_listings():
"""Extract lstlisting code blocks from main.tex and save to scripts folder."""
# Define paths
base_dir = Path(__file__).parent.parent # ~/edu/paso/dnsmasq
tex_file = base_dir / 'doc' / 'main.tex'
scripts_dir = base_dir / 'scripts'
# Ensure scripts directory exists
scripts_dir.mkdir(exist_ok=True)
# Read the LaTeX file
try:
with open(tex_file, 'r', encoding='utf-8') as f:
content = f.read()
except FileNotFoundError:
print(f"Error: {tex_file} not found")
return
except Exception as e:
print(f"Error reading {tex_file}: {str(e)}")
return
# Regex to match lstlisting environments
# Captures optional attributes [language=...,label=...] and code block content
pattern = r'\\begin\{lstlisting\}(\[(.*?)\])?(.*?)\\end\{lstlisting\}'
matches = re.finditer(pattern, content, re.DOTALL)
extracted_count = 0
for match in matches:
# Extract attributes (if any) and code
attributes = match.group(2) or '' # Empty string if no attributes
code = match.group(3).strip()
# Extract label from attributes (e.g., label={setup_container})
label_match = re.search(r'label=\{([^}]+)\}', attributes)
label = label_match.group(1) if label_match else None
# Get file extension and default name
extension, default_name = get_file_extension_and_default_name(attributes, code)
# Use label if available, otherwise use default name
base_name = label if label else default_name
# Generate unique filename
file_path = generate_unique_filename(scripts_dir, base_name, extension)
# Save the code block to a file
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(code)
print(f"Saved code block to {file_path}")
extracted_count += 1
except Exception as e:
print(f"Error saving {file_path}: {str(e)}")
if extracted_count == 0:
print("No lstlisting code blocks found in main.tex")
else:
print(f"Completed: Extracted {extracted_count} code block(s) to {scripts_dir}")
if __name__ == "__main__":
extract_listings()

429
py/link.,py Normal file
View File

@ -0,0 +1,429 @@
import argparse
import os
import subprocess
import sys
from pyroute2 import IPRoute, NetNS
class NetworkNamespaceManager:
"""
Provides functionalities to manage network namespaces.
Network namespaces partition network resources such as network links,
IP addresses, and port numbers into disjoint sets.
"""
@staticmethod
def list_netns():
"""
List all available network namespaces.
Scans the '/var/run/netns' directory and prints out all
the network namespace files present. If no namespaces are found, it
prints a message indicating that.
"""
netns_dir = "/var/run/netns"
try:
netns_files = os.listdir(netns_dir)
if netns_files:
print("List of available network namespaces:")
for netns_file in netns_files:
print(f"- {netns_file}")
else:
print("No network namespaces found.")
except OSError as e:
print(f"Error listing network namespaces: {e}")
class ContainerNetnsExposer:
"""
Responsible for exposing network namespaces of containers.
Supports Docker, LXC, LXD, and Incus containers, allowing the user to interact
with their network namespaces.
"""
def __init__(self):
"""
Initializes the ContainerNetnsExposer instance.
"""
self.netns_pid = None
self.netns_path = None
def expose_container_netns(self, container_id_or_name, container_type='docker'):
"""
Expose the network namespace of a specified container.
Parameters:
container_id_or_name (str): The identifier or name of the container.
container_type (str): The type of the container ('docker', 'lxc', 'lxd', or 'incus').
Returns:
str: The PID of the container as a string.
Raises:
SystemExit: If the container type is unsupported or if there is an error
in retrieving the container's PID.
"""
if container_type == 'docker':
self.get_docker_container_pid(container_id_or_name)
elif container_type in ('lxc', 'lxd', 'incus'):
self.get_lxc_container_pid(container_id_or_name, container_type)
else:
print("Unsupported container type. Only 'docker', 'lxc', 'lxd', and 'incus' are supported.")
sys.exit(1)
self.create_netns_directory()
self.create_or_remove_netns_symlink()
return str(self.netns_pid)
def get_docker_container_pid(self, container_id_or_name):
"""
Retrieve the PID of a Docker container.
Parameters:
container_id_or_name (str): The identifier or name of the Docker container.
Raises:
SystemExit: If there is an error in retrieving the Docker container's PID.
"""
try:
self.netns_pid = subprocess.check_output(
["sudo", "docker", "inspect", "-f", "{{.State.Pid}}", container_id_or_name],
universal_newlines=True
).strip()
except subprocess.CalledProcessError:
print("Error retrieving Docker container PID. Make sure the container exists and is running.")
sys.exit(1)
def get_lxc_container_pid(self, container_name, container_type='lxc'):
"""
Retrieve the PID of an LXC, LXD, or Incus container.
Parameters:
container_name (str): The name of the container.
container_type (str): The type of the container ('lxc', 'lxd', or 'incus').
Raises:
SystemExit: If there is an error in retrieving the container's PID.
"""
try:
if container_type == 'lxc':
output = subprocess.check_output(
["lxc-info", "-n", container_name, "-p"],
universal_newlines=True
)
self.netns_pid = output.strip().split()[-1]
elif container_type == 'lxd':
output = subprocess.check_output(
["sudo", "lxc", "info", container_name],
universal_newlines=True
)
for line in output.splitlines():
if line.strip().startswith("PID:"):
self.netns_pid = line.split(':')[1].strip()
break
else:
print(f"PID not found in 'lxc info' output for container '{container_name}'.")
sys.exit(1)
elif container_type == 'incus':
output = subprocess.check_output(
["sudo", "incus", "info", container_name],
universal_newlines=True
)
for line in output.splitlines():
if line.strip().startswith("PID:"):
self.netns_pid = line.split(':')[1].strip()
break
else:
print(f"PID not found in 'incus info' output for container '{container_name}'.")
sys.exit(1)
except subprocess.CalledProcessError as e:
print(f"Error retrieving {container_type.upper()} container PID for '{container_name}'. Error: {e}")
sys.exit(1)
def create_netns_directory(self):
"""
Create the network namespace directory if it does not exist.
Ensures that the directory '/var/run/netns' exists, which is used
to store network namespace symlinks.
"""
try:
subprocess.run(["sudo", "mkdir", "-p", "/var/run/netns"], check=True)
except subprocess.CalledProcessError as e:
print(f"Error creating network namespace directory: {e.stderr.strip()}")
sys.exit(1)
def create_or_remove_netns_symlink(self):
"""
Create or remove a symlink to the network namespace of a container.
Sets up a symlink in '/var/run/netns', pointing to the network namespace
of the container identified by its PID. If a symlink with the same name
already exists, it is removed before creating a new one.
"""
self.netns_path = f"/var/run/netns/{self.netns_pid}"
try:
if os.path.exists(self.netns_path):
subprocess.run(["sudo", "rm", "-rf", self.netns_path], check=True)
subprocess.run(["sudo", "ln", "-s", f"/proc/{self.netns_pid}/ns/net", self.netns_path], check=True)
except subprocess.CalledProcessError as e:
print(f"Error creating or removing symlink: {e}")
sys.exit(1)
class IfaceManager:
"""
Manages network interfaces, including creation, deletion, and configuration
of veth pairs, VLANs, and bridges.
"""
def delete(self, iface_name, namespace=None):
"""
Delete a network interface.
Parameters:
iface_name (str): The name of the interface to delete.
namespace (str, optional): The network namespace where the interface exists.
Raises:
Exception: If the interface cannot be deleted.
"""
try:
if namespace:
context_manager = NetNS(namespace)
else:
context_manager = IPRoute()
with context_manager as ns:
iface_idx_list = ns.link_lookup(ifname=iface_name)
if not iface_idx_list:
raise ValueError(f"Interface {iface_name} not found.")
iface_idx = iface_idx_list[0]
ns.link('del', index=iface_idx)
except Exception as e:
print(f"Error deleting interface {iface_name} in namespace {namespace}: {e}")
def create_veth(self, iface1, iface2, namespace=None):
"""
Create a veth pair.
Parameters:
iface1 (str): The name of the first interface.
iface2 (str): The name of the second interface.
namespace (str, optional): The network namespace where to create the veth pair.
Raises:
Exception: If the veth pair cannot be created.
"""
try:
if namespace:
context_manager = NetNS(namespace)
else:
context_manager = IPRoute()
with context_manager as ns:
ns.link('add', ifname=iface1, peer={'ifname': iface2}, kind='veth')
except Exception as e:
print(f"Error creating veth pair {iface1} and {iface2}: {e}")
def create_vlan(self, base_iface, vlan_id, namespace=None):
"""
Create a VLAN interface.
Parameters:
base_iface (str): The base interface name.
vlan_id (int): The VLAN ID.
namespace (str, optional): The network namespace where to create the VLAN interface.
Raises:
Exception: If the VLAN interface cannot be created.
"""
try:
if namespace:
context_manager = NetNS(namespace)
else:
context_manager = IPRoute()
with context_manager as ns:
base_iface_idx_list = ns.link_lookup(ifname=base_iface)
if not base_iface_idx_list:
raise ValueError(f"Base interface {base_iface} not found.")
base_iface_idx = base_iface_idx_list[0]
vlan_iface = f"{base_iface}.{vlan_id}"
ns.link('add', ifname=vlan_iface, link=base_iface_idx, kind='vlan', vlan_info={'id': vlan_id})
except Exception as e:
print(f"Error creating VLAN on interface {base_iface}: {e}")
def create_bridge(self, bridge_name, namespace=None):
"""
Create a bridge interface.
Parameters:
bridge_name (str): The name of the bridge.
namespace (str, optional): The network namespace where to create the bridge.
Raises:
Exception: If the bridge cannot be created.
"""
try:
if namespace:
context_manager = NetNS(namespace)
else:
context_manager = IPRoute()
with context_manager as ns:
ns.link('add', ifname=bridge_name, kind='bridge')
except Exception as e:
print(f"Error creating bridge {bridge_name}: {e}")
def move(self, iface, namespace):
"""
Move a network interface to another namespace.
Parameters:
iface (str): The interface name to move.
namespace (str): The target network namespace.
Raises:
Exception: If the interface cannot be moved.
"""
try:
ipr = IPRoute()
idx_list = ipr.link_lookup(ifname=iface)
if not idx_list:
raise ValueError(f"Interface {iface} not found.")
idx = idx_list[0]
ipr.link('set', index=idx, net_ns_fd=namespace)
except Exception as e:
print(f"Error moving interface {iface} to namespace {namespace}: {e}")
def set_interface_up(self, iface_name, namespace=None):
"""
Set a network interface up.
Parameters:
iface_name (str): The name of the interface.
namespace (str, optional): The network namespace where the interface exists.
Raises:
Exception: If the interface cannot be set up.
"""
try:
if namespace:
context_manager = NetNS(namespace)
else:
context_manager = IPRoute()
with context_manager as ns:
iface_idx_list = ns.link_lookup(ifname=iface_name)
if not iface_idx_list:
raise ValueError(f"Interface {iface_name} not found.")
iface_idx = iface_idx_list[0]
ns.link("set", index=iface_idx, state="up")
except Exception as e:
print(f"Error setting up interface {iface_name} in namespace {namespace}: {e}")
def attach_to_bridge(self, iface_name, bridge_name, namespace=None):
"""
Attach an interface to a bridge.
Parameters:
iface_name (str): The name of the interface.
bridge_name (str): The name of the bridge.
namespace (str, optional): The network namespace where the interface and bridge exist.
Raises:
Exception: If the interface cannot be attached to the bridge.
"""
try:
if namespace:
context_manager = NetNS(namespace)
else:
context_manager = IPRoute()
with context_manager as ns:
iface_idx_list = ns.link_lookup(ifname=iface_name)
if not iface_idx_list:
raise ValueError(f"Interface {iface_name} not found.")
iface_idx = iface_idx_list[0]
bridge_idx_list = ns.link_lookup(ifname=bridge_name)
if not bridge_idx_list:
raise ValueError(f"Bridge {bridge_name} not found.")
bridge_idx = bridge_idx_list[0]
ns.link("set", index=iface_idx, master=bridge_idx)
except Exception as e:
print(f"Error attaching interface {iface_name} to bridge {bridge_name} in namespace {namespace}: {e}")
def interpret_namespace(namespace_arg):
"""
Interpret the namespace argument.
If the argument is '1', converts it to None (representing the default namespace),
otherwise returns the argument as is.
Parameters:
namespace_arg (str): The namespace argument.
Returns:
str or None: The interpreted namespace.
"""
return None if namespace_arg == '1' else namespace_arg
def main():
parser = argparse.ArgumentParser(description="Create veth pairs between containers with optional bridge attachment.")
parser.add_argument("-ns1", "--namespace1", default=None, help="Name of the first namespace or container, or '1' for the default namespace.")
parser.add_argument("-ns2", "--namespace2", default=None, help="Name of the second namespace or container, or '1' for the default namespace.")
parser.add_argument("-n1", "--name1", required=True, help="Name of the first veth interface.")
parser.add_argument("-n2", "--name2", required=True, help="Name of the second veth interface.")
parser.add_argument("-b1", "--bridge1", default=None, help="Name of the network bridge for ns1.")
parser.add_argument("-b2", "--bridge2", default=None, help="Name of the network bridge for ns2.")
parser.add_argument("-t1", "--type1", default=None, help="Container type for ns1 ('docker', 'lxc', 'lxd', 'incus', or 'None' for the default namespace).")
parser.add_argument("-t2", "--type2", default=None, help="Container type for ns2 ('docker', 'lxc', 'lxd', 'incus', or 'None' for the default namespace).")
args = parser.parse_args()
# Processing namespace arguments and container types
ns1, type1 = interpret_namespace(args.namespace1), args.type1
ns2, type2 = interpret_namespace(args.namespace2), args.type2
# Instantiate management classes
iface_manager = IfaceManager()
container_exposer = ContainerNetnsExposer()
# Expose container network namespaces if applicable
if type1 and ns1:
ns1_pid = container_exposer.expose_container_netns(ns1, type1)
ns1 = ns1_pid
if type2 and ns2:
ns2_pid = container_exposer.expose_container_netns(ns2, type2)
ns2 = ns2_pid
# Create veth pair
iface_manager.create_veth(args.name1, args.name2)
# Move ends of the veth pair to appropriate namespaces if required
if ns1:
iface_manager.move(args.name1, ns1)
if ns2:
iface_manager.move(args.name2, ns2)
# Optional: Attach to network bridge and bring interfaces up
if args.bridge1 and args.name1:
iface_manager.attach_to_bridge(args.name1, args.bridge1, ns1)
iface_manager.set_interface_up(args.name1, ns1)
if args.bridge2 and args.name2:
iface_manager.attach_to_bridge(args.name2, args.bridge2, ns2)
iface_manager.set_interface_up(args.name2, ns2)
# Bring up interfaces if not already up
if not args.bridge1 and args.name1:
iface_manager.set_interface_up(args.name1, ns1)
if not args.bridge2 and args.name2:
iface_manager.set_interface_up(args.name2, ns2)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,13 @@
# DNS settings
domain-needed
bogus-priv
no-resolv
server=8.8.8.8
server=8.8.4.4
local=/example.local/
domain=example.local
# DHCP settings
dhcp-range=192.168.1.100,192.168.1.200,12h
dhcp-option=3,192.168.1.1
dhcp-option=6,8.8.8.8,8.8.4.4

View File

@ -0,0 +1,5 @@
incus create images:debian/12 dnsmasq-container
incus config set dnsmasq-container security.syscalls.intercept.mount true
incus config set dnsmasq-container security.nesting true
incus config set dnsmasq-container security.privileged true
incus start dnsmasq-container

View File

@ -0,0 +1,2 @@
incus exec dnsmasq-container -- systemctl restart dnsmasq
incus exec dnsmasq-container -- systemctl enable dnsmasq

View File

@ -0,0 +1 @@
incus exec dnsmasq-container -- systemctl status dnsmasq

View File

@ -0,0 +1 @@
incus exec dnsmasq-container -- nslookup example.local 192.168.1.10

View File

@ -0,0 +1,7 @@
incus exec dnsmasq-container -- apt update
incus exec dnsmasq-container -- apt install -y \
netplan.io \
sudo vim nano git tmux mc zip unzip curl wget htop lynx \
iproute2 termshark bridge-utils \
python3 python3-ipython python3-pyroute2 python3-scapy \
docker.io docker-compose

View File

@ -0,0 +1 @@
incus exec dnsmasq-container -- bash -c 'echo "root:passroot" | chpasswd'

View File

@ -0,0 +1,4 @@
incus exec dnsmasq-container -- useradd -m -s /bin/bash user
incus exec dnsmasq-container -- bash -c 'echo "user:pass" | chpasswd'
incus exec dnsmasq-container -- usermod -aG sudo user
incus exec dnsmasq-container -- usermod -aG docker user

View File

@ -0,0 +1 @@
incus exec dnsmasq-container -- bash

View File

@ -0,0 +1,2 @@
incus exec dnsmasq-container -- apt update
incus exec dnsmasq-container -- apt install dnsmasq -y

View File

@ -0,0 +1 @@
incus exec dnsmasq-container -- nano /etc/netplan/01-netcfg.yaml

View File

@ -0,0 +1 @@
incus exec dnsmasq-container -- netplan apply

View File

@ -0,0 +1 @@
incus exec dnsmasq-container -- nano /etc/dnsmasq.conf

View File

@ -0,0 +1,12 @@
network:
version: 2
ethernets:
eth0:
dhcp4: no
addresses:
- 192.168.1.10/24
routes:
- to: default
via: 192.168.1.1
nameservers:
addresses: [8.8.8.8, 8.8.4.4]