From ca4d03cf893812496b9059cd22c0467feae7b7d5 Mon Sep 17 00:00:00 2001
From: Matt Borgerson <contact@mborgerson.com>
Date: Sat, 30 May 2020 17:42:09 -0700
Subject: [PATCH] ui: Add ability to enable user (NAT) network backend

---
 ui/xemu-hud.cc     | 64 +++++++++++++++++++++++++++-------------------
 ui/xemu-net.c      | 25 +++++++++++++-----
 ui/xemu-settings.c |  8 ++++++
 ui/xemu-settings.h |  8 ++++++
 4 files changed, 73 insertions(+), 32 deletions(-)

diff --git a/ui/xemu-hud.cc b/ui/xemu-hud.cc
index d8846d8922..74879529ca 100644
--- a/ui/xemu-hud.cc
+++ b/ui/xemu-hud.cc
@@ -823,6 +823,7 @@ class NetworkWindow
 {
 public:
     bool is_open;
+    int  backend;
     char remote_addr[64];
     char local_addr[64];
 
@@ -851,45 +852,56 @@ public:
             strncpy(remote_addr, tmp, sizeof(remote_addr)-1);
             xemu_settings_get_string(XEMU_SETTINGS_NETWORK_LOCAL_ADDR, &tmp);
             strncpy(local_addr, tmp, sizeof(local_addr)-1);
+            xemu_settings_get_enum(XEMU_SETTINGS_NETWORK_BACKEND, &backend);
         }
 
-        ImGui::TextWrapped(
-            "xemu socket networking works by sending and receiving packets over "
-            "UDP which encapsulate the network traffic that the machine would "
-            "send or receive when connected to a Local Area Network (LAN)."
-            );
-
-        ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
-        ImGui::Separator();
-        ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
-
-        ImGui::Columns(2, "", false);
-        ImGui::SetColumnWidth(0, ImGui::GetWindowWidth()*0.33);
-
         ImGuiInputTextFlags flg = 0;
         bool is_enabled = xemu_net_is_enabled();
         if (is_enabled) {
             flg |= ImGuiInputTextFlags_ReadOnly;
         }
 
-        ImGui::Text("Remote Host");
-        ImGui::SameLine(); HelpMarker("The remote <IP address>:<Port> to forward packets to (e.g. 1.2.3.4:9368)");
+        ImGui::Columns(2, "", false);
+        ImGui::SetColumnWidth(0, ImGui::GetWindowWidth()*0.33);
+
+        ImGui::Text("Attached To");
+        ImGui::SameLine(); HelpMarker("The network backend which the emulated NIC interacts with");
         ImGui::NextColumn();
-        float w = ImGui::GetColumnWidth()-10*g_ui_scale;
-        ImGui::SetNextItemWidth(w);
         if (is_enabled) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.6f);
-        ImGui::InputText("###remote_host", remote_addr, sizeof(remote_addr), flg);
+        int temp_backend = backend; // Temporary to make backend combo read-only (FIXME: surely there's a nicer way)
+        if (ImGui::Combo("##backend", is_enabled ? &temp_backend : &backend, "User (NAT)\0Socket\0") && !is_enabled) {
+            xemu_settings_set_enum(XEMU_SETTINGS_NETWORK_BACKEND, backend);
+            xemu_settings_save();
+        }
         if (is_enabled) ImGui::PopStyleVar();
+        ImGui::SameLine();
+        if (backend == XEMU_NET_BACKEND_USER) {
+            HelpMarker("User-mode TCP/IP stack with a NAT'd network");
+        } else if (backend == XEMU_NET_BACKEND_SOCKET_UDP) {
+            HelpMarker("Encapsulates link-layer traffic in UDP packets");
+        }
         ImGui::NextColumn();
 
-        ImGui::Text("Local Host");
-        ImGui::SameLine(); HelpMarker("The local <IP address>:<Port> to receive packets on (e.g. 0.0.0.0:9368)");
-        ImGui::NextColumn();
-        ImGui::SetNextItemWidth(w);
-        if (is_enabled) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.6f);
-        ImGui::InputText("###local_host", local_addr, sizeof(local_addr), flg);
-        if (is_enabled) ImGui::PopStyleVar();
-        ImGui::NextColumn();
+        if (backend == XEMU_NET_BACKEND_SOCKET_UDP) {
+            ImGui::Text("Remote Host");
+            ImGui::SameLine(); HelpMarker("The remote <IP address>:<Port> to forward packets to (e.g. 1.2.3.4:9368)");
+            ImGui::NextColumn();
+            float w = ImGui::GetColumnWidth()-10*g_ui_scale;
+            ImGui::SetNextItemWidth(w);
+            if (is_enabled) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.6f);
+            ImGui::InputText("###remote_host", remote_addr, sizeof(remote_addr), flg);
+            if (is_enabled) ImGui::PopStyleVar();
+            ImGui::NextColumn();
+
+            ImGui::Text("Local Host");
+            ImGui::SameLine(); HelpMarker("The local <IP address>:<Port> to receive packets on (e.g. 0.0.0.0:9368)");
+            ImGui::NextColumn();
+            ImGui::SetNextItemWidth(w);
+            if (is_enabled) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.6f);
+            ImGui::InputText("###local_host", local_addr, sizeof(local_addr), flg);
+            if (is_enabled) ImGui::PopStyleVar();
+            ImGui::NextColumn();
+        }
 
         ImGui::Columns(1);
 
diff --git a/ui/xemu-net.c b/ui/xemu-net.c
index 3432603eef..72acc822e0 100644
--- a/ui/xemu-net.c
+++ b/ui/xemu-net.c
@@ -46,16 +46,29 @@ void xemu_net_enable(void)
         return;
     }
 
+    int backend;
     const char *local_addr, *remote_addr;
+    xemu_settings_get_enum(XEMU_SETTINGS_NETWORK_BACKEND, &backend);
     xemu_settings_get_string(XEMU_SETTINGS_NETWORK_REMOTE_ADDR, &remote_addr);
     xemu_settings_get_string(XEMU_SETTINGS_NETWORK_LOCAL_ADDR, &local_addr);
 
-    // Create the UDP netdev
-    QDict *qdict = qdict_new();
-    qdict_put_str(qdict, "id",        id);
-    qdict_put_str(qdict, "type",      "socket");
-    qdict_put_str(qdict, "udp",       remote_addr);
-    qdict_put_str(qdict, "localaddr", local_addr);
+    // Create the netdev
+    QDict *qdict;
+    if (backend == XEMU_NET_BACKEND_USER) {
+        qdict = qdict_new();
+        qdict_put_str(qdict, "id",   id);
+        qdict_put_str(qdict, "type", "user");
+    } else if (backend == XEMU_NET_BACKEND_SOCKET_UDP) {
+        qdict = qdict_new();
+        qdict_put_str(qdict, "id",        id);
+        qdict_put_str(qdict, "type",      "socket");
+        qdict_put_str(qdict, "udp",       remote_addr);
+        qdict_put_str(qdict, "localaddr", local_addr);
+    } else {
+        // Unsupported backend type
+        return;
+    }
+
     QemuOpts *opts = qemu_opts_from_qdict(qemu_find_opts("netdev"), qdict, &error_abort);
     qobject_unref(qdict);
     netdev_add(opts, &local_err);
diff --git a/ui/xemu-settings.c b/ui/xemu-settings.c
index 267600b24d..14804872ba 100644
--- a/ui/xemu-settings.c
+++ b/ui/xemu-settings.c
@@ -56,6 +56,7 @@ struct xemu_settings {
 
 	// [network]
 	int   net_enabled; // Boolean
+	int   net_backend;
 	char *net_local_addr;
 	char *net_remote_addr;
 };
@@ -72,6 +73,12 @@ static const struct enum_str_map display_scale_map[DISPLAY_SCALE__COUNT+1] = {
 	{ 0,                     NULL      },
 };
 
+static const struct enum_str_map net_backend_map[XEMU_NET_BACKEND__COUNT+1] = {
+	{ XEMU_NET_BACKEND_USER,       "user" },
+	{ XEMU_NET_BACKEND_SOCKET_UDP, "udp"  },
+	{ 0,                           NULL   },
+};
+
 struct config_offset_table {
 	enum config_types type;
 	const char *section;
@@ -102,6 +109,7 @@ struct config_offset_table {
 	[XEMU_SETTINGS_INPUT_CONTROLLER_4_GUID] = { CONFIG_TYPE_STRING,   "input", "controller_4_guid", offsetof(struct xemu_settings, controller_4_guid), { .default_str = "" } },
 
 	[XEMU_SETTINGS_NETWORK_ENABLED]     = { CONFIG_TYPE_BOOL,   "network", "enabled",     offsetof(struct xemu_settings, net_enabled),     { .default_bool = 0              } },
+	[XEMU_SETTINGS_NETWORK_BACKEND]     = { CONFIG_TYPE_ENUM,   "network", "backend",     offsetof(struct xemu_settings, net_backend),     { .default_int = XEMU_NET_BACKEND_USER }, net_backend_map },
 	[XEMU_SETTINGS_NETWORK_LOCAL_ADDR]  = { CONFIG_TYPE_STRING, "network", "local_addr",  offsetof(struct xemu_settings, net_local_addr),  { .default_str  = "0.0.0.0:9368" } },
 	[XEMU_SETTINGS_NETWORK_REMOTE_ADDR] = { CONFIG_TYPE_STRING, "network", "remote_addr", offsetof(struct xemu_settings, net_remote_addr), { .default_str  = "1.2.3.4:9368" } },
 };
diff --git a/ui/xemu-settings.h b/ui/xemu-settings.h
index e92ca66ea7..c7c26a6f64 100644
--- a/ui/xemu-settings.h
+++ b/ui/xemu-settings.h
@@ -43,6 +43,7 @@ enum xemu_settings_keys {
 	XEMU_SETTINGS_INPUT_CONTROLLER_3_GUID,
 	XEMU_SETTINGS_INPUT_CONTROLLER_4_GUID,
 	XEMU_SETTINGS_NETWORK_ENABLED,
+	XEMU_SETTINGS_NETWORK_BACKEND,
 	XEMU_SETTINGS_NETWORK_LOCAL_ADDR,
 	XEMU_SETTINGS_NETWORK_REMOTE_ADDR,
 	XEMU_SETTINGS__COUNT,
@@ -58,6 +59,13 @@ enum DISPLAY_SCALE
     DISPLAY_SCALE_INVALID = -1
 };
 
+enum xemu_net_backend {
+	XEMU_NET_BACKEND_USER,
+	XEMU_NET_BACKEND_SOCKET_UDP,
+	XEMU_NET_BACKEND__COUNT,
+	XEMU_NET_BACKEND_INVALID = -1
+};
+
 // Determine whether settings were loaded or not
 int xemu_settings_did_fail_to_load(void);