diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 169d2f5..8d5dd88 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,4 +1,4 @@
-FROM debian:12-slim as dev
+FROM debian:12-slim
 
 RUN apt -y update && \
     apt -y upgrade && \
@@ -9,7 +9,7 @@ RUN apt -y update && \
         inotify-tools curl wget ifupdown2 jq build-essential git \
         unzip zip cmake automake autoconf libtool autopoint gettext \
         tar gzip zsh vim nano sudo zstd less gnupg ripgrep gdb cgdb locales \
-        systemd systemd-resolved systemd-sysv
+        systemd systemd-resolved systemd-sysv dbus iproute2 iputils-ping
 
 RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
     sed -i -e 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && \
@@ -39,4 +39,25 @@ RUN echo "\n\
 \n\
 set show-all-if-ambiguous on \n\
 set completion-ignore-case on \n\
-" >> /etc/inputrc
\ No newline at end of file
+" >> /etc/inputrc
+
+RUN echo "\n\
+[Match]\n\
+Name=calaos-*\n\
+\n\
+[Network]\n\
+DHCP=yes\n\
+" > /etc/systemd/network/calaos.network
+
+COPY setup_veth.sh /usr/local/bin/setup_veth.sh
+RUN chmod +x /usr/local/bin/setup_veth.sh
+COPY setup-veth.service /etc/systemd/system/setup-veth.service
+RUN systemctl enable setup-veth.service
+
+RUN systemctl enable setup-veth.service
+RUN systemctl enable systemd-resolved.service
+RUN systemctl enable systemd-networkd.service
+
+ENV container docker
+STOPSIGNAL SIGRTMIN+3
+CMD ["/lib/systemd/systemd", "--system", "--unit=basic.target"]
\ No newline at end of file
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 643ba1b..5d1c3da 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -19,10 +19,16 @@
 	"customizations": {
 			"vscode": {
 					"extensions": [
-							"ms-vscode.cpptools-extension-pack",
-							"ms-vscode.makefile-tools",
-							"lizebang.bash-extension-pack",
-							"ms-vscode.cmake-tools"
+						"ms-vscode.cpptools-extension-pack",
+						"ms-vscode.makefile-tools",
+						"lizebang.bash-extension-pack",
+						"ms-vscode.cmake-tools",
+						"ms-azuretools.vscode-docker",
+						"golang.go",
+						"jinliming2.vscode-go-template",
+						"mesonbuild.mesonbuild",
+						"esbenp.prettier-vscode",
+						"foxundermoon.shell-format"
 					]
 			}
 	},
@@ -30,5 +36,14 @@
 	// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
 	"remoteUser": "root",
 
-	"runArgs": ["--privileged"]
+	"runArgs": [
+		"--privileged",
+		"--cap-add=SYS_ADMIN",
+        "--tmpfs", "/run",
+        "--tmpfs", "/run/lock",
+        "-v", "/sys/fs/cgroup:/sys/fs/cgroup"
+	],
+
+	"overrideCommand": false,
+	"privileged": true
 }
\ No newline at end of file
diff --git a/.devcontainer/setup-veth.service b/.devcontainer/setup-veth.service
new file mode 100644
index 0000000..e79aaf6
--- /dev/null
+++ b/.devcontainer/setup-veth.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Setup virtual ethernet interfaces (veth)
+Before=systemd-networkd.service
+
+[Service]
+Type=oneshot
+ExecStart=/usr/local/bin/setup_veth.sh
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/.devcontainer/setup_veth.sh b/.devcontainer/setup_veth.sh
new file mode 100755
index 0000000..ae19725
--- /dev/null
+++ b/.devcontainer/setup_veth.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+# Create 4 pairs of veth interfaces
+
+for i in {0..1}; do
+    veth_in="calaos-${i}-0"
+    veth_out="calaos-${i}-1"
+
+    ip link add "$veth_in" type veth peer name "$veth_out"
+    ip link set "$veth_in" up
+    ip link set "$veth_out" up
+done
diff --git a/app/app.go b/app/app.go
index 3161517..39958e3 100644
--- a/app/app.go
+++ b/app/app.go
@@ -99,6 +99,10 @@ func NewApp() (a *AppServer, err error) {
 		return a.apiNetIntfList(c)
 	})
 
+	api.Get("/network/dns", func(c *fiber.Ctx) error {
+		return a.apiNetDNS(c)
+	})
+
 	//Force an update check
 	api.Get("/update/check", func(c *fiber.Ctx) error {
 		return a.apiUpdateCheck(c)
diff --git a/app/network.go b/app/network.go
index 62c7950..1cc45ba 100644
--- a/app/network.go
+++ b/app/network.go
@@ -21,3 +21,19 @@ func (a *AppServer) apiNetIntfList(c *fiber.Ctx) (err error) {
 		"output": nets,
 	})
 }
+
+func (a *AppServer) apiNetDNS(c *fiber.Ctx) (err error) {
+	dns, err := models.GetDNSConfig()
+	if err != nil {
+		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+			"error": true,
+			"msg":   err.Error(),
+		})
+	}
+
+	return c.Status(fiber.StatusOK).JSON(fiber.Map{
+		"error":  false,
+		"msg":    "ok",
+		"output": dns,
+	})
+}
diff --git a/debian/usb-serial-touchscreen@.service b/debian/usb-serial-touchscreen@.service
index c20be4a..6f9d7aa 100644
--- a/debian/usb-serial-touchscreen@.service
+++ b/debian/usb-serial-touchscreen@.service
@@ -1,6 +1,6 @@
 [Unit]
 Description=Inputattach tool for serial2USB converter on %i
-BindTo=dev-%i.device
+BindsTo=dev-%i.device
 After=dev-%i.device xserver-nodm.service
 
 [Service]
diff --git a/models/network.go b/models/network.go
index 4b53a6f..ed56830 100644
--- a/models/network.go
+++ b/models/network.go
@@ -1,49 +1,153 @@
 package models
 
-import "net"
+import (
+	"bufio"
+	"encoding/json"
+	"fmt"
+	"os/exec"
+	"regexp"
+	"strings"
+)
 
 type NetIntf struct {
 	Name       string `json:"name"`
 	IPv4       string `json:"ipv4"`
+	Mask       string `json:"mask"`
+	Gateway    string `json:"gateway"`
 	IPv6       string `json:"ipv6"`
 	MAC        string `json:"mac"`
 	IsLoopback bool   `json:"is_loopback"`
+	State      string `json:"state"`
+}
+
+type DNSConfig struct {
+	Interface     string   `json:"interface"`
+	DNSServers    []string `json:"dns_servers"`
+	SearchDomains []string `json:"search_domains"`
+}
+
+type RawNetInterface struct {
+	Name      string `json:"Name"`
+	Flags     int    `json:"Flags"`
+	IPv4      string `json:"IPv4Address"`
+	IPv4Mask  int    `json:"IPv4Mask"`
+	IPv6      string `json:"IPv6LinkLocalAddress"`
+	MAC       string `json:"HardwareAddress"`
+	State     string `json:"KernelOperationalStateString"`
+	Addresses []struct {
+		Family int    `json:"Family"`
+		Scope  string `json:"ScopeString"`
+		Prefix int    `json:"PrefixLength"`
+	} `json:"Addresses"`
+	Routes []struct {
+		Family    int    `json:"Family"`
+		Type      string `json:"TypeString"`
+		Gateway   string `json:"Gateway"`
+		Dest      string `json:"Destination"`
+		PrefixLen int    `json:"DestinationPrefixLength"`
+	} `json:"Routes"`
+}
+
+type RawDNSConfig struct {
+	Link          int      `json:"Link"`
+	CurrentDNS    []string `json:"CurrentDNSServer"`
+	SearchDomains []string `json:"Domains"`
+	LLMNR         string   `json:"LLMNR"`
+	MulticastDNS  string   `json:"MulticastDNS"`
+	DNSSEC        string   `json:"DNSSEC"`
+	InterfaceName string   `json:"InterfaceName"`
+}
+
+func parseMask(prefixLen int) string {
+	mask := (0xFFFFFFFF << (32 - prefixLen)) & 0xFFFFFFFF
+	return fmt.Sprintf("%d.%d.%d.%d", (mask>>24)&0xFF, (mask>>16)&0xFF, (mask>>8)&0xFF, mask&0xFF)
 }
 
 func GetAllNetInterfaces() (nets []*NetIntf, err error) {
-	ifaces, err := net.Interfaces()
+	cmd := exec.Command("/usr/bin/networkctl", "list", "--json=short")
+	output, err := cmd.Output()
 	if err != nil {
 		return nil, err
 	}
 
-	for _, iface := range ifaces {
-		addrs, err := iface.Addrs()
-		if err != nil {
-			return nil, err
+	var rawInterfaces []RawNetInterface
+	if err := json.Unmarshal(output, &rawInterfaces); err != nil {
+		return nil, err
+	}
+
+	for _, rawIntf := range rawInterfaces {
+		netIntf := &NetIntf{
+			Name:       rawIntf.Name,
+			MAC:        rawIntf.MAC,
+			State:      rawIntf.State,
+			IsLoopback: (rawIntf.Flags & 0x8) != 0, // check IFF_LOOPBACK
 		}
 
-		intf := &NetIntf{
-			Name:       iface.Name,
-			IsLoopback: iface.Flags&net.FlagLoopback != 0,
-			MAC:        iface.HardwareAddr.String(),
+		// Look for IPv4 address and mask
+		for _, addr := range rawIntf.Addresses {
+			if addr.Family == 2 { // IPv4
+				netIntf.IPv4 = fmt.Sprintf("%d.%d.%d.%d", addr.Scope[0], addr.Scope[1], addr.Scope[2], addr.Scope[3])
+				netIntf.Mask = parseMask(addr.Prefix)
+				break
+			}
 		}
 
-		for _, addr := range addrs {
-			ipNet, ok := addr.(*net.IPNet)
-			if !ok {
-				continue
+		// Look for IPv6 address
+		for _, addr := range rawIntf.Addresses {
+			if addr.Family == 10 { // IPv6
+				netIntf.IPv6 = rawIntf.IPv6
+				break
 			}
+		}
 
-			// Skip IPv6 addresses
-			if ipNet.IP.To4() == nil {
-				intf.IPv6 = ipNet.IP.String()
-			} else if ipNet.IP.To16() != nil {
-				intf.IPv4 = ipNet.IP.String()
+		// Look for default gateway
+		for _, route := range rawIntf.Routes {
+			if route.Type == "unicast" && route.Gateway != "" {
+				netIntf.Gateway = route.Gateway
+				break
 			}
 		}
 
-		nets = append(nets, intf)
+		nets = append(nets, netIntf)
+	}
+
+	return nets, nil
+}
+
+func GetDNSConfig() ([]*DNSConfig, error) {
+	cmd := exec.Command("/usr/bin/resolvectl", "status")
+	output, err := cmd.Output()
+	if err != nil {
+		return nil, err
+	}
+
+	var dnsConfigs []*DNSConfig
+	var currentConfig *DNSConfig
+
+	scanner := bufio.NewScanner(strings.NewReader(string(output)))
+	dnsServerRegex := regexp.MustCompile(`DNS Servers? ([\d.]+)`)
+	interfaceRegex := regexp.MustCompile(`Link (\d+) \(([^)]+)\)`)
+
+	for scanner.Scan() {
+		line := scanner.Text()
+
+		if strings.HasPrefix(line, "Global") {
+			currentConfig = &DNSConfig{Interface: "global"}
+			dnsConfigs = append(dnsConfigs, currentConfig)
+		} else if matches := interfaceRegex.FindStringSubmatch(line); len(matches) == 3 {
+			currentConfig = &DNSConfig{Interface: matches[2]}
+			dnsConfigs = append(dnsConfigs, currentConfig)
+		}
+
+		if dnsServerRegex.MatchString(line) {
+			server := dnsServerRegex.FindStringSubmatch(line)[1]
+			currentConfig.DNSServers = append(currentConfig.DNSServers, server)
+		}
+	}
+
+	if err := scanner.Err(); err != nil {
+		return nil, err
 	}
 
-	return
+	return dnsConfigs, nil
 }