From 05358b427bee7e66eee3368b5282e7f9b6c962ae Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Fri, 11 Jun 2021 18:56:13 -0700 Subject: [PATCH] ui: Expose pcap net client in user interface --- ui/xemu-hud.cc | 167 +++++++++++++++++++++++++++++++++++++++++++-- ui/xemu-net.c | 15 ++++ ui/xemu-settings.c | 3 + ui/xemu-settings.h | 2 + 4 files changed, 183 insertions(+), 4 deletions(-) diff --git a/ui/xemu-hud.cc b/ui/xemu-hud.cc index e83e7cbd81..bb93ee4a34 100644 --- a/ui/xemu-hud.cc +++ b/ui/xemu-hud.cc @@ -21,6 +21,9 @@ #include #include #include +#include +#include +#include #include "xemu-hud.h" #include "xemu-input.h" @@ -56,6 +59,7 @@ extern "C" { #include "sysemu/runstate.h" #include "hw/xbox/mcpx/apu_debug.h" #include "hw/xbox/nv2a/debug.h" +#include "net/pcap.h" #undef typename #undef atomic_fetch_add @@ -858,6 +862,100 @@ public: } }; +class NetworkInterface +{ +public: + std::string pcap_name; + std::string description; + std::string friendlyname; + + NetworkInterface(pcap_if_t *pcap_desc, char *_friendlyname = NULL) + { + pcap_name = pcap_desc->name; + description = pcap_desc->description ?: pcap_desc->name; + if (_friendlyname) { + char *tmp = g_strdup_printf("%s (%s)", _friendlyname, description.c_str()); + friendlyname = tmp; + g_free((gpointer)tmp); + } else { + friendlyname = description; + } + } +}; + +class NetworkInterfaceManager +{ +public: + std::vector> ifaces; + NetworkInterface *current_iface; + const char *current_iface_name; + bool failed_to_load_lib; + + NetworkInterfaceManager() + { + current_iface = NULL; + xemu_settings_get_string(XEMU_SETTINGS_NETWORK_PCAP_INTERFACE, + ¤t_iface_name); + failed_to_load_lib = false; + } + + void refresh(void) + { + pcap_if_t *alldevs, *iter; + char err[PCAP_ERRBUF_SIZE]; + + if (xemu_net_is_enabled()) { + return; + } + +#if defined(_WIN32) + if (pcap_load_library()) { + failed_to_load_lib = true; + return; + } +#endif + + ifaces.clear(); + current_iface = NULL; + + if (pcap_findalldevs(&alldevs, err)) { + return; + } + + for (iter=alldevs; iter != NULL; iter=iter->next) { +#if defined(_WIN32) + char *friendlyname = get_windows_interface_friendly_name(iter->name); + ifaces.emplace_back(new NetworkInterface(iter, friendlyname)); + if (friendlyname) { + g_free((gpointer)friendlyname); + } +#else + ifaces.emplace_back(new NetworkInterface(iter)); +#endif + if (!strcmp(current_iface_name, iter->name)) { + current_iface = ifaces.back().get(); + } + } + + pcap_freealldevs(alldevs); + } + + void select(NetworkInterface &iface) + { + current_iface = &iface; + xemu_settings_set_string(XEMU_SETTINGS_NETWORK_PCAP_INTERFACE, + iface.pcap_name.c_str()); + xemu_settings_get_string(XEMU_SETTINGS_NETWORK_PCAP_INTERFACE, + ¤t_iface_name); + } + + bool is_current(NetworkInterface &iface) + { + return &iface == current_iface; + } +}; + + class NetworkWindow { public: @@ -865,6 +963,7 @@ public: int backend; char remote_addr[64]; char local_addr[64]; + std::unique_ptr iface_mgr; NetworkWindow() { @@ -879,7 +978,7 @@ public: { if (!is_open) return; - ImGui::SetNextWindowContentSize(ImVec2(400.0f*g_ui_scale, 0.0f)); + ImGui::SetNextWindowContentSize(ImVec2(500.0f*g_ui_scale, 0.0f)); if (!ImGui::Begin("Network", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::End(); return; @@ -908,16 +1007,18 @@ public: ImGui::NextColumn(); if (is_enabled) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.6f); 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) { + if (ImGui::Combo("##backend", is_enabled ? &temp_backend : &backend, "NAT\0UDP Tunnel\0Bridged Adapter\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"); + HelpMarker("User-mode TCP/IP stack with network address translation"); } else if (backend == XEMU_NET_BACKEND_SOCKET_UDP) { - HelpMarker("Encapsulates link-layer traffic in UDP packets"); + HelpMarker("Tunnels link-layer traffic to a remote host via UDP"); + } else if (backend == XEMU_NET_BACKEND_PCAP) { + HelpMarker("Bridges with a host network interface"); } ImGui::NextColumn(); @@ -940,6 +1041,64 @@ public: ImGui::InputText("###local_host", local_addr, sizeof(local_addr), flg); if (is_enabled) ImGui::PopStyleVar(); ImGui::NextColumn(); + } else if (backend == XEMU_NET_BACKEND_PCAP) { + static bool should_refresh = true; + if (iface_mgr.get() == nullptr) { + iface_mgr.reset(new NetworkInterfaceManager()); + iface_mgr->refresh(); + } + + if (iface_mgr->failed_to_load_lib) { +#if defined(_WIN32) + ImGui::Columns(1); + ImGui::Dummy(ImVec2(0,20*g_ui_scale)); + const char *msg = "WinPcap/npcap library could not be loaded.\n" + "To use this attachment, please install npcap."; + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - g_ui_scale*ImGui::CalcTextSize(msg).x)/2); + ImGui::Text("%s", msg); + ImGui::Dummy(ImVec2(0,10*g_ui_scale)); + ImGui::SetCursorPosX((ImGui::GetWindowWidth()-120*g_ui_scale)/2); + if (ImGui::Button("Install npcap", ImVec2(120*g_ui_scale, 0))) { + xemu_open_web_browser("https://nmap.org/npcap/"); + } + ImGui::Dummy(ImVec2(0,10*g_ui_scale)); +#endif + } else { + ImGui::Text("Network Interface"); + ImGui::SameLine(); HelpMarker("Host network interface to bridge with"); + ImGui::NextColumn(); + + float w = ImGui::GetColumnWidth()-10*g_ui_scale; + ImGui::SetNextItemWidth(w); + const char *selected_display_name = ( + iface_mgr->current_iface + ? iface_mgr->current_iface->friendlyname.c_str() + : iface_mgr->current_iface_name + ); + if (is_enabled) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.6f); + if (ImGui::BeginCombo("###network_iface", selected_display_name)) { + if (should_refresh) { + iface_mgr->refresh(); + should_refresh = false; + } + int i = 0; + for (auto& iface : iface_mgr->ifaces) { + bool is_selected = iface_mgr->is_current((*iface)); + ImGui::PushID(i++); + if (ImGui::Selectable(iface->friendlyname.c_str(), is_selected)) { + if (!is_enabled) iface_mgr->select((*iface)); + } + if (is_selected) ImGui::SetItemDefaultFocus(); + ImGui::PopID(); + } + ImGui::EndCombo(); + } else { + should_refresh = true; + } + if (is_enabled) ImGui::PopStyleVar(); + + ImGui::NextColumn(); + } } ImGui::Columns(1); diff --git a/ui/xemu-net.c b/ui/xemu-net.c index c59e852f0b..2354934065 100644 --- a/ui/xemu-net.c +++ b/ui/xemu-net.c @@ -33,6 +33,9 @@ #include "qemu/config-file.h" #include "net/net.h" #include "net/hub.h" +#if defined(_WIN32) +#include +#endif static const char *id = "xemu-netdev"; static const char *id_hubport = "xemu-netdev-hubport"; @@ -64,6 +67,18 @@ void xemu_net_enable(void) qdict_put_str(qdict, "type", "socket"); qdict_put_str(qdict, "udp", remote_addr); qdict_put_str(qdict, "localaddr", local_addr); + } else if (backend == XEMU_NET_BACKEND_PCAP) { +#if defined(_WIN32) + if (pcap_load_library()) { + return; + } +#endif + const char *iface; + xemu_settings_get_string(XEMU_SETTINGS_NETWORK_PCAP_INTERFACE, &iface); + qdict = qdict_new(); + qdict_put_str(qdict, "id", id); + qdict_put_str(qdict, "type", "pcap"); + qdict_put_str(qdict, "ifname", iface); } else { // Unsupported backend type return; diff --git a/ui/xemu-settings.c b/ui/xemu-settings.c index 7c1bb1c827..e5771e6ae5 100644 --- a/ui/xemu-settings.c +++ b/ui/xemu-settings.c @@ -65,6 +65,7 @@ struct xemu_settings { int net_backend; char *net_local_addr; char *net_remote_addr; + char *net_pcap_iface; // [misc] char *user_token; @@ -86,6 +87,7 @@ static const struct enum_str_map display_scale_map[DISPLAY_SCALE__COUNT+1] = { 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" }, + { XEMU_NET_BACKEND_PCAP, "pcap" }, { 0, NULL }, }; @@ -125,6 +127,7 @@ struct config_offset_table { [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" } }, + [XEMU_SETTINGS_NETWORK_PCAP_INTERFACE] = { CONFIG_TYPE_STRING, "network", "pcap_iface", offsetof(struct xemu_settings, net_pcap_iface), { .default_str = "" } }, [XEMU_SETTINGS_MISC_USER_TOKEN] = { CONFIG_TYPE_STRING, "misc", "user_token", offsetof(struct xemu_settings, user_token), { .default_str = "" } }, [XEMU_SETTINGS_MISC_CHECK_FOR_UPDATE] = { CONFIG_TYPE_BOOL, "misc", "check_for_update", offsetof(struct xemu_settings, check_for_update), { .default_bool = -1 } }, diff --git a/ui/xemu-settings.h b/ui/xemu-settings.h index cced57dc9f..ea15828cd2 100644 --- a/ui/xemu-settings.h +++ b/ui/xemu-settings.h @@ -48,6 +48,7 @@ enum xemu_settings_keys { XEMU_SETTINGS_NETWORK_BACKEND, XEMU_SETTINGS_NETWORK_LOCAL_ADDR, XEMU_SETTINGS_NETWORK_REMOTE_ADDR, + XEMU_SETTINGS_NETWORK_PCAP_INTERFACE, XEMU_SETTINGS_MISC_USER_TOKEN, XEMU_SETTINGS_MISC_CHECK_FOR_UPDATE, XEMU_SETTINGS__COUNT, @@ -66,6 +67,7 @@ enum DISPLAY_SCALE enum xemu_net_backend { XEMU_NET_BACKEND_USER, XEMU_NET_BACKEND_SOCKET_UDP, + XEMU_NET_BACKEND_PCAP, XEMU_NET_BACKEND__COUNT, XEMU_NET_BACKEND_INVALID = -1 };